Implement abstract method bridges for functional expressions.

The synthetic inner class that implements lambdas and method references
might need bridges for the single abstract method (and also for
default methods).

This patch creates the bridge methods for the single abstract method,
leaving the task of creating bridges for the default methods for a
follow up patch.

Bug: #9406
Bug-Link: https://github.com/gwtproject/gwt/issues/9406
Change-Id: I80bb32b3f81e08a1f583ed78f1765df8cdd21caf
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 b3c8969..ba7e267 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
@@ -1255,6 +1255,9 @@
       ctor.freezeParamTypes();
       samMethod.freezeParamTypes();
 
+      // Create necessary bridges.
+      createFunctionalExpressionBridges(innerLambdaClass, x, samMethod);
+
       // replace (x,y,z) -> expr with 'new Lambda(args)'
       replaceLambdaWithInnerClassAllocation(x, info, innerLambdaClass, ctor, synthArgs);
       popMethodInfo();
@@ -1262,6 +1265,20 @@
       newTypes.add(innerLambdaClass);
     }
 
+    private void createFunctionalExpressionBridges(
+        JClassType functionalExpressionImplementationClass,
+        FunctionalExpression functionalExpression,
+        JMethod functionalInterfaceAbstractMethod) {
+      // TODO(rluble): create bridges that might be needed for default methods.
+      if (functionalExpression.getRequiredBridges() != null) {
+        for (MethodBinding methodBinding : functionalExpression.getRequiredBridges()) {
+          // Create bridges.
+          createBridgeMethod(functionalExpressionImplementationClass, methodBinding,
+              functionalInterfaceAbstractMethod);
+        }
+      }
+    }
+
     private void createLambdaSamMethod(LambdaExpression x, JMethod interfaceMethod, SourceInfo info,
         JClassType innerLambdaClass, List<JField> locals, JField outerField, JMethod lambdaMethod,
         JMethod samMethod) {
@@ -1411,6 +1428,7 @@
         assert capturedLocalReference != null;
         allocLambda.addArg(capturedLocalReference);
       }
+
       // put the result on the stack, and pop out synthetic method from the scope
       push(allocLambda);
     }
@@ -1883,7 +1901,9 @@
             samBinding.parameters[paramNumber
                 + (samBinding.parameters.length - referredMethodBinding.parameters.length)];
         // if it is not the trailing param or varargs, or interface method is already varargs
-        if (varArgInitializers == null || !referredMethodBinding.isVarargs() || (paramNumber < varArg)) {
+        if (varArgInitializers == null
+            || !referredMethodBinding.isVarargs()
+            || (paramNumber < varArg)) {
           destParam = referredMethodBinding.parameters[paramNumber];
           paramExpr = boxOrUnboxExpression(paramExpr, samParameterBinding, destParam);
           samCall.addArg(paramExpr);
@@ -1920,6 +1940,8 @@
       ctor.freezeParamTypes();
       samMethod.freezeParamTypes();
 
+      createFunctionalExpressionBridges(innerLambdaClass, x, samMethod);
+
       JConstructor lambdaCtor = null;
       for (JMethod method : innerLambdaClass.getMethods()) {
         if (method instanceof JConstructor) {
@@ -2703,15 +2725,16 @@
      * have been installed on the GWT types.
      * </p>
      */
-    private void addBridgeMethods(SourceTypeBinding clazzBinding) {
+    private void addBridgeMethods(SourceTypeBinding classBinding) {
       /*
        * JDT adds bridge methods in all the places GWT needs them. Use JDT's
        * bridge methods.
        */
-      if (clazzBinding.syntheticMethods() != null) {
-        for (SyntheticMethodBinding synthmeth : clazzBinding.syntheticMethods()) {
-          if (synthmeth.purpose == SyntheticMethodBinding.BridgeMethod && !synthmeth.isStatic()) {
-            createBridgeMethod(synthmeth);
+      if (classBinding.syntheticMethods() != null) {
+        for (SyntheticMethodBinding syntheticMethodBinding : classBinding.syntheticMethods()) {
+          if (syntheticMethodBinding.purpose == SyntheticMethodBinding.BridgeMethod
+              && !syntheticMethodBinding.isStatic()) {
+            createBridgeMethod(syntheticMethodBinding);
           }
         }
       }
@@ -2748,35 +2771,65 @@
       return call;
     }
 
-    /**
-     * Create a bridge method. It calls a same-named method with the same
-     * arguments, but with a different type signature.
-     */
     private void createBridgeMethod(SyntheticMethodBinding jdtBridgeMethod) {
-      JMethod implmeth = typeMap.get(jdtBridgeMethod.targetMethod);
-      SourceInfo info = implmeth.getSourceInfo();
+      JMethod targetMethod = typeMap.get(jdtBridgeMethod.targetMethod);
+      createBridgeMethod(curClass.type, jdtBridgeMethod, targetMethod);
+    }
+
+    private void createBridgeMethod(
+        JDeclaredType enclosingType, MethodBinding sourceMethodBinding, JMethod targetMethod) {
+      JType returnType = typeMap.get(sourceMethodBinding.returnType);
+      Iterable<JType> parameterTypes =
+          FluentIterable.from(Arrays.asList(sourceMethodBinding.parameters)).transform(
+              new Function<TypeBinding, JType>() {
+                @Override
+                public JType apply(TypeBinding typeBinding) {
+                  return typeMap.get(typeBinding.erasure());
+                }
+              });
+
+      Iterable<JClassType> thrownExceptionTypes =
+          FluentIterable.from(Arrays.asList(sourceMethodBinding.thrownExceptions)).transform(
+          new Function<ReferenceBinding, JClassType>() {
+            @Override
+            public JClassType apply(ReferenceBinding exceptionReferenceBinding) {
+              return (JClassType) typeMap.get(exceptionReferenceBinding.erasure());
+            }
+          });
+
+      JMethod bridgeMethod = createBridgeMethod(
+          enclosingType, targetMethod, parameterTypes, returnType, thrownExceptionTypes);
+      typeMap.setMethod(sourceMethodBinding, bridgeMethod);
+    }
+
+      /**
+       * Create a bridge method. It calls a same-named method with the same
+       * arguments, but with a different type signature.
+       */
+    private JMethod createBridgeMethod(JDeclaredType enclosingType,
+        JMethod targetMethod, Iterable<JType> parameterTypes, JType returnType,
+        Iterable<JClassType> thrownExceptions) {
+      SourceInfo info = targetMethod.getSourceInfo();
       JMethod bridgeMethod =
-          new JMethod(info, implmeth.getName(), curClass.type, typeMap
-              .get(jdtBridgeMethod.returnType), false, false, implmeth.isFinal(), implmeth
-              .getAccess());
-      typeMap.setMethod(jdtBridgeMethod, bridgeMethod);
+          new JMethod(info, targetMethod.getName(), enclosingType, returnType, false, false,
+              targetMethod.isFinal(), targetMethod.getAccess());
       bridgeMethod.setBody(new JMethodBody(info));
-      curClass.type.addMethod(bridgeMethod);
+      enclosingType.addMethod(bridgeMethod);
       bridgeMethod.setSynthetic();
       int paramIdx = 0;
-      List<JParameter> implParams = implmeth.getParams();
-      for (TypeBinding jdtParamType : jdtBridgeMethod.parameters) {
-        JParameter param = implParams.get(paramIdx++);
-        JType paramType = typeMap.get(jdtParamType.erasure());
-        bridgeMethod.createFinalParameter(param.getSourceInfo(), param.getName(), paramType);
+      List<JParameter> implParams = targetMethod.getParams();
+      for (JType parameterType : parameterTypes) {
+        JParameter parameter = implParams.get(paramIdx++);
+        bridgeMethod.createFinalParameter(
+            parameter.getSourceInfo(), parameter.getName(), parameterType);
       }
-      for (ReferenceBinding exceptionReference : jdtBridgeMethod.thrownExceptions) {
-        bridgeMethod.addThrownException((JClassType) typeMap.get(exceptionReference.erasure()));
+      for (JClassType thrownException : thrownExceptions) {
+        bridgeMethod.addThrownException(thrownException);
       }
       bridgeMethod.freezeParamTypes();
 
       // create a call and pass all arguments through, casting if necessary
-      JMethodCall call = new JMethodCall(info, makeThisRef(info), implmeth);
+      JMethodCall call = new JMethodCall(info, makeThisRef(info), targetMethod);
       for (int i = 0; i < bridgeMethod.getParams().size(); i++) {
         JParameter param = bridgeMethod.getParams().get(i);
         call.addArg(maybeCast(implParams.get(i).getType(), param.makeRef(info)));
@@ -2788,6 +2841,7 @@
       } else {
         body.getBlock().addStmt(call.makeReturnStatement());
       }
+      return bridgeMethod;
     }
 
     private void writeEnumValuesMethod(JEnumType type, JMethod method) {
diff --git a/user/test-super/com/google/gwt/dev/jjs/super/com/google/gwt/dev/jjs/test/Java8Test.java b/user/test-super/com/google/gwt/dev/jjs/super/com/google/gwt/dev/jjs/test/Java8Test.java
index a040c85..e4ae64d 100644
--- a/user/test-super/com/google/gwt/dev/jjs/super/com/google/gwt/dev/jjs/test/Java8Test.java
+++ b/user/test-super/com/google/gwt/dev/jjs/super/com/google/gwt/dev/jjs/test/Java8Test.java
@@ -1623,4 +1623,47 @@
     assertEquals(2, f.callM().intValue());
     assertEquals(5, createNative().callM().intValue());
   }
+
+  interface FunctionalExpressionBridges_I<T> {
+    T apply(T t);
+    // TODO(rluble): uncomment the line below to when bridges for default methods are created
+    // in functional expressions
+    // FunctionalExpressionBridges_I<T> m(T t);
+  }
+
+  @FunctionalInterface
+  interface FunctionalExpressionBridges_J<T extends Comparable>
+      extends FunctionalExpressionBridges_I<T> {
+    T apply(T t);
+
+    // Overrides I.m() and specializes return type
+    default FunctionalExpressionBridges_J<T> m(T t) {
+      return this;
+    }
+  }
+
+  public static String identity(String s) {
+    return s;
+  }
+
+  public void testFunctionalExpressionBridges() {
+    FunctionalExpressionBridges_J<String> ann = new FunctionalExpressionBridges_J<String>() {
+      @Override
+      public String apply(String string) {
+        return string;
+      }
+    };
+
+    assertBrigdeDispatchIsCorrect(ann);
+    assertBrigdeDispatchIsCorrect((String s) -> s + "");
+    assertBrigdeDispatchIsCorrect(Java8Test::identity);
+  }
+
+  private void assertBrigdeDispatchIsCorrect(
+      FunctionalExpressionBridges_J<String> functionalExpression) {
+    assertEquals("Hello", functionalExpression.m(null).apply("Hello"));
+    assertEquals("Hello", functionalExpression.apply("Hello"));
+    assertEquals("Hello",
+        ((FunctionalExpressionBridges_I<String>) functionalExpression).apply("Hello"));
+  }
 }
diff --git a/user/test/com/google/gwt/dev/jjs/test/Java8Test.java b/user/test/com/google/gwt/dev/jjs/test/Java8Test.java
index e77792f..6ec27ac 100644
--- a/user/test/com/google/gwt/dev/jjs/test/Java8Test.java
+++ b/user/test/com/google/gwt/dev/jjs/test/Java8Test.java
@@ -300,6 +300,10 @@
     assertFalse(isGwtSourceLevel8());
   }
 
+  public void testFunctionalExpressionBridges() {
+    assertFalse(isGwtSourceLevel8());
+  }
+
   private boolean isGwtSourceLevel8() {
     return JUnitShell.getCompilerOptions().getSourceLevel().compareTo(SourceLevel.JAVA8) >= 0;
   }