Implement internal annotation @DoNotAutobox.

Change-Id: I7760fee29392237c335ddc46fa8b276c86f48bcd
diff --git a/dev/core/src/com/google/gwt/dev/javac/JdtUtil.java b/dev/core/src/com/google/gwt/dev/javac/JdtUtil.java
index fb03ec8..06e96a8 100644
--- a/dev/core/src/com/google/gwt/dev/javac/JdtUtil.java
+++ b/dev/core/src/com/google/gwt/dev/javac/JdtUtil.java
@@ -31,6 +31,8 @@
 import org.eclipse.jdt.internal.compiler.impl.BooleanConstant;
 import org.eclipse.jdt.internal.compiler.impl.StringConstant;
 import org.eclipse.jdt.internal.compiler.lookup.AnnotationBinding;
+import org.eclipse.jdt.internal.compiler.lookup.BaseTypeBinding;
+import org.eclipse.jdt.internal.compiler.lookup.ClassScope;
 import org.eclipse.jdt.internal.compiler.lookup.ElementValuePair;
 import org.eclipse.jdt.internal.compiler.lookup.FieldBinding;
 import org.eclipse.jdt.internal.compiler.lookup.LocalTypeBinding;
@@ -40,6 +42,7 @@
 import org.eclipse.jdt.internal.compiler.lookup.SourceTypeBinding;
 import org.eclipse.jdt.internal.compiler.lookup.SyntheticArgumentBinding;
 import org.eclipse.jdt.internal.compiler.lookup.TypeBinding;
+import org.eclipse.jdt.internal.compiler.lookup.TypeIds;
 
 import java.util.Arrays;
 import java.util.Collections;
@@ -445,4 +448,74 @@
       }
     });
   }
+
+  /*
+   * Implicit conversion helpers.
+   */
+
+  public static boolean requiresBoxing(int implicitConversion) {
+    return implicitConversion != -1
+        && (implicitConversion & TypeIds.BOXING) != 0;
+  }
+
+  public static boolean requiresUnboxing(int implicitConversion) {
+    return implicitConversion != -1
+        && (implicitConversion & TypeIds.UNBOXING) != 0;
+  }
+
+  public static BaseTypeBinding getBoxingPrimitiveType(ClassScope scope, int implicitConversion) {
+    int typeId = (implicitConversion & TypeIds.IMPLICIT_CONVERSION_MASK) >> 4;
+    return getBaseTypeBinding(scope, typeId);
+  }
+
+  public static BaseTypeBinding getUnboxingPrimitiveType(
+      ClassScope scope, int implicitConversion) {
+    if (needsCastBeforeUnbox(scope, implicitConversion)) {
+      int typeId = (implicitConversion & TypeIds.IMPLICIT_CONVERSION_MASK) >> 4;
+      return getBaseTypeBinding(scope, typeId);
+    }
+    int compileTypeId = implicitConversion & TypeIds.COMPILE_TYPE_MASK;
+    return getBaseTypeBinding(scope, compileTypeId);
+  }
+
+  public static BaseTypeBinding getBaseTypeBinding(ClassScope scope, int typeId) {
+    return  (BaseTypeBinding) TypeBinding.wellKnownType(scope, typeId);
+  }
+
+  public static ReferenceBinding getBoxedTypeBinding(
+      ClassScope scope, BaseTypeBinding primitiveType) {
+    return (ReferenceBinding) scope.boxing(primitiveType);
+  }
+
+  public static boolean needsCastBeforeUnbox(ClassScope scope, int implicitConversion) {
+    // values of specific types like j.l.Object need casting before auto unboxing.
+    int compileTypeId = implicitConversion & TypeIds.COMPILE_TYPE_MASK;
+    return !(TypeBinding.wellKnownType(scope, compileTypeId) instanceof BaseTypeBinding);
+  }
+
+  private static final String VALUE_SUFFIX = "Value";
+  private static final String VALUE_OF_METHOD_NAME = "valueOf";
+
+  private static final char[] VALUE_SUFFIX_ = VALUE_SUFFIX.toCharArray();
+  private static final char[] VALUE_OF_ = VALUE_OF_METHOD_NAME.toCharArray();
+
+  public static MethodBinding getBoxingMethodBinding(
+      ClassScope scope, BaseTypeBinding primitiveType) {
+    ReferenceBinding boxType = (ReferenceBinding) scope.boxing(primitiveType);
+    MethodBinding valueOfMethod = boxType.getExactMethod(VALUE_OF_,
+        new TypeBinding[]{primitiveType}, scope.compilationUnitScope());
+    assert valueOfMethod != null;
+    return valueOfMethod;
+  }
+
+  public static MethodBinding getUnboxingMethodBinding(
+      ClassScope scope, BaseTypeBinding primitiveType) {
+    ReferenceBinding boxType = (ReferenceBinding) scope.boxing(primitiveType);
+    char[] selector = CharOperation.concat(primitiveType.simpleName, VALUE_SUFFIX_);
+
+    MethodBinding valueMethod =
+        boxType.getExactMethod(selector, new TypeBinding[0], scope.compilationUnitScope());
+    assert valueMethod != null;
+    return valueMethod;
+  }
 }
diff --git a/dev/core/src/com/google/gwt/dev/javac/testing/impl/JavaResourceBase.java b/dev/core/src/com/google/gwt/dev/javac/testing/impl/JavaResourceBase.java
index 46bc6f5..aae771d 100644
--- a/dev/core/src/com/google/gwt/dev/javac/testing/impl/JavaResourceBase.java
+++ b/dev/core/src/com/google/gwt/dev/javac/testing/impl/JavaResourceBase.java
@@ -368,12 +368,18 @@
   public static final MockJavaResource SPECIALIZE_METHOD =
       createMockJavaResource("javaemul.internal.annotations.SpecializeMethod",
           "package javaemul.internal.annotations;",
-          "public @interface SpecializeMethod {\n",
-          "  Class<?>[] params();\n" +
-          "  String target();\n",
+          "public @interface SpecializeMethod {",
+          "  Class<?>[] params();",
+          "  String target();",
           "}"
       );
 
+  public static final MockJavaResource DO_NOT_AUTOBOX =
+      createMockJavaResource("javaemul.internal.annotations.DoNotAutobox",
+          "package javaemul.internal.annotations;",
+          "public @interface DoNotAutobox {\n",
+          "}"
+      );
   // TODO: move JS* annotations to intrinsic mock resource base
   public static final MockJavaResource JSTYPE =
       createMockJavaResource("jsinterop.annotations.JsType",
@@ -438,8 +444,8 @@
         ERROR, FUNCTIONALINTERFACE, FLOAT, INTEGER, IS_SERIALIZABLE, JAVASCRIPTEXCEPTION,
         JAVASCRIPTOBJECT, LIST, LONG, MAP, NO_CLASS_DEF_FOUND_ERROR, NUMBER, OBJECT,
         RUNTIME_EXCEPTION, SERIALIZABLE, SHORT, STRING, STRING_BUILDER, SUPPRESS_WARNINGS, SYSTEM,
-        THROWABLE, SPECIALIZE_METHOD, JSTYPE, JSCONSTRUCTOR, JSPACKAGE, JSPROPERTY, JSMETHOD,
-        JSIGNORE, JSFUNCTION, JSOVERLAY, JSOPTIONAL};
+        THROWABLE, SPECIALIZE_METHOD, DO_NOT_AUTOBOX, JSTYPE, JSCONSTRUCTOR, JSPACKAGE, JSPROPERTY,
+        JSMETHOD, JSIGNORE, JSFUNCTION, JSOVERLAY, JSOPTIONAL};
   }
 
   /**
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 ba927d8..266a53a 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
@@ -125,7 +125,6 @@
 import com.google.gwt.thirdparty.guava.common.collect.Sets;
 import com.google.gwt.util.regexfilter.WhitelistRegexFilter;
 
-import org.eclipse.jdt.core.compiler.CharOperation;
 import org.eclipse.jdt.internal.compiler.ast.AND_AND_Expression;
 import org.eclipse.jdt.internal.compiler.ast.ASTNode;
 import org.eclipse.jdt.internal.compiler.ast.AbstractMethodDeclaration;
@@ -379,7 +378,7 @@
     public void endVisit(AllocationExpression x, BlockScope scope) {
       try {
         SourceInfo info = makeSourceInfo(x);
-        List<JExpression> arguments = popCallArgs(info, x.arguments, x.binding);
+        List<JExpression> arguments = popCallArguments(info, x.arguments, x.binding);
         pushNewExpression(info, x, null, arguments, scope);
       } catch (Throwable e) {
         throw translateException(x, e);
@@ -405,7 +404,7 @@
         if (x.initializer != null) {
           // handled by ArrayInitializer.
         } else {
-          List<JExpression> dims = pop(x.dimensions);
+          List<JExpression> dims = performBoxUnboxConversions(pop(x.dimensions), x.dimensions);
           push(JNewArray.createArrayWithDimensionExpressions(info, type, dims));
         }
       } catch (Throwable e) {
@@ -418,7 +417,8 @@
       try {
         SourceInfo info = makeSourceInfo(x);
         JArrayType type = (JArrayType) typeMap.get(x.resolvedType);
-        List<JExpression> expressions = pop(x.expressions);
+        List<JExpression> expressions =
+            performBoxUnboxConversions(pop(x.expressions), x.expressions);
         push(JNewArray.createArrayWithInitializers(info, type, expressions));
       } catch (Throwable e) {
         throw translateException(x, e);
@@ -773,7 +773,7 @@
         JConstructor ctor = (JConstructor) typeMap.get(x.binding);
         JExpression trueQualifier = makeThisRef(info);
         JMethodCall call = new JMethodCall(info, trueQualifier, ctor);
-        List<JExpression> callArgs = popCallArgs(info, x.arguments, x.binding);
+        List<JExpression> callArgs = popCallArguments(info, x.arguments, x.binding);
 
         if (curClass.classType.isEnumOrSubclass() != null) {
           // Enums: wire up synthetic name/ordinal params to the super method.
@@ -1367,7 +1367,7 @@
       // Deal with any boxing/unboxing needed
       JNode node = pop();
       if (node instanceof JExpression) {
-        node = simplify((JExpression) node, (Expression) x.body);
+        node = maybeBoxOrUnbox((JExpression) node, (Expression) x.body);
       }
 
       JMethodBody body = (JMethodBody) curMethod.method.getBody();
@@ -1525,7 +1525,7 @@
         SourceInfo info = makeSourceInfo(x);
         JMethod method = typeMap.get(x.binding);
 
-        List<JExpression> arguments = popCallArgs(info, x.arguments, x.binding);
+        List<JExpression> arguments = popCallArguments(info, x.arguments, x.binding);
         JExpression receiver = pop(x.receiver);
         if (x.receiver instanceof ThisReference) {
           if (method.isStatic()) {
@@ -1560,7 +1560,7 @@
         if (x.valueCast != null) {
           JType[] targetTypes = processCastType(x.valueCast);
           push(isUncheckedGenericMethodCall(x)
-              ? maybeInsertUnsafeTypeCoersion(targetTypes[0], methodCall)
+              ? maybeInsertUnsafeTypeCoercion(targetTypes[0], methodCall)
               : maybeCast(targetTypes, methodCall));
         } else {
           push(methodCall);
@@ -1649,7 +1649,7 @@
     public void endVisit(QualifiedAllocationExpression x, BlockScope scope) {
       try {
         SourceInfo info = makeSourceInfo(x);
-        List<JExpression> arguments = popCallArgs(info, x.arguments, x.binding);
+        List<JExpression> arguments = popCallArguments(info, x.arguments, x.binding);
         pushNewExpression(info, x, x.enclosingInstance(), arguments, scope);
       } catch (Throwable e) {
         throw translateException(x, e);
@@ -1939,7 +1939,7 @@
       if (samMethod.getType() != JPrimitiveType.VOID) {
         JExpression samExpression = boxOrUnboxExpression(samCall, referredMethodBinding.returnType,
             declarationSamBinding.returnType);
-        samMethodBody.getBlock().addStmt(simplify(samExpression, x).makeReturnStatement());
+        samMethodBody.getBlock().addStmt(maybeBoxOrUnbox(samExpression, x).makeReturnStatement());
       } else {
         samMethodBody.getBlock().addStmt(samCall.makeStatement());
       }
@@ -1985,15 +1985,11 @@
       }
 
       if (fromType.isBaseType() && !toType.isBaseType()) {
-        int implicitConversion = (fromType.id & TypeIds.IMPLICIT_CONVERSION_MASK) << 4;
-        implicitConversion = implicitConversion | TypeIds.BOXING;
-        return box(expr, implicitConversion);
+        return box(expr, JdtUtil.getBaseTypeBinding(curClass.scope, fromType.id));
       }
 
       if (!fromType.isBaseType() && toType.isBaseType()) {
-        int implicitConversion = (toType.id & TypeIds.IMPLICIT_CONVERSION_MASK) << 4;
-        implicitConversion = implicitConversion | TypeIds.UNBOXING;
-        return unbox(expr, implicitConversion);
+        return unbox(expr, JdtUtil.getBaseTypeBinding(curClass.scope, toType.id));
       }
 
       TypeBinding castToType = fromType.genericCast(toType);
@@ -2585,23 +2581,26 @@
         assert x instanceof NameReference;
         return null;
       }
-      result = simplify(result, x);
+      result = maybeBoxOrUnbox(result, x);
+      return result;
+    }
+
+    protected <T extends JExpression> List<T> performBoxUnboxConversions(
+        List<T> result, Expression[] expressions) {
+      for (int i = 0; i < result.size(); i++) {
+        result.set(i, (T) maybeBoxOrUnbox(result.get(i), expressions[i]));
+      }
       return result;
     }
 
     @SuppressWarnings("unchecked")
-    protected <T extends JExpression> List<T> pop(Expression[] expressions) {
+    protected List<JExpression> pop(Expression[] expressions) {
       if (expressions == null) {
         return Collections.emptyList();
       }
 
-      List<T> result = (List<T>) popList(Collections2.filter(Arrays.asList(expressions),
+      return  (List<JExpression>) popList(Collections2.filter(Arrays.asList(expressions),
           Predicates.notNull()).size());
-
-      for (int i = 0; i < result.size(); i++) {
-        result.set(i, (T) simplify(result.get(i), expressions[i]));
-      }
-      return result;
     }
 
     protected JDeclarationStatement pop(LocalDeclaration decl) {
@@ -2611,7 +2610,7 @@
     protected JStatement pop(Statement x) {
       JNode pop = (x == null) ? null : pop();
       if (x instanceof Expression) {
-        return simplify((JExpression) pop, (Expression) x).makeStatement();
+        return maybeBoxOrUnbox((JExpression) pop, (Expression) x).makeStatement();
       }
       return (JStatement) pop;
     }
@@ -2628,7 +2627,8 @@
         if (element == null) {
           it.remove();
         } else if (element instanceof JExpression) {
-          it.set((T) simplify((JExpression) element, (Expression) statements[i]).makeStatement());
+          it.set((T)
+              maybeBoxOrUnbox((JExpression) element, (Expression) statements[i]).makeStatement());
         }
       }
       return result;
@@ -2756,25 +2756,47 @@
           new JBinaryOperation(info, lhs.getType(), JBinaryOperator.ASG, lhs, param.makeRef(info));
     }
 
-    private JExpression box(JExpression original, int implicitConversion) {
-      int typeId = (implicitConversion & TypeIds.IMPLICIT_CONVERSION_MASK) >> 4;
-      ClassScope scope = curClass.scope;
-      BaseTypeBinding primitiveType = (BaseTypeBinding) TypeBinding.wellKnownType(scope, typeId);
-      ReferenceBinding boxType = (ReferenceBinding) scope.boxing(primitiveType);
-      MethodBinding valueOfMethod = boxType.getExactMethod(VALUE_OF_,
-          new TypeBinding[]{primitiveType}, scope.compilationUnitScope());
-      assert valueOfMethod != null;
+    private JExpression box(JExpression original, BaseTypeBinding primitiveType) {
+      return box(original, primitiveType, false);
+    }
 
+    private JExpression box(
+        JExpression original, BaseTypeBinding primitiveType, boolean doNotAutobox) {
       // Add a cast to the correct primitive type if needed.
       JType targetPrimitiveType = typeMap.get(primitiveType);
       if (original.getType() != targetPrimitiveType) {
         original = new JCastOperation(original.getSourceInfo(), targetPrimitiveType, original);
       }
 
-      JMethod boxMethod = typeMap.get(valueOfMethod);
-      JMethodCall call = new JMethodCall(original.getSourceInfo(), null, boxMethod);
-      call.addArg(original);
-      return call;
+      if (doNotAutobox) {
+        // Protect the primitive @DoNotAutobox values from optimizations, etc but encapsulating
+        // them in an opaque unsafe coercion to Object.
+        return new JUnsafeTypeCoercion(original.getSourceInfo(), javaLangObject, original);
+      }
+
+      ClassScope scope = curClass.scope;
+      JMethod boxingMethod = typeMap.get(JdtUtil.getBoxingMethodBinding(scope, primitiveType));
+      return new JMethodCall(original.getSourceInfo(), null, boxingMethod, original);
+    }
+
+    private JExpression unbox(JExpression original, BaseTypeBinding primitiveType) {
+      return unbox(original, primitiveType, true);
+    }
+
+    private JExpression unbox(
+        JExpression original, BaseTypeBinding primitiveType, boolean needsExplicitCast) {
+
+      ClassScope scope = curClass.scope;
+      if (needsExplicitCast) {
+        // Direct cast from non-boxed-type reference type to a primitive type,
+        // wrap with a cast operation of the (boxed) expected type.
+        JReferenceType boxedType =
+            (JReferenceType) typeMap.get(JdtUtil.getBoxedTypeBinding(scope, primitiveType));
+        original =
+            new JCastOperation(original.getSourceInfo(), boxedType, original);
+      }
+      JMethod unboxingMethod = typeMap.get(JdtUtil.getUnboxingMethodBinding(scope, primitiveType));
+      return new JMethodCall(original.getSourceInfo(), original, unboxingMethod);
     }
 
     private void createBridgeMethod(SyntheticMethodBinding jdtBridgeMethod) {
@@ -3063,12 +3085,18 @@
     }
 
     private JExpression maybeBoxOrUnbox(JExpression original, int implicitConversion) {
-      if (implicitConversion != -1) {
-        if ((implicitConversion & TypeIds.BOXING) != 0) {
-          return box(original, implicitConversion);
-        } else if ((implicitConversion & TypeIds.UNBOXING) != 0) {
-          return unbox(original, implicitConversion);
-        }
+      return maybeBoxOrUnbox(original, implicitConversion, false);
+    }
+
+    private JExpression maybeBoxOrUnbox(
+        JExpression original, int implicitConversion, boolean doNotAutobox) {
+      if (JdtUtil.requiresBoxing(implicitConversion)) {
+        return box(original, JdtUtil.getBoxingPrimitiveType(curClass.scope, implicitConversion),
+            doNotAutobox);
+      }
+      if (JdtUtil.requiresUnboxing(implicitConversion)) {
+        return unbox(original, JdtUtil.getUnboxingPrimitiveType(curClass.scope, implicitConversion),
+            JdtUtil.needsCastBeforeUnbox(curClass.scope, implicitConversion));
       }
       return original;
     }
@@ -3088,7 +3116,7 @@
       return expression;
     }
 
-      private JExpression maybeInsertUnsafeTypeCoersion(JType expected, JExpression expression) {
+      private JExpression maybeInsertUnsafeTypeCoercion(JType expected, JExpression expression) {
       if (expected != expression.getType()) {
         // A generic call marked as @UncheckedCast.
         return new JUnsafeTypeCoercion(expression.getSourceInfo(), expected, expression);
@@ -3101,25 +3129,33 @@
       return nodeStack.remove(nodeStack.size() - 1);
     }
 
-    private List<JExpression> popCallArgs(SourceInfo info, Expression[] jdtArgs,
-        MethodBinding binding) {
-      List<JExpression> args = pop(jdtArgs);
-      if (!binding.isVarargs()) {
+    private List<JExpression> popCallArguments(SourceInfo info, Expression[] arguments,
+        MethodBinding methodBinding) {
+      List<JExpression> args = pop(arguments);
+      for (int i = 0; i < args.size(); i++) {
+        // Account for varargs parameter.
+        int parameterIndex = Math.min(i, methodBinding.parameters.length - 1);
+        args.set(i, maybeBoxOrUnbox(
+            args.get(i),
+            arguments[i].implicitConversion,
+            isDoNotAutoBoxParameter(methodBinding, parameterIndex)));
+      }
+      if (!methodBinding.isVarargs()) {
         return args;
       }
 
       // Handle the odd var-arg case.
-      if (jdtArgs == null) {
+      if (arguments == null) {
         // Get writable collection (args is currently Collections.emptyList()).
         args = Lists.newArrayListWithCapacity(1);
       }
 
-      TypeBinding[] params = binding.parameters;
+      TypeBinding[] params = methodBinding.parameters;
       int varArg = params.length - 1;
 
       // See if there's a single varArg which is already an array.
       if (args.size() == params.length) {
-        if (jdtArgs[varArg].resolvedType.isCompatibleWith(params[varArg])) {
+        if (arguments[varArg].resolvedType.isCompatibleWith(params[varArg])) {
           // Already the correct array type.
           return args;
         }
@@ -3135,6 +3171,15 @@
       return args;
     }
 
+    private boolean isDoNotAutoBoxParameter(MethodBinding methodBinding, int parameterIndex) {
+      AnnotationBinding[][] parameterAnnotations = methodBinding.getParameterAnnotations();
+      return parameterAnnotations != null
+          && parameterAnnotations.length > parameterIndex
+          && parameterAnnotations[parameterIndex] != null
+          && JdtUtil.getAnnotationByName(parameterAnnotations[parameterIndex],
+          "javaemul.internal.annotations.DoNotAutobox") != null;
+    }
+
     private List<? extends JNode> popList(int count) {
       List<JNode> tail = nodeStack.subList(nodeStack.size() - count, nodeStack.size());
       // Make a copy.
@@ -3455,7 +3500,7 @@
       return result;
     }
 
-    private JExpression simplify(JExpression result, Expression x) {
+    private JExpression maybeBoxOrUnbox(JExpression result, Expression x) {
       return maybeBoxOrUnbox(result, x.implicitConversion);
     }
 
@@ -3467,34 +3512,6 @@
       return expression;
     }
 
-    private JExpression unbox(JExpression original, int implicitConversion) {
-      int compileTypeId = implicitConversion & TypeIds.COMPILE_TYPE_MASK;
-      ClassScope scope = curClass.scope;
-      TypeBinding targetBinding = TypeBinding.wellKnownType(scope, compileTypeId);
-      if (!(targetBinding instanceof BaseTypeBinding)) {
-        // Direct cast from non-boxed-type reference type to a primitive type,
-        // wrap with a cast operation of the (boxed) expected type.
-        int runtimeTypeId = (implicitConversion & TypeIds.IMPLICIT_CONVERSION_MASK) >> 4;
-        TypeBinding runtimeTypeBinding = TypeBinding.wellKnownType(scope, runtimeTypeId);
-        ReferenceBinding boxType = (ReferenceBinding) scope.boxing(runtimeTypeBinding);
-        original =
-            new JCastOperation(original.getSourceInfo(), typeMap.get(boxType), original);
-        targetBinding = runtimeTypeBinding;
-        assert (targetBinding instanceof BaseTypeBinding);
-      }
-
-      BaseTypeBinding primitiveType = (BaseTypeBinding) targetBinding;
-
-      ReferenceBinding boxType = (ReferenceBinding) scope.boxing(primitiveType);
-      char[] selector = CharOperation.concat(primitiveType.simpleName, VALUE_SUFFIX_);
-      MethodBinding valueMethod =
-          boxType.getExactMethod(selector, NO_TYPES, scope.compilationUnitScope());
-      assert valueMethod != null;
-      JMethod unboxMethod = typeMap.get(valueMethod);
-      JMethodCall call = new JMethodCall(original.getSourceInfo(), original, unboxMethod);
-      return call;
-    }
-
     private void writeEnumValueOfMethod(JEnumType type, JMethod method, JMethod valuesMethod) {
       JField mapField;
       TypeBinding mapType;
@@ -3732,10 +3749,8 @@
 
   private static final String CREATE_VALUE_OF_MAP_METHOD_NAME = "createValueOfMap";
   private static final String LENGTH_FIELD_NAME = "length";
-  private static final String VALUE_SUFFIX = "Value";
 
   private static final char[] CREATE_VALUE_OF_MAP_ = CREATE_VALUE_OF_MAP_METHOD_NAME.toCharArray();
-  private static final char[] VALUE_SUFFIX_ = VALUE_SUFFIX.toCharArray();
   private static final char[] VALUE_OF_ = VALUE_OF_METHOD_NAME.toCharArray();
   private static final char[] VALUES_ = VALUES_METHOD_NAME.toCharArray();
   private static final char[] ORDINAL_ = ORDINAL_METHOD_NAME.toCharArray();
diff --git a/user/super/com/google/gwt/emul/javaemul/internal/annotations/DoNotAutobox.java b/user/super/com/google/gwt/emul/javaemul/internal/annotations/DoNotAutobox.java
new file mode 100644
index 0000000..479548f
--- /dev/null
+++ b/user/super/com/google/gwt/emul/javaemul/internal/annotations/DoNotAutobox.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2017 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 javaemul.internal.annotations;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Target;
+
+/**
+ * An annotation to prevent autoboxing of arguments on method calls.
+ */
+@Target(ElementType.PARAMETER)
+public @interface DoNotAutobox {
+}
diff --git a/user/test/com/google/gwt/core/interop/JsMethodTest.java b/user/test/com/google/gwt/core/interop/JsMethodTest.java
index dd3124d..828e016 100644
--- a/user/test/com/google/gwt/core/interop/JsMethodTest.java
+++ b/user/test/com/google/gwt/core/interop/JsMethodTest.java
@@ -19,6 +19,7 @@
 
 import com.google.gwt.junit.client.GWTTestCase;
 
+import javaemul.internal.annotations.DoNotAutobox;
 import jsinterop.annotations.JsMethod;
 import jsinterop.annotations.JsProperty;
 
@@ -80,4 +81,36 @@
     setJsInteropSecret("very secret!");
     assertEquals("very secret!", getJsInteropSecret());
   }
+
+  public static double returnWithoutBoxing(@DoNotAutobox Object object) {
+    return (Double) object;
+  }
+
+  public static double sumWithoutBoxing(@DoNotAutobox Object... objects) {
+    double sum = 0;
+    for (Object o : objects) {
+      sum += (Double) o;
+    }
+    return sum;
+  }
+
+  @JsMethod
+  public static double sumWithoutBoxingJsVarargs(@DoNotAutobox Object... objects) {
+    double sum = 0;
+    for (Object o : objects) {
+      sum += (Double) o;
+    }
+    return sum;
+  }
+
+  public void testDoNotAutobox() {
+    assertEquals(3.0, returnWithoutBoxing(3));
+    assertEquals(4.5, sumWithoutBoxing(1, 1.5, (short) 1, (byte) 1));
+    assertEquals(4.5, sumWithoutBoxingJsVarargs(1, 1.5, (short) 1, (byte) 1));
+    try {
+      returnWithoutBoxing(Long.MAX_VALUE);
+      fail("Should have thrown ClassCastException");
+    } catch (ClassCastException expected) {
+    }
+  }
 }