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); + } +}