Devirtualize JsFunction implementations

Change-Id: Id352e6a877f30ff8710fae32e34ec45d09583a60
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/Devirtualizer.java b/dev/core/src/com/google/gwt/dev/jjs/impl/Devirtualizer.java
index 67d5e4d..5bd2725 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/Devirtualizer.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/Devirtualizer.java
@@ -301,14 +301,19 @@
 
   /**
    * Returns true if {@code method} is an overlay method. Overlay methods include the ones that
-   * are marked as JsOverlay but also (synthetic) private instance methods on interfaces.
-   * <p>
-   * Synthetic private methods on interfaces are the result of lambdas that capture the enclosing
-   * instance and are defined on default methods.
+   * are marked as JsOverlay but also implicit overlays.
    */
   private boolean isOverlayMethod(JMethod method) {
     return method.isJsOverlay()
-        || (method.getEnclosingType() instanceof JInterfaceType && method.isPrivate());
+        // Synthetic private methods on interfaces are the result of lambdas that capture the
+        // enclosing instance and are defined on default methods; these can appear in native
+        // interfaces and thus need to be treated as overlays.
+        || (method.getEnclosingType() instanceof JInterfaceType && method.isPrivate())
+        // JsFunction implementation methods other than the other than the SAM implementation are
+        // also considered overalys to allow for lighter weight JsFuncitons.
+        // TODO(rluble): SAM implementation should also be devirtualized.
+        || (method.getEnclosingType().isJsFunctionImplementation()
+            && !method.isOrOverridesJsFunctionMethod());
   }
 
   public static void exec(JProgram program) {
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 133cd79..f21fe19 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
@@ -985,18 +985,24 @@
           : 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);
+        // Synthesize makeLambdaFunction(samMethodReference, constructorReference, ctorArguments)
+        // which will create the function instance and run the constructor on it.
+        // TODO(rluble): optimize the constructor call away if it is empty.
+        return constructJsFunctionObject(
+            sourceInfo,
+            newInstance.getClassType(),
+            ctorName,
+            reference,
+            new JsArrayLiteral(sourceInfo, arguments));
       }
 
-      return newExpr;
+      return JsUtils.createInvocationOrPropertyAccess(
+          InvocationStyle.NEWINSTANCE, sourceInfo, ctor, null, reference, arguments);
     }
 
-    private JsNode constructJsFunctionObject(SourceInfo sourceInfo, JClassType type,
-        JsName ctorName, JsNew newExpr) {
+    private JsExpression constructJsFunctionObject(SourceInfo sourceInfo, JClassType type,
+        JsName ctorName, JsNameRef ctorReference, JsExpression ctorArguments) {
       // Foo.prototype.functionMethodName
       JMethod jsFunctionMethod = getJsFunctionMethod(type);
       JsNameRef funcNameRef = JsUtils.createQualifiedNameRef(sourceInfo,
@@ -1004,7 +1010,7 @@
 
       // makeLambdaFunction(Foo.prototype.functionMethodName, new Foo(...))
       return constructInvocation(sourceInfo, RuntimeConstants.RUNTIME_MAKE_LAMBDA_FUNCTION,
-          funcNameRef, newExpr);
+          funcNameRef, ctorReference, ctorArguments);
     }
 
     private JMethod getJsFunctionMethod(JClassType type) {
@@ -2513,6 +2519,11 @@
      * literal in each constructor.
      */
     private boolean initializeAtTopScope(JField x) {
+      if (x.getEnclosingType().isJsFunctionImplementation()) {
+        // JsFunction implementation are plain JS functions with no class prototype, fields
+        // need to be initialized and placed on the instance itself.
+        return false;
+      }
       if (x.getLiteralInitializer() == null) {
         return false;
       }
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 3772b19..4f3e702 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
@@ -126,7 +126,7 @@
       return TypeCategory.TYPE_JS_UNKNOWN_NATIVE;
     } else if (type instanceof JClassType && type.isJsNative()) {
       return TypeCategory.TYPE_JS_NATIVE;
-    } else if (type.isJsFunction()) {
+    } else if (type.isJsFunction() || type.isJsFunctionImplementation()) {
       return TypeCategory.TYPE_JS_FUNCTION;
     }
     return TypeCategory.TYPE_JAVA_OBJECT;
diff --git a/dev/core/super/com/google/gwt/dev/jjs/intrinsic/com/google/gwt/lang/Runtime.java b/dev/core/super/com/google/gwt/dev/jjs/intrinsic/com/google/gwt/lang/Runtime.java
index 0cdfd56..45bd626 100644
--- a/dev/core/super/com/google/gwt/dev/jjs/intrinsic/com/google/gwt/lang/Runtime.java
+++ b/dev/core/super/com/google/gwt/dev/jjs/intrinsic/com/google/gwt/lang/Runtime.java
@@ -156,18 +156,12 @@
    * Create a function that applies the specified samMethod on itself, and whose __proto__ points to
    * <code>instance</code>.
    */
-  public static native JavaScriptObject makeLambdaFunction(JavaScriptObject samMethod,
-      JavaScriptObject instance) /*-{
+  public static native JavaScriptObject makeLambdaFunction(
+      JavaScriptObject samMethod,
+      JavaScriptObject ctor,
+      JavaScriptObject ctorArguments) /*-{
     var lambda = function() { return samMethod.apply(lambda, arguments); }
-
-    if (lambda.__proto__) {
-      lambda.__proto__ = instance;
-    } else {
-      for (var prop in instance) {
-        lambda[prop] = instance[prop];
-      }
-    }
-
+    ctor.apply(lambda, ctorArguments);
     return lambda;
   }-*/;
 
diff --git a/user/test/com/google/gwt/core/interop/JsFunctionTest.java b/user/test/com/google/gwt/core/interop/JsFunctionTest.java
index 4f74b93..8c8ccad 100644
--- a/user/test/com/google/gwt/core/interop/JsFunctionTest.java
+++ b/user/test/com/google/gwt/core/interop/JsFunctionTest.java
@@ -146,13 +146,8 @@
     assertNotNull(c2);
     ElementLikeNativeInterface i = (ElementLikeNativeInterface) createFunction();
     assertNotNull(i);
-    try {
-      MyJsFunctionInterfaceImpl c3 = (MyJsFunctionInterfaceImpl) createFunction();
-      assertNotNull(c3);
-      fail("ClassCastException should be caught.");
-    } catch (ClassCastException cce) {
-      // Expected.
-    }
+    MyJsFunctionInterfaceImpl c3 = (MyJsFunctionInterfaceImpl) createFunction();
+    assertNotNull(c3);
   }
 
   public void testCast_fromJsObject() {
@@ -304,6 +299,19 @@
     assertEquals(MyJsFunctionInterface.class, ((Object) createMyJsFunction()).getClass());
   }
 
+  public void testInstanceField() {
+
+    MyJsFunctionInterface jsfunctionImplementation =
+        new MyJsFunctionInterface() {
+          String hello = new Object().getClass().getName();
+          @Override
+          public int foo(int a) {
+            return hello.length() + a;
+          }
+        };
+    assertEquals(Object.class.getName().length() + 4, jsfunctionImplementation.foo(4));
+  }
+
   // uncomment when Java8 is supported.
 //  public void testJsFunctionLambda_JS() {
 //    MyJsFunctionInterface jsFunctionInterface = a -> { return a + 2; };