Adds tests of JsInterop accuracy in incremental recompiles.
Change-Id: Iebdf7c1e21bd4e6632f6ba9748701402ad37c7f2
Review-Link: https://gwt-review.googlesource.com/#/c/13770/
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/GenerateJavaScriptAST.java b/dev/core/src/com/google/gwt/dev/jjs/impl/GenerateJavaScriptAST.java
index de8ede7..94a2e99 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/GenerateJavaScriptAST.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/GenerateJavaScriptAST.java
@@ -527,8 +527,6 @@
private final Set<JDeclaredType> alreadyRan = Sets.newLinkedHashSet();
- private final Map<String, Object> exportedMembersByExportName = new TreeMap<String, Object>();
-
private final Map<JDeclaredType, JsFunction> clinitFunctionForType = Maps.newHashMap();
private JMethod currentMethod = null;
@@ -650,8 +648,6 @@
generateTypeSetup(type);
emitFields(type);
-
- collectExports(type);
return null;
}
@@ -1443,41 +1439,69 @@
}
private void generateExports() {
- if (exportedMembersByExportName.isEmpty()) {
- return;
+ Map<String, Object> exportedMembersByExportName = new TreeMap<String, Object>();
+ Set<JDeclaredType> hoistedClinits = Sets.newHashSet();
+ JsInteropExportsGenerator exportGenerator =
+ closureCompilerFormatEnabled
+ ? new ClosureJsInteropExportsGenerator(getGlobalStatements(), names)
+ : new DefaultJsInteropExportsGenerator(
+ getGlobalStatements(), globalTemp, indexedFunctions);
+
+ // Gather exported things in JsNamespace order.
+ for (JDeclaredType type : program.getDeclaredTypes()) {
+ if (type.isJsNative()) {
+ // JsNative types have no implementation and so shouldn't export anything.
+ continue;
+ }
+
+ if (type.isJsType() && !type.getClassDisposition().isLocalType()) {
+ // only types with explicit source names in Java may have an exported prototype
+ exportedMembersByExportName.put(type.getQualifiedJsName(), type);
+ }
+
+ for (JMethod method : type.getMethods()) {
+ if (method.isJsInteropEntryPoint()) {
+ exportedMembersByExportName.put(method.getQualifiedJsName(), method);
+ }
+ }
+
+ for (JField field : type.getFields()) {
+ if (field.isJsInteropEntryPoint()) {
+ if (!field.isFinal()) {
+ logger.log(
+ TreeLogger.Type.WARN,
+ "Exporting effectively non-final field "
+ + field.getQualifiedName()
+ + ". Due to the way exporting works, the value of the"
+ + " exported field will not be reflected across Java/JavaScript border.");
+ }
+ exportedMembersByExportName.put(field.getQualifiedJsName(), field);
+ }
+ }
}
- JsInteropExportsGenerator exportGenerator;
- if (closureCompilerFormatEnabled) {
- exportGenerator = new ClosureJsInteropExportsGenerator(getGlobalStatements(), names);
- } else {
- exportGenerator = new DefaultJsInteropExportsGenerator(getGlobalStatements(), globalTemp,
- indexedFunctions);
- }
-
- Set<JDeclaredType> generatedClinits = Sets.newHashSet();
-
+ // Output the exports.
for (Object exportedEntity : exportedMembersByExportName.values()) {
if (exportedEntity instanceof JDeclaredType) {
exportGenerator.exportType((JDeclaredType) exportedEntity);
} else {
JMember member = (JMember) exportedEntity;
- maybeHoistClinit(generatedClinits, member);
+ maybeHoistClinit(hoistedClinits, member);
exportGenerator.exportMember(member, names.get(member).makeRef(member.getSourceInfo()));
}
}
}
- private void maybeHoistClinit(Set<JDeclaredType> generatedClinits, JMember member) {
+ private void maybeHoistClinit(Set<JDeclaredType> hoistedClinits, JMember member) {
JDeclaredType enclosingType = member.getEnclosingType();
- if (generatedClinits.contains(enclosingType)) {
+ if (hoistedClinits.contains(enclosingType)) {
return;
}
JsInvocation clinitCall = member instanceof JMethod ? maybeCreateClinitCall((JMethod) member)
: maybeCreateClinitCall((JField) member);
if (clinitCall != null) {
- generatedClinits.add(enclosingType);
+ hoistedClinits.add(enclosingType);
getGlobalStatements().add(clinitCall.makeStmt());
}
}
@@ -2363,30 +2387,6 @@
: globalTemp.makeRef(info);
}
- private void collectExports(JDeclaredType type) {
- if (type.isJsType() && !type.getClassDisposition().isLocalType()) {
- // only types with explicit source names in Java may have an exported prototype
- exportedMembersByExportName.put(type.getQualifiedJsName(), type);
- }
-
- for (JMethod method : type.getMethods()) {
- if (method.isJsInteropEntryPoint()) {
- exportedMembersByExportName.put(method.getQualifiedJsName(), method);
- }
- }
-
- for (JField field : type.getFields()) {
- if (field.isJsInteropEntryPoint()) {
- if (!field.isFinal()) {
- logger.log(TreeLogger.Type.WARN, "Exporting effectively non-final field "
- + field.getQualifiedName() + ". Due to the way exporting works, the value of the"
- + " exported field will not be reflected across Java/JavaScript border.");
- }
- exportedMembersByExportName.put(field.getQualifiedJsName(), field);
- }
- }
- }
-
/**
* Returns the package private JsName for {@code method}.
*/
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/JsTypeLinker.java b/dev/core/src/com/google/gwt/dev/jjs/impl/JsTypeLinker.java
index 92cb4c0..6979f11 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/JsTypeLinker.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/JsTypeLinker.java
@@ -112,9 +112,7 @@
for (NamedRange typeRange : typeRanges) {
extractOne(typeRange);
}
- if (minimalRebuildCache.getJs(FOOTER_NAME) == null) {
- extractOne(footerRange);
- }
+ extractOne(footerRange);
// Link new and old JS.
linkOne(HEADER_NAME);
diff --git a/dev/core/test/com/google/gwt/dev/CompilerTest.java b/dev/core/test/com/google/gwt/dev/CompilerTest.java
index e61e312..b7bda62 100644
--- a/dev/core/test/com/google/gwt/dev/CompilerTest.java
+++ b/dev/core/test/com/google/gwt/dev/CompilerTest.java
@@ -453,6 +453,17 @@
" Foo foo = new Foo();",
"}");
+ private MockJavaResource jsTypeBarResource =
+ JavaResourceBase.createMockJavaResource(
+ "com.foo.Bar",
+ "package com.foo;",
+ "import com.google.gwt.core.client.js.JsExport;",
+ "import com.google.gwt.core.client.js.JsType;",
+ "@JsType @JsExport public class Bar {",
+ " void doInstanceBar() {}",
+ " public static void doStaticBaz() {}",
+ "}");
+
private MockJavaResource nonCompilableFooResource =
JavaResourceBase.createMockJavaResource("com.foo.Foo",
"package com.foo;",
@@ -943,6 +954,9 @@
assertEquals(SourceLevel.JAVA7, SourceLevel.getBestMatchingVersion("1.7b3"));
}
+ /**
+ * Verify that a compile with a @JsType at least compiles successfully.
+ */
public void testGwtCreateJsTypeRebindResult() throws Exception {
CompilerOptions compilerOptions = new CompilerOptionsImpl();
compilerOptions.setJsInteropMode(OptionJsInteropMode.Mode.JS);
@@ -1015,6 +1029,310 @@
assertTrue(js.contains("var " + classliteralHolderVarName + " = "));
}
+ /**
+ * Tests that changing @JsNamespace name on an exported method comes out accurately.
+ *
+ * <p>An unrelated and non-updated @JsType is also included in each compile to verify that updated
+ * exports do not forget non-edited items in a recompile.
+ */
+ public void testChangeJsNamespaceOnMethod() throws Exception {
+ CompilerOptions compilerOptions = new CompilerOptionsImpl();
+ compilerOptions.setUseDetailedTypeIds(true);
+ compilerOptions.setJsInteropMode(OptionJsInteropMode.Mode.JS);
+
+ MockJavaResource jsNamespaceFooResource =
+ JavaResourceBase.createMockJavaResource(
+ "com.foo.Foo",
+ "package com.foo;",
+ "import com.google.gwt.core.client.js.JsExport;",
+ "import com.google.gwt.core.client.js.JsNamespace;",
+ "import com.google.gwt.core.client.js.JsType;",
+ "@JsExport public class Foo {",
+ " @JsNamespace(\"spazz\") public static void doStaticBar() {}",
+ "}");
+
+ MockJavaResource regularFooResource =
+ JavaResourceBase.createMockJavaResource(
+ "com.foo.Foo",
+ "package com.foo;",
+ "import com.google.gwt.core.client.js.JsExport;",
+ "import com.google.gwt.core.client.js.JsType;",
+ "@JsExport public class Foo {",
+ " public static void doStaticBar() {}",
+ "}");
+
+ checkRecompiledModifiedApp(
+ compilerOptions,
+ "com.foo.SimpleModule",
+ Lists.newArrayList(simpleModuleResource, emptyEntryPointResource, jsTypeBarResource),
+ regularFooResource,
+ jsNamespaceFooResource,
+ stringSet("com.foo.Bar", "com.foo.Foo"),
+ JsOutputOption.DETAILED);
+ }
+
+ /**
+ * Tests that changing @JsNamespace name on a class comes out accurately.
+ *
+ * <p>An unrelated and non-updated @JsType is also included in each compile to verify that updated
+ * exports do not forget non-edited items in a recompile.
+ */
+ public void testChangeJsNamespaceOnClass() throws Exception {
+ CompilerOptions compilerOptions = new CompilerOptionsImpl();
+ compilerOptions.setUseDetailedTypeIds(true);
+ compilerOptions.setJsInteropMode(OptionJsInteropMode.Mode.JS);
+
+ MockJavaResource jsNamespaceFooResource =
+ JavaResourceBase.createMockJavaResource(
+ "com.foo.Foo",
+ "package com.foo;",
+ "import com.google.gwt.core.client.js.JsExport;",
+ "import com.google.gwt.core.client.js.JsNamespace;",
+ "import com.google.gwt.core.client.js.JsType;",
+ "@JsNamespace(\"spazz\") @JsExport public class Foo {",
+ " public static void doStaticBar() {}",
+ "}");
+
+ MockJavaResource regularFooResource =
+ JavaResourceBase.createMockJavaResource(
+ "com.foo.Foo",
+ "package com.foo;",
+ "import com.google.gwt.core.client.js.JsExport;",
+ "import com.google.gwt.core.client.js.JsType;",
+ "@JsExport public class Foo {",
+ " public static void doStaticBar() {}",
+ "}");
+
+ checkRecompiledModifiedApp(
+ compilerOptions,
+ "com.foo.SimpleModule",
+ Lists.newArrayList(simpleModuleResource, emptyEntryPointResource, jsTypeBarResource),
+ regularFooResource,
+ jsNamespaceFooResource,
+ stringSet("com.foo.Bar", "com.foo.Foo"),
+ JsOutputOption.DETAILED);
+ }
+
+ /**
+ * Tests that changing @JsFunction name on an interface comes out accurately.
+ *
+ * <p>An unrelated and non-updated @JsType is also included in each compile to verify that updated
+ * exports do not forget non-edited items in a recompile.
+ */
+ public void testChangeJsFunction() throws Exception {
+ CompilerOptions compilerOptions = new CompilerOptionsImpl();
+ compilerOptions.setUseDetailedTypeIds(true);
+ compilerOptions.setJsInteropMode(OptionJsInteropMode.Mode.JS);
+
+ MockJavaResource jsFunctionIFooResource =
+ JavaResourceBase.createMockJavaResource(
+ "com.foo.IFoo",
+ "package com.foo;",
+ "import com.google.gwt.core.client.js.JsFunction;",
+ "@JsFunction public interface IFoo {",
+ " int foo(int x);",
+ "}");
+
+ MockJavaResource regularIFooResource =
+ JavaResourceBase.createMockJavaResource(
+ "com.foo.IFoo",
+ "package com.foo;",
+ "public interface IFoo {",
+ " int foo(int x);",
+ "}");
+
+ MockJavaResource fooResource =
+ JavaResourceBase.createMockJavaResource(
+ "com.foo.Foo",
+ "package com.foo;",
+ "import com.google.gwt.core.client.js.JsExport;",
+ "@JsExport public class Foo implements IFoo {",
+ " @Override public int foo(int x) { return 0; }",
+ "}");
+
+ checkRecompiledModifiedApp(
+ compilerOptions,
+ "com.foo.SimpleModule",
+ Lists.newArrayList(
+ simpleModuleResource, emptyEntryPointResource, fooResource, jsTypeBarResource),
+ regularIFooResource,
+ jsFunctionIFooResource,
+ stringSet("com.foo.Bar", "com.foo.Foo", "com.foo.IFoo"),
+ JsOutputOption.DETAILED);
+ }
+
+ /**
+ * Tests that toggling JsProperty methods in an interface comes out accurately.
+ *
+ * <p>An unrelated and non-updated @JsType is also included in each compile to verify that updated
+ * exports do not forget non-edited items in a recompile.
+ */
+ public void testChangeJsProperty() throws Exception {
+ CompilerOptions compilerOptions = new CompilerOptionsImpl();
+ compilerOptions.setUseDetailedTypeIds(true);
+ compilerOptions.setJsInteropMode(OptionJsInteropMode.Mode.JS);
+
+ MockJavaResource jsPropertyIFooResource =
+ JavaResourceBase.createMockJavaResource(
+ "com.foo.IFoo",
+ "package com.foo;",
+ "import com.google.gwt.core.client.js.JsProperty;",
+ "import com.google.gwt.core.client.js.JsType;",
+ "@JsType public interface IFoo {",
+ " @JsProperty int getX();",
+ " @JsProperty int getY();",
+ "}");
+
+ MockJavaResource regularIFooResource =
+ JavaResourceBase.createMockJavaResource(
+ "com.foo.IFoo",
+ "package com.foo;",
+ "import com.google.gwt.core.client.js.JsType;",
+ "@JsType public interface IFoo {",
+ " int getX();",
+ " int getY();",
+ "}");
+
+ MockJavaResource fooResource =
+ JavaResourceBase.createMockJavaResource(
+ "com.foo.Foo",
+ "package com.foo;",
+ "import com.google.gwt.core.client.js.JsExport;",
+ "@JsExport public class Foo implements IFoo {",
+ " @Override public int getX() { return 0; }",
+ " @Override public int getY() { return 0; }",
+ "}");
+
+ checkRecompiledModifiedApp(
+ compilerOptions,
+ "com.foo.SimpleModule",
+ Lists.newArrayList(
+ simpleModuleResource, emptyEntryPointResource, fooResource, jsTypeBarResource),
+ regularIFooResource,
+ jsPropertyIFooResource,
+ stringSet("com.foo.Bar", "com.foo.Foo", "com.foo.IFoo"),
+ JsOutputOption.DETAILED);
+ }
+
+ /**
+ * Tests that adding a @JsType annotation on a class comes out accurately and that removing it
+ * comes out accurately as well.
+ *
+ * <p>An unrelated and non-updated @JsType is also included in each compile to verify that updated
+ * exports do not forget non-edited items in a recompile.
+ */
+ public void testChangeJsType() throws Exception {
+ CompilerOptions compilerOptions = new CompilerOptionsImpl();
+ compilerOptions.setUseDetailedTypeIds(true);
+ compilerOptions.setJsInteropMode(OptionJsInteropMode.Mode.JS);
+
+ MockJavaResource jsTypeFooResource =
+ JavaResourceBase.createMockJavaResource(
+ "com.foo.Foo",
+ "package com.foo;",
+ "import com.google.gwt.core.client.js.JsExport;",
+ "import com.google.gwt.core.client.js.JsType;",
+ "@JsType @JsExport public class Foo {",
+ " void doInstanceBar() {}",
+ " public static void doStaticBar() {}",
+ "}");
+
+ MockJavaResource regularFooResource =
+ JavaResourceBase.createMockJavaResource(
+ "com.foo.Foo", "package com.foo;", "public class Foo {}");
+
+ checkRecompiledModifiedApp(
+ compilerOptions,
+ "com.foo.SimpleModule",
+ Lists.newArrayList(simpleModuleResource, emptyEntryPointResource, jsTypeBarResource),
+ regularFooResource,
+ jsTypeFooResource,
+ stringSet("com.foo.Bar", "com.foo.Foo"),
+ JsOutputOption.DETAILED);
+ }
+
+ /**
+ * Tests that changing a prototype on a @JsType annotated class comes out accurately.
+ *
+ * <p>An unrelated and non-updated @JsType is also included in each compile to verify that updated
+ * exports do not forget non-edited items in a recompile.
+ */
+ public void testChangeJsTypePrototype() throws Exception {
+ CompilerOptions compilerOptions = new CompilerOptionsImpl();
+ compilerOptions.setUseDetailedTypeIds(true);
+ compilerOptions.setJsInteropMode(OptionJsInteropMode.Mode.JS);
+
+ MockJavaResource prototypeFooResource =
+ JavaResourceBase.createMockJavaResource(
+ "com.foo.Foo",
+ "package com.foo;",
+ "import com.google.gwt.core.client.js.JsExport;",
+ "import com.google.gwt.core.client.js.JsType;",
+ "@JsType(prototype = \"window.Date\") @JsExport public class Foo {",
+ " public static void doStaticBar() {}",
+ "}");
+
+ MockJavaResource regularFooResource =
+ JavaResourceBase.createMockJavaResource(
+ "com.foo.Foo",
+ "package com.foo;",
+ "import com.google.gwt.core.client.js.JsExport;",
+ "import com.google.gwt.core.client.js.JsType;",
+ "@JsType @JsExport public class Foo {",
+ " public static void doStaticBar() {}",
+ "}");
+
+ checkRecompiledModifiedApp(
+ compilerOptions,
+ "com.foo.SimpleModule",
+ Lists.newArrayList(simpleModuleResource, emptyEntryPointResource, jsTypeBarResource),
+ regularFooResource,
+ prototypeFooResource,
+ stringSet("com.foo.Bar", "com.foo.Foo"),
+ JsOutputOption.DETAILED);
+ }
+
+ /**
+ * Tests that adding a @JsNoExport annotation on a method comes out accurately and that removing
+ * it comes out accurately as well.
+ *
+ * <p>An unrelated and non-updated @JsType is also included in each compile to verify that updated
+ * exports do not forget non-edited items in a recompile.
+ */
+ public void testChangeJsNoExport() throws Exception {
+ CompilerOptions compilerOptions = new CompilerOptionsImpl();
+ compilerOptions.setUseDetailedTypeIds(true);
+ compilerOptions.setJsInteropMode(OptionJsInteropMode.Mode.JS);
+
+ MockJavaResource jsNoExportFooResource =
+ JavaResourceBase.createMockJavaResource(
+ "com.foo.Foo",
+ "package com.foo;",
+ "import com.google.gwt.core.client.js.JsExport;",
+ "import com.google.gwt.core.client.js.JsNoExport;",
+ "@JsExport public class Foo {",
+ " @JsNoExport public static void doStaticBar() {}",
+ "}");
+
+ MockJavaResource regularFooResource =
+ JavaResourceBase.createMockJavaResource(
+ "com.foo.Foo",
+ "package com.foo;",
+ "import com.google.gwt.core.client.js.JsExport;",
+ "@JsExport public class Foo {",
+ " public static void doStaticBar() {}",
+ "}");
+
+ checkRecompiledModifiedApp(
+ compilerOptions,
+ "com.foo.SimpleModule",
+ Lists.newArrayList(simpleModuleResource, emptyEntryPointResource, jsTypeBarResource),
+ regularFooResource,
+ jsNoExportFooResource,
+ stringSet("com.foo.Bar", "com.foo.Foo"),
+ JsOutputOption.DETAILED);
+ }
+
public void testJsInteropNameCollision() throws Exception {
MinimalRebuildCache minimalRebuildCache = new MinimalRebuildCache();
File applicationDir = Files.createTempDir();
@@ -1928,18 +2246,39 @@
}
}
- private void checkRecompiledModifiedApp(String moduleName, List<MockResource> sharedResources,
- MockResource originalResource, MockResource modifiedResource,
- Set<String> expectedStaleTypeNamesOnModify, JsOutputOption output) throws IOException,
- UnableToCompleteException, InterruptedException {
+ /**
+ * Compiles an initial application with version 1 of file Foo, then recompiles using version 2 of
+ * file Foo. Lastly it performs a final from scratch compile using version 2 of file Foo and
+ * verifies that the recompile and the full compile (both of which used version 2 of file Foo)
+ * come out the same.
+ */
+ private void checkRecompiledModifiedApp(
+ String moduleName,
+ List<MockResource> sharedResources,
+ MockResource originalResource,
+ MockResource modifiedResource,
+ Set<String> expectedStaleTypeNamesOnModify,
+ JsOutputOption output)
+ throws IOException, UnableToCompleteException, InterruptedException {
checkRecompiledModifiedApp(new CompilerOptionsImpl(), moduleName, sharedResources,
originalResource, modifiedResource, expectedStaleTypeNamesOnModify, output);
}
- private void checkRecompiledModifiedApp(CompilerOptions compilerOptions, String moduleName,
- List<MockResource> sharedResources, MockResource originalResource,
- MockResource modifiedResource, Set<String> expectedStaleTypeNamesOnModify,
- JsOutputOption output) throws IOException, UnableToCompleteException, InterruptedException {
+ /**
+ * Compiles an initial application with version 1 of file Foo, then recompiles using version 2 of
+ * file Foo. Lastly it performs a final from scratch compile using version 2 of file Foo and
+ * verifies that the recompile and the full compile (both of which used version 2 of file Foo)
+ * come out the same.
+ */
+ private void checkRecompiledModifiedApp(
+ CompilerOptions compilerOptions,
+ String moduleName,
+ List<MockResource> sharedResources,
+ MockResource originalResource,
+ MockResource modifiedResource,
+ Set<String> expectedStaleTypeNamesOnModify,
+ JsOutputOption output)
+ throws IOException, UnableToCompleteException, InterruptedException {
List<MockResource> originalResources = Lists.newArrayList(sharedResources);
originalResources.add(originalResource);
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 02b5068..2bf9eef 100644
--- a/dev/core/test/com/google/gwt/dev/jjs/JavaAstConstructor.java
+++ b/dev/core/test/com/google/gwt/dev/jjs/JavaAstConstructor.java
@@ -265,18 +265,19 @@
new MockJavaResource("com.google.gwt.lang.Runtime") {
@Override
public CharSequence getContent() {
- return Joiner.on("\n").join(
- "package com.google.gwt.lang;",
- "public class Runtime {",
- " public static Object defineClass(int typeId, int superTypeId, Object map) {",
- " return null;",
- " }",
- " public static void bootstrap() {}",
- " public static void emptyMethod() {}",
- " public static void getClassPrototype() {}",
- " static native void typeMarkerFn() /*-{}-*/;",
- "}"
- );
+ return Joiner.on("\n")
+ .join(
+ "package com.google.gwt.lang;",
+ "public class Runtime {",
+ " public static Object defineClass(int typeId, int superTypeId, Object map) {",
+ " return null;",
+ " }",
+ " public static void provide() {}",
+ " public static void bootstrap() {}",
+ " public static void emptyMethod() {}",
+ " public static void getClassPrototype() {}",
+ " static native void typeMarkerFn() /*-{}-*/;",
+ "}");
}
};