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() /*-{}-*/;", + "}"); } };