Give a better error message when RunAsyncCode.runAsyncCode is passed something other than a class literal. Review at http://gwt-code-reviews.appspot.com/220802 Review by: scottb@google.com git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@7757 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/ReplaceRunAsyncs.java b/dev/core/src/com/google/gwt/dev/jjs/impl/ReplaceRunAsyncs.java index b02af7e..203ae03 100644 --- a/dev/core/src/com/google/gwt/dev/jjs/impl/ReplaceRunAsyncs.java +++ b/dev/core/src/com/google/gwt/dev/jjs/impl/ReplaceRunAsyncs.java
@@ -18,6 +18,7 @@ import com.google.gwt.core.ext.TreeLogger; import com.google.gwt.core.ext.UnableToCompleteException; import com.google.gwt.dev.jjs.InternalCompilerException; +import com.google.gwt.dev.jjs.SourceInfo; import com.google.gwt.dev.jjs.ast.Context; import com.google.gwt.dev.jjs.ast.JClassLiteral; import com.google.gwt.dev.jjs.ast.JClassType; @@ -40,11 +41,10 @@ * Replaces calls to * {@link com.google.gwt.core.client.GWT#runAsync(com.google.gwt.core.client.RunAsyncCallback)} * and - * {@link com.google.gwt.core.client.GWT#runAsync(Class, com.google.gwt.core.client.RunAsyncCallback) + * {@link com.google.gwt.core.client.GWT#runAsync(Class, com.google.gwt.core.client.RunAsyncCallback)} * by calls to a fragment loader. Additionally, replaces access to - * - * @link RunAsyncCode#forSplitPoint(Class)} by an equivalent call using an - * integer rather than a class literal. + * {@link com.google.gwt.core.client.prefetch.RunAsyncCode#runAsyncCode(Class)} + * by an equivalent call using an integer rather than a class literal. */ public class ReplaceRunAsyncs { /** @@ -178,17 +178,25 @@ @Override public void endVisit(JMethodCall x, Context ctx) { if (x.getTarget() == program.getIndexedMethod("RunAsyncCode.runAsyncCode")) { - JClassLiteral lit = (JClassLiteral) x.getArgs().get(0); + JExpression arg0 = x.getArgs().get(0); + if (!(arg0 instanceof JClassLiteral)) { + error(arg0.getSourceInfo(), + "Only a class literal may be passed to runAsyncCode"); + return; + } + JClassLiteral lit = (JClassLiteral) arg0; String name = nameFromClassLiteral(lit); List<RunAsyncReplacement> matches = replacementsByName.get(name); if (matches == null || matches.size() == 0) { - error("No runAsync call is named " + name); + error(x.getSourceInfo(), "No runAsync call is named " + name); return; } if (matches.size() > 1) { - TreeLogger branch = error("Multiple runAsync calls are named " + name); + TreeLogger branch = error(x.getSourceInfo(), + "Multiple runAsync calls are named " + name); for (RunAsyncReplacement match : matches) { - branch.log(TreeLogger.ERROR, "One call is in " + methodDescription(match.getEnclosingMethod())); + branch.log(TreeLogger.ERROR, "One call is in " + + methodDescription(match.getEnclosingMethod())); } return; } @@ -211,7 +219,7 @@ desc.append(':'); desc.append(method.getSourceInfo().getStartLine()); desc.append(")"); - + return desc.toString(); } } @@ -252,9 +260,16 @@ this.program = program; } - private TreeLogger error(String message) { + private TreeLogger error(SourceInfo info, String message) { errorsFound = true; - return logger.branch(TreeLogger.ERROR, message); + TreeLogger fileLogger = logger.branch(TreeLogger.ERROR, "Error in '" + + info.getFileName() + "'"); + String linePrefix = ""; + if (info.getStartLine() > 0) { + linePrefix = "Line " + info.getStartLine() + ": "; + } + fileLogger.log(TreeLogger.ERROR, linePrefix + message); + return fileLogger; } private void execImpl() throws UnableToCompleteException {
diff --git a/dev/core/test/com/google/gwt/dev/jjs/JavaAstConstructor.java b/dev/core/test/com/google/gwt/dev/jjs/JavaAstConstructor.java index 8a6e49e..9fbd3bd 100644 --- a/dev/core/test/com/google/gwt/dev/jjs/JavaAstConstructor.java +++ b/dev/core/test/com/google/gwt/dev/jjs/JavaAstConstructor.java
@@ -73,6 +73,7 @@ code.append(" static <T> Class<T> createForClass(String packageName, String className, String seedName, Class<? super T> superclass) { return new Class<T>(); }\n"); code.append(" static <T> Class<T> createForEnum(String packageName, String className, String seedName, Class<? super T> superclass, JavaScriptObject enumConstantsFunc) { return new Class<T>(); }\n"); code.append(" static <T> Class<T> createForInterface(String packageName, String className) { return new Class<T>(); }\n"); + code.append(" static <T> Class<T> createForPrimitive(String packageName, String className, String jni) { return new Class<T>(); }\n"); code.append(" static boolean isClassMetadataEnabled() { return true; }\n"); code.append(" public boolean desiredAssertionStatus() { return true; }\n"); code.append("}\n"); @@ -99,12 +100,22 @@ code.append("public final class GWT {\n"); code.append(" public boolean isClient() { return true; };\n"); code.append(" public boolean isScript() { return true; };\n"); - code.append(" public static void runAsync(Object callback) { }\n"); - code.append(" public static void runAsync(Class<?> name, Object callback) { }\n"); + code.append(" public static void runAsync(RunAsyncCallback callback) { }\n"); + code.append(" public static void runAsync(Class<?> name, RunAsyncCallback callback) { }\n"); code.append("}\n"); return code; } }; + public static final MockJavaResource RUNASYNCCALLBACK = new MockJavaResource( + "com.google.gwt.core.client.RunAsyncCallback") { + @Override + protected CharSequence getContent() { + StringBuffer code = new StringBuffer(); + code.append("package com.google.gwt.core.client;\n"); + code.append("public interface RunAsyncCallback { }\n"); + return code; + } + }; public static final MockJavaResource STATS = new MockJavaResource( "com.google.gwt.lang.Stats") { @Override @@ -198,7 +209,8 @@ Collections.addAll(result, JavaResourceBase.getStandardResources()); // Replace the basic Class with a compiler-specific one. result.remove(JavaResourceBase.CLASS); - Collections.addAll(result, ARRAY, CLASS, CLASSLITERALHOLDER, GWT); + Collections.addAll(result, ARRAY, CLASS, CLASSLITERALHOLDER, GWT, + RUNASYNCCALLBACK); return result.toArray(new MockJavaResource[result.size()]); } }
diff --git a/dev/core/test/com/google/gwt/dev/jjs/impl/ReplaceRunAsyncsErrorMessagesTest.java b/dev/core/test/com/google/gwt/dev/jjs/impl/ReplaceRunAsyncsErrorMessagesTest.java new file mode 100644 index 0000000..629f7eb --- /dev/null +++ b/dev/core/test/com/google/gwt/dev/jjs/impl/ReplaceRunAsyncsErrorMessagesTest.java
@@ -0,0 +1,188 @@ +/* + * Copyright 2008 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.gwt.dev.jjs.impl; + +import com.google.gwt.core.ext.TreeLogger; +import com.google.gwt.core.ext.UnableToCompleteException; +import com.google.gwt.dev.javac.impl.MockJavaResource; +import com.google.gwt.dev.jjs.ast.JProgram; +import com.google.gwt.dev.util.UnitTestTreeLogger; + +/** + * Tests the error messages generated by {@link ReplaceRunAsyncs}. + */ +public class ReplaceRunAsyncsErrorMessagesTest extends OptimizerTestBase { + private UnitTestTreeLogger.Builder testLoggerBuilder; + + @Override + public void setUp() { + addCommonTestCode(); + initializeTestLoggerBuilder(); + } + + public void testAmbiguousClassLiteral() { + sourceOracle.addOrReplace(new MockJavaResource("test.SplitPoint3") { + @Override + protected CharSequence getContent() { + StringBuffer code = new StringBuffer(); + code.append("package test;\n"); + code.append("import com.google.gwt.core.client.GWT;\n"); + code.append("public class SplitPoint3 {\n"); + code.append(" void doStuff() {\n"); + // Intentionally reuse SplitPoint1.class + code.append(" GWT.runAsync(SplitPoint1.class, null);\n"); + code.append(" }\n"); + code.append("}\n"); + return code; + } + }); + addSnippetImport("test.SplitPoint3"); + + expectError("Line 12: Multiple runAsync calls are named test.SplitPoint1"); + expectError("One call is in test.SplitPoint1.doStuff (/mock/test/SplitPoint1.java:4)"); + expectError("One call is in test.SplitPoint3.doStuff (/mock/test/SplitPoint3.java:4)"); + + testSnippet("RunAsyncCode.runAsyncCode(SplitPoint1.class);"); + } + + public void testNonClassLiteral() { + expectError("Line 11: Only a class literal may be passed to runAsyncCode"); + testSnippet("RunAsyncCode.runAsyncCode(new SplitPoint1().getClass());"); + } + + public void testNonExistentSplitPoint() { + expectError("Line 11: No runAsync call is named java.lang.String"); + testSnippet("RunAsyncCode.runAsyncCode(String.class);"); + } + + private void addAsyncLoader(final int sp) { + sourceOracle.addOrReplace(new MockJavaResource( + "com.google.gwt.lang.asyncloaders.AsyncLoader" + sp) { + @Override + protected CharSequence getContent() { + StringBuffer code = new StringBuffer(); + code.append("package com.google.gwt.lang.asyncloaders;\n"); + code.append("import com.google.gwt.core.client.RunAsyncCallback;"); + code.append("public class AsyncLoader" + sp + " {\n"); + code.append(" public static void onLoad() { }\n"); + code.append(" public static void runAsync(RunAsyncCallback cb) { }\n"); + code.append("}\n"); + return code; + } + }); + + addSnippetImport("com.google.gwt.lang.asyncloaders.AsyncLoader" + sp); + } + + private void addCommonTestCode() { + addAsyncLoader(1); + addAsyncLoader(2); + addAsyncLoader(3); + + sourceOracle.addOrReplace(new MockJavaResource( + "com.google.gwt.core.client.impl.AsyncFragmentLoader") { + @Override + protected CharSequence getContent() { + StringBuffer code = new StringBuffer(); + code.append("package com.google.gwt.core.client.impl;\n"); + code.append("public class AsyncFragmentLoader {\n"); + code.append(" private static AsyncFragmentLoader BROWSER_LOADER =\n"); + code.append(" makeBrowserLoader(1, new int[] {});\n"); + code.append(" private static AsyncFragmentLoader makeBrowserLoader(\n"); + code.append(" int numSp, int[] initial) {\n"); + code.append(" return null;\n"); + code.append(" }\n"); + code.append("}\n"); + return code; + } + }); + addSnippetImport("com.google.gwt.core.client.impl.AsyncFragmentLoader"); + + sourceOracle.addOrReplace(new MockJavaResource( + "com.google.gwt.core.client.prefetch.RunAsyncCode") { + @Override + protected CharSequence getContent() { + StringBuffer code = new StringBuffer(); + code.append("package com.google.gwt.core.client.prefetch;\n"); + code.append("public class RunAsyncCode {\n"); + code.append(" public static RunAsyncCode runAsyncCode(Class<?> splitPoint) {\n"); + code.append(" return null;\n"); + code.append(" }"); + code.append("}"); + return code; + } + }); + addSnippetImport("com.google.gwt.core.client.prefetch.RunAsyncCode"); + + sourceOracle.addOrReplace(new MockJavaResource("test.SplitPoint1") { + @Override + protected CharSequence getContent() { + StringBuffer code = new StringBuffer(); + code.append("package test;\n"); + code.append("import com.google.gwt.core.client.GWT;\n"); + code.append("public class SplitPoint1 {\n"); + code.append(" void doStuff() {\n"); + code.append(" GWT.runAsync(SplitPoint1.class, null);\n"); + code.append(" }\n"); + code.append("}\n"); + return code; + } + }); + addSnippetImport("test.SplitPoint1"); + + sourceOracle.addOrReplace(new MockJavaResource("test.SplitPoint2") { + @Override + protected CharSequence getContent() { + StringBuffer code = new StringBuffer(); + code.append("package test;\n"); + code.append("import com.google.gwt.core.client.GWT;\n"); + code.append("public class SplitPoint2 {\n"); + code.append(" void doStuff() {\n"); + code.append(" GWT.runAsync(SplitPoint2.class, null);\n"); + code.append(" }\n"); + code.append("}\n"); + return code; + } + }); + addSnippetImport("test.SplitPoint2"); + } + + private void expectError(String msg) { + testLoggerBuilder.expectError(msg, null); + } + + private void initializeTestLoggerBuilder() { + testLoggerBuilder = new UnitTestTreeLogger.Builder(); + testLoggerBuilder.setLowestLogLevel(TreeLogger.ERROR); + expectError("Error in '/mock/test/EntryPoint.java'"); + } + + private void testSnippet(String codeSnippet) { + UnitTestTreeLogger testLogger = testLoggerBuilder.createLogger(); + logger = testLogger; + + try { + JProgram program = compileSnippet("void", codeSnippet); + ReplaceRunAsyncs.exec(logger, program); + fail("Expected a compile error"); + } catch (UnableToCompleteException e) { + // expected + } + + testLogger.assertCorrectLogEntries(); + } +}