Fix runtime type error due to missing lambda implementation method.

Due to the current startegy of implementing the code for the lambda
method in the enclosing class, if the lambda refers to the enclosing
"this" and is declared in a default method in an interface, it ends
up as a private instance method in such interface.

Interfaces are not expected to have any instance methods (appart from
default methdods) and thus the implementation method was never
emitted.

This is yet another hack which makes those methods devirtualized.
The better solution for this and other bugs related to lambdas is
to syntehsize these methods in the inner class created for the
lambda instead.

Change-Id: Ie59bb5df3c3b5ba29f3616f75abb1df1d0f7366c
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 bdcb8b3..795a91c 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
@@ -175,7 +175,7 @@
         JMethod jsoStaticImpl =
             staticImplCreator.getOrCreateStaticImpl(program, overridingMethod);
         devirtualMethodByMethod.put(method, jsoStaticImpl);
-      } else if (method.isJsOverlay()) {
+      } else if (isOverlayMethod(method)) {
         // A virtual dispatch on a target that is already known to be a JavaScriptObject, this
         // should have been handled by MakeCallsStatic.
         // TODO(rluble): verify that this case can not arise in optimized mode and if so
@@ -200,7 +200,7 @@
       if (devirtualMethodByMethod.containsKey(method)) {
         return true;
       }
-      if (method.isJsOverlay()) {
+      if (isOverlayMethod(method)) {
         return true;
       }
       if (method.getEnclosingType().isJsNative()) {
@@ -213,6 +213,18 @@
     }
   }
 
+  /**
+   * 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.
+   */
+  private boolean isOverlayMethod(JMethod method) {
+    return method.isJsOverlay()
+        || (method.getEnclosingType() instanceof JInterfaceType && method.isPrivate());
+  }
+
   public static void exec(JProgram program) {
     new Devirtualizer(program).execImpl();
   }
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 5c9d601..d38bf76 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
@@ -1576,4 +1576,28 @@
   public void testNativeJsOverlay_lambda() {
     assertSame("Hello", NativeClassWithJsOverlay.m("Hello"));
   }
+
+  interface IntefaceWithDefaultMethodAndLambda {
+    boolean f();
+
+    default BooleanPredicate fAsPredicate() {
+      // This lambda will be defined as an instance method in the enclosing class, which is an
+      // interface. In this case the methdod will be devirtualized.
+      return () -> this.f();
+    }
+  }
+
+  interface BooleanPredicate {
+    boolean apply();
+  }
+
+  public void testLambdaCapturingThis_onDefaultMethod() {
+    assertTrue(
+        new IntefaceWithDefaultMethodAndLambda() {
+          @Override
+          public boolean f() {
+            return true;
+          }
+        }.fAsPredicate().apply());
+  }
 }
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 1fda5c6..e435da0 100644
--- a/user/test/com/google/gwt/dev/jjs/test/Java8Test.java
+++ b/user/test/com/google/gwt/dev/jjs/test/Java8Test.java
@@ -292,6 +292,10 @@
     assertFalse(isGwtSourceLevel8());
   }
 
+  public void testLambdaCapturingThis_onDefaultMethod() {
+    assertFalse(isGwtSourceLevel8());
+  }
+
   private boolean isGwtSourceLevel8() {
     return JUnitShell.getCompilerOptions().getSourceLevel().compareTo(SourceLevel.JAVA8) >= 0;
   }