Allow casting native arrays to Object[].

Change-Id: I51888274d10d3f51bce8644d44a32a524cb63631
diff --git a/dev/core/src/com/google/gwt/dev/jjs/ast/JProgram.java b/dev/core/src/com/google/gwt/dev/jjs/ast/JProgram.java
index 13b6402..72254c7 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/ast/JProgram.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/ast/JProgram.java
@@ -331,16 +331,14 @@
 
   private final Map<JMethod, JMethod> staticToInstanceMap = Maps.newIdentityHashMap();
 
-  private JClassType typeClass;
-
-  private JClassType typeJavaLangObject;
-
   private final Map<String, JDeclaredType> typeNameMap = Maps.newHashMap();
 
   private Map<JField, JType> typesByClassLiteralField;
 
+  private JClassType typeClass;
+  private JClassType typeJavaLangObject;
+  private JArrayType typeJavaLangObjectArray;
   private JClassType typeSpecialClassLiteralHolder;
-
   private JClassType typeSpecialJavaScriptObject;
 
   private JClassType typeString;
@@ -414,6 +412,7 @@
     switch (name) {
       case "java.lang.Object":
         typeJavaLangObject = (JClassType) type;
+        typeJavaLangObjectArray = getOrCreateArrayType(type, 1);
         break;
       case "java.lang.String":
         typeString = (JClassType) type;
@@ -1076,6 +1075,10 @@
     return typeJavaLangObject;
   }
 
+  public JArrayType getTypeJavaLangObjectArray() {
+    return typeJavaLangObjectArray;
+  }
+
   public JClassType getTypeJavaLangString() {
     return typeString;
   }
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/ImplementCastsAndTypeChecks.java b/dev/core/src/com/google/gwt/dev/jjs/impl/ImplementCastsAndTypeChecks.java
index 674cde1..3dfe1e0 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/ImplementCastsAndTypeChecks.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/ImplementCastsAndTypeChecks.java
@@ -37,7 +37,6 @@
 import com.google.gwt.dev.jjs.ast.js.JsniMethodRef;
 import com.google.gwt.thirdparty.guava.common.collect.Maps;
 
-import java.util.EnumSet;
 import java.util.Map;
 
 /**
@@ -228,17 +227,7 @@
   private TypeCategory determineTypeCategoryForType(JReferenceType type) {
     TypeCategory typeCategory = TypeCategory.typeCategoryForType(type, program);
 
-    assert EnumSet.of(TypeCategory.TYPE_JSO,
-        TypeCategory.TYPE_JAVA_OBJECT_OR_JSO,
-        TypeCategory.TYPE_NATIVE_ARRAY,
-        TypeCategory.TYPE_JAVA_LANG_OBJECT,
-        TypeCategory.TYPE_JAVA_LANG_STRING,
-        TypeCategory.TYPE_JAVA_LANG_DOUBLE,
-        TypeCategory.TYPE_JAVA_LANG_BOOLEAN,
-        TypeCategory.TYPE_JAVA_OBJECT,
-        TypeCategory.TYPE_JS_UNKNOWN_NATIVE,
-        TypeCategory.TYPE_JS_NATIVE,
-        TypeCategory.TYPE_JS_FUNCTION).contains(typeCategory);
+    assert typeCategory.castInstanceOfQualifier() != null;
 
     return typeCategory;
   }
@@ -298,9 +287,13 @@
     this.pruneTrivialCasts = pruneTrivialCasts;
 
     for (TypeCategory t : TypeCategory.values()) {
-      String instanceOfMethod = "Cast.instanceOf" + t.castInstanceOfQualifier();
+      String castInstanceOfQualifier = t.castInstanceOfQualifier();
+      if (castInstanceOfQualifier == null) {
+        continue;
+      }
+      String instanceOfMethod = "Cast.instanceOf" + castInstanceOfQualifier;
       instanceOfMethodsByTargetTypeCategory.put(t, program.getIndexedMethod(instanceOfMethod));
-      String castMethod = "Cast.castTo" + t.castInstanceOfQualifier();
+      String castMethod = "Cast.castTo" + castInstanceOfQualifier;
       dynamicCastMethodsByTargetTypeCategory.put(t, program.getIndexedMethod(castMethod));
     }
   }
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/TypeCategory.java b/dev/core/src/com/google/gwt/dev/jjs/impl/TypeCategory.java
index b0a9f1e..c90ea42 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/TypeCategory.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/TypeCategory.java
@@ -40,10 +40,11 @@
    * initialize to zero vs. null).
    */
 
-  TYPE_JAVA_OBJECT,
+  TYPE_JAVA_OBJECT(""),
   TYPE_JAVA_OBJECT_OR_JSO("AllowJso"),
   TYPE_JSO("Jso"),
   TYPE_NATIVE_ARRAY("NativeArray"),
+  TYPE_ARRAY("Array"),
   TYPE_JAVA_LANG_OBJECT("AllowJso"),
   TYPE_JAVA_LANG_STRING("String"),
   TYPE_JAVA_LANG_DOUBLE("Double"),
@@ -58,7 +59,7 @@
   private final String castInstanceOfQualifier;
 
   TypeCategory() {
-    this("");
+    this(null);
   }
 
   TypeCategory(String castInstanceOfQualifier) {
@@ -85,7 +86,9 @@
 
     assert type instanceof JReferenceType;
     type = type.getUnderlyingType();
-    if (getJsSpecialType(type) != null) {
+    if (type == program.getTypeJavaLangObjectArray()) {
+      return TypeCategory.TYPE_ARRAY;
+    } else if (getJsSpecialType(type) != null) {
       return getJsSpecialType(type);
     } else if (program.isUntypedArrayType(type)) {
       return TypeCategory.TYPE_NATIVE_ARRAY;
diff --git a/dev/core/super/com/google/gwt/dev/jjs/intrinsic/com/google/gwt/lang/Array.java b/dev/core/super/com/google/gwt/dev/jjs/intrinsic/com/google/gwt/lang/Array.java
index cc4ceea..9e843fa 100644
--- a/dev/core/super/com/google/gwt/dev/jjs/intrinsic/com/google/gwt/lang/Array.java
+++ b/dev/core/super/com/google/gwt/dev/jjs/intrinsic/com/google/gwt/lang/Array.java
@@ -32,17 +32,18 @@
   private static final int TYPE_JAVA_OBJECT = 0;
   private static final int TYPE_JAVA_OBJECT_OR_JSO = 1;
   private static final int TYPE_JSO = 2;
-  private static final int TYPE_NATIVE_ARRAYs = 3;
-  private static final int TYPE_JAVA_LANG_OBJECT = 4;
-  private static final int TYPE_JAVA_LANG_STRING = 5;
-  private static final int TYPE_JAVA_LANG_DOUBLE = 6;
-  private static final int TYPE_JAVA_LANG_BOOLEAN = 7;
-  private static final int TYPE_JS_NATIVE = 8;
-  private static final int TYPE_JS_UNKNOWN_NATIVE = 9;
-  private static final int TYPE_JS_FUNCTION = 10;
-  private static final int TYPE_PRIMITIVE_LONG = 11;
-  private static final int TYPE_PRIMITIVE_NUMBER = 12;
-  private static final int TYPE_PRIMITIVE_BOOLEAN = 13;
+  private static final int TYPE_NATIVE_ARRAY = 3;
+  private static final int TYPE_ARRAY = 4;
+  private static final int TYPE_JAVA_LANG_OBJECT = 5;
+  private static final int TYPE_JAVA_LANG_STRING = 6;
+  private static final int TYPE_JAVA_LANG_DOUBLE = 7;
+  private static final int TYPE_JAVA_LANG_BOOLEAN = 8;
+  private static final int TYPE_JS_NATIVE = 9;
+  private static final int TYPE_JS_UNKNOWN_NATIVE = 10;
+  private static final int TYPE_JS_FUNCTION = 11;
+  private static final int TYPE_PRIMITIVE_LONG = 12;
+  private static final int TYPE_PRIMITIVE_NUMBER = 13;
+  private static final int TYPE_PRIMITIVE_BOOLEAN = 14;
 
   public static <T> T[] stampJavaTypeInfo(Object array, T[] referenceType) {
     if (Array.getElementTypeCategory(referenceType) != TYPE_JS_UNKNOWN_NATIVE) {
@@ -180,6 +181,10 @@
         return Cast.instanceOfDouble(value);
       case TYPE_JAVA_LANG_BOOLEAN:
         return Cast.instanceOfBoolean(value);
+      case TYPE_ARRAY:
+        return Cast.instanceOfArray(value);
+      case TYPE_JS_FUNCTION:
+        return Cast.instanceOfFunction(value);
       case TYPE_JAVA_OBJECT:
         return Cast.canCast(value, Array.getElementTypeId(array));
       case TYPE_JSO:
@@ -307,6 +312,15 @@
     return Cast.isArray(src) && Util.hasTypeMarker(src);
   }
 
+  /**
+   * Returns true if {@code src} is a Java array.
+   */
+  static boolean isPrimitiveArray(Object array) {
+    int elementTypeCategory = getElementTypeCategory(array);
+    return elementTypeCategory >= TYPE_PRIMITIVE_LONG
+        && elementTypeCategory <= TYPE_PRIMITIVE_BOOLEAN;
+  };
+
   private Array() {
   }
 }
diff --git a/dev/core/super/com/google/gwt/dev/jjs/intrinsic/com/google/gwt/lang/Cast.java b/dev/core/super/com/google/gwt/dev/jjs/intrinsic/com/google/gwt/lang/Cast.java
index b230808..6add2d7 100644
--- a/dev/core/super/com/google/gwt/dev/jjs/intrinsic/com/google/gwt/lang/Cast.java
+++ b/dev/core/super/com/google/gwt/dev/jjs/intrinsic/com/google/gwt/lang/Cast.java
@@ -93,6 +93,14 @@
   }
 
   /**
+   * Allow a cast to an java.lang.Object array, accepting also untyped arrays.
+   */
+  static Object castToArray(Object src) {
+    checkType(src == null || instanceOfArray(src));
+    return src;
+  }
+
+  /**
    * Allow a cast to (untyped) array. This case covers single and multidimensional JsType arrays.
    */
   static Object castToNativeArray(Object src) {
@@ -157,6 +165,13 @@
   }-*/;
 
   /**
+   * Returns true if {@code src} is Java object array or an untyped array.
+   */
+  static boolean instanceOfArray(Object src) {
+    return isArray(src) && !Array.isPrimitiveArray(src);
+  }
+
+  /**
    * Returns true if {@code src} is an array (native or not).
    */
   static boolean instanceOfNativeArray(Object src) {
diff --git a/dev/core/test/com/google/gwt/dev/jjs/JavaAstConstructor.java b/dev/core/test/com/google/gwt/dev/jjs/JavaAstConstructor.java
index 322f5f0..e9b0b3e 100644
--- a/dev/core/test/com/google/gwt/dev/jjs/JavaAstConstructor.java
+++ b/dev/core/test/com/google/gwt/dev/jjs/JavaAstConstructor.java
@@ -91,6 +91,7 @@
           "  public static native String charToString(char x) /*-{ }-*/;",
           "  public static Object castTo(Object src, int dstId) { return src;}",
           "  public static Object castToAllowJso(Object src, int dstId) { return src;}",
+          "  public static Object castToArray(Object src) { return src;}",
           "  public static Object castToNativeArray(Object src) { return src;}",
           "  public static Object castToJso(Object src) { return src;}",
           "  public static Object castToString(Object src) { return src;}",
@@ -105,6 +106,7 @@
           "  public static boolean instanceOfString(Object o) { return true; }",
           "  public static boolean instanceOfDouble(Object o) { return true; }",
           "  public static boolean instanceOfBoolean(Object o) { return true; }",
+          "  public static boolean instanceOfArray(Object src) { return false;}",
           "  public static boolean instanceOfNativeArray(Object src) { return false;}",
           "  public static boolean instanceOfAllowJso(Object src, int dst) { return false;}",
           "  public static boolean instanceOfJso(Object src) { return false;}",
diff --git a/dev/core/test/com/google/gwt/dev/jjs/impl/ArrayNormalizerTest.java b/dev/core/test/com/google/gwt/dev/jjs/impl/ArrayNormalizerTest.java
index 5e3b74a..9e159f1 100644
--- a/dev/core/test/com/google/gwt/dev/jjs/impl/ArrayNormalizerTest.java
+++ b/dev/core/test/com/google/gwt/dev/jjs/impl/ArrayNormalizerTest.java
@@ -51,16 +51,16 @@
   public void testObjectArray() throws Exception {
     optimize("void", "Object[] o = new Object[10];")
         .intoString("Object[] o = Array.initUnidimensionalArray(Object.class, [], " +
-            "/* JRuntimeTypeReference */\"java.lang.Object\", 10, 4, 1);");
+            "/* JRuntimeTypeReference */\"java.lang.Object\", 10, 5, 1);");
     optimize("void", "Object[] o = {null, null, Object.class};")
         .intoString("Object[] o = " +
             "Array.stampJavaTypeInfo(" +
             "Array.getClassLiteralForArray(ClassLiteralHolder.Ljava_lang_Object_2_classLit, 1), " +
-            "[], /* JRuntimeTypeReference */\"java.lang.Object\", 4, [null, null, Object.class]);");
+            "[], /* JRuntimeTypeReference */\"java.lang.Object\", 5, [null, null, Object.class]);");
     optimize("void", "Object[] o = new Object[] {};")
         .intoString("Object[] o = Array.stampJavaTypeInfo(Array.getClassLiteralForArray(" +
             "ClassLiteralHolder.Ljava_lang_Object_2_classLit, 1), [], " +
-            "/* JRuntimeTypeReference */\"java.lang.Object\", 4, []);");
+            "/* JRuntimeTypeReference */\"java.lang.Object\", 5, []);");
   }
 
   public void testNativeJsTypeArray() throws Exception {
diff --git a/user/test/com/google/gwt/core/interop/JsTypeArrayTest.java b/user/test/com/google/gwt/core/interop/JsTypeArrayTest.java
index 3091eda..9ace5d3 100644
--- a/user/test/com/google/gwt/core/interop/JsTypeArrayTest.java
+++ b/user/test/com/google/gwt/core/interop/JsTypeArrayTest.java
@@ -18,6 +18,7 @@
 import com.google.gwt.core.client.ScriptInjector;
 import com.google.gwt.junit.client.GWTTestCase;
 
+import jsinterop.annotations.JsFunction;
 import jsinterop.annotations.JsPackage;
 import jsinterop.annotations.JsProperty;
 import jsinterop.annotations.JsType;
@@ -138,11 +139,7 @@
   public void testObjectArray_castFromNative() {
     SimpleJsTypeReturnForMultiDimArray[] array =
         (SimpleJsTypeReturnForMultiDimArray[]) returnObjectArrayFromNative();
-    try {
-      assertNotNull((Object[]) array);
-
-    } catch (ClassCastException expected) {
-    }
+    assertNotNull((Object[]) array);
     assertEquals(3, array.length);
     assertEquals("1", array[0]);
   }
@@ -175,7 +172,7 @@
 
   public void testJsTypeArray_instanceOf() {
     Object array = returnJsType3DimFromNative();
-    assertFalse(array instanceof Object[]);
+    assertTrue(array instanceof Object[]);
     assertFalse(array instanceof Double[]);
     assertFalse(array instanceof int[]);
     assertFalse(array instanceof SimpleJsTypeReturnForMultiDimArray);
@@ -184,7 +181,43 @@
     assertTrue(array instanceof SimpleJsTypeReturnForMultiDimArray[][][]);
   }
 
+  @JsFunction
+  interface SomeFunction {
+    int m(int i);
+  }
+
+  @JsFunction
+  interface SomeOtherFunction {
+    int m(int i);
+  }
+
+  public void testJsFunctionArray() {
+    Object[] array = new SomeFunction[10];
+
+    array[0] = returnSomeFunction();
+
+    assertTrue(array instanceof SomeFunction[]);
+    assertFalse(array instanceof SomeOtherFunction[]);
+
+    try {
+      SomeOtherFunction[] other = (SomeOtherFunction[]) array;
+      fail("Should have thrown");
+    } catch (ClassCastException expected) {
+    }
+
+    try {
+      array[1] = new Object();
+      fail("Should have thrown");
+    } catch (ArrayStoreException expected) {
+    }
+  }
+
   private native Object returnObjectArrayFromNative() /*-{
     return ["1", "2", "3"];
   }-*/;
+
+  private native Object returnSomeFunction() /*-{
+    return function(a) { return a + 2; };
+  }-*/;
+
 }
diff --git a/user/test/com/google/gwt/dev/jjs/test/ArrayTest.java b/user/test/com/google/gwt/dev/jjs/test/ArrayTest.java
index 2a6c0ad..908f417 100644
--- a/user/test/com/google/gwt/dev/jjs/test/ArrayTest.java
+++ b/user/test/com/google/gwt/dev/jjs/test/ArrayTest.java
@@ -36,7 +36,7 @@
 
   public void testObjectArray_empty() {
     Object nativeArray = createJsArray(0);
-    assertFalse(nativeArray instanceof Object[]);
+    assertTrue(nativeArray instanceof Object[]);
     assertFalse(nativeArray instanceof Object[][]);
     assertFalse(nativeArray instanceof int[]);
     assertFalse(nativeArray instanceof List[]);
@@ -55,7 +55,7 @@
   public void testObjectArray_nonEmpty() {
     // Native array is an object array
     Object nativeArray = createJsArray(10);
-    assertFalse(nativeArray instanceof Object[]);
+    assertTrue(nativeArray instanceof Object[]);
     assertFalse(nativeArray instanceof Object[][]);
     assertFalse(nativeArray instanceof int[]);
     assertFalse(nativeArray instanceof List[]);