Allow Java 7+ casts from Object to primitive types.

Java 7 allows casting from Object to primitive types as in

  Object o = 5;
  int i = (int) o;

Bug: issue 8749.
Change-Id: Id02964ead4dc5e79a36d4ad184a388286d766311
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 b6d1667..0220f05 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
@@ -2802,9 +2802,23 @@
     }
 
     private JExpression unbox(JExpression original, int implicitConversion) {
-      int typeId = implicitConversion & TypeIds.COMPILE_TYPE_MASK;
+      int compileTypeId = implicitConversion & TypeIds.COMPILE_TYPE_MASK;
       ClassScope scope = curClass.scope;
-      BaseTypeBinding primitiveType = (BaseTypeBinding) TypeBinding.wellKnownType(scope, typeId);
+      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);
       MethodBinding valueMethod =
diff --git a/dev/core/test/com/google/gwt/dev/jjs/impl/Java7AstTest.java b/dev/core/test/com/google/gwt/dev/jjs/impl/Java7AstTest.java
index 28183b2..f0b8481 100644
--- a/dev/core/test/com/google/gwt/dev/jjs/impl/Java7AstTest.java
+++ b/dev/core/test/com/google/gwt/dev/jjs/impl/Java7AstTest.java
@@ -61,7 +61,6 @@
         "}");
   }
 
-
   public void testCompileDiamondOperator() throws Exception {
     addSnippetImport("java.util.List");
     addSnippetImport("java.util.ArrayList");
@@ -70,6 +69,46 @@
         "List<String> l = new ArrayList<>();");
   }
 
+  public void testCastingToPrimitiveTypes() throws UnableToCompleteException {
+    assertEqualBlock(
+        "Object o = null; byte s = (byte) ((Byte) o).byteValue();",
+        "Object o = null; byte s = (byte) o;");
+
+    assertEqualBlock(
+        "Object o = null; short s = (short) ((Short) o).shortValue();",
+        "Object o = null; short s = (short) o;");
+
+    assertEqualBlock(
+        "Object o = null; int s = (int) ((Integer) o).intValue();",
+        "Object o = null; int s = (int) o;");
+
+    assertEqualBlock(
+        "Object o = null; long s = (long) ((Long) o).longValue();",
+        "Object o = null; long s = (long) o;");
+
+    assertEqualBlock(
+        "Object o = null; float s = (float) ((Float) o).floatValue();",
+        "Object o = null; float s = (float) o;");
+
+    assertEqualBlock(
+        "Object o = null; double s = (double) ((Double) o).doubleValue();",
+        "Object o = null; double s = (double) o;");
+
+    assertEqualBlock(
+        "Object o = null; char s = (char) ((Character) o).charValue();",
+        "Object o = null; char s = (char) o;");
+
+    assertEqualBlock(
+        "Object o = null; char s = (char) ((Character) o).charValue();",
+        "Object o = null; char s = (char) o;");
+
+    // This is the expected behaviour, however JDT 3.8.3 emits a compiler error.
+    // TODO(rluble): uncomment when JDT is updated.
+    // assertEqualBlock(
+    //    "Number o = 1; int s = (int) ((Integer) o).intValue();",
+    //    "Number o= 1 ; int s = (int) o;");
+  }
+
   private void addAll(Resource... sourceFiles) {
     for (Resource sourceFile : sourceFiles) {
       sourceOracle.addOrReplace(sourceFile);
diff --git a/user/test-super/com/google/gwt/dev/jjs/super/com/google/gwt/dev/jjs/test/Java7Test.java b/user/test-super/com/google/gwt/dev/jjs/super/com/google/gwt/dev/jjs/test/Java7Test.java
index 26a32ac..631248a 100644
--- a/user/test-super/com/google/gwt/dev/jjs/super/com/google/gwt/dev/jjs/test/Java7Test.java
+++ b/user/test-super/com/google/gwt/dev/jjs/super/com/google/gwt/dev/jjs/test/Java7Test.java
@@ -1,12 +1,12 @@
 /*
  * 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
@@ -304,4 +304,56 @@
     } catch (E1 x) {
     }
   }
+
+  private Object unoptimizableId(Object o) {
+    if (Math.random() > -10) {
+      return o;
+    }
+    return null;
+  }
+
+  public void testPrimitiveCastsFromObject() {
+    Object o = unoptimizableId((byte) 2);
+    assertEquals((byte) 2, (byte) o);
+    o = unoptimizableId((short) 3);
+    assertEquals((short) 3, (short) o);
+    o = unoptimizableId(1);
+    assertEquals(1, (int) o);
+    o = unoptimizableId(1L);
+    assertEquals(1L, (long) o);
+    o = unoptimizableId(0.1f);
+    assertEquals(0.1f, (float) o);
+    o = unoptimizableId(0.1);
+    assertEquals(0.1, (double) o);
+    o = unoptimizableId(true);
+    assertEquals(true, (boolean) o);
+    o = unoptimizableId('a');
+    assertEquals('a', (char) o);
+    // Test cast from supers.
+    // TODO(rluble): enable these after JDT upgrade as the currenct JDT will
+    // give compilation errors.
+    // Number n = (Number) unoptimizableId(5);
+    // assertEquals(5, (int) n);
+    // Serializable s = (Serializable) unoptimizableId(6);
+    // assertEquals(6, (int) s);
+    // Comparable<Integer> c = (Comparable<Integer>) unoptimizableId(7);
+    // assertEquals(7, (int) c);
+
+    // Failing casts.
+    try {
+      Object boxedChar = unoptimizableId('a');
+      boolean b = (boolean) boxedChar;
+      fail("Should have thrown a ClassCastException");
+    } catch (ClassCastException e) {
+      // Expected.
+    }
+
+    try {
+      Object string = unoptimizableId("string");
+      int n = (int) string;
+      fail("Should have thrown a ClassCastException");
+    } catch (ClassCastException e) {
+      // Expected.
+    }
+  }
 }
diff --git a/user/test/com/google/gwt/dev/jjs/CompilerSuite.java b/user/test/com/google/gwt/dev/jjs/CompilerSuite.java
index 656f31f..71841ed 100644
--- a/user/test/com/google/gwt/dev/jjs/CompilerSuite.java
+++ b/user/test/com/google/gwt/dev/jjs/CompilerSuite.java
@@ -89,6 +89,8 @@
     suite.addTestSuite(InitialLoadSequenceTest.class);
     suite.addTestSuite(InnerClassTest.class);
     suite.addTestSuite(InnerOuterSuperTest.class);
+    // Java7Test cannot be the first one in a suite. It uses a hack
+    // to avoid executing if not in a Java 7+ environment.
     suite.addTestSuite(Java7Test.class);
     suite.addTestSuite(JavaAccessFromJavaScriptTest.class);
     suite.addTestSuite(JsniConstructorTest.class);
diff --git a/user/test/com/google/gwt/dev/jjs/test/Java7Test.java b/user/test/com/google/gwt/dev/jjs/test/Java7Test.java
index e463279..93b9c69 100644
--- a/user/test/com/google/gwt/dev/jjs/test/Java7Test.java
+++ b/user/test/com/google/gwt/dev/jjs/test/Java7Test.java
@@ -67,4 +67,7 @@
 
   public void testAddSuppressedExceptions() {
   }
+
+  public void testPrimitiveCastsFromObject() {
+  }
 }