Add support for JsInterop varargs.
Java 8 JsFunction lambda varargs are not yet supported; will be
done in a follow up patch.
Change-Id: Icb1a6ad47d2264f4a11def3b187d4d49e7b63f60
diff --git a/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java b/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java
index d3144d0..888466d 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java
@@ -82,6 +82,7 @@
import com.google.gwt.dev.jjs.impl.HandleCrossFragmentReferences;
import com.google.gwt.dev.jjs.impl.ImplementCastsAndTypeChecks;
import com.google.gwt.dev.jjs.impl.ImplementClassLiteralsAsFields;
+import com.google.gwt.dev.jjs.impl.ImplementJsVarargs;
import com.google.gwt.dev.jjs.impl.JavaAstVerifier;
import com.google.gwt.dev.jjs.impl.JavaToJavaScriptMap;
import com.google.gwt.dev.jjs.impl.JjsUtils;
@@ -504,6 +505,7 @@
}
ImplementCastsAndTypeChecks.exec(jprogram, shouldOptimize() /* pruneTrivialCasts */);
+ ImplementJsVarargs.exec(jprogram);
ArrayNormalizer.exec(jprogram);
EqualityNormalizer.exec(jprogram);
diff --git a/dev/core/src/com/google/gwt/dev/jjs/ast/HasJsInfo.java b/dev/core/src/com/google/gwt/dev/jjs/ast/HasJsInfo.java
index 262b81f..6eb4092 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/ast/HasJsInfo.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/ast/HasJsInfo.java
@@ -124,6 +124,8 @@
boolean isJsNative();
+ boolean isJsMethodVarargs();
+
boolean isJsOverlay();
boolean canBeReferencedExternally();
diff --git a/dev/core/src/com/google/gwt/dev/jjs/ast/JField.java b/dev/core/src/com/google/gwt/dev/jjs/ast/JField.java
index 02cf508..be6caaa 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/ast/JField.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/ast/JField.java
@@ -200,6 +200,11 @@
}
@Override
+ public boolean isJsMethodVarargs() {
+ return false;
+ }
+
+ @Override
public String getJsName() {
return jsName;
}
diff --git a/dev/core/src/com/google/gwt/dev/jjs/ast/JLocal.java b/dev/core/src/com/google/gwt/dev/jjs/ast/JLocal.java
index 15c96c4..86c6660 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/ast/JLocal.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/ast/JLocal.java
@@ -26,6 +26,10 @@
super(info, name, type, isFinal);
}
+ public JLocalRef createRef(SourceInfo info) {
+ return new JLocalRef(info, this);
+ }
+
@Override
public JLocalRef makeRef(SourceInfo info) {
return new JLocalRef(info, this);
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 0eddc4c..d9f155b 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
@@ -94,50 +94,58 @@
* Adds a new final parameter to this method.
*/
public JParameter createFinalParameter(SourceInfo info, String name, JType type) {
- return createParameter(info, name, type, true, false);
+ return createParameter(info, name, type, true, 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);
+ return createParameter(info, name, type, false, false, false);
}
/**
- * Adds a new final parameter to this method.
+ * Adds a new parameter to this method.
*/
- public JParameter createParameter(SourceInfo info, String name, JType type, boolean isFinal) {
- return createParameter(info, name, type, isFinal, false);
+ public JParameter createParameter(SourceInfo info, String name, JType type, boolean isFinal,
+ boolean isVarargs) {
+ return createParameter(info, name, type, isFinal, isVarargs, false);
}
/**
* Adds a new parameter to this method that is a copy of {@code from}.
*/
public JParameter cloneParameter(JParameter from) {
- return createParameter(
- from.getSourceInfo(), from.getName(), from.getType(), from.isFinal(), from.isThis());
+ return createParameter(from.getSourceInfo(), from.getName(), from.getType(), from.isFinal(),
+ from.isVarargs(), from.isThis());
}
/**
* 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, true);
- }
-
- private void addParameter(JParameter x) {
- params = Lists.add(params, x);
+ return createParameter(info, "this$static", type, true, false, true);
}
private JParameter createParameter(SourceInfo info, String name, JType type,
- boolean isFinal, boolean isThis) {
+ boolean isFinal, boolean isVarargs, boolean isThis) {
assert (name != null);
assert (type != null);
- JParameter x = new JParameter(info, name, type, isFinal, isThis);
- addParameter(x);
- return x;
+ JParameter parameter = new JParameter(info, name, type, isFinal, isVarargs, isThis);
+ addParameter(parameter);
+ return parameter;
+ }
+
+ /**
+ * Adds a parameter to this method.
+ */
+ private void addParameter(JParameter x) {
+ // Local types can capture local variables and sandwich the parameters of constructors between
+ // the outer reference and the local captures.
+ assert params.isEmpty() || !params.get(params.size() - 1).isVarargs()
+ || getEnclosingType().getClassDisposition().isLocalType();
+ params = Lists.add(params, x);
}
private boolean isJsInterfaceMethod() {
@@ -301,6 +309,14 @@
this.preventDevirtualization = true;
}
+ public boolean isJsMethodVarargs() {
+ if (getParams().isEmpty() || !canBeReferencedExternally()) {
+ return false;
+ }
+
+ JParameter lastParameter = Iterables.getLast(getParams());
+ return lastParameter.isVarargs();
+ }
/**
* AST representation of @SpecializeMethod.
*/
diff --git a/dev/core/src/com/google/gwt/dev/jjs/ast/JMethodCall.java b/dev/core/src/com/google/gwt/dev/jjs/ast/JMethodCall.java
index 334da39..a13fb16 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/ast/JMethodCall.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/ast/JMethodCall.java
@@ -18,6 +18,7 @@
import com.google.gwt.dev.jjs.SourceInfo;
import com.google.gwt.dev.util.collect.Lists;
+import java.util.Arrays;
import java.util.Collections;
import java.util.List;
@@ -73,25 +74,26 @@
* be specified, and the new object has no arguments on initialization. This
* forces the caller to potentially deal with cloning objects if needed.
*/
- public JMethodCall(JMethodCall other, JExpression instance) {
+ public JMethodCall(JMethodCall other, JExpression instance, JExpression... args) {
+ this(other, instance, Arrays.asList(args));
+ }
+
+ /**
+ * Initialize a new method call equivalent to another one. A new instance must
+ * be specified, and the new object has no arguments on initialization. This
+ * forces the caller to potentially deal with cloning objects if needed.
+ */
+ public JMethodCall(JMethodCall other, JExpression instance, List<JExpression> args) {
super(other.getSourceInfo());
this.instance = instance;
this.method = other.method;
this.overriddenReturnType = other.overriddenReturnType;
this.polymorphism = other.polymorphism;
this.markedAsSideAffectFree = other.markedAsSideAffectFree;
+ addArgs(args);
}
-
/**
- * Create a method call whose type is overridden to the specified type,
- * ignoring the return type of the target method. This constructor is used
- * during normalizing transformations to preserve type semantics when calling
- * externally-defined compiler implementation methods.
- *
- * For example, Cast.dynamicCast() returns Object but that method is used to
- * implement the cast operation. Using a stronger type on the call expression
- * allows us to preserve type information during the latter phases of
- * compilation.
+ * Create a method call.
*/
public JMethodCall(SourceInfo info, JExpression instance, JMethod method, JExpression... args) {
super(info);
@@ -196,6 +198,14 @@
/**
* Override the return type.
+ * <p>
+ * The method call expression will have {@code overridentReturnType} as its type ignoring the
+ * return type of the target method. This is used during normalizing transformations to preserve
+ * type semantics when calling externally-defined compiler implementation methods.
+ * <p>
+ * For example, Cast.dynamicCast() returns Object but that method is used to implement the cast
+ * operation. Using a stronger type on the call expression allows us to preserve type information
+ * during the latter phases of compilation.
*/
public void overrideReturnType(JType overridenReturnType) {
assert this.overriddenReturnType == null;
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 f6d385e..4fa7e00 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
@@ -21,11 +21,24 @@
* Java method parameter definition.
*/
public class JParameter extends JVariable {
- private final boolean isThis;
- JParameter(SourceInfo info, String name, JType type, boolean isFinal, boolean isThis) {
+ private final boolean isThis;
+ private final boolean isVarags;
+
+ public JParameter(SourceInfo info, String name, JType type, boolean isFinal) {
+ this(info, name, type, isFinal, false, false);
+ }
+
+ JParameter(SourceInfo info, String name, JType type, boolean isFinal, boolean isVarargs,
+ boolean isThis) {
super(info, name, type, isFinal);
this.isThis = isThis;
+ this.isVarags = isVarargs;
+ assert !isVarargs || type.isArrayType();
+ }
+
+ public JParameterRef createRef(SourceInfo info) {
+ return new JParameterRef(info, this);
}
/**
* Returns <code>true</code> if this parameter is the this parameter of a
@@ -35,6 +48,13 @@
return isThis;
}
+ /**
+ * Returns <code>true</code> if this parameter is a varargs parameter.
+ */
+ public boolean isVarargs() {
+ return isVarags;
+ }
+
@Override
public JParameterRef makeRef(SourceInfo info) {
return new JParameterRef(info, this);
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/ArrayNormalizer.java b/dev/core/src/com/google/gwt/dev/jjs/impl/ArrayNormalizer.java
index 2f98cca..687a434 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/ArrayNormalizer.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/ArrayNormalizer.java
@@ -50,28 +50,14 @@
@Override
public void endVisit(JBinaryOperation x, Context ctx) {
- if (x.getOp() != JBinaryOperator.ASG || !(x.getLhs() instanceof JArrayRef)) {
- return;
- }
- JArrayRef arrayRef = (JArrayRef) x.getLhs();
- JType elementType = arrayRef.getType();
- JExpression arrayInstance = arrayRef.getInstance();
- if (elementType.isNullType()) {
- // JNullType will generate a null pointer exception instead,
- return;
- } else if (!(elementType instanceof JReferenceType)) {
- // Primitive array types are statically correct, no need to set check.
- return;
- } else if (!arrayInstance.getType().canBeSubclass() &&
- program.typeOracle.castSucceedsTrivially((JReferenceType) x.getRhs().getType(),
- (JReferenceType) elementType)) {
- // There is no need to check as the static check already proved the cast is correct.
+ JArrayRef arrayRef = needsSetCheck(x);
+ if (arrayRef == null) {
return;
}
// replace this assignment with a call to setCheck()
JMethodCall call = new JMethodCall(x.getSourceInfo(), null, setCheckMethod);
- call.addArgs(arrayInstance, arrayRef.getIndexExpr(), x.getRhs());
+ call.addArgs(arrayRef.getInstance(), arrayRef.getIndexExpr(), x.getRhs());
ctx.replaceMe(call);
}
@@ -81,7 +67,7 @@
List<JExpression> initializers = x.getInitializers();
if (initializers != null) {
- JsonArray initializerArray = new JsonArray(x.getSourceInfo(), type, initializers);
+ JsonArray initializerArray = getInitializerArray(x);
if (program.isUntypedArrayType(type)) {
ctx.replaceMe(initializerArray);
return;
@@ -216,6 +202,32 @@
}
}
+ private JArrayRef needsSetCheck(JBinaryOperation x) {
+ if (x.getOp() != JBinaryOperator.ASG || !(x.getLhs() instanceof JArrayRef)) {
+ return null;
+ }
+ JArrayRef arrayRef = (JArrayRef) x.getLhs();
+ JType elementType = arrayRef.getType();
+ JExpression arrayInstance = arrayRef.getInstance();
+ if (elementType.isNullType()) {
+ // JNullType will generate a null pointer exception instead,
+ return null;
+ } else if (!(elementType instanceof JReferenceType)) {
+ // Primitive array types are statically correct, no need to set check.
+ return null;
+ } else if (!arrayInstance.getType().canBeSubclass() &&
+ program.typeOracle.castSucceedsTrivially((JReferenceType) x.getRhs().getType(),
+ (JReferenceType) elementType)) {
+ // There is no need to check as the static check already proved the cast is correct.
+ return null;
+ }
+ return arrayRef;
+ }
+
+ public static JsonArray getInitializerArray(JNewArray x) {
+ return new JsonArray(x.getSourceInfo(), x.getType(), x.getInitializers());
+ }
+
public static void exec(JProgram program) {
new ArrayNormalizer(program).execImpl();
}
@@ -237,7 +249,6 @@
}
private void execImpl() {
- ArrayVisitor visitor = new ArrayVisitor();
- visitor.accept(program);
+ new ArrayVisitor().accept(program);
}
}
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/CompoundAssignmentNormalizer.java b/dev/core/src/com/google/gwt/dev/jjs/impl/CompoundAssignmentNormalizer.java
index 29de891..4e71c98 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/CompoundAssignmentNormalizer.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/CompoundAssignmentNormalizer.java
@@ -16,7 +16,6 @@
package com.google.gwt.dev.jjs.impl;
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.JArrayRef;
import com.google.gwt.dev.jjs.ast.JBinaryOperation;
@@ -27,7 +26,6 @@
import com.google.gwt.dev.jjs.ast.JLocal;
import com.google.gwt.dev.jjs.ast.JLocalRef;
import com.google.gwt.dev.jjs.ast.JLongLiteral;
-import com.google.gwt.dev.jjs.ast.JMethodBody;
import com.google.gwt.dev.jjs.ast.JModVisitor;
import com.google.gwt.dev.jjs.ast.JNode;
import com.google.gwt.dev.jjs.ast.JParameterRef;
@@ -35,7 +33,6 @@
import com.google.gwt.dev.jjs.ast.JPrefixOperation;
import com.google.gwt.dev.jjs.ast.JPrimitiveType;
import com.google.gwt.dev.jjs.ast.JThisRef;
-import com.google.gwt.dev.jjs.ast.JType;
import com.google.gwt.dev.jjs.ast.JUnaryOperator;
import com.google.gwt.dev.jjs.ast.js.JMultiExpression;
@@ -125,7 +122,7 @@
}
// Create a temp local
- JLocal tempLocal = createTempLocal(x.getSourceInfo(), x.getType());
+ JLocal tempLocal = createTempLocal(x.getSourceInfo(), x.getType(), TEMP_LOCAL_NAME);
// Create an assignment for this temp and add it to multi.
JLocalRef tempRef = tempLocal.makeRef(x.getSourceInfo());
@@ -138,11 +135,6 @@
}
@Override
- protected String newTemporaryLocalName(SourceInfo info, JType type, JMethodBody methodBody) {
- return CompoundAssignmentNormalizer.this.newTemporaryLocalName(info, type, methodBody);
- }
-
- @Override
public void endVisit(JBinaryOperation x, Context ctx) {
JBinaryOperator op = x.getOp();
if (op.getNonAssignmentOf() == null) {
@@ -204,7 +196,8 @@
JExpression expressionReturn = expressionToReturn(newArg);
// Now generate the appropriate expressions.
- JLocal tempLocal = createTempLocal(x.getSourceInfo(), expressionReturn.getType());
+ JLocal tempLocal =
+ createTempLocal(x.getSourceInfo(), expressionReturn.getType(), TEMP_LOCAL_NAME);
// t = x
JLocalRef tempRef = tempLocal.makeRef(x.getSourceInfo());
@@ -286,17 +279,6 @@
private static final String TEMP_LOCAL_NAME = "$tmp";
/**
- * Gets a new temporary local variable name in {@code methodBody}. Locals might have duplicate
- * names as they are always referred to by reference.
- * {@link GenerateJavaScriptAST} will attempt coalesce variables of same name.
- *
- * <p> Subclasses might decide on different approaches to naming local temporaries.
- */
- protected String newTemporaryLocalName(SourceInfo info, JType type, JMethodBody methodBody) {
- return TEMP_LOCAL_NAME;
- }
-
- /**
* Decide what expression to return when breaking up a compound assignment of
* the form <code>lhs op= rhs</code>. By default the <code>lhs</code> is
* returned.
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/ControlFlowAnalyzer.java b/dev/core/src/com/google/gwt/dev/jjs/impl/ControlFlowAnalyzer.java
index 1c404a3..4c77d58 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/ControlFlowAnalyzer.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/ControlFlowAnalyzer.java
@@ -621,6 +621,11 @@
// Parameters in JsExport, JsType, JsFunction methods should not be pruned in order to
// keep the API intact.
rescue(param);
+ if (param.isVarargs()) {
+ assert method.isJsMethodVarargs();
+ // Rescue the (array) type of varargs parameters as the array creation is implicit.
+ rescue((JReferenceType) param.getType(), true);
+ }
}
}
rescueOverridingMethods(method);
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 19e322b..779e8af 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
@@ -16,6 +16,7 @@
package com.google.gwt.dev.jjs.impl;
import static com.google.gwt.dev.js.JsUtils.createAssignment;
+import static com.google.gwt.dev.js.JsUtils.createInvocationOrPropertyAccess;
import com.google.gwt.core.ext.TreeLogger;
import com.google.gwt.core.ext.linker.impl.StandardSymbolData;
@@ -104,6 +105,7 @@
import com.google.gwt.dev.jjs.impl.ResolveRuntimeTypeReferences.TypeMapper;
import com.google.gwt.dev.js.JsStackEmulator;
import com.google.gwt.dev.js.JsUtils;
+import com.google.gwt.dev.js.JsUtils.InvocationStyle;
import com.google.gwt.dev.js.ast.JsArrayAccess;
import com.google.gwt.dev.js.ast.JsArrayLiteral;
import com.google.gwt.dev.js.ast.JsBinaryOperation;
@@ -213,6 +215,8 @@
private final Stack<JsScope> scopeStack = new Stack<JsScope>();
+ private JMethod currentMethod;
+
@Override
public boolean visit(JProgram x, Context ctx) {
// Scopes and name objects need to be calculated within all types, even reference-only ones.
@@ -280,6 +284,10 @@
@Override
public void endVisit(JParameter x, Context ctx) {
+ if (x.isVarargs() && currentMethod.isJsMethodVarargs()) {
+ names.put(x, scopeStack.peek().declareUnobfuscatableName("arguments"));
+ return;
+ }
names.put(x, scopeStack.peek().declareName(x.getName()));
}
@@ -356,6 +364,7 @@
@Override
public boolean visit(JMethod x, Context ctx) {
+ currentMethod = x;
// my polymorphic name
String name = x.getName();
if (x.needsDynamicDispatch()) {
@@ -517,11 +526,8 @@
private JMethod currentMethod = null;
private final JsName arrayLength = objectScope.declareUnobfuscatableName("length");
-
private final JsName globalTemp = topScope.declareUnobfuscatableName("_");
-
private final JsName prototype = objectScope.declareUnobfuscatableName("prototype");
-
private final JsName call = objectScope.declareUnobfuscatableName("call");
@Override
@@ -783,7 +789,11 @@
if (!method.isJsniMethod()) {
// Setup params on the generated function. A native method already got
// its jsParams set when parsed from JSNI.
- transformInto(method.getParams(), function.getParameters());
+ List<JParameter> parameterList = method.getParams();
+ if (method.isJsMethodVarargs()) {
+ parameterList = parameterList.subList(0, parameterList.size() - 1);
+ }
+ transformInto(parameterList, function.getParameters());
}
JsInvocation jsInvocation = maybeCreateClinitCall(method);
@@ -863,23 +873,25 @@
JsExpression qualifier = transform(methodCall.getInstance());
List<JsExpression> args = transform(methodCall.getArgs());
+ SourceInfo sourceInfo = methodCall.getSourceInfo();
if (method.isStatic()) {
- return dispatchToStatic(qualifier, method, args, methodCall.getSourceInfo());
+ return dispatchToStatic(qualifier, method, args, sourceInfo);
} else if (methodCall.isStaticDispatchOnly()) {
- return dispatchToSuper(qualifier, method, args, methodCall.getSourceInfo());
+ return dispatchToSuper(qualifier, method, args, sourceInfo);
} else if (method.isOrOverridesJsFunctionMethod()) {
- return dispatchToJsFunction(qualifier, args, methodCall.getSourceInfo());
+ return dispatchToJsFunction(qualifier, method, args, sourceInfo);
} else {
- return dispatchToInstanceMethod(qualifier, method, args, methodCall.getSourceInfo());
+ return dispatchToInstanceMethod(qualifier, method, args, sourceInfo);
}
}
private JsExpression dispatchToStatic(JsExpression unnecessaryQualifier, JMethod method,
List<JsExpression> args, SourceInfo sourceInfo) {
JsNameRef methodName = createStaticReference(method, sourceInfo);
- JsExpression result = JsUtils.createInvocationOrPropertyAccess(
- sourceInfo, method.getJsMemberType(), methodName, args);
- return JsUtils.createCommaExpression(unnecessaryQualifier, result);
+ return JsUtils.createCommaExpression(
+ unnecessaryQualifier,
+ createInvocationOrPropertyAccess(
+ InvocationStyle.NORMAL, sourceInfo, method, null, methodName, args));
}
private JsExpression dispatchToSuper(
@@ -900,7 +912,7 @@
methodNameRef = names.get(method).makeRef(sourceInfo);
} else {
// These are regular super method call. These calls are always dispatched statically and
- // optimizations will statify them (except in a few cases, like being target of
+ // optimizations will devirtualize them (except in a few cases, like being target of
// {@link Impl.getNameOf} or calls to the native classes.
JDeclaredType superClass = method.getEnclosingType();
@@ -908,12 +920,8 @@
methodNameRef = polymorphicNames.get(method).makeQualifiedRef(sourceInfo, protoRef);
}
- // <method_qualifier>.call(instance, args);
- JsNameRef qualifiedMethodName = call.makeQualifiedRef(sourceInfo, methodNameRef);
- JsInvocation jsInvocation = new JsInvocation(sourceInfo, qualifiedMethodName);
- jsInvocation.getArguments().add(instance);
- jsInvocation.getArguments().addAll(args);
- return jsInvocation;
+ return JsUtils.createInvocationOrPropertyAccess(InvocationStyle.SUPER,
+ sourceInfo, method, instance, methodNameRef, args);
}
private JsExpression getPrototypeQualifierViaLookup(JDeclaredType type, SourceInfo sourceInfo) {
@@ -929,16 +937,16 @@
}
}
- private JsExpression dispatchToJsFunction(
- JsExpression instance, List<JsExpression> args, SourceInfo sourceInfo) {
- return new JsInvocation(sourceInfo, instance, args);
+ private JsExpression dispatchToJsFunction(JsExpression instance, JMethod method,
+ List<JsExpression> args, SourceInfo sourceInfo) {
+ return createInvocationOrPropertyAccess(InvocationStyle.FUNCTION, sourceInfo, method, instance, null, args);
}
- private JsExpression dispatchToInstanceMethod(
- JsExpression instance, JMethod method, List<JsExpression> args, SourceInfo sourceInfo) {
+ private JsExpression dispatchToInstanceMethod(JsExpression instance, JMethod method,
+ List<JsExpression> args, SourceInfo sourceInfo) {
JsNameRef reference = polymorphicNames.get(method).makeQualifiedRef(sourceInfo, instance);
- return JsUtils.createInvocationOrPropertyAccess(
- sourceInfo, method.getJsMemberType(), reference, args);
+ return createInvocationOrPropertyAccess(
+ InvocationStyle.NORMAL, sourceInfo, method, instance, reference, args);
}
@Override
@@ -971,10 +979,13 @@
SourceInfo sourceInfo = newInstance.getSourceInfo();
JConstructor ctor = newInstance.getTarget();
JsName ctorName = names.get(ctor);
- JsNew newExpr = ctor.isJsNative()
- ? new JsNew(sourceInfo, createJsQualifier(ctor.getQualifiedJsName(), sourceInfo))
- : new JsNew(sourceInfo, ctorName.makeRef(sourceInfo));
- transformInto(newInstance.getArgs(), newExpr.getArguments());
+ JsNameRef reference = ctor.isJsNative()
+ ? createJsQualifier(ctor.getQualifiedJsName(), sourceInfo)
+ : ctorName.makeRef(sourceInfo);
+ List<JsExpression> arguments = transform(newInstance.getArgs());
+
+ JsNew newExpr = (JsNew) JsUtils.createInvocationOrPropertyAccess(
+ InvocationStyle.NEWINSTANCE, sourceInfo, ctor, null, reference, arguments);
if (newInstance.getClassType().isJsFunctionImplementation()) {
return constructJsFunctionObject(sourceInfo, newInstance.getClassType(), ctorName, newExpr);
@@ -991,7 +1002,8 @@
ctorName, prototype, polymorphicNames.get(jsFunctionMethod));
// makeLambdaFunction(Foo.prototype.functionMethodName, new Foo(...))
- return constructInvocation(sourceInfo, RuntimeConstants.RUNTIME_MAKE_LAMBDA_FUNCTION, funcNameRef, newExpr);
+ return constructInvocation(sourceInfo, RuntimeConstants.RUNTIME_MAKE_LAMBDA_FUNCTION,
+ funcNameRef, newExpr);
}
private JMethod getJsFunctionMethod(JClassType type) {
@@ -1010,6 +1022,7 @@
@Override
public JsNode transformParameter(JParameter parameter) {
+ assert !(currentMethod.isJsMethodVarargs() && parameter.isVarargs());
return new JsParameter(parameter.getSourceInfo(), names.get(parameter));
}
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 2bc446f..fb1e6a7 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
@@ -4016,7 +4016,7 @@
if (alreadyNamedVariables.contains(argName)) {
argName += "_" + i;
}
- createParameter(info, arg, argName, method);
+ createParameter(info, arg, argName, method, false);
alreadyNamedVariables.add(argName);
}
}
@@ -4042,7 +4042,7 @@
if (alreadyNamedVariables.contains(argName)) {
argName += "_" + i;
}
- createParameter(info, arg, argName, method);
+ createParameter(info, arg, argName, method, false);
alreadyNamedVariables.add(argName);
}
}
@@ -4126,15 +4126,15 @@
method.setSpecialization(paramTypes, returnsType, targetMethod);
}
- private void createParameter(SourceInfo info, LocalVariableBinding binding, JMethod method,
- Annotation... annotations) {
- createParameter(info, binding, intern(binding.name), method, annotations);
+ private void createParameter(SourceInfo info, LocalVariableBinding binding, boolean isVarargs,
+ JMethod method, Annotation... annotations) {
+ createParameter(info, binding, intern(binding.name), method, isVarargs, annotations);
}
private void createParameter(SourceInfo info, LocalVariableBinding binding, String name,
- JMethod method, Annotation... annotations) {
+ JMethod method, boolean isVarargs, Annotation... annotations) {
JParameter param =
- method.createParameter(info, name, typeMap.get(binding.type), binding.isFinal());
+ method.createParameter(info, name, typeMap.get(binding.type), binding.isFinal(), isVarargs);
processSuppressedWarnings(param, annotations);
}
@@ -4143,7 +4143,9 @@
for (Argument argument : x.arguments) {
SourceInfo info = makeSourceInfo(argument);
LocalVariableBinding binding = argument.binding;
- createParameter(info, binding, method, argument.annotations);
+ boolean isVarargs = x.binding.isVarargs()
+ && argument == x.arguments[x.arguments.length - 1];
+ createParameter(info, binding, isVarargs, method, argument.annotations);
}
}
method.freezeParamTypes();
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/ImplementJsVarargs.java b/dev/core/src/com/google/gwt/dev/jjs/impl/ImplementJsVarargs.java
new file mode 100644
index 0000000..610af99
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/ImplementJsVarargs.java
@@ -0,0 +1,476 @@
+/*
+ * 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.dev.jjs.SourceInfo;
+import com.google.gwt.dev.jjs.ast.Context;
+import com.google.gwt.dev.jjs.ast.JArrayLength;
+import com.google.gwt.dev.jjs.ast.JArrayRef;
+import com.google.gwt.dev.jjs.ast.JArrayType;
+import com.google.gwt.dev.jjs.ast.JBinaryOperation;
+import com.google.gwt.dev.jjs.ast.JBinaryOperator;
+import com.google.gwt.dev.jjs.ast.JBlock;
+import com.google.gwt.dev.jjs.ast.JDeclarationStatement;
+import com.google.gwt.dev.jjs.ast.JExpression;
+import com.google.gwt.dev.jjs.ast.JForStatement;
+import com.google.gwt.dev.jjs.ast.JIntLiteral;
+import com.google.gwt.dev.jjs.ast.JLocal;
+import com.google.gwt.dev.jjs.ast.JMethod;
+import com.google.gwt.dev.jjs.ast.JMethodBody;
+import com.google.gwt.dev.jjs.ast.JMethodCall;
+import com.google.gwt.dev.jjs.ast.JModVisitor;
+import com.google.gwt.dev.jjs.ast.JNewArray;
+import com.google.gwt.dev.jjs.ast.JParameter;
+import com.google.gwt.dev.jjs.ast.JParameterRef;
+import com.google.gwt.dev.jjs.ast.JPostfixOperation;
+import com.google.gwt.dev.jjs.ast.JPrimitiveType;
+import com.google.gwt.dev.jjs.ast.JProgram;
+import com.google.gwt.dev.jjs.ast.JStatement;
+import com.google.gwt.dev.jjs.ast.JUnaryOperation;
+import com.google.gwt.dev.jjs.ast.JUnaryOperator;
+import com.google.gwt.dev.jjs.ast.JVariableRef;
+import com.google.gwt.dev.jjs.ast.JVisitor;
+import com.google.gwt.thirdparty.guava.common.collect.Iterables;
+
+import java.util.Collections;
+
+/**
+ * Implements JavaScript varargs calling convention by rewriting varargs calls and adding a
+ * prolog to varargs JsMethods.
+ * <p>
+ * At the calls sites, inline array creation is replaced by array literals, which in turn will be
+ * unwrapped as individual parameters at generation time.
+ * <p>
+ * To implement varargs methods, we analyze the usage of the varargs parameter to determine whether
+ * it can be accessed directly (with possibly an offset in the index) or it has to be copied.
+ */
+public class ImplementJsVarargs {
+ /**
+ * Analyzes a method body to check whether the varargs parameter can be used directly or not.
+ * <p>
+ * The arguments variable cannot be used directly if is referenced without indexing or accessing
+ * its length, or if it is written to.
+ */
+ private class NeedsArgumentsCopyAnalyzer extends JVisitor {
+
+ private VarargsProcessingResult result = VarargsProcessingResult.SIMPLE_ACCESS;
+ private JParameter varargsParameter;
+ private int varargsParameterIndex;
+
+ private NeedsArgumentsCopyAnalyzer(JMethod method) {
+ assert method.isJsMethodVarargs();
+ this.varargsParameter = Iterables.getLast(method.getParams());
+ this.varargsParameterIndex = method.getParams().size() - 1;
+ if (varargsParameterIndex != 0) {
+ upgradeResult(VarargsProcessingResult.OFFSET_ACCESS);
+ }
+ }
+
+ @Override
+ public void endVisit(JParameterRef x, Context ctx) {
+ // Any reference that is not .length or indexed means that we need to copy the varargs array.
+ if (isVarargsReference(x)) {
+ upgradeResult(VarargsProcessingResult.GENERAL_ACCESS);
+ }
+ }
+
+ @Override
+ public boolean visit(JArrayLength x, Context ctx) {
+ if (isVarargsReference(x.getInstance())) {
+ // This is a safe reference.
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public boolean visit(JArrayRef x, Context ctx) {
+ if (isVarargsReference(x.getInstance())) {
+ // This is a safe reference, so only check the index expression.
+ accept(x.getIndexExpr());
+ return false;
+ }
+
+ return true;
+ }
+
+ @Override
+ public boolean visit(JBinaryOperation x, Context ctx) {
+ if (isModifyingVarargs(x)) {
+ // The varargs parameter is written to, so upgrade to copy.
+ upgradeResult(VarargsProcessingResult.GENERAL_ACCESS);
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public boolean visit(JUnaryOperation x, Context ctx) {
+ if (isModifyingVarargs(x)) {
+ // The varargs parameter is written to, so upgrade to copy.
+ upgradeResult(VarargsProcessingResult.GENERAL_ACCESS);
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public boolean visit(JMethodCall x, Context ctx) {
+ // Allow
+ if (x.getTarget().isJsMethodVarargs() && x.getArgs().size() == 1
+ && isVarargsReference(x.getArgs().get(0))) {
+ // The varargs parameter is passed directly and it is the only parameter, so if it is the
+ // only parameter in the current method it can be passed directly.
+ upgradeResult(VarargsProcessingResult.PASS_WHOLE);
+ if (x.getInstance() != null) {
+ accept(x.getInstance());
+ }
+ for (JExpression arg : x.getArgs().subList(1, x.getArgs().size())) {
+ accept(arg);
+ }
+ return false;
+ }
+ return true;
+ }
+
+ private boolean isModifyingVarargs(JBinaryOperation x) {
+ if (!x.getOp().isAssignment()) {
+ return false;
+ }
+ if (!(x.getLhs() instanceof JArrayRef)) {
+ return false;
+ }
+ JArrayRef arrayRef = (JArrayRef) x.getLhs();
+ JExpression instance = arrayRef.getInstance();
+ return isVarargsReference(instance);
+ }
+
+ private boolean isModifyingVarargs(JUnaryOperation x) {
+ if (!x.getOp().isModifying()) {
+ return false;
+ }
+ if (!(x.getArg() instanceof JArrayRef)) {
+ return false;
+ }
+ JArrayRef arrayRef = (JArrayRef) x.getArg();
+ JExpression instance = arrayRef.getInstance();
+ return isVarargsReference(instance);
+ }
+
+ private boolean isVarargsReference(JExpression instance) {
+ if (!(instance instanceof JParameterRef)) {
+ return false;
+ }
+
+ return (((JParameterRef) instance).getTarget() == varargsParameter);
+ }
+
+ private void upgradeResult(VarargsProcessingResult upgradeTo) {
+ result = VarargsProcessingResult.join(result, upgradeTo);
+ }
+ }
+
+ // Defines the analysis lattice
+ // SIMPLE_ACCESS (only arguments[i] or arguments.length, no writing)
+ // / \
+ // (complete passing of / \
+ // arguments) PASS_WHOLE OFFSET_ACCESS (Simple access but needs to offset index)
+ // \ /
+ // \ /
+ // GENERAL_ACCESS
+ //
+ private enum VarargsProcessingResult {
+ SIMPLE_ACCESS, PASS_WHOLE, OFFSET_ACCESS, GENERAL_ACCESS;
+
+ private static VarargsProcessingResult join(
+ VarargsProcessingResult thisResult, VarargsProcessingResult thatResult) {
+ if (thisResult.ordinal() > thatResult.ordinal()) {
+ VarargsProcessingResult swap = thisResult;
+ thisResult = thatResult;
+ thatResult = swap;
+ }
+
+ if ((thisResult == PASS_WHOLE && thatResult == OFFSET_ACCESS)) {
+ return GENERAL_ACCESS;
+ }
+ return thatResult;
+ }
+ }
+
+ private VarargsProcessingResult needsVarargsProcessing(JMethod method) {
+ NeedsArgumentsCopyAnalyzer analyzer = new NeedsArgumentsCopyAnalyzer(method);
+ analyzer.accept(method);
+ return analyzer.result;
+ }
+
+ private abstract class VarargsReplacer {
+ abstract JExpression replace(JParameterRef expression);
+
+ JExpression replace(JArrayRef expression) {
+ return new JArrayRef(expression.getSourceInfo(),
+ replace((JParameterRef) expression.getInstance()),
+ expression.getIndexExpr());
+ }
+
+ JExpression replace(JArrayLength expression) {
+ return new JArrayLength(expression.getSourceInfo(),
+ replace((JParameterRef) expression.getInstance()));
+ }
+ }
+
+ /**
+ * Replaces varargs parameter accesses with accesses to the copy.
+ */
+ private class ReplaceVarargsVariable extends VarargsReplacer {
+ private JLocal localVariable;
+ ReplaceVarargsVariable(JLocal localVariable) {
+ this.localVariable = localVariable;
+ }
+
+ @Override
+ public JExpression replace(JParameterRef expression) {
+ return localVariable.createRef(expression.getSourceInfo());
+ }
+ }
+
+ /**
+ * Fixes this indexing of vararg accesses.
+ */
+ private class ReindexAccess extends VarargsReplacer {
+ private int varargsIndex;
+ ReindexAccess(int varargsIndex) {
+ this.varargsIndex = varargsIndex;
+ }
+
+ @Override
+ public JExpression replace(JParameterRef expression) {
+ return expression;
+ }
+
+ JExpression replace(JArrayRef expression) {
+ SourceInfo sourceInfo = expression.getSourceInfo();
+ return new JArrayRef(expression.getSourceInfo(),
+ expression.getInstance(),
+ new JBinaryOperation(sourceInfo, JPrimitiveType.INT, JBinaryOperator.ADD,
+ expression.getIndexExpr(), new JIntLiteral(sourceInfo, varargsIndex)));
+ }
+
+ JExpression replace(JArrayLength expression) {
+ SourceInfo sourceInfo = expression.getSourceInfo();
+ return new JBinaryOperation(sourceInfo, JPrimitiveType.INT, JBinaryOperator.SUB,
+ expression, new JIntLiteral(sourceInfo, varargsIndex));
+ }
+ }
+
+ private class VarargsMethodNormalizer extends JModVisitor {
+ private JParameter varargsParameter;
+ private int varargsIndex;
+ private VarargsReplacer replacer;
+ private JLocal argumentsCopyVariable;
+
+ @Override
+ public boolean visit(JMethod x, Context ctx) {
+ if (!x.isJsMethodVarargs()) {
+ return false;
+ }
+ varargsParameter = Iterables.getLast(x.getParams());
+ varargsIndex = x.getParams().size() - 1;
+
+ argumentsCopyVariable = null;
+ switch (needsVarargsProcessing(x)) {
+ case GENERAL_ACCESS:
+ argumentsCopyVariable = JProgram.createLocal(varargsParameter.getSourceInfo(),
+ varargsParameter.getName(), varargsParameter.getType(), false,
+ (JMethodBody) x.getBody());
+ replacer = new ReplaceVarargsVariable(argumentsCopyVariable);
+ return true;
+ case OFFSET_ACCESS:
+ replacer = new ReindexAccess(varargsIndex);
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ @Override
+ public void endVisit(JParameterRef x, Context ctx) {
+ if (x.getTarget() == varargsParameter) {
+ maybeReplace(x, replacer.replace(x), ctx);
+ }
+ }
+
+ @Override
+ public void endVisit(JArrayRef x, Context ctx) {
+ if (x.getInstance() instanceof JParameterRef
+ && ((JParameterRef) x.getInstance()).getTarget() == varargsParameter) {
+ maybeReplace(x, replacer.replace(x), ctx);
+ }
+ }
+
+ @Override
+ public void endVisit(JArrayLength x, Context ctx) {
+ if (x.getInstance() instanceof JParameterRef
+ && ((JParameterRef) x.getInstance()).getTarget() == varargsParameter) {
+ maybeReplace(x, replacer.replace(x), ctx);
+ }
+ }
+
+ @Override
+ public void endVisit(JMethod x, Context ctx) {
+ if (!x.isJsMethodVarargs()) {
+ return;
+ }
+ // rename the varargs variable to _arguments_.
+ varargsParameter.setName("_arguments_");
+ }
+
+ private void maybeReplace(JExpression x, JExpression replacement, Context ctx) {
+ if (replacement != x) {
+ ctx.replaceMe(replacement);
+ }
+ }
+
+ @Override
+ public void endVisit(JMethodBody x, Context ctx) {
+ if (argumentsCopyVariable == null) {
+ return;
+ }
+
+ // Needs to populate the copy; add preamble.
+ //
+ // {
+ // <Type>[] args = new <Type>[arguments.length - offset];
+ // for (int $i = 0; $i < arguments.length - offset; i++) {
+ // args[i] = arguments[i + offset];
+ // }
+ // }
+
+ SourceInfo sourceInfo = varargsParameter.getSourceInfo();
+ JBlock preamble = new JBlock(sourceInfo);
+
+ // (1) varargs_ = new VarArgsType[varargs.length - varArgsParameterIndex]
+ JExpression lengthMinusVarargsIndex = varargsIndex == 0
+ ? new JArrayLength(sourceInfo, varargsParameter.createRef(sourceInfo))
+ : new JBinaryOperation(sourceInfo, JPrimitiveType.INT, JBinaryOperator.SUB,
+ new JArrayLength(sourceInfo, varargsParameter.createRef(sourceInfo)),
+ new JIntLiteral(sourceInfo, varargsIndex));
+ JNewArray arrayVariable = JNewArray.createArrayWithDimensionExpressions(sourceInfo,
+ (JArrayType) varargsParameter.getType(),
+ Collections.singletonList(lengthMinusVarargsIndex));
+ arrayVariable.getLeafTypeClassLiteral().setField(
+ program.getClassLiteralField(((JArrayType) varargsParameter.getType()).getLeafType()));
+ preamble.addStmt(new JDeclarationStatement(
+ sourceInfo, argumentsCopyVariable.createRef(sourceInfo), arrayVariable));
+
+ JLocal index = JProgram.createLocal(sourceInfo, "$i", JPrimitiveType.INT, false, x);
+
+ // (2) (copy loop body) varargs_[i] = varargs[i + varargsIndex];
+ JExpression iPlusVarargsIndex = varargsIndex == 0 ? index.createRef(sourceInfo)
+ : new JBinaryOperation(sourceInfo, JPrimitiveType.INT, JBinaryOperator.ADD,
+ index.createRef(sourceInfo), new JIntLiteral(sourceInfo, varargsIndex));
+
+ JBlock block = new JBlock(sourceInfo);
+ block.addStmt(new JBinaryOperation(
+ sourceInfo,
+ ((JArrayType) varargsParameter.getType()).getElementType(),
+ JBinaryOperator.ASG,
+ new JArrayRef(sourceInfo, replacer.replace(varargsParameter.createRef(sourceInfo)),
+ index.createRef(sourceInfo)),
+ new JArrayRef(sourceInfo, varargsParameter.createRef(sourceInfo), iPlusVarargsIndex))
+ .makeStatement());
+ // (3) for (int $i = 0 ; i < arguments.length - index; i++) {
+ // varargs_[i] = varargs[i + varargsIndex];
+ // }
+ preamble.addStmt(new JForStatement(sourceInfo, Collections.<JStatement>singletonList(
+ new JDeclarationStatement(sourceInfo, index.createRef(sourceInfo), JIntLiteral.ZERO)),
+ new JBinaryOperation(sourceInfo, JPrimitiveType.INT,JBinaryOperator.LT,
+ index.createRef(sourceInfo),
+ new CloneExpressionVisitor().cloneExpression(lengthMinusVarargsIndex)),
+ new JPostfixOperation(sourceInfo, JUnaryOperator.INC, index.createRef(sourceInfo)),
+ block));
+ x.getStatements().add(0, preamble);
+ }
+ }
+
+ // Normalizes JsVarargsCalls so that
+ // (1) inline new array expressions resulting from a "regular" varargs invocation are replaced
+ // by plain array literals.
+ // (2) side effecting instances in JsVarargs instance method calls with array calling convention
+ // are hoited into temporary variables.
+ private class VarargsCallsNormalizer extends JModVisitor {
+ private JMethodBody currentMethodBody;
+
+ @Override
+ public boolean visit(JMethodBody x, Context ctx) {
+ currentMethodBody = x;
+ return true;
+ }
+
+ @Override
+ public void endVisit(JMethodBody x, Context ctx) {
+ currentMethodBody = null;
+ }
+
+ @Override
+ public void endVisit(JMethodCall x, Context ctx) {
+ JMethod method = x.getTarget();
+ if (!method.isJsMethodVarargs()) {
+ return;
+ }
+
+ int varargIndex = method.getParams().size() - 1;
+ JExpression varargArgument = x.getArgs().get(varargIndex);
+ if (varargArgument instanceof JNewArray) {
+ JNewArray varargArray = (JNewArray) varargArgument;
+ if (varargArray.getInitializers() != null) {
+ x.setArg(varargIndex, ArrayNormalizer.getInitializerArray(varargArray));
+ return;
+ }
+ }
+ // Passed as an array to varargs method.
+ JExpression instance = x.getInstance();
+ if (x.getTarget().needsDynamicDispatch() && !x.isStaticDispatchOnly()
+ && instance != null && !(instance instanceof JVariableRef)) {
+ // Move the potentially sideffecting qualifier to a temporary variable so that
+ // the code generation for calls that need .apply don't need to hande the case.
+ SourceInfo sourceInfo = x.getSourceInfo();
+ JLocal tempInstance = JProgram.createLocal(sourceInfo, "$instance",
+ instance.getType(), false, currentMethodBody);
+ // (tempInstance = instance,
+ // tempInstance.method(pars);
+ ctx.replaceMe(JjsUtils.createOptimizedMultiExpression(
+ new JBinaryOperation(sourceInfo, instance.getType(),
+ JBinaryOperator.ASG, tempInstance.createRef(sourceInfo), instance),
+ new JMethodCall(x, tempInstance.createRef(sourceInfo), x.getArgs())));
+ }
+ }
+ }
+
+ public static void exec(JProgram program) {
+ new ImplementJsVarargs(program).execImpl();
+ }
+
+ private final JProgram program;
+
+ private ImplementJsVarargs(JProgram program) {
+ this.program = program;
+ }
+
+ private void execImpl() {
+ new VarargsMethodNormalizer().accept(program);
+ new VarargsCallsNormalizer().accept(program);
+ }
+}
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/JModVisitorWithTemporaryVariableCreation.java b/dev/core/src/com/google/gwt/dev/jjs/impl/JModVisitorWithTemporaryVariableCreation.java
index 39405da..dc71da7 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/JModVisitorWithTemporaryVariableCreation.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/JModVisitorWithTemporaryVariableCreation.java
@@ -60,17 +60,18 @@
return super.visit(x, ctx);
}
- protected JLocal createTempLocal(SourceInfo info, JType type) {
+ /**
+ * Gets a new temporary local variable name in the current method body.
+ * Locals might have duplicate names as they are always referred to by reference and name
+ * collisions are fixed by {@link NameClashesFixer}.
+ */
+ protected JLocal createTempLocal(SourceInfo info, JType type, String temporaryLocalName) {
assert !getCurrentMethod().isJsniMethod();
JMethodBody currentMethodBody = (JMethodBody) getCurrentMethod().getBody();
- String temporaryLocalName = newTemporaryLocalName(info, type, currentMethodBody);
JLocal local = JProgram.createLocal(info, temporaryLocalName, type, false, currentMethodBody);
JDeclarationStatement declarationStatement =
new JDeclarationStatement(info, local.makeRef(info), null);
currentDeclarationInsertionPoint.peek().insertBefore(declarationStatement);
return local;
}
-
- protected abstract String newTemporaryLocalName(SourceInfo info, JType type,
- JMethodBody methodBody);
}
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 3625162..9846181 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
@@ -45,7 +45,13 @@
import com.google.gwt.dev.jjs.ast.JStatement;
import com.google.gwt.dev.jjs.ast.JType;
import com.google.gwt.dev.jjs.ast.JVisitor;
+import com.google.gwt.dev.jjs.ast.js.JsniMethodBody;
import com.google.gwt.dev.js.JsUtils;
+import com.google.gwt.dev.js.ast.JsContext;
+import com.google.gwt.dev.js.ast.JsFunction;
+import com.google.gwt.dev.js.ast.JsNameRef;
+import com.google.gwt.dev.js.ast.JsParameter;
+import com.google.gwt.dev.js.ast.JsVisitor;
import com.google.gwt.dev.util.Pair;
import com.google.gwt.dev.util.log.AbstractTreeLogger;
import com.google.gwt.thirdparty.guava.common.base.Predicate;
@@ -250,6 +256,10 @@
return;
}
+ if (member.isJsMethodVarargs()) {
+ checkJsVarargs(member);
+ }
+
checkMemberQualifiedJsName(member);
if (isCheckedLocalName(member)) {
@@ -337,6 +347,25 @@
}
}
+ private void checkJsVarargs(JMember member) {
+ final JMethod method = (JMethod) member;
+ if (!method.isJsniMethod()) {
+ return;
+ }
+ final JsFunction function = ((JsniMethodBody) method.getBody()).getFunc();
+ final JsParameter varargParameter = Iterables.getLast(function.getParameters());
+ new JsVisitor() {
+ @Override
+ public void endVisit(JsNameRef x, JsContext ctx) {
+ if (x.getName() == varargParameter.getName()) {
+ logError(x, "Cannot access vararg parameter '%s' from JSNI in JsMethod %s."
+ + " Use 'arguments' instead.", x.getIdent(),
+ getMemberDescription(method));
+ }
+ }
+ }.accept(function);
+ }
+
private boolean checkJsPropertyAccessor(JMember member) {
if (member.getJsName().equals(JsInteropUtil.INVALID_JSNAME)) {
assert member.getJsMemberType().isPropertyAccessor();
@@ -358,6 +387,13 @@
getMemberDescription(member));
}
}
+
+ if (member.getJsMemberType() == JsMemberType.SETTER) {
+ if (((JMethod) member).getParams().get(0).isVarargs()) {
+ logError(member, "JsProperty %s cannot have a vararg parameter.",
+ getMemberDescription(member));
+ }
+ }
return true;
}
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/Pruner.java b/dev/core/src/com/google/gwt/dev/jjs/impl/Pruner.java
index f37f99f..6380929 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/Pruner.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/Pruner.java
@@ -368,20 +368,13 @@
JExpression lastArg = Iterables.getLast(replacementCall.getArgs());
JLocal tempVar =
createTempLocal(sourceInfo, Iterables.getLast(
- Iterables.filter(originalParams, Predicates.in(referencedNonTypes))).getType());
+ Iterables.filter(originalParams, Predicates.in(referencedNonTypes))).getType(), "lastArg");
unevaluatedArgumentsForPrunedParameters.addExpressions(0, JProgram.createAssignment(
lastArg.getSourceInfo(), tempVar.makeRef(sourceInfo), lastArg));
unevaluatedArgumentsForPrunedParameters.addExpressions(tempVar.makeRef(sourceInfo));
replacementCall.setArg(replacementCall.getArgs().size() - 1, unevaluatedArgumentsForPrunedParameters);
ctx.replaceMe(replacementCall);
}
-
- @Override
- protected String newTemporaryLocalName(SourceInfo info, JType type, JMethodBody methodBody) {
- // The name can be reused a later pass will make sure each instance of JLocal in a method
- // has a different name.
- return "lastArg";
- }
}
/**
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/codesplitter/FragmentExtractor.java b/dev/core/src/com/google/gwt/dev/jjs/impl/codesplitter/FragmentExtractor.java
index cd4efb1..507518e 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/codesplitter/FragmentExtractor.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/codesplitter/FragmentExtractor.java
@@ -24,7 +24,7 @@
import com.google.gwt.dev.jjs.ast.JProgram;
import com.google.gwt.dev.jjs.ast.RuntimeConstants;
import com.google.gwt.dev.jjs.impl.JavaToJavaScriptMap;
-import com.google.gwt.dev.js.JsHoister.Cloner;
+import com.google.gwt.dev.js.JsSafeCloner.Cloner;
import com.google.gwt.dev.js.JsUtils;
import com.google.gwt.dev.js.ast.JsBinaryOperation;
import com.google.gwt.dev.js.ast.JsBinaryOperator;
diff --git a/dev/core/src/com/google/gwt/dev/js/JsConstructExpressionVisitor.java b/dev/core/src/com/google/gwt/dev/js/JsConstructExpressionVisitor.java
index 68640e0..fd2c209 100644
--- a/dev/core/src/com/google/gwt/dev/js/JsConstructExpressionVisitor.java
+++ b/dev/core/src/com/google/gwt/dev/js/JsConstructExpressionVisitor.java
@@ -80,7 +80,7 @@
@Override
public boolean visit(JsNameRef x, JsContext ctx) {
- if (!x.isLeaf()) {
+ if (x.getQualifier() != null) {
accept(x.getQualifier());
}
return false;
diff --git a/dev/core/src/com/google/gwt/dev/js/JsFirstExpressionVisitor.java b/dev/core/src/com/google/gwt/dev/js/JsFirstExpressionVisitor.java
index ecb6234..26a8327 100644
--- a/dev/core/src/com/google/gwt/dev/js/JsFirstExpressionVisitor.java
+++ b/dev/core/src/com/google/gwt/dev/js/JsFirstExpressionVisitor.java
@@ -110,7 +110,7 @@
@Override
public boolean visit(JsNameRef x, JsContext ctx) {
- if (!x.isLeaf()) {
+ if (x.getQualifier() != null) {
accept(x.getQualifier());
}
return false;
diff --git a/dev/core/src/com/google/gwt/dev/js/JsInliner.java b/dev/core/src/com/google/gwt/dev/js/JsInliner.java
index 98a00b1..a24b669 100644
--- a/dev/core/src/com/google/gwt/dev/js/JsInliner.java
+++ b/dev/core/src/com/google/gwt/dev/js/JsInliner.java
@@ -1737,7 +1737,7 @@
}
assert expression != null;
- return JsHoister.hoist(expression);
+ return JsSafeCloner.clone(expression);
}
/**
diff --git a/dev/core/src/com/google/gwt/dev/js/JsNamespaceChooser.java b/dev/core/src/com/google/gwt/dev/js/JsNamespaceChooser.java
index 1bbb7ce..f6e6aea 100644
--- a/dev/core/src/com/google/gwt/dev/js/JsNamespaceChooser.java
+++ b/dev/core/src/com/google/gwt/dev/js/JsNamespaceChooser.java
@@ -279,7 +279,7 @@
@Override
public void endVisit(JsNameRef x, JsContext ctx) {
- if (!x.isLeaf() || x.getQualifier() != null || x.getName() == null) {
+ if (x.getQualifier() != null || x.getName() == null) {
return;
}
diff --git a/dev/core/src/com/google/gwt/dev/js/JsPrecedenceVisitor.java b/dev/core/src/com/google/gwt/dev/js/JsPrecedenceVisitor.java
index ef0f40d..ffa3204 100644
--- a/dev/core/src/com/google/gwt/dev/js/JsPrecedenceVisitor.java
+++ b/dev/core/src/com/google/gwt/dev/js/JsPrecedenceVisitor.java
@@ -221,7 +221,7 @@
@Override
public boolean visit(JsNameRef x, JsContext ctx) {
- if (x.isLeaf()) {
+ if (x.getQualifier() == null) {
answer = 17; // primary
} else {
answer = 16; // property access
diff --git a/dev/core/src/com/google/gwt/dev/js/JsHoister.java b/dev/core/src/com/google/gwt/dev/js/JsSafeCloner.java
similarity index 93%
rename from dev/core/src/com/google/gwt/dev/js/JsHoister.java
rename to dev/core/src/com/google/gwt/dev/js/JsSafeCloner.java
index 2dfb9b4..39b7452 100644
--- a/dev/core/src/com/google/gwt/dev/js/JsHoister.java
+++ b/dev/core/src/com/google/gwt/dev/js/JsSafeCloner.java
@@ -45,11 +45,10 @@
import java.util.List;
/**
- * A utility class to clone JsExpression AST members for use by
- * {@link JsInliner}. <b>Not all expressions are necessarily implemented</b>,
- * only those that are safe to hoist into outer call sites.
+ * A utility class to clone JsExpression AST members. <b>Not all expressions are necessarily
+ * cloned </b>, only those expressions that are safe to hoist into outer call sites.
*/
-public final class JsHoister {
+public final class JsSafeCloner {
/**
* Implements actual cloning logic. We rely on the JsExpressions to provide
* traversal logic. The {@link #stack} field is used to accumulate
@@ -144,6 +143,11 @@
*/
@Override
public void endVisit(JsNameRef x, JsContext ctx) {
+ if (x.getQualifier() == null && x.getIdent() == "arguments") {
+ // References to the arguments object can not be hoisted.
+ successful = false;
+ stack.push(null);
+ }
JsNameRef toReturn = new JsNameRef(x.getSourceInfo(), x.getName());
if (x.getQualifier() != null) {
@@ -249,7 +253,7 @@
}
/**
- * Given a JsStatement, construct an expression to hoist into the outer
+ * Given a JsStatement, construct an expression to clone into the outer
* caller. This does not perform any name replacement, nor does it verify the
* scope of referenced elements, but simply constructs a mutable copy of the
* expression that can be manipulated at-will.
@@ -257,7 +261,7 @@
* @return A copy of the original expression, or <code>null</code> if the
* expression cannot be hoisted.
*/
- public static JsExpression hoist(JsExpression expression) {
+ public static JsExpression clone(JsExpression expression) {
if (expression == null) {
return null;
}
@@ -267,6 +271,6 @@
return c.getExpression();
}
- private JsHoister() {
+ private JsSafeCloner() {
}
}
diff --git a/dev/core/src/com/google/gwt/dev/js/JsUtils.java b/dev/core/src/com/google/gwt/dev/js/JsUtils.java
index bc296c7..2f94c75 100644
--- a/dev/core/src/com/google/gwt/dev/js/JsUtils.java
+++ b/dev/core/src/com/google/gwt/dev/js/JsUtils.java
@@ -22,6 +22,7 @@
import com.google.gwt.dev.jjs.ast.JPrimitiveType;
import com.google.gwt.dev.jjs.ast.JProgram;
import com.google.gwt.dev.jjs.impl.JavaToJavaScriptMap;
+import com.google.gwt.dev.js.ast.JsArrayLiteral;
import com.google.gwt.dev.js.ast.JsBinaryOperation;
import com.google.gwt.dev.js.ast.JsBinaryOperator;
import com.google.gwt.dev.js.ast.JsBlock;
@@ -31,15 +32,20 @@
import com.google.gwt.dev.js.ast.JsInvocation;
import com.google.gwt.dev.js.ast.JsName;
import com.google.gwt.dev.js.ast.JsNameRef;
+import com.google.gwt.dev.js.ast.JsNew;
import com.google.gwt.dev.js.ast.JsNode;
+import com.google.gwt.dev.js.ast.JsNullLiteral;
import com.google.gwt.dev.js.ast.JsParameter;
import com.google.gwt.dev.js.ast.JsReturn;
import com.google.gwt.dev.js.ast.JsScope;
import com.google.gwt.dev.js.ast.JsStatement;
import com.google.gwt.dev.js.ast.JsThisRef;
import com.google.gwt.dev.util.StringInterner;
+import com.google.gwt.thirdparty.guava.common.base.Preconditions;
+import com.google.gwt.thirdparty.guava.common.collect.Iterables;
import com.google.gwt.thirdparty.guava.common.collect.Lists;
+import java.util.Collections;
import java.util.List;
import java.util.regex.Pattern;
@@ -100,15 +106,14 @@
JsName name = bridge.getScope().declareName(p.getName());
bridge.getParameters().add(new JsParameter(sourceInfo, name));
}
- JsNameRef ref = polyName.makeRef(sourceInfo);
- ref.setQualifier(new JsThisRef(sourceInfo));
+ JsNameRef reference = polyName.makeQualifiedRef(sourceInfo, new JsThisRef(sourceInfo));
List<JsExpression> args = Lists.newArrayList();
for (JsParameter p : bridge.getParameters()) {
args.add(p.getName().makeRef(sourceInfo));
}
- JsExpression invocation =
- createInvocationOrPropertyAccess(sourceInfo, method.getJsMemberType(), ref, args);
+ JsExpression invocation = createInvocationOrPropertyAccess(
+ InvocationStyle.NORMAL, sourceInfo, method, reference.getQualifier(), reference, args);
JsBlock block = new JsBlock(sourceInfo);
if (method.getType() == JPrimitiveType.VOID) {
@@ -149,32 +154,17 @@
return func;
}
- public static JsNameRef createQualifiedNameRef(SourceInfo info, JsName... names) {
- JsNameRef result = null;
- for (JsName name : names) {
- if (result == null) {
- result = name.makeRef(info);
- continue;
- }
- result = name.makeQualifiedRef(info, result);
+ public static JsExpression createQualifiedNameRef(
+ SourceInfo info, JsExpression base, String... names) {
+ JsExpression result = base;
+ for (String name : names) {
+ JsNameRef nameRef = new JsNameRef(info, name);
+ nameRef.setQualifier(result);
+ result = nameRef;
}
return result;
}
- public static JsExpression createInvocationOrPropertyAccess(SourceInfo sourceInfo,
- JsMemberType memberType, JsNameRef reference, List<JsExpression> args) {
- switch (memberType) {
- case SETTER:
- assert args.size() == 1;
- return createAssignment(reference, args.get(0));
- case GETTER:
- assert args.size() == 0;
- return reference;
- default:
- return new JsInvocation(sourceInfo, reference, args);
- }
- }
-
/**
* Given a string qualifier such as 'foo.bar.Baz', returns a chain of JsNameRef's representing
* this qualifier.
@@ -192,6 +182,232 @@
return ref;
}
+ public static JsNameRef createQualifiedNameRef(SourceInfo info, JsName... names) {
+ JsNameRef result = null;
+ for (JsName name : names) {
+ if (result == null) {
+ result = name.makeRef(info);
+ continue;
+ }
+ result = name.makeQualifiedRef(info, result);
+ }
+ return result;
+ }
+
+ private enum TargetType {
+ SETTER, GETTER, NEWINSTANCE, FUNCTION, METHOD
+ }
+
+ private enum CallStyle {
+ DIRECT, USING_CALL_FOR_SUPER, USING_APPLY_FOR_VARARGS_ARRAY
+ }
+
+ private static class InvocationDescriptor {
+ private final TargetType targetType;
+ private final CallStyle callStyle;
+ private final List<JsExpression> nonVarargsArguments;
+ private final JsExpression varargsArgument;
+ private final JsExpression instance;
+ private final JsNameRef reference;
+
+ InvocationDescriptor(TargetType targetType, CallStyle callStyle,
+ JsExpression instance, JsNameRef reference,
+ List<JsExpression> nonVarargsArguments, JsExpression varargsArgument) {
+ this.targetType = targetType;
+ this.callStyle = callStyle;
+ this.nonVarargsArguments = nonVarargsArguments;
+ this.varargsArgument = varargsArgument;
+ this.instance = instance;
+ this.reference = reference;
+ }
+ }
+
+ /**
+ * Decides the type of invokation to perform, tranforming vararg calls into plain calls if
+ * possible.
+ */
+ private static InvocationDescriptor createInvocationDescriptor(InvocationStyle invocationStyle,
+ JMethod method, JsExpression instance, JsNameRef reference, List<JsExpression> args) {
+
+ CallStyle callStyle = invocationStyle == InvocationStyle.SUPER
+ ? CallStyle.USING_CALL_FOR_SUPER : CallStyle.DIRECT;
+
+ TargetType targetType;
+ switch (invocationStyle) {
+ case NEWINSTANCE:
+ assert method.isConstructor();
+ targetType = TargetType.NEWINSTANCE;
+ break;
+ case FUNCTION:
+ assert method.isOrOverridesJsFunctionMethod();
+ targetType = TargetType.FUNCTION;
+ break;
+ default:
+ if (method.getJsMemberType().isPropertyAccessor()) {
+ targetType = method.getJsMemberType() == JsMemberType.GETTER
+ ? TargetType.GETTER : TargetType.SETTER;
+ } else {
+ targetType = TargetType.METHOD;
+ }
+ break;
+ }
+
+ JsExpression lastArgument = Iterables.getLast(args, null);
+ boolean needsVarargsApply = method.isJsMethodVarargs() && !(lastArgument instanceof JsArrayLiteral);
+ List<JsExpression> nonVarargArguments = args;
+ JsExpression varargArgument = null;
+ if (method.isJsMethodVarargs()) {
+ nonVarargArguments = nonVarargArguments.subList(0, args.size() - 1);
+ if (!needsVarargsApply) {
+ nonVarargArguments.addAll(((JsArrayLiteral) lastArgument).getExpressions());
+ } else {
+ varargArgument = lastArgument;
+ callStyle = CallStyle.USING_APPLY_FOR_VARARGS_ARRAY;
+ }
+ }
+
+ instance = instance != null ? instance : JsNullLiteral.INSTANCE;
+ return new InvocationDescriptor(targetType, callStyle, instance, reference,
+ nonVarargArguments, varargArgument);
+ }
+
+ private static JsExpression prepareArgumentsForApply(SourceInfo sourceInfo,
+ Iterable<JsExpression> nonVarargsArguments, JsExpression varargsArgument) {
+ if (Iterables.isEmpty(nonVarargsArguments)) {
+ return varargsArgument;
+ }
+
+ JsArrayLiteral argumentsArray = new JsArrayLiteral(sourceInfo, nonVarargsArguments);
+ JsNameRef argumentsConcat = new JsNameRef(sourceInfo,"concat");
+ argumentsConcat.setQualifier(argumentsArray);
+ return new JsInvocation(sourceInfo, argumentsConcat, varargsArgument);
+ }
+
+ public static JsExpression createApplyInvocation(
+ SourceInfo sourceInfo, InvocationDescriptor invocationDescriptor) {
+ assert invocationDescriptor.callStyle == CallStyle.USING_APPLY_FOR_VARARGS_ARRAY;
+ switch (invocationDescriptor.targetType) {
+ case FUNCTION:
+ // fn.apply(null, [p1, ..., pn].concat(varargsArray));
+ return new JsInvocation(sourceInfo,
+ createQualifiedNameRef(sourceInfo, invocationDescriptor.instance, "apply"),
+ JsNullLiteral.INSTANCE,
+ prepareArgumentsForApply(sourceInfo,
+ invocationDescriptor.nonVarargsArguments,
+ invocationDescriptor.varargsArgument));
+ case METHOD:
+ // Static method:
+ // q.name.apply(null, [p1, ..., pn].concat(varargsArray));
+ // Instance method:
+ // instance.name.apply(instance, [p1, ..., pn].concat(varargsArray));
+ // Super call:
+ // q.name.apply(instance, [p1, ..., pn].concat(varargsArray));
+ JsExpression instance = invocationDescriptor.instance;
+ if (instance == invocationDescriptor.reference.getQualifier()) {
+ // If instance == qualifier, instance needs to be cloned as it can not appear in two
+ // places in the JS AST. This needs to be done only in the case of VARRAGS_ARRAY.
+ // Instance here has been normalized to be just a "leaf" JsNameRef by
+ // {@link ImplementJsVarargs} so that the following translation can be avoided here.
+ // (_t = instance).name.apply(_t, [p1, ..., pn].concat(varargsArray));
+ assert (instance instanceof JsNameRef && ((JsNameRef) instance).isLeaf());
+ instance = Preconditions.checkNotNull(JsSafeCloner.clone(instance));
+ }
+
+ return new JsInvocation(sourceInfo,
+ createQualifiedNameRef(sourceInfo, invocationDescriptor.reference, "apply"),
+ instance,
+ prepareArgumentsForApply(sourceInfo,
+ invocationDescriptor.nonVarargsArguments,
+ invocationDescriptor.varargsArgument));
+ case NEWINSTANCE:
+ // new (q.name.bind.apply(q, [null, p1, ... pn])())()
+ return new JsNew(sourceInfo, new JsInvocation(sourceInfo,
+ createQualifiedNameRef(sourceInfo, invocationDescriptor.reference, "bind", "apply"),
+ invocationDescriptor.reference,
+ prepareArgumentsForApply(sourceInfo,
+ Iterables.concat(
+ Collections.singleton(JsNullLiteral.INSTANCE),
+ invocationDescriptor.nonVarargsArguments),
+ invocationDescriptor.varargsArgument)));
+ default:
+ throw new AssertionError("Target type " + invocationDescriptor.targetType
+ + " invalid for varargs apply invocation");
+ }
+ }
+
+ public static JsExpression createDirectInvocationOrPropertyAccess(
+ SourceInfo sourceInfo, InvocationDescriptor invocationDescriptor) {
+ assert invocationDescriptor.callStyle == CallStyle.DIRECT;
+ switch (invocationDescriptor.targetType) {
+ case SETTER:
+ assert invocationDescriptor.nonVarargsArguments.size() == 1;
+ return createAssignment(invocationDescriptor.reference,
+ invocationDescriptor.nonVarargsArguments.get(0));
+ case GETTER:
+ assert invocationDescriptor.nonVarargsArguments.size() == 0;
+ return invocationDescriptor.reference;
+ case FUNCTION:
+ return new JsInvocation(sourceInfo, invocationDescriptor.instance,
+ invocationDescriptor.nonVarargsArguments);
+ case METHOD:
+ return new JsInvocation(sourceInfo, invocationDescriptor.reference,
+ invocationDescriptor.nonVarargsArguments);
+ case NEWINSTANCE:
+ return new JsNew(
+ sourceInfo, invocationDescriptor.reference, invocationDescriptor.nonVarargsArguments);
+ default:
+ throw new AssertionError("Target type " + invocationDescriptor.targetType
+ + " invalid for direct invocation");
+ }
+ }
+
+ public static JsExpression createSuperInvocationOrPropertyAccess(
+ SourceInfo sourceInfo, InvocationDescriptor invocationDescriptor) {
+ assert invocationDescriptor.callStyle == CallStyle.USING_CALL_FOR_SUPER;
+ switch (invocationDescriptor.targetType) {
+ case SETTER:
+ assert invocationDescriptor.nonVarargsArguments.size() == 1;
+ // TODO(rluble): implement super setters.
+ throw new UnsupportedOperationException("Super.setter is unsupported");
+ case GETTER:
+ assert invocationDescriptor.nonVarargsArguments.size() == 0;
+ // TODO(rluble): implement super getters.
+ throw new UnsupportedOperationException("Super.getter is unsupported");
+ case METHOD:
+ // q.name.call(instance, p1, ..., pn)
+ return new JsInvocation(sourceInfo,
+ createQualifiedNameRef(sourceInfo, invocationDescriptor.reference, "call"),
+ Iterables.concat(Collections.singleton(invocationDescriptor.instance),
+ invocationDescriptor.nonVarargsArguments));
+ default:
+ throw new AssertionError("Target type " + invocationDescriptor.targetType
+ + " invalid for super invocation");
+ }
+ }
+
+ /**
+ * Invocation styles.
+ */
+ public enum InvocationStyle {
+ NORMAL, FUNCTION, SUPER, NEWINSTANCE
+ }
+
+ public static JsExpression createInvocationOrPropertyAccess(InvocationStyle invocationStyle,
+ SourceInfo sourceInfo, JMethod method, JsExpression instance, JsNameRef reference,
+ List<JsExpression> args) {
+ InvocationDescriptor invocationDescriptor =
+ createInvocationDescriptor(invocationStyle, method, instance, reference, args);
+ switch (invocationDescriptor.callStyle) {
+ case DIRECT:
+ return createDirectInvocationOrPropertyAccess(sourceInfo, invocationDescriptor);
+ case USING_CALL_FOR_SUPER:
+ return createSuperInvocationOrPropertyAccess(sourceInfo, invocationDescriptor);
+ case USING_APPLY_FOR_VARARGS_ARRAY:
+ return createApplyInvocation(sourceInfo, invocationDescriptor);
+ }
+ throw new AssertionError();
+ }
+
/**
* Attempts to extract a single expression from a given statement and returns
* it. If no such expression exists, returns <code>null</code>.
diff --git a/dev/core/src/com/google/gwt/dev/js/ast/JsArrayLiteral.java b/dev/core/src/com/google/gwt/dev/js/ast/JsArrayLiteral.java
index fc3313b..a0b8145 100644
--- a/dev/core/src/com/google/gwt/dev/js/ast/JsArrayLiteral.java
+++ b/dev/core/src/com/google/gwt/dev/js/ast/JsArrayLiteral.java
@@ -14,9 +14,10 @@
package com.google.gwt.dev.js.ast;
import com.google.gwt.dev.jjs.SourceInfo;
+import com.google.gwt.thirdparty.guava.common.collect.Iterables;
import com.google.gwt.thirdparty.guava.common.collect.Lists;
-import java.util.Collections;
+import java.util.Arrays;
import java.util.List;
/**
@@ -28,9 +29,13 @@
private boolean internable = false;
- public JsArrayLiteral(SourceInfo sourceInfo, JsExpression... expressions) {
+ public JsArrayLiteral(SourceInfo sourceInfo, Iterable<JsExpression> expressions) {
super(sourceInfo);
- Collections.addAll(this.exprs, expressions);
+ Iterables.addAll(this.exprs, expressions);
+ }
+
+ public JsArrayLiteral(SourceInfo sourceInfo, JsExpression... expressions) {
+ this(sourceInfo, Arrays.asList(expressions));
}
public List<JsExpression> getExpressions() {
diff --git a/dev/core/src/com/google/gwt/dev/js/ast/JsInvocation.java b/dev/core/src/com/google/gwt/dev/js/ast/JsInvocation.java
index b400016..5c9fc1d 100644
--- a/dev/core/src/com/google/gwt/dev/js/ast/JsInvocation.java
+++ b/dev/core/src/com/google/gwt/dev/js/ast/JsInvocation.java
@@ -14,9 +14,9 @@
package com.google.gwt.dev.js.ast;
import com.google.gwt.dev.jjs.SourceInfo;
+import com.google.gwt.thirdparty.guava.common.collect.Iterables;
import java.util.ArrayList;
-import java.util.Collection;
import java.util.Collections;
import java.util.List;
@@ -43,14 +43,15 @@
Collections.addAll(this.args, args);
}
- public JsInvocation(SourceInfo sourceInfo, JsFunction function, Collection<JsExpression> args) {
+ public JsInvocation(SourceInfo sourceInfo, JsFunction function, Iterable<JsExpression> args) {
this(sourceInfo, function.getName().makeRef(sourceInfo), args);
}
- public JsInvocation(SourceInfo sourceInfo, JsExpression function, Collection<JsExpression> args) {
+ public JsInvocation(SourceInfo sourceInfo, JsExpression function, Iterable<JsExpression> args) {
super(sourceInfo);
+ assert function != null;
setQualifier(function);
- this.args.addAll(args);
+ Iterables.addAll(this.args, args);
}
@Override
diff --git a/dev/core/src/com/google/gwt/dev/js/ast/JsNameRef.java b/dev/core/src/com/google/gwt/dev/js/ast/JsNameRef.java
index 32d9619..2c1512a 100644
--- a/dev/core/src/com/google/gwt/dev/js/ast/JsNameRef.java
+++ b/dev/core/src/com/google/gwt/dev/js/ast/JsNameRef.java
@@ -85,9 +85,9 @@
public boolean isLeaf() {
if (qualifier == null) {
return true;
- } else {
- return false;
}
+
+ return qualifier.isLeaf();
}
public boolean isResolved() {
diff --git a/dev/core/src/com/google/gwt/dev/js/ast/JsNew.java b/dev/core/src/com/google/gwt/dev/js/ast/JsNew.java
index 0fc6edf..eec5320 100644
--- a/dev/core/src/com/google/gwt/dev/js/ast/JsNew.java
+++ b/dev/core/src/com/google/gwt/dev/js/ast/JsNew.java
@@ -14,8 +14,10 @@
package com.google.gwt.dev.js.ast;
import com.google.gwt.dev.jjs.SourceInfo;
+import com.google.gwt.thirdparty.guava.common.collect.Lists;
-import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
import java.util.List;
/**
@@ -23,13 +25,20 @@
*/
public final class JsNew extends JsExpression implements HasArguments {
- private final List<JsExpression> args = new ArrayList<JsExpression>();
+ private final List<JsExpression> args = Lists.newArrayList();
private JsExpression ctorExpr;
- public JsNew(SourceInfo sourceInfo, JsExpression ctorExpr) {
+ public JsNew(SourceInfo sourceInfo, JsExpression ctorExpr, JsExpression... args) {
+ this(sourceInfo, ctorExpr, Arrays.asList(args));
+ }
+
+ public JsNew(SourceInfo sourceInfo, JsExpression ctorExpr, Collection<JsExpression> args) {
super(sourceInfo);
this.ctorExpr = ctorExpr;
+ if (args != null) {
+ this.args.addAll(args);
+ }
}
@Override
diff --git a/dev/core/test/com/google/gwt/dev/jjs/impl/ImplementJsVarargsTest.java b/dev/core/test/com/google/gwt/dev/jjs/impl/ImplementJsVarargsTest.java
new file mode 100644
index 0000000..10c0f80
--- /dev/null
+++ b/dev/core/test/com/google/gwt/dev/jjs/impl/ImplementJsVarargsTest.java
@@ -0,0 +1,178 @@
+/*
+ * 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.dev.jjs.impl;
+
+import com.google.gwt.core.ext.TreeLogger;
+import com.google.gwt.dev.jjs.ast.JMethod;
+import com.google.gwt.dev.jjs.ast.JProgram;
+import com.google.gwt.thirdparty.guava.common.base.Joiner;
+
+/**
+ * Test for {@link ImplementJsVarargs}.
+ */
+public class ImplementJsVarargsTest extends OptimizerTestBase {
+ // TODO(rluble): add unit test for the rest of the functionality.
+
+ public void testOptimizedArguments_justPassThru() throws Exception {
+ addSnippetImport("jsinterop.annotations.JsType");
+ addSnippetClassDecl(
+ "@JsType static class A {",
+ " public static void m(Object... obj) { n(obj); }",
+ " public static void n(Object... obj) { }",
+ "}");
+
+ Result result = optimize("void", "A.m();");
+
+ assertEquals(
+ Joiner.on('\n').join(
+ "public static void m(Object[] _arguments_){",
+ " EntryPoint$A.n(_arguments_);",
+ "}"), result.findMethod("test.EntryPoint$A.m([Ljava/lang/Object;)V").toSource());
+ }
+
+ public void testOptimizedArguments_onlyAccess() throws Exception {
+ addSnippetImport("jsinterop.annotations.JsType");
+ addSnippetClassDecl(
+ "@JsType static class A {",
+ " public static void m(Object... obj) { n(obj[5], obj.length); }",
+ " public static void n(Object arg1, Object arg2) { }",
+ "}");
+
+ Result result = optimize("void", "A.m();");
+
+ assertEquals(
+ Joiner.on('\n').join(
+ "public static void m(Object[] _arguments_){",
+ " EntryPoint$A.n(_arguments_[5], Integer.valueOf(_arguments_.length));",
+ "}"), result.findMethod("test.EntryPoint$A.m([Ljava/lang/Object;)V").toSource());
+ }
+
+ public void testOptimizedArguments_offsetAccess() throws Exception {
+ addSnippetImport("jsinterop.annotations.JsType");
+ addSnippetClassDecl(
+ "@JsType static class A {",
+ " public static void m(int i, Object... obj) { n(obj[5], obj.length); }",
+ " public static void n(Object arg1, Object arg2) { }",
+ "}");
+
+ Result result = optimize("void", "A.m(2);");
+
+ assertEquals(
+ Joiner.on('\n').join(
+ "public static void m(int i, Object[] _arguments_){",
+ " EntryPoint$A.n(_arguments_[5 + 1], Integer.valueOf(_arguments_.length - 1));",
+ "}"), result.findMethod("test.EntryPoint$A.m(I[Ljava/lang/Object;)V").toSource());
+ }
+
+ public void testOptimizedArguments_writeToArguments() throws Exception {
+ addSnippetImport("jsinterop.annotations.JsType");
+ addSnippetClassDecl(
+ "@JsType static class A {",
+ " public static void m(Object... obj) { obj[5] = 1; }",
+ "}");
+
+ Result result = optimize("void", "A.m(2);");
+
+ assertEquals(
+ Joiner.on('\n').join(
+ "public static void m(Object[] _arguments_){",
+ " {",
+ " Object[] obj = new Object[][_arguments_.length];",
+ " for (int $i = 0; $i < _arguments_.length; $i++) {",
+ " obj[$i] = _arguments_[$i];",
+ " }",
+ " }",
+ " obj[5] = Integer.valueOf(1);",
+ "}"), result.findMethod("test.EntryPoint$A.m([Ljava/lang/Object;)V").toSource());
+ }
+
+ public void testOptimizedArguments_postIncrement() throws Exception {
+ addSnippetImport("jsinterop.annotations.JsType");
+ addSnippetClassDecl(
+ "@JsType static class A {",
+ " public static void m(int... obj) { obj[5]++; }",
+ "}");
+
+ Result result = optimize("void", "A.m(2);");
+
+ assertEquals(
+ Joiner.on('\n').join(
+ "public static void m(int[] _arguments_){",
+ " {",
+ " int[] obj = new int[][_arguments_.length];",
+ " for (int $i = 0; $i < _arguments_.length; $i++) {",
+ " obj[$i] = _arguments_[$i];",
+ " }",
+ " }",
+ " obj[5]++;",
+ "}"), result.findMethod("test.EntryPoint$A.m([I)V").toSource());
+ }
+
+ public void testOptimizedArguments_preDecrement() throws Exception {
+ addSnippetImport("jsinterop.annotations.JsType");
+ addSnippetClassDecl(
+ "@JsType static class A {",
+ " public static void m(int... obj) { --obj[5]; }",
+ "}");
+
+ Result result = optimize("void", "A.m(2);");
+
+ assertEquals(
+ Joiner.on('\n').join(
+ "public static void m(int[] _arguments_){",
+ " {",
+ " int[] obj = new int[][_arguments_.length];",
+ " for (int $i = 0; $i < _arguments_.length; $i++) {",
+ " obj[$i] = _arguments_[$i];",
+ " }",
+ " }",
+ " --obj[5];",
+ "}"), result.findMethod("test.EntryPoint$A.m([I)V").toSource());
+ }
+
+ public void testOptimizedArguments_call() throws Exception {
+ addSnippetImport("jsinterop.annotations.JsType");
+ addSnippetClassDecl(
+ "@JsType static class A {",
+ " public static void m(int... obj) { n(obj); }",
+ " public static void n(int[] obj) { }",
+ "}");
+
+ Result result = optimize("void", "A.m(2);");
+
+ assertEquals(
+ Joiner.on('\n').join(
+ "public static void m(int[] _arguments_){",
+ " {",
+ " int[] obj = new int[][_arguments_.length];",
+ " for (int $i = 0; $i < _arguments_.length; $i++) {",
+ " obj[$i] = _arguments_[$i];",
+ " }",
+ " }",
+ " EntryPoint$A.n(obj);",
+ "}"), result.findMethod("test.EntryPoint$A.m([I)V").toSource());
+ }
+
+ @Override
+ protected boolean doOptimizeMethod(TreeLogger logger, JProgram program, JMethod method) {
+ program.addEntryMethod(findMainMethod(program));
+ boolean didChange = true;
+ do {
+ didChange &= TypeTightener.exec(program).didChange();
+ didChange &= MethodCallTightener.exec(program).didChange();
+ } while (didChange);
+ ImplementJsVarargs.exec(program);
+ return true;
+ }
+}
diff --git a/dev/core/test/com/google/gwt/dev/jjs/impl/JModVisitorWithTemporaryVariableCreationTest.java b/dev/core/test/com/google/gwt/dev/jjs/impl/JModVisitorWithTemporaryVariableCreationTest.java
index 5d58b3c..521fe49 100644
--- a/dev/core/test/com/google/gwt/dev/jjs/impl/JModVisitorWithTemporaryVariableCreationTest.java
+++ b/dev/core/test/com/google/gwt/dev/jjs/impl/JModVisitorWithTemporaryVariableCreationTest.java
@@ -22,7 +22,6 @@
import com.google.gwt.dev.jjs.ast.JExpression;
import com.google.gwt.dev.jjs.ast.JExpressionStatement;
import com.google.gwt.dev.jjs.ast.JLocal;
-import com.google.gwt.dev.jjs.ast.JMethodBody;
import com.google.gwt.dev.jjs.ast.JType;
/**
@@ -54,17 +53,12 @@
if (x != dontBother && !ctx.isLvalue()) {
SourceInfo info = x.getSourceInfo();
JType type = x.getType();
- JLocal local = createTempLocal(info, type);
+ JLocal local = createTempLocal(info, type, "$t" + nextIdToAssign++);
ctx.replaceMe(new JBinaryOperation(info, type, JBinaryOperator.ASG,
local.makeRef(info), x));
}
}
-
private int nextIdToAssign;
- @Override
- protected String newTemporaryLocalName(SourceInfo info, JType type, JMethodBody methodBody) {
- return "$t" + nextIdToAssign++;
- }
}
public void testBasic() throws Exception {
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 00d0880..3634fe5 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
@@ -168,6 +168,7 @@
" @JsProperty void setY();",
" @JsProperty int setZ(int z);",
" @JsProperty static void setStatic(){}",
+ " @JsProperty void setW(int... z);",
"}");
assertBuggyFails(
@@ -183,7 +184,8 @@
"Line 12: JsProperty 'int EntryPoint.Buggy.setZ(int)' should have a correct setter "
+ "or getter signature.",
"Line 13: JsProperty 'void EntryPoint.Buggy.setStatic()' should have a correct setter "
- + "or getter signature.");
+ + "or getter signature.",
+ "Line 14: JsProperty 'void EntryPoint.Buggy.setW(int[])' cannot have a vararg parameter.");
}
public void testJsPropertyNonGetterStyleFails() throws Exception {
@@ -893,6 +895,29 @@
+ "both use the same JavaScript name 'z'.");
}
+ public void testJsMethodJSNIVarargsWithNoReferenceSucceeds()
+ throws Exception {
+ addSnippetImport("jsinterop.annotations.JsMethod");
+ addSnippetClassDecl(
+ "public static class Buggy {",
+ " @JsMethod public native void m(int i, int... z) /*-{ return arguments[i]; }-*/;",
+ "}");
+
+ assertBuggySucceeds();
+ }
+
+ public void testJsMethodJSNIVarargsWithReferenceFails() {
+ addSnippetImport("jsinterop.annotations.JsMethod");
+ addSnippetClassDecl(
+ "public static class Buggy {",
+ " @JsMethod public native void m(int i, int... z) /*-{ return z[0];}-*/;",
+ "}");
+
+ assertBuggyFails(
+ "Line 5: Cannot access vararg parameter 'z' from JSNI in JsMethod "
+ + "'void EntryPoint.Buggy.m(int, int[])'. Use 'arguments' instead.");
+ }
+
public void testMultiplePrivateConstructorsExportSucceeds() throws Exception {
addSnippetImport("jsinterop.annotations.JsType");
addSnippetClassDecl(
diff --git a/user/test/com/google/gwt/core/CoreJsInteropSuite.java b/user/test/com/google/gwt/core/CoreJsInteropSuite.java
index 55602f5..828cf97 100644
--- a/user/test/com/google/gwt/core/CoreJsInteropSuite.java
+++ b/user/test/com/google/gwt/core/CoreJsInteropSuite.java
@@ -22,6 +22,7 @@
import com.google.gwt.core.interop.JsTypeArrayTest;
import com.google.gwt.core.interop.JsTypeBridgeTest;
import com.google.gwt.core.interop.JsTypeTest;
+import com.google.gwt.core.interop.JsTypeVarargsTest;
import com.google.gwt.core.interop.NativeJsTypeTest;
import junit.framework.Test;
@@ -41,6 +42,7 @@
suite.addTestSuite(JsMethodTest.class);
suite.addTestSuite(JsTypeArrayTest.class);
suite.addTestSuite(JsFunctionTest.class);
+ suite.addTestSuite(JsTypeVarargsTest.class);
suite.addTestSuite(NativeJsTypeTest.class);
return suite;
diff --git a/user/test/com/google/gwt/core/interop/JsTypeVarargsTest.java b/user/test/com/google/gwt/core/interop/JsTypeVarargsTest.java
new file mode 100644
index 0000000..82ce8b0
--- /dev/null
+++ b/user/test/com/google/gwt/core/interop/JsTypeVarargsTest.java
@@ -0,0 +1,229 @@
+/*
+ * Copyright 2013 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.interop;
+
+import static jsinterop.annotations.JsPackage.GLOBAL;
+
+import com.google.gwt.core.client.ScriptInjector;
+import com.google.gwt.junit.client.GWTTestCase;
+
+import jsinterop.annotations.JsFunction;
+import jsinterop.annotations.JsMethod;
+import jsinterop.annotations.JsPackage;
+import jsinterop.annotations.JsType;
+
+/**
+ * Tests JsType functionality.
+ */
+@SuppressWarnings("cast")
+public class JsTypeVarargsTest extends GWTTestCase {
+
+ @Override
+ public String getModuleName() {
+ return "com.google.gwt.core.Interop";
+ }
+
+ @Override
+ protected void gwtSetUp() throws Exception {
+ ScriptInjector.fromString(
+ "function JsTypeVarargsTest_MyNativeJsType() {}\n"
+ + "function JsTypeVarargsTest_MyNativeJsTypeVarargsConstructor(i) {"
+ + " this.a = arguments[i]; this.b = arguments.length; }\n")
+ .setWindow(ScriptInjector.TOP_WINDOW)
+ .inject();
+ setupGlobal();
+ }
+
+ // $global always points to scope of exports
+ private native void setupGlobal() /*-{
+ $global = window.goog && window.goog.global || $wnd;
+ $wnd.$global = $global;
+ }-*/;
+
+ @JsMethod
+ public static native int varargsMethod1(Object... varargs) /*-{
+ return arguments.length;
+ }-*/;
+
+ @JsMethod
+ public static int varargsMethod2(Object... varargs) {
+ return varargs.length;
+ }
+
+ @JsMethod(namespace = JsPackage.GLOBAL)
+ public static Object varargsMethod3(int slot, Object... varargs) {
+ return varargs[slot];
+ }
+
+ @JsMethod(namespace = JsPackage.GLOBAL)
+ public static Object[] varargsMethod4(int slot, Object... varargs) {
+ varargs[slot] = null;
+ return varargs;
+ }
+
+ private static native Object callVarargsMethod3FromJSNI() /*-{
+ return $global.varargsMethod3(2, "1", "2", "3", "4");
+ }-*/;
+
+ @JsType(isNative = true, namespace = GLOBAL, name = "Object")
+ static class NativeJsType {
+ }
+
+ @JsType(isNative = true, namespace = GLOBAL,
+ name = "JsTypeVarargsTest_MyNativeJsTypeVarargsConstructor")
+ static class NativeJsTypeWithVarargsConstructor {
+ public Object a;
+ public int b;
+ NativeJsTypeWithVarargsConstructor(int i, Object... args) { }
+ }
+
+ static class SubclassNativeWithVarargsConstructor extends NativeJsTypeWithVarargsConstructor {
+ SubclassNativeWithVarargsConstructor(String s, Object... args) {
+ super(1, args[0], args[1], null);
+ }
+
+ SubclassNativeWithVarargsConstructor(int i, Object... args) {
+ super(i, args);
+ }
+
+ @JsMethod
+ Object varargsMethod(int i, Object... args) {
+ return args[i];
+ }
+ }
+
+ static class SubSubclassNativeWithVarargsConstructor
+ extends SubclassNativeWithVarargsConstructor {
+ SubSubclassNativeWithVarargsConstructor() {
+ super(0, null);
+ }
+
+ Object varargsMethod(int i, Object... args) {
+ return super.varargsMethod(i, args);
+ }
+
+ Object nonJsVarargsMethod() {
+ return super.varargsMethod(1, null ,this);
+ }
+ }
+
+ public void testVarargsCall_regularMethods() {
+ assertEquals(3, varargsMethod1("A", "B", "C"));
+ assertEquals(4, varargsMethod2("A", "B", "C", "D"));
+ assertEquals(2, varargsMethod1(new NativeJsType[]{null, null}));
+ assertEquals(5, varargsMethod2(new NativeJsType[]{null, null, null, null, null}));
+ assertEquals("C", varargsMethod3(2, "A", "B", "C", "D"));
+ assertEquals("3", callVarargsMethod3FromJSNI());
+ assertNull(varargsMethod4(1, "A", "B", "C")[1]);
+ assertEquals("A", varargsMethod4(1, "A", "B", "C")[0]);
+ assertEquals(3, varargsMethod4(1, "A", "B", "C").length);
+ }
+
+ public void testVarargsCall_constructors() {
+ NativeJsType someNativeObject = new NativeJsType();
+ NativeJsTypeWithVarargsConstructor object =
+ new NativeJsTypeWithVarargsConstructor(1, someNativeObject, null);
+
+ assertSame(someNativeObject, object.a);
+ assertEquals(3, object.b);
+
+ Object[] params = new Object[] { someNativeObject, null };
+ object = new NativeJsTypeWithVarargsConstructor(1, params);
+
+ assertSame(someNativeObject, object.a);
+ assertEquals(3, object.b);
+
+ object = new SubclassNativeWithVarargsConstructor("", someNativeObject, null);
+
+ assertSame(someNativeObject, object.a);
+ assertEquals(4, object.b);
+
+ object = new SubclassNativeWithVarargsConstructor(1, someNativeObject, null);
+
+ assertSame(someNativeObject, object.a);
+ assertEquals(3, object.b);
+ }
+
+ @JsMethod(namespace = JsPackage.GLOBAL)
+ public static Double sumAndMultiply(Double multiplier, Double... numbers) {
+ double result = 0.0d;
+ for (double d : numbers) {
+ result += d;
+ }
+ result *= multiplier;
+ return result;
+ }
+
+ @JsMethod(namespace = JsPackage.GLOBAL)
+ public static int sumAndMultiplyInt(int multiplier, int... numbers) {
+ int result = 0;
+ for (int d : numbers) {
+ result += d;
+ }
+ result *= multiplier;
+ return result;
+ }
+
+ @JsFunction
+ interface Function {
+ Object f(int i, Object... args);
+ }
+
+ static class AFunction implements Function {
+
+ @Override
+ public Object f(int i, Object... args) {
+ return args[i];
+ }
+ static Function create() {
+ return new AFunction();
+ }
+ }
+
+ public native void testVarargsCall_fromJavaScript() /*-{
+ @GWTTestCase::assertEquals(DDD)(60, $global.sumAndMultiply(2, 10, 20), 0);
+ @GWTTestCase::assertEquals(II)(30, $global.sumAndMultiplyInt(3, 2, 8));
+ var f = @JsTypeVarargsTest.AFunction::create()()
+ @GWTTestCase::assertSame(Ljava/lang/Object;Ljava/lang/Object;)(
+ f, f(2, null, null, f, null));
+ }-*/;
+
+ public void testVarargsCall_jsFunction() {
+ Function function = new AFunction();
+ assertSame(function, function.f(2, null, null, function, null));
+ assertSame(null, function.f(1, null, null, function, null));
+ }
+
+ public void testVarargsCall_superCalls() {
+ SubSubclassNativeWithVarargsConstructor object = new SubSubclassNativeWithVarargsConstructor();
+ assertSame(object, object.nonJsVarargsMethod());
+ assertSame(object, object.varargsMethod(1, null, object, null));
+ }
+
+ private static int sideEffectCount;
+ private SubclassNativeWithVarargsConstructor doSideEffect(
+ SubclassNativeWithVarargsConstructor obj) {
+ sideEffectCount++;
+ return obj;
+ }
+ public void testVarargsCall_sideEffectingInstance() {
+ SubclassNativeWithVarargsConstructor object = new SubclassNativeWithVarargsConstructor(0, null);
+ sideEffectCount = 0;
+ Object[] params = new Object[] { object, null };
+ assertSame(object, doSideEffect(object).varargsMethod(0, params));
+ assertSame(1, sideEffectCount);
+ }
+}