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();
+ }
+}