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