JsOptional restriction checks
Change-Id: Ic9ce57e392f9208ae316916bee4e32b4718be45f
diff --git a/dev/core/src/com/google/gwt/dev/javac/JsInteropUtil.java b/dev/core/src/com/google/gwt/dev/javac/JsInteropUtil.java
index 020ce6e..e15b624 100644
--- a/dev/core/src/com/google/gwt/dev/javac/JsInteropUtil.java
+++ b/dev/core/src/com/google/gwt/dev/javac/JsInteropUtil.java
@@ -21,6 +21,7 @@
import com.google.gwt.dev.jjs.ast.JField;
import com.google.gwt.dev.jjs.ast.JMember;
import com.google.gwt.dev.jjs.ast.JMethod;
+import com.google.gwt.dev.jjs.ast.JParameter;
import com.google.gwt.dev.jjs.ast.JPrimitiveType;
import org.eclipse.jdt.internal.compiler.ast.Annotation;
@@ -68,6 +69,12 @@
setJsInteropProperties(method, annotations, annotation, isPropertyAccessor, generateExport);
}
+ public static void maybeSetJsInteropProperties(JParameter parameter, Annotation... annotations) {
+ if (getInteropAnnotation(annotations, "JsOptional") != null) {
+ parameter.setOptional();
+ }
+ }
+
public static void maybeSetJsInteropProperties(
JField field, boolean generateExport, Annotation... annotations) {
AnnotationBinding annotation = getInteropAnnotation(annotations, "JsProperty");
@@ -86,7 +93,8 @@
boolean isPublicMemberForJsType = member.getEnclosingType().isJsType() && member.isPublic();
boolean memberForNativeType = member.getEnclosingType().isJsNative();
- if (!isPublicMemberForJsType && !memberForNativeType && memberAnnotation == null) {
+ if (memberAnnotation == null
+ && (!isPublicMemberForJsType && !memberForNativeType || member.isJsOverlay())) {
return;
}
diff --git a/dev/core/src/com/google/gwt/dev/javac/testing/impl/JavaResourceBase.java b/dev/core/src/com/google/gwt/dev/javac/testing/impl/JavaResourceBase.java
index 37d7e7a..022f114 100644
--- a/dev/core/src/com/google/gwt/dev/javac/testing/impl/JavaResourceBase.java
+++ b/dev/core/src/com/google/gwt/dev/javac/testing/impl/JavaResourceBase.java
@@ -419,6 +419,11 @@
"package jsinterop.annotations;",
"public @interface JsFunction {",
"}");
+ public static final MockJavaResource JSOPTIONAL =
+ createMockJavaResource("jsinterop.annotations.JsOptional",
+ "package jsinterop.annotations;",
+ "public @interface JsOptional {",
+ "}");
public static final MockJavaResource JSOVERLAY =
createMockJavaResource("jsinterop.annotations.JsOverlay",
"package jsinterop.annotations;",
@@ -433,7 +438,7 @@
JAVASCRIPTOBJECT, LIST, LONG, MAP, NO_CLASS_DEF_FOUND_ERROR, NUMBER, OBJECT,
RUNTIME_EXCEPTION, SERIALIZABLE, SHORT, STRING, STRING_BUILDER, SUPPRESS_WARNINGS, SYSTEM,
THROWABLE, SPECIALIZE_METHOD, JSTYPE, JSCONSTRUCTOR, JSPACKAGE, JSPROPERTY, JSMETHOD,
- JSIGNORE, JSFUNCTION, JSOVERLAY};
+ JSIGNORE, JSFUNCTION, JSOVERLAY, JSOPTIONAL};
}
/**
diff --git a/dev/core/src/com/google/gwt/dev/jjs/ast/JMethod.java b/dev/core/src/com/google/gwt/dev/jjs/ast/JMethod.java
index fbfbd0a..8615cae 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/ast/JMethod.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/ast/JMethod.java
@@ -99,14 +99,14 @@
* Adds a new final parameter to this method.
*/
public JParameter createFinalParameter(SourceInfo info, String name, JType type) {
- return createParameter(info, name, type, true, false, false);
+ return createParameter(info, name, type, true, false, false, false);
}
/**
* Adds a new parameter to this method.
*/
public JParameter createParameter(SourceInfo info, String name, JType type) {
- return createParameter(info, name, type, false, false, false);
+ return createParameter(info, name, type, false, false, false, false);
}
/**
@@ -114,7 +114,7 @@
*/
public JParameter createParameter(SourceInfo info, String name, JType type, boolean isFinal,
boolean isVarargs) {
- return createParameter(info, name, type, isFinal, isVarargs, false);
+ return createParameter(info, name, type, isFinal, isVarargs, false, false);
}
/**
@@ -122,22 +122,22 @@
*/
public JParameter cloneParameter(JParameter from) {
return createParameter(from.getSourceInfo(), from.getName(), from.getType(), from.isFinal(),
- from.isVarargs(), from.isThis());
+ from.isVarargs(), from.isThis(), from.isOptional());
}
/**
* Creates a parameter to hold the value of this in devirtualized methods.
*/
public JParameter createThisParameter(SourceInfo info, JType type) {
- return createParameter(info, "this$static", type, true, false, true);
+ return createParameter(info, "this$static", type, true, false, true, false);
}
private JParameter createParameter(SourceInfo info, String name, JType type,
- boolean isFinal, boolean isVarargs, boolean isThis) {
+ boolean isFinal, boolean isVarargs, boolean isThis, boolean isOptional) {
assert (name != null);
assert (type != null);
- JParameter parameter = new JParameter(info, name, type, isFinal, isVarargs, isThis);
+ JParameter parameter = new JParameter(info, name, type, isFinal, isVarargs, isThis, isOptional);
addParameter(parameter);
return parameter;
}
diff --git a/dev/core/src/com/google/gwt/dev/jjs/ast/JParameter.java b/dev/core/src/com/google/gwt/dev/jjs/ast/JParameter.java
index 4fa7e00..ca9e7c1 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/ast/JParameter.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/ast/JParameter.java
@@ -24,22 +24,32 @@
private final boolean isThis;
private final boolean isVarags;
+ private boolean isOptional;
public JParameter(SourceInfo info, String name, JType type, boolean isFinal) {
- this(info, name, type, isFinal, false, false);
+ this(info, name, type, isFinal, false, false, false);
}
JParameter(SourceInfo info, String name, JType type, boolean isFinal, boolean isVarargs,
- boolean isThis) {
+ boolean isThis, boolean isOptional) {
super(info, name, type, isFinal);
this.isThis = isThis;
this.isVarags = isVarargs;
+ this.isOptional = isOptional;
assert !isVarargs || type.isArrayType();
}
public JParameterRef createRef(SourceInfo info) {
return new JParameterRef(info, this);
}
+
+ /**
+ * Returns <code>true</code> if this parameter marked as optional.
+ */
+ public boolean isOptional() {
+ return isOptional;
+ }
+
/**
* Returns <code>true</code> if this parameter is the this parameter of a
* static impl method.
@@ -55,6 +65,10 @@
return isVarags;
}
+ public void setOptional() {
+ isOptional = true;
+ }
+
@Override
public JParameterRef makeRef(SourceInfo info) {
return new JParameterRef(info, this);
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/GwtAstBuilder.java b/dev/core/src/com/google/gwt/dev/jjs/impl/GwtAstBuilder.java
index dd5e9ec..6debb78 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/GwtAstBuilder.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/GwtAstBuilder.java
@@ -4081,6 +4081,11 @@
processSuppressedWarnings(method, x.annotations);
}
+ private void processAnnotations(JParameter parameter, Annotation... annotations) {
+ JsInteropUtil.maybeSetJsInteropProperties(parameter, annotations);
+ processSuppressedWarnings(parameter, annotations);
+ }
+
private void processSuppressedWarnings(CanHaveSuppressedWarnings x, Annotation... annotations) {
x.setSuppressedWarnings(JdtUtil.getSuppressedWarnings(annotations));
}
@@ -4137,9 +4142,9 @@
private void createParameter(SourceInfo info, LocalVariableBinding binding, String name,
JMethod method, boolean isVarargs, Annotation... annotations) {
- JParameter param =
+ JParameter parameter =
method.createParameter(info, name, typeMap.get(binding.type), binding.isFinal(), isVarargs);
- processSuppressedWarnings(param, annotations);
+ processAnnotations(parameter, annotations);
}
private void createParameters(JMethod method, AbstractMethodDeclaration x) {
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/JsInteropRestrictionChecker.java b/dev/core/src/com/google/gwt/dev/jjs/impl/JsInteropRestrictionChecker.java
index 583431e..0a0139f 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/JsInteropRestrictionChecker.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/JsInteropRestrictionChecker.java
@@ -243,6 +243,10 @@
checkIllegalOverrides(member);
}
+ if (member instanceof JMethod) {
+ checkMethodParameters((JMethod) member);
+ }
+
if (member.isJsOverlay()) {
checkJsOverlay(member);
return;
@@ -260,10 +264,6 @@
return;
}
- if (member.isJsMethodVarargs()) {
- checkJsVarargs(member);
- }
-
checkMemberQualifiedJsName(member);
if (isCheckedLocalName(member)) {
@@ -307,33 +307,38 @@
return;
}
- String methodDescription = JjsUtils.getReadableDescription(member);
+ String memberDescription = JjsUtils.getReadableDescription(member);
if (!member.getEnclosingType().isJsNative()) {
- logError(member, "JsOverlay '%s' can only be declared in a native type.", methodDescription);
+ logError(member, "JsOverlay '%s' can only be declared in a native type.", memberDescription);
+ }
+
+ if (member instanceof JConstructor) {
+ logError(member, "JsOverlay method '%s' cannot be a constructor.", memberDescription);
+ return;
+ }
+
+ if (member.getJsMemberType() != JsMemberType.NONE) {
+ logError(member, "JsOverlay method '%s' cannot be nor override a JsProperty or a JsMethod.",
+ memberDescription);
+ return;
}
if (member instanceof JField) {
JField field = (JField) member;
if (field.needsDynamicDispatch()) {
- logError(
- member, "JsOverlay field '%s' can only be static.",
- methodDescription);
+ logError(member, "JsOverlay field '%s' can only be static.", memberDescription);
}
return;
}
JMethod method = (JMethod) member;
- if (!method.getOverriddenMethods().isEmpty()) {
- logError(member,
- "JsOverlay method '%s' cannot override a supertype method.", methodDescription);
- return;
- }
+
+ assert method.getOverriddenMethods().isEmpty();
if (method.getBody() == null || (!method.isFinal() && !method.isStatic()
&& !method.isDefaultMethod())) {
- logError(member,
- "JsOverlay method '%s' cannot be non-final nor native.", methodDescription);
+ logError(member, "JsOverlay method '%s' cannot be non-final nor native.", memberDescription);
}
}
@@ -380,8 +385,34 @@
}
}
- private void checkJsVarargs(JMember member) {
- final JMethod method = (JMethod) member;
+ private void checkMethodParameters(JMethod method) {
+ boolean hasOptionalParameters = false;
+ for (JParameter parameter : method.getParams()) {
+ if (parameter.isOptional()) {
+ hasOptionalParameters = true;
+ continue;
+ }
+ if (hasOptionalParameters && !parameter.isVarargs()) {
+ logError(method, "JsOptional parameter '%s' in method %s cannot precede parameters "
+ + "that are not optional.", parameter.getName(), getMemberDescription(method));
+ break;
+ }
+ }
+
+ if (hasOptionalParameters
+ && method.getJsMemberType() != JsMemberType.CONSTRUCTOR
+ && method.getJsMemberType() != JsMemberType.METHOD
+ && !method.isOrOverridesJsFunctionMethod()) {
+ logError(method, "Method %s has JsOptional parameters and is not a JsMethod, "
+ + "a JsConstructor or a JsFunction method.", getMemberDescription(method));
+ }
+
+ if (method.isJsMethodVarargs()) {
+ checkJsVarargs(method);
+ }
+ }
+
+ private void checkJsVarargs(final JMethod method) {
if (!method.isJsniMethod()) {
return;
}
diff --git a/dev/core/test/com/google/gwt/dev/jjs/impl/JsInteropRestrictionCheckerTest.java b/dev/core/test/com/google/gwt/dev/jjs/impl/JsInteropRestrictionCheckerTest.java
index eb92871..3a35948 100644
--- a/dev/core/test/com/google/gwt/dev/jjs/impl/JsInteropRestrictionCheckerTest.java
+++ b/dev/core/test/com/google/gwt/dev/jjs/impl/JsInteropRestrictionCheckerTest.java
@@ -1496,7 +1496,84 @@
+ " method 'void EntryPoint.Interface.someOtherMethod()'.");
}
- public void testJsOverlayOnNativeJsTypeInterfaceSucceds() throws Exception {
+ public void testJsOptionalSucceeds() throws Exception {
+ addSnippetImport("jsinterop.annotations.JsConstructor");
+ addSnippetImport("jsinterop.annotations.JsMethod");
+ addSnippetImport("jsinterop.annotations.JsOptional");
+ addSnippetClassDecl(
+ "public static class Buggy {",
+ " @JsConstructor public Buggy(@JsOptional Object a) {}",
+ " @JsMethod public void fun(int a, Object b, @JsOptional String c) {}",
+ " @JsMethod public void bar(int a, @JsOptional Object b, @JsOptional String c) {}",
+ " @JsMethod public void baz(@JsOptional int a, @JsOptional Object b) {}",
+ "}");
+
+ assertBuggySucceeds();
+ }
+
+ public void testJsOptionalWithVarargsSucceeds() throws Exception {
+ addSnippetImport("jsinterop.annotations.JsMethod");
+ addSnippetImport("jsinterop.annotations.JsOptional");
+ addSnippetClassDecl(
+ "public class Buggy {",
+ " @JsMethod public void fun(@JsOptional String c, Object... os) {}",
+ " @JsMethod public void bar(int a, @JsOptional Object b, @JsOptional String... c) {}",
+ "}");
+
+ assertBuggySucceeds();
+ }
+
+ public void testJsOptionalNotAtEndFails() throws Exception {
+ addSnippetImport("jsinterop.annotations.JsConstructor");
+ addSnippetImport("jsinterop.annotations.JsMethod");
+ addSnippetImport("jsinterop.annotations.JsOptional");
+ addSnippetClassDecl(
+ "public static class Buggy {",
+ " @JsConstructor public Buggy(@JsOptional int a, Object b, @JsOptional String c) {}",
+ " @JsMethod public void bar(int a, @JsOptional Object b, String c) {}",
+ " @JsMethod public void baz(@JsOptional Object b, String c, Object... os) {}",
+ "}");
+
+ assertBuggyFails(
+ "Line 7: JsOptional parameter 'b' in method 'EntryPoint.Buggy.EntryPoint$Buggy(int, "
+ + "Object, String)' cannot precede parameters that are not optional.",
+ "Line 8: JsOptional parameter 'c' in method 'void EntryPoint.Buggy.bar(int, Object,"
+ + " String)' cannot precede parameters that are not optional.",
+ "Line 9: JsOptional parameter 'c' in method 'void EntryPoint.Buggy.baz(Object, String, "
+ + "Object[])' cannot precede parameters that are not optional.");
+ }
+
+ public void testJsOptionalOnNonJsExposedMethodsFails() throws Exception {
+ addSnippetImport("jsinterop.annotations.JsProperty");
+ addSnippetImport("jsinterop.annotations.JsOptional");
+ addSnippetClassDecl(
+ "public static class Buggy {",
+ " public void fun(int a, @JsOptional Object b, @JsOptional String c) {}",
+ " @JsProperty public void setBar(@JsOptional Object o) {}",
+ "}");
+
+ assertBuggyFails(
+ "Line 6: Method 'void EntryPoint.Buggy.fun(int, Object, String)' has JsOptional parameters "
+ + "and is not a JsMethod, a JsConstructor or a JsFunction method.",
+ "Line 7: Method 'void EntryPoint.Buggy.setBar(Object)' has JsOptional parameters and is "
+ + "not a JsMethod, a JsConstructor or a JsFunction method.");
+ }
+
+ public void testJsOptionalOnJsOverlayMethodsFails() throws Exception {
+ addSnippetImport("jsinterop.annotations.JsType");
+ addSnippetImport("jsinterop.annotations.JsOptional");
+ addSnippetImport("jsinterop.annotations.JsOverlay");
+ addSnippetClassDecl(
+ "@JsType(isNative = true) public static class Buggy {",
+ " @JsOverlay public final void fun(@JsOptional Object a) {}",
+ "}");
+
+ assertBuggyFails(
+ "Line 7: Method 'void EntryPoint.Buggy.fun(Object)' has JsOptional parameters and "
+ + "is not a JsMethod, a JsConstructor or a JsFunction method.");
+ }
+
+ public void testJsOverlayOnNativeJsTypeInterfaceSucceeds() throws Exception {
addSnippetImport("jsinterop.annotations.JsType");
addSnippetImport("jsinterop.annotations.JsOverlay");
addSnippetClassDecl(
@@ -1538,8 +1615,8 @@
" @JsOverlay public void m() { }",
"}");
- assertBuggyFails("Line 9: JsOverlay method 'void EntryPoint.Buggy.m()' cannot override a "
- + "supertype method.");
+ assertBuggyFails("Line 9: JsOverlay method 'void EntryPoint.Buggy.m()' cannot be nor override"
+ + " a JsProperty or a JsMethod.");
}
public void testJsOverlayOverridingSuperclassMethodFails() {
@@ -1553,8 +1630,8 @@
" @JsOverlay public void m() { }",
"}");
- assertBuggyFails(
- "Line 9: JsOverlay method 'void EntryPoint.Buggy.m()' cannot override a supertype method.");
+ assertBuggyFails("Line 9: JsOverlay method 'void EntryPoint.Buggy.m()' cannot be nor override"
+ + " a JsProperty or a JsMethod.");
}
public void testJsOverlayOnNonFinalMethodAndInstanceFieldFails() {
@@ -1614,6 +1691,33 @@
assertBuggySucceeds();
}
+ public void testJsOverlayOnJsMemberFails() {
+ addSnippetImport("jsinterop.annotations.JsType");
+ addSnippetImport("jsinterop.annotations.JsOverlay");
+ addSnippetImport("jsinterop.annotations.JsMethod");
+ addSnippetImport("jsinterop.annotations.JsProperty");
+ addSnippetImport("jsinterop.annotations.JsConstructor");
+ addSnippetClassDecl(
+ "@JsType(isNative=true) public static class Buggy {",
+ " @JsOverlay public Buggy() { }",
+ " @JsOverlay @JsConstructor protected Buggy(int i) { }",
+ " @JsMethod @JsOverlay public final void m() { }",
+ " @JsMethod @JsOverlay public static void n() { }",
+ " @JsProperty @JsOverlay public static void setA(String value) { }",
+ "}");
+
+ assertBuggyFails(
+ "Line 9: JsOverlay method 'EntryPoint.Buggy.EntryPoint$Buggy()' cannot be a constructor.",
+ "Line 10: JsOverlay method 'EntryPoint.Buggy.EntryPoint$Buggy(int)' cannot be a "
+ + "constructor.",
+ "Line 11: JsOverlay method 'void EntryPoint.Buggy.m()' cannot be nor override"
+ + " a JsProperty or a JsMethod.",
+ "Line 12: JsOverlay method 'void EntryPoint.Buggy.n()' cannot be nor override"
+ + " a JsProperty or a JsMethod.",
+ "Line 13: JsOverlay method 'void EntryPoint.Buggy.setA(String)' cannot be nor override"
+ + " a JsProperty or a JsMethod.");
+ }
+
public void testImplicitJsOverlayOnJsoMethodSucceeds() throws Exception {
addSnippetImport("com.google.gwt.core.client.JavaScriptObject");
addSnippetImport("jsinterop.annotations.JsOverlay");