Fix implementation of @JsExport on constructors and add test cases.
When @JsExport is annotated on a class:
1. If the class has only one non-private constructor
(explicit or implicit), the constructor is exported automatically.
2. If the class has more than one non-private constructor,
each non-private constructor has to be annotated by
@JsExport("foo") or @JsNoExport.
Change-Id: I1d6ae14193ac7164d16d966a517b4d2155885e96
diff --git a/dev/core/src/com/google/gwt/dev/javac/JSORestrictionsChecker.java b/dev/core/src/com/google/gwt/dev/javac/JSORestrictionsChecker.java
index 45b800c..18503fb 100644
--- a/dev/core/src/com/google/gwt/dev/javac/JSORestrictionsChecker.java
+++ b/dev/core/src/com/google/gwt/dev/javac/JSORestrictionsChecker.java
@@ -19,6 +19,7 @@
import com.google.gwt.dev.util.InstalledHelpInfo;
import com.google.gwt.dev.util.collect.Stack;
import com.google.gwt.thirdparty.guava.common.base.Strings;
+import com.google.gwt.thirdparty.guava.common.collect.Lists;
import org.eclipse.jdt.core.compiler.CharOperation;
import org.eclipse.jdt.internal.compiler.ast.ASTNode;
@@ -41,6 +42,7 @@
import org.eclipse.jdt.internal.compiler.lookup.TypeBinding;
import java.util.HashMap;
+import java.util.List;
import java.util.Map;
/**
@@ -67,6 +69,8 @@
+ "static final fields in public classes.";
public static final String ERR_EITHER_JSEXPORT_JSNOEXPORT =
"@JsExport and @JsNoExport is not allowed at the same time.";
+ public static final String ERR_EXPLICIT_JSEXPORT_OR_JSNOEXPORT_ON_CONSTRUCTORS =
+ "@JsExport or @JsNoExport should be set on constructors if a class is @JsExported and has more than one non private constructors";
public static final String ERR_JSPROPERTY_ONLY_BEAN_OR_FLUENT_STYLE_NAMING =
"@JsProperty is only allowed on JavaBean-style or fluent-style named methods";
public static final String ERR_JSEXPORT_ON_ENUMERATION =
@@ -265,6 +269,35 @@
}
}
+ private void checkJsExport(ReferenceBinding rb) {
+ if (JdtUtil.getAnnotation(rb, JsInteropUtil.JSEXPORT_CLASS) == null) {
+ return;
+ }
+ List<MethodBinding> publicConstructors = getPublicConstructors(rb);
+ if (publicConstructors.size() <= 1) {
+ return;
+ }
+ for (MethodBinding mb : publicConstructors) {
+ AnnotationBinding jsexportAnnotation =
+ JdtUtil.getAnnotation(mb, JsInteropUtil.JSEXPORT_CLASS);
+ AnnotationBinding jsnoexportAnnotation =
+ JdtUtil.getAnnotation(mb, JsInteropUtil.JSNOEXPORT_CLASS);
+ if (jsexportAnnotation == null && jsnoexportAnnotation == null) {
+ errorOn(mb, ERR_EXPLICIT_JSEXPORT_OR_JSNOEXPORT_ON_CONSTRUCTORS);
+ }
+ }
+ }
+
+ private List<MethodBinding> getPublicConstructors(ReferenceBinding rb) {
+ List<MethodBinding> publicConstructors = Lists.newArrayList();
+ for (MethodBinding mb : rb.methods()) {
+ if (mb.isConstructor() && mb.isPublic()) {
+ publicConstructors.add(mb);
+ }
+ }
+ return publicConstructors;
+ }
+
private void checkJsExport(MethodBinding mb) {
if (JdtUtil.getAnnotation(mb, JsInteropUtil.JSEXPORT_CLASS) != null) {
boolean isStatic = mb.isConstructor() || mb.isStatic();
@@ -349,7 +382,7 @@
private ClassState checkType(TypeDeclaration type) {
SourceTypeBinding binding = type.binding;
-
+ checkJsExport(binding);
if (isJsType(type.binding)) {
checkJsType(type, type.binding);
return ClassState.JSTYPE;
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 17d2dd5..7148b4e 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
@@ -2739,31 +2739,16 @@
String lastProvidedNamespace = null;
boolean createdClinit = false;
- // export 1 constructor
- JConstructor ctor = null;
for (JMethod m : x.getMethods()) {
- if (m instanceof JConstructor) {
- if (!((JConstructor) m).isDefaultConstructor()
- && typeOracle.isExportedMethod(m)) {
- ctor = (JConstructor) m;
- break;
- }
- }
- }
-
- for (JMethod m : x.getMethods()) {
- if (m instanceof JConstructor && m != ctor) {
- continue;
- }
// static functions or constructors may be exported
- if (m == ctor && !m.isPrivate() ||
- (m.isStatic()) && typeOracle.isExportedMethod(m)) {
+ if (typeOracle.isExportedMethod(m)) {
createdClinit = maybeHoistClinit(exportStmts, createdClinit, maybeCreateClinitCall(m));
- JsExpression exportRhs = createJsInteropBridgeMethod(m,
- names.get(m).makeRef(m.getSourceInfo()));
+ JsExpression exportRhs =
+ createJsInteropBridgeMethod(m, names.get(m).makeRef(m.getSourceInfo()));
String exportName = m.getQualifiedExportName();
- lastProvidedNamespace = exportMember(x, globalStmts, lastProvidedNamespace, exportRhs, exportName);
+ lastProvidedNamespace =
+ exportMember(x, globalStmts, lastProvidedNamespace, exportRhs, exportName);
}
}
@@ -2774,11 +2759,12 @@
+ " is discouraged. Due to the way exporting works, the value of the exported field"
+ " will not be reflected across Java&JavaScript border.");
}
- createdClinit = maybeHoistClinit(exportStmts, createdClinit,
- maybeCreateClinitCall(f, true));
+ createdClinit =
+ maybeHoistClinit(exportStmts, createdClinit, maybeCreateClinitCall(f, true));
JsNameRef exportRhs = names.get(f).makeRef(f.getSourceInfo());
String exportName = f.getQualifiedExportName();
- lastProvidedNamespace = exportMember(x, globalStmts, lastProvidedNamespace, exportRhs, exportName);
+ lastProvidedNamespace =
+ exportMember(x, globalStmts, lastProvidedNamespace, exportRhs, exportName);
}
}
}
diff --git a/dev/core/test/com/google/gwt/dev/javac/JSORestrictionsTest.java b/dev/core/test/com/google/gwt/dev/javac/JSORestrictionsTest.java
index 81f6b24..efefe4e 100644
--- a/dev/core/test/com/google/gwt/dev/javac/JSORestrictionsTest.java
+++ b/dev/core/test/com/google/gwt/dev/javac/JSORestrictionsTest.java
@@ -433,6 +433,77 @@
shouldGenerateError(buggyCode, "Line 3: " + JSORestrictionsChecker.ERR_JSEXPORT_ON_ENUMERATION);
}
+ public void testJsExportOnConstructors() {
+ StringBuilder goodCode = new StringBuilder();
+ goodCode.append("import com.google.gwt.core.client.js.JsExport;\n");
+ goodCode.append("public class Buggy {\n");
+ // A constructor JsExported without explicit symbol is fine here.
+ // Leave it to NameConflictionChecker.
+ goodCode.append(" @JsExport public Buggy() { }\n");
+ goodCode.append(" @JsExport(\"buggy1\") public Buggy(int a) { }\n");
+ goodCode.append(" public Buggy(int a, int b) { }\n");
+ goodCode.append("}");
+
+ shouldGenerateNoError(goodCode);
+ }
+
+ public void testJsExportOnClassWithDefaultConstructor() {
+ StringBuilder goodCode = new StringBuilder();
+ goodCode.append("import com.google.gwt.core.client.js.JsExport;\n");
+ goodCode.append("@JsExport public class Buggy {}");
+
+ shouldGenerateNoError(goodCode);
+ }
+
+ public void testJsExportOnClassWithExplicitConstructor() {
+ StringBuilder goodCode = new StringBuilder();
+ goodCode.append("import com.google.gwt.core.client.js.JsExport;\n");
+ goodCode.append("@JsExport public class Buggy {\n");
+ goodCode.append(" public Buggy() { }");
+ goodCode.append("}");
+
+ shouldGenerateNoError(goodCode);
+ }
+
+ public void testJsExportOnClassWithOnePublicConstructor() {
+ StringBuilder goodCode = new StringBuilder();
+ goodCode.append("import com.google.gwt.core.client.js.JsExport;\n");
+ goodCode.append("@JsExport public class Buggy {\n");
+ goodCode.append(" public Buggy() { }\n");
+ goodCode.append(" private Buggy(int a) { }\n");
+ goodCode.append(" protected Buggy(int a, int b) { }\n");
+ goodCode.append(" Buggy(int a, int b, int c) { }\n");
+ goodCode.append("}");
+
+ shouldGenerateNoError(goodCode);
+ }
+
+ public void testJsExportOnClassWithMultipleConstructors() {
+ StringBuilder goodCode = new StringBuilder();
+ goodCode.append("import com.google.gwt.core.client.js.JsExport;\n");
+ goodCode.append("import com.google.gwt.core.client.js.JsNoExport;\n");
+ goodCode.append("@JsExport public class Buggy {\n");
+ goodCode.append(" @JsExport(\"Buggy1\") public Buggy() { }\n");
+ goodCode.append(" @JsExport(\"Buggy2\") public Buggy(int a) { }\n");
+ goodCode.append(" @JsExport public Buggy(int a, int b) { }\n");
+ goodCode.append(" @JsNoExport public Buggy(int a, int b, int c) { }\n");
+ goodCode.append("}");
+
+ shouldGenerateNoError(goodCode);
+ }
+
+ public void testJsExportNotOnClassWithMultipleConstructors() {
+ StringBuilder buggyCode = new StringBuilder();
+ buggyCode.append("import com.google.gwt.core.client.js.JsExport;\n");
+ buggyCode.append("@JsExport public class Buggy {\n");
+ buggyCode.append(" public Buggy() { }\n");
+ buggyCode.append(" @JsExport(\"foo\") public Buggy(int a) { }\n");
+ buggyCode.append("}");
+
+ shouldGenerateError(buggyCode, "Line 3: "
+ + JSORestrictionsChecker.ERR_EXPLICIT_JSEXPORT_OR_JSNOEXPORT_ON_CONSTRUCTORS);
+ }
+
public void testJsExportNotOnNonPublicClass() {
StringBuilder buggyCode = new StringBuilder();
buggyCode.append("import com.google.gwt.core.client.js.JsExport;\n");
diff --git a/user/test/com/google/gwt/core/client/interop/JsExportTest.java b/user/test/com/google/gwt/core/client/interop/JsExportTest.java
index 4681da0..57c6475 100644
--- a/user/test/com/google/gwt/core/client/interop/JsExportTest.java
+++ b/user/test/com/google/gwt/core/client/interop/JsExportTest.java
@@ -95,6 +95,58 @@
return obj.getInstance();
}-*/;
+ public void testExportClass_implicitConstructor() {
+ assertNotNull(createMyExportedClassWithImplicitConstructor());
+ }
+
+ private native Object createMyExportedClassWithImplicitConstructor() /*-{
+ return new $wnd.woo.MyExportedClassWithImplicitConstructor();
+ }-*/;
+
+ public void testExportClass_multipleConstructors() {
+ assertEquals(3, getSumByDefaultConstructor());
+ assertEquals(30, getSumByConstructor());
+ }
+
+ private native int getSumByDefaultConstructor() /*-{
+ var obj = new $wnd.MyClassConstructor1();
+ return obj.sum();
+ }-*/;
+
+ private native int getSumByConstructor() /*-{
+ var obj = new $wnd.MyClassConstructor2(10, 20);
+ return obj.sum();
+ }-*/;
+
+ public void testExportClass_instanceOf() {
+ assertTrue(createMyExportedClassWithMultipleConstructors1()
+ instanceof MyExportedClassWithMultipleConstructors);
+ assertTrue(createMyExportedClassWithMultipleConstructors2()
+ instanceof MyExportedClassWithMultipleConstructors);
+ }
+
+ private native Object createMyExportedClassWithMultipleConstructors1() /*-{
+ return new $wnd.MyClassConstructor1();
+ }-*/;
+
+ private native Object createMyExportedClassWithMultipleConstructors2() /*-{
+ return new $wnd.MyClassConstructor2(10, 20);
+ }-*/;
+
+ public void testExportConstructors() {
+ assertEquals(4, getFooByConstructorWithExportSymbol());
+ assertNull(getNotExportedConstructor());
+ }
+
+ private native int getFooByConstructorWithExportSymbol() /*-{
+ var obj = new $wnd.MyClassExportsConstructors1(2);
+ return obj.foo();
+ }-*/;
+
+ private native Object getNotExportedConstructor() /*-{
+ return $wnd.woo.MyClassExportsConstructors;
+ }-*/;
+
public void testExportedField() {
assertEquals(100, MyExportedClass.EXPORTED_1);
assertEquals(100, getExportedField());
diff --git a/user/test/com/google/gwt/core/client/interop/MyClassExportsConstructors.java b/user/test/com/google/gwt/core/client/interop/MyClassExportsConstructors.java
new file mode 100644
index 0000000..2384b65
--- /dev/null
+++ b/user/test/com/google/gwt/core/client/interop/MyClassExportsConstructors.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2015 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.core.client.interop;
+
+import com.google.gwt.core.client.js.JsExport;
+import com.google.gwt.core.client.js.JsType;
+
+/**
+ * A test class that exhibits a variety of @JsExports on constructors.
+ */
+@JsType
+public class MyClassExportsConstructors {
+ private int a;
+
+ @JsExport("MyClassExportsConstructors1")
+ public MyClassExportsConstructors(int a) {
+ this.a = a;
+ }
+
+ public MyClassExportsConstructors() {
+ a = 1;
+ }
+
+ public int foo() {
+ return a * 2;
+ }
+}
diff --git a/user/test/com/google/gwt/core/client/interop/MyExportedClassWithImplicitConstructor.java b/user/test/com/google/gwt/core/client/interop/MyExportedClassWithImplicitConstructor.java
new file mode 100644
index 0000000..f4d62a5
--- /dev/null
+++ b/user/test/com/google/gwt/core/client/interop/MyExportedClassWithImplicitConstructor.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2015 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.core.client.interop;
+
+import com.google.gwt.core.client.js.JsExport;
+
+/**
+ * A class which only has implicit default constructor and is annotated by "JsExport".
+ * Its default constructor is exported automatically.
+ */
+@JsExport
+public class MyExportedClassWithImplicitConstructor {
+
+}
diff --git a/user/test/com/google/gwt/core/client/interop/MyExportedClassWithMultipleConstructors.java b/user/test/com/google/gwt/core/client/interop/MyExportedClassWithMultipleConstructors.java
new file mode 100644
index 0000000..8bc60fc
--- /dev/null
+++ b/user/test/com/google/gwt/core/client/interop/MyExportedClassWithMultipleConstructors.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2015 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.core.client.interop;
+
+import com.google.gwt.core.client.js.JsExport;
+import com.google.gwt.core.client.js.JsType;
+
+/**
+ * A class which has two public constructors and is annotated by "JsExport". These two constructors
+ * have to be annotated by "JsExport" with explicit symbol or by "JsNoExport".
+ */
+@JsExport
+@JsType
+public class MyExportedClassWithMultipleConstructors {
+ public int a;
+ public int b;
+
+ @JsExport("MyClassConstructor1")
+ public MyExportedClassWithMultipleConstructors() {
+ a = 1;
+ b = 2;
+ }
+
+ @JsExport("MyClassConstructor2")
+ public MyExportedClassWithMultipleConstructors(int a, int b) {
+ this.a = a;
+ this.b = b;
+ }
+
+ public int sum() {
+ return a + b;
+ }
+}
diff --git a/user/test/com/google/gwt/core/client/interop/StaticInitializerVirtualMethod.java b/user/test/com/google/gwt/core/client/interop/StaticInitializerVirtualMethod.java
index b603d96..2cd3676 100644
--- a/user/test/com/google/gwt/core/client/interop/StaticInitializerVirtualMethod.java
+++ b/user/test/com/google/gwt/core/client/interop/StaticInitializerVirtualMethod.java
@@ -29,7 +29,6 @@
return STATIC;
}
- // only explicit constructors are exported
public StaticInitializerVirtualMethod() {
}
}