Java8 IntersectionCast support.

Add support for the following cast expression syntax:
CastExpression:
  (ReferenceType {& InterfaceType} ) UnaryExpression
  (ReferenceType {& InterfaceType} ) LambdaExpression

Change-Id: I8066d5f5d3d1b071c90a3016b265f418e051cb1e
diff --git a/dev/core/src/com/google/gwt/dev/javac/JdtUtil.java b/dev/core/src/com/google/gwt/dev/javac/JdtUtil.java
index d17441e..e427346 100644
--- a/dev/core/src/com/google/gwt/dev/javac/JdtUtil.java
+++ b/dev/core/src/com/google/gwt/dev/javac/JdtUtil.java
@@ -265,4 +265,36 @@
       return null;
     }
   }
+
+  public static String signature(FieldBinding binding) {
+    StringBuilder sb = new StringBuilder();
+    sb.append(binding.declaringClass.constantPoolName());
+    sb.append('.');
+    sb.append(binding.name);
+    sb.append(':');
+    sb.append(binding.type.signature());
+    return sb.toString();
+  }
+
+  public static String signature(MethodBinding binding) {
+    StringBuilder sb = new StringBuilder();
+    sb.append(binding.declaringClass.constantPoolName());
+    sb.append('.');
+    sb.append(binding.selector);
+    sb.append('(');
+    for (TypeBinding paramType : binding.parameters) {
+      sb.append(paramType.signature());
+    }
+    sb.append(')');
+    sb.append(binding.returnType.signature());
+    return sb.toString();
+  }
+
+  public static String signature(TypeBinding binding) {
+    if (binding.isBaseType()) {
+      return String.valueOf(binding.sourceName());
+    } else {
+      return String.valueOf(binding.constantPoolName());
+    }
+  }
 }
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 25a60fa..8ba59f7 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
@@ -115,6 +115,7 @@
 import com.google.gwt.thirdparty.guava.common.base.Function;
 import com.google.gwt.thirdparty.guava.common.base.Preconditions;
 import com.google.gwt.thirdparty.guava.common.collect.Interner;
+import com.google.gwt.thirdparty.guava.common.collect.Iterables;
 import com.google.gwt.thirdparty.guava.common.collect.Lists;
 import com.google.gwt.thirdparty.guava.common.collect.Maps;
 import com.google.gwt.thirdparty.guava.common.collect.Sets;
@@ -208,12 +209,15 @@
 import org.eclipse.jdt.internal.compiler.lookup.ClassScope;
 import org.eclipse.jdt.internal.compiler.lookup.CompilationUnitScope;
 import org.eclipse.jdt.internal.compiler.lookup.FieldBinding;
+import org.eclipse.jdt.internal.compiler.lookup.IntersectionTypeBinding18;
 import org.eclipse.jdt.internal.compiler.lookup.LocalTypeBinding;
 import org.eclipse.jdt.internal.compiler.lookup.LocalVariableBinding;
 import org.eclipse.jdt.internal.compiler.lookup.MethodBinding;
 import org.eclipse.jdt.internal.compiler.lookup.MethodScope;
+import org.eclipse.jdt.internal.compiler.lookup.MethodVerifier;
 import org.eclipse.jdt.internal.compiler.lookup.NestedTypeBinding;
 import org.eclipse.jdt.internal.compiler.lookup.ReferenceBinding;
+import org.eclipse.jdt.internal.compiler.lookup.Scope;
 import org.eclipse.jdt.internal.compiler.lookup.SourceTypeBinding;
 import org.eclipse.jdt.internal.compiler.lookup.SyntheticArgumentBinding;
 import org.eclipse.jdt.internal.compiler.lookup.SyntheticMethodBinding;
@@ -540,11 +544,16 @@
 
     @Override
     public void endVisit(CastExpression x, BlockScope scope) {
+      /**
+       * Our output of a ((A & I1 & I2) a) looks like this:
+       *
+       * ((A)(I1)(I2)a).
+       */
       try {
         SourceInfo info = makeSourceInfo(x);
-        JType type = typeMap.get(x.resolvedType);
+        JType[] type = processCastType(x.resolvedType);
         JExpression expression = pop(x.expression);
-        push(new JCastOperation(info, type, expression));
+        push(buildCastOperation(info, type, expression));
       } catch (Throwable e) {
         throw translateException(x, e);
       }
@@ -1201,17 +1210,26 @@
       TypeBinding binding = x.expectedType();
       // Find the single abstract method of this interface
       MethodBinding samBinding = binding.getSingleAbstractMethod(blockScope, false);
+      assert (samBinding != null && samBinding.isValidBinding());
 
       // Lookup the JMethod version
       JMethod interfaceMethod = typeMap.get(samBinding);
       // And its JInterface container we must implement
-      JInterfaceType funcType = (JInterfaceType) typeMap.get(binding);
+      // There may be more than more JInterface containers to be implemented
+      // if the lambda expression is cast to a IntersectionCastType.
+      JInterfaceType[] funcType;
+      if (binding instanceof IntersectionTypeBinding18) {
+        funcType = processIntersectionTypeForLambda((IntersectionTypeBinding18) binding, blockScope,
+            JdtUtil.signature(samBinding));
+      } else {
+        funcType = new JInterfaceType[] {(JInterfaceType) typeMap.get(binding)};
+      }
       SourceInfo info = makeSourceInfo(x);
 
       // Create an inner class to implement the interface and SAM method.
       // class lambda$0$Type implements T {}
       JClassType innerLambdaClass = createInnerClass(JdtUtil.asDottedString(x.binding.declaringClass.compoundName) +
-          "$" + new String(x.binding.selector), x, funcType, info);
+          "$" + new String(x.binding.selector), x, info, funcType);
       JConstructor ctor = new JConstructor(info, innerLambdaClass);
 
       // locals captured by the lambda and saved as fields on the anonymous inner class
@@ -1400,11 +1418,13 @@
       return outerParam;
     }
 
-    private JClassType createInnerClass(String name, FunctionalExpression x,
-        JInterfaceType funcType, SourceInfo info) {
+    private JClassType createInnerClass(String name, FunctionalExpression x, SourceInfo info,
+        JInterfaceType... funcType) {
       JClassType innerLambdaClass = new JClassType(info, name + "$Type", false, true);
       innerLambdaClass.setEnclosingType((JDeclaredType) typeMap.get(x.binding.declaringClass));
-      innerLambdaClass.addImplements(funcType);
+      for (JInterfaceType type : funcType) {
+        innerLambdaClass.addImplements(type);
+      }
       innerLambdaClass.setSuperClass(javaLangObject);
 
       createSyntheticMethod(info, CLINIT_NAME, innerLambdaClass, JPrimitiveType.VOID, false, true,
@@ -1461,7 +1481,7 @@
             ReferenceBinding targetType =
                 scope.enclosingSourceType().enclosingTypeAt(
                     (x.bits & ASTNode.DepthMASK) >> ASTNode.DepthSHIFT);
-            receiver = makeThisReference(info, targetType, true, scope);
+            receiver = resolveThisReference(info, targetType, true, scope);
           } else if (x.receiver.sourceStart == 0) {
             // Synthetic this ref with bad source info; fix the info.
             JThisRef oldRef = (JThisRef) receiver;
@@ -1635,7 +1655,7 @@
           // Java8 super reference to default method from subtype, X.super.someDefaultMethod
           push(makeThisRef(info));
         } else {
-          push(makeThisReference(info, targetType, true, scope));
+          push(resolveThisReference(info, targetType, true, scope));
         }
       } catch (Throwable e) {
         throw translateException(x, e);
@@ -1647,7 +1667,7 @@
       try {
         SourceInfo info = makeSourceInfo(x);
         ReferenceBinding targetType = (ReferenceBinding) x.qualification.resolvedType;
-        push(makeThisReference(info, targetType, true, scope));
+        push(resolveThisReference(info, targetType, true, scope));
       } catch (Throwable e) {
         throw translateException(x, e);
       }
@@ -1718,7 +1738,7 @@
       List<JExpression> enclosingThisRefs = new ArrayList<JExpression>();
 
       if (innerLambdaClass == null) {
-        innerLambdaClass = createInnerClass(lambdaName, x, funcType, info);
+        innerLambdaClass = createInnerClass(lambdaName, x, info, funcType);
         lambdaNameToInnerLambdaType.put(lambdaName, innerLambdaClass);
         newTypes.add(innerLambdaClass);
 
@@ -1745,7 +1765,7 @@
           if (JdtUtil.isInnerClass(targetBinding)) {
             for (ReferenceBinding argType : targetBinding.syntheticEnclosingInstanceTypes()) {
               argType = (ReferenceBinding) argType.erasure();
-              JExpression enclosingThisRef = makeThisReference(info, argType, false, blockScope);
+              JExpression enclosingThisRef = resolveThisReference(info, argType, false, blockScope);
               JField enclosingInstance = createAndBindCapturedLambdaParameter(info,
                   String.valueOf(argType.readableName()).replace('.', '_'),
                   enclosingThisRef.getType(), ctor, ctorBody);
@@ -2906,7 +2926,7 @@
       return new JThisRef(info, curClass.getClassOrInterface());
     }
 
-    private JExpression makeThisReference(SourceInfo info, ReferenceBinding targetType,
+    private JExpression resolveThisReference(SourceInfo info, ReferenceBinding targetType,
         boolean exactMatch, BlockScope scope) {
       targetType = (ReferenceBinding) targetType.erasure();
 
@@ -3095,7 +3115,7 @@
           call.addArg(qualifier);
         } else {
           // Get implicit outer object.
-          call.addArg(makeThisReference(call.getSourceInfo(), targetType, false, curMethod.scope));
+          call.addArg(resolveThisReference(call.getSourceInfo(), targetType, false, curMethod.scope));
         }
       }
     }
@@ -3197,7 +3217,7 @@
             if (qualifier != null && argType == targetEnclosingType) {
               call.addArg(qualExpr);
             } else {
-              JExpression thisRef = makeThisReference(info, argType, false, scope);
+              JExpression thisRef = resolveThisReference(info, argType, false, scope);
               call.addArg(thisRef);
             }
           }
@@ -3329,7 +3349,7 @@
         assert field != null;
         JExpression thisRef = null;
         if (!b.isStatic()) {
-          thisRef = makeThisReference(info, (ReferenceBinding) x.actualReceiverType, false, scope);
+          thisRef = resolveThisReference(info, (ReferenceBinding) x.actualReceiverType, false, scope);
         }
         result = new JFieldRef(info, thisRef, field, curClass.type);
       } else {
@@ -3438,6 +3458,119 @@
         implementMethod(method, call);
       }
     }
+
+    private JCastOperation buildCastOperation(SourceInfo info, JType[] castTypes,
+        JExpression expression) {
+      return buildCastOperation(info, castTypes, expression, 0);
+    }
+
+    private JCastOperation buildCastOperation(SourceInfo info, JType[] castTypes,
+        JExpression expression, int idx) {
+      if (idx == castTypes.length - 1) {
+        return new JCastOperation(info, castTypes[idx], expression);
+      } else {
+        return new JCastOperation(info, castTypes[idx],
+            buildCastOperation(info, castTypes, expression, idx + 1));
+      }
+    }
+
+    private JReferenceType[] processIntersectionCastType(IntersectionTypeBinding18 type) {
+      JReferenceType[] castTypes = new JReferenceType[type.intersectingTypes.length];
+      int i = 0;
+      for (ReferenceBinding intersectingTypeBinding : type.intersectingTypes) {
+        JType intersectingType = typeMap.get(intersectingTypeBinding);
+        assert (intersectingType instanceof JReferenceType);
+        castTypes[i++] = ((JReferenceType) intersectingType);
+      }
+      return castTypes;
+    }
+
+    private JType[] processCastType(TypeBinding type) {
+      if (type instanceof IntersectionTypeBinding18) {
+        return processIntersectionCastType((IntersectionTypeBinding18) type);
+      } else {
+        return new JType[] {typeMap.get(type)};
+      }
+    }
+
+    private JInterfaceType[] processIntersectionTypeForLambda(IntersectionTypeBinding18 type,
+        BlockScope scope, String samSignature) {
+      List<JInterfaceType> interfaces = Lists.newArrayList();
+      for (ReferenceBinding intersectingTypeBinding : type.intersectingTypes) {
+        if (shouldImplements(intersectingTypeBinding, scope, samSignature)) {
+          JType intersectingType = typeMap.get(intersectingTypeBinding);
+          assert (intersectingType instanceof JInterfaceType);
+          interfaces.add(((JInterfaceType) intersectingType));
+        }
+      }
+      return Iterables.toArray(interfaces, JInterfaceType.class);
+    }
+
+    private boolean isFunctionalInterfaceWithMethod(ReferenceBinding referenceBinding, Scope scope,
+        String samSignature) {
+      if (!referenceBinding.isInterface()) {
+        return false;
+      }
+      MethodBinding abstractMethod = referenceBinding.getSingleAbstractMethod(scope, false);
+      return abstractMethod != null && abstractMethod.isValidBinding()
+          && JdtUtil.signature(abstractMethod).equals(samSignature);
+    }
+
+    private boolean isInterfaceHasNoAbstractMethod(ReferenceBinding referenceBinding, Scope scope) {
+      List<MethodBinding> abstractMethods = getInterfaceAbstractMethods(referenceBinding, scope);
+      return abstractMethods != null && abstractMethods.size() == 0;
+    }
+
+    private boolean shouldImplements(ReferenceBinding referenceBinding, Scope scope,
+        String samSignature) {
+      return isFunctionalInterfaceWithMethod(referenceBinding, scope, samSignature)
+          || isInterfaceHasNoAbstractMethod(referenceBinding, scope);
+    }
+
+    /**
+     * Collect all abstract methods in an interface and its super interfaces.
+     *
+     * In the case of multiple inheritance like this,
+     *
+     * interface I {m();}
+     * interface J {default m();}
+     * interface K extends I, J{}
+     *
+     * the abstract methods of K include m();
+     */
+    private List<MethodBinding> getInterfaceAbstractMethods(ReferenceBinding referenceBinding,
+        Scope scope) {
+      if (!referenceBinding.isInterface() || !referenceBinding.isValidBinding()) {
+        return null;
+      }
+      List<MethodBinding> abstractMethods = Lists.newLinkedList();
+
+      // add all abstract methods from super interfaces.
+      for (ReferenceBinding superInterface : referenceBinding.superInterfaces()) {
+        List<MethodBinding> abstractMethodsFromSupers =
+            getInterfaceAbstractMethods(superInterface, scope);
+        if (abstractMethodsFromSupers != null && abstractMethodsFromSupers.size() > 0) {
+          abstractMethods.addAll(abstractMethodsFromSupers);
+        }
+      }
+      for (MethodBinding method : referenceBinding.methods()) {
+        if (method == null || method.isStatic() || method.redeclaresPublicObjectMethod(scope)) {
+          continue;
+        }
+        // remove the overridden methods in the super interfaces.
+        for (MethodBinding abstractMethodFromSupers : abstractMethods) {
+          if (MethodVerifier.doesMethodOverride(method, abstractMethodFromSupers,
+              scope.environment())) {
+            abstractMethods.remove(abstractMethodFromSupers);
+          }
+        }
+        // add method to abstract methods if it is not a default method.
+        if (!method.isDefaultMethod()) {
+          abstractMethods.add(method);
+        }
+      }
+      return abstractMethods;
+    }
   }
 
   static class ClassInfo {
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/ReferenceMapper.java b/dev/core/src/com/google/gwt/dev/jjs/impl/ReferenceMapper.java
index 59b38cb..865985f 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/ReferenceMapper.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/ReferenceMapper.java
@@ -81,7 +81,7 @@
 
   public JField get(FieldBinding binding) {
     binding = binding.original();
-    String key = signature(binding);
+    String key = JdtUtil.signature(binding);
     JField sourceField = sourceFields.get(key);
     if (sourceField != null) {
       assert !sourceField.isExternal();
@@ -98,7 +98,7 @@
 
   public JMethod get(MethodBinding binding) {
     binding = binding.original();
-    String key = signature(binding);
+    String key = JdtUtil.signature(binding);
     JMethod sourceMethod = sourceMethods.get(key);
     if (sourceMethod != null) {
       assert !sourceMethod.isExternal();
@@ -119,7 +119,7 @@
 
   public JType get(TypeBinding binding) {
     binding = binding.erasure();
-    String key = signature(binding);
+    String key = JdtUtil.signature(binding);
     JReferenceType sourceType = sourceTypes.get(key);
 
     if (sourceType != null) {
@@ -184,17 +184,17 @@
   }
 
   public void setField(FieldBinding binding, JField field) {
-    String key = signature(binding);
+    String key = JdtUtil.signature(binding);
     sourceFields.put(key, field);
   }
 
   public void setMethod(MethodBinding binding, JMethod method) {
-    String key = signature(binding);
+    String key = JdtUtil.signature(binding);
     sourceMethods.put(key, method);
   }
 
   public void setSourceType(SourceTypeBinding binding, JDeclaredType type) {
-    String key = signature(binding);
+    String key = JdtUtil.signature(binding);
     sourceTypes.put(key, type);
   }
 
@@ -350,36 +350,4 @@
       types.put(type.getName(), type);
     }
   }
-
-  private String signature(FieldBinding binding) {
-    StringBuilder sb = new StringBuilder();
-    sb.append(binding.declaringClass.constantPoolName());
-    sb.append('.');
-    sb.append(binding.name);
-    sb.append(':');
-    sb.append(binding.type.signature());
-    return sb.toString();
-  }
-
-  private String signature(MethodBinding binding) {
-    StringBuilder sb = new StringBuilder();
-    sb.append(binding.declaringClass.constantPoolName());
-    sb.append('.');
-    sb.append(binding.selector);
-    sb.append('(');
-    for (TypeBinding paramType : binding.parameters) {
-      sb.append(paramType.signature());
-    }
-    sb.append(')');
-    sb.append(binding.returnType.signature());
-    return sb.toString();
-  }
-
-  private String signature(TypeBinding binding) {
-    if (binding.isBaseType()) {
-      return String.valueOf(binding.sourceName());
-    } else {
-      return String.valueOf(binding.constantPoolName());
-    }
-  }
 }
diff --git a/dev/core/test/com/google/gwt/dev/jjs/impl/Java8AstTest.java b/dev/core/test/com/google/gwt/dev/jjs/impl/Java8AstTest.java
index 7e27e0f..069703b 100644
--- a/dev/core/test/com/google/gwt/dev/jjs/impl/Java8AstTest.java
+++ b/dev/core/test/com/google/gwt/dev/jjs/impl/Java8AstTest.java
@@ -486,6 +486,164 @@
         formatSource(samMethod.toSource()));
   }
 
+  public void testIntersectionCast() throws Exception {
+    addSnippetClassDecl("static class A {void print() {} }");
+    addSnippetClassDecl("interface I1 {}");
+    addSnippetClassDecl("interface I2 {}");
+    addSnippetClassDecl("interface I3 {}");
+    addSnippetClassDecl("static class B extends A implements I1 {}");
+    addSnippetClassDecl("static class C extends A implements I1, I2, I3 {}");
+    String cast1 = "B b = new B(); ((A & I1) b).print();";
+    assertEqualBlock("EntryPoint$B b=new EntryPoint$B();((EntryPoint$A)(EntryPoint$I1)b).print();",
+        cast1);
+    String cast2 = "C c = new C(); ((A & I1 & I2 & I3)c).print();";
+    assertEqualBlock("EntryPoint$C c=new EntryPoint$C();"
+        + "((EntryPoint$A)(EntryPoint$I1)(EntryPoint$I2)(EntryPoint$I3)c).print();", cast2);
+  }
+
+  public void testIntersectionCastOfLambda() throws Exception {
+    addSnippetClassDecl("interface I1 { public void foo(); }");
+    addSnippetClassDecl("interface I2 { }");
+    String lambda = "Object o = (I2 & I1) () -> {};";
+    assertEqualBlock("Object o=(EntryPoint$I2)(EntryPoint$I1)new EntryPoint$lambda$0$Type();",
+        lambda);
+
+    JProgram program = compileSnippet("void", lambda, false);
+    // created by JDT, should exist
+    assertNotNull(getMethod(program, "lambda$0"));
+
+    // created by GwtAstBuilder
+    JClassType lambdaInnerClass = (JClassType) getType(program, "test.EntryPoint$lambda$0$Type");
+    assertNotNull(lambdaInnerClass);
+
+    // no fields
+    assertEquals(0, lambdaInnerClass.getFields().size());
+
+    // should have constructor taking no args
+    JMethod ctor = findMethod(lambdaInnerClass, "EntryPoint$lambda$0$Type");
+    assertTrue(ctor instanceof JConstructor);
+    assertEquals(0, ctor.getParams().size());
+
+    // should implements I1 and I2
+    assertTrue(
+        lambdaInnerClass.getImplements().contains(program.getFromTypeMap("test.EntryPoint$I1")));
+    assertTrue(
+        lambdaInnerClass.getImplements().contains(program.getFromTypeMap("test.EntryPoint$I2")));
+    // should implement foo method
+    JMethod samMethod = findMethod(lambdaInnerClass, "foo");
+    assertEquals("public final void foo(){EntryPoint.lambda$0();}",
+        formatSource(samMethod.toSource()));
+  }
+
+  public void testMultipleIntersectionCastOfLambda() throws Exception {
+    addSnippetClassDecl("interface I1 { public void foo(); }");
+    addSnippetClassDecl("interface I2 { }");
+    addSnippetClassDecl("interface I3 { }");
+    String lambda = "I2 o = (I3 & I2 & I1) () -> {};";
+    assertEqualBlock(
+        "EntryPoint$I2 o=(EntryPoint$I3)(EntryPoint$I2)(EntryPoint$I1)new EntryPoint$lambda$0$Type();",
+        lambda);
+
+    JProgram program = compileSnippet("void", lambda, false);
+    // created by JDT, should exist
+    assertNotNull(getMethod(program, "lambda$0"));
+
+    // created by GwtAstBuilder
+    JClassType lambdaInnerClass = (JClassType) getType(program, "test.EntryPoint$lambda$0$Type");
+    assertNotNull(lambdaInnerClass);
+
+    // no fields
+    assertEquals(0, lambdaInnerClass.getFields().size());
+
+    // should have constructor taking no args
+    JMethod ctor = findMethod(lambdaInnerClass, "EntryPoint$lambda$0$Type");
+    assertTrue(ctor instanceof JConstructor);
+    assertEquals(0, ctor.getParams().size());
+
+    // should extends java.lang.Object, implements I1, I2 and I3
+    assertEquals("java.lang.Object", lambdaInnerClass.getSuperClass().getName());
+    assertTrue(
+        lambdaInnerClass.getImplements().contains(program.getFromTypeMap("test.EntryPoint$I1")));
+    assertTrue(
+        lambdaInnerClass.getImplements().contains(program.getFromTypeMap("test.EntryPoint$I2")));
+    assertTrue(
+        lambdaInnerClass.getImplements().contains(program.getFromTypeMap("test.EntryPoint$I3")));
+    // should implement foo method
+    JMethod samMethod = findMethod(lambdaInnerClass, "foo");
+    assertEquals("public final void foo(){EntryPoint.lambda$0();}",
+        formatSource(samMethod.toSource()));
+  }
+
+  public void testIntersectionCastOfLambdaWithClassType() throws Exception {
+    addSnippetClassDecl("interface I1 { public void foo(); }");
+    addSnippetClassDecl("class A { }");
+    String lambda = "Object o = (A & I1) () -> {};";
+    assertEqualBlock("Object o=(EntryPoint$A)(EntryPoint$I1)new EntryPoint$lambda$0$Type();",
+        lambda);
+
+    JProgram program = compileSnippet("void", lambda, false);
+
+    assertNotNull(getMethod(program, "lambda$0"));
+
+    JClassType lambdaInnerClass = (JClassType) getType(program, "test.EntryPoint$lambda$0$Type");
+    assertNotNull(lambdaInnerClass);
+    assertEquals("java.lang.Object", lambdaInnerClass.getSuperClass().getName());
+    assertEquals(1, lambdaInnerClass.getImplements().size());
+    assertTrue(
+        lambdaInnerClass.getImplements().contains(program.getFromTypeMap("test.EntryPoint$I1")));
+    // should implement foo method
+    JMethod samMethod = findMethod(lambdaInnerClass, "foo");
+    assertEquals("public final void foo(){EntryPoint.lambda$0();}",
+        formatSource(samMethod.toSource()));
+  }
+
+  public void testIntersectionCastOfLambdaOneAbstractMethod() throws Exception {
+    addSnippetClassDecl("interface I1 { public void foo(); }");
+    addSnippetClassDecl("interface I2 extends I1{ public void foo();}");
+    String lambda = "Object o = (I1 & I2) () -> {};";
+    // (I1 & I2) is resolved to I2 by JDT.
+    assertEqualBlock("Object o=(EntryPoint$I2)new EntryPoint$lambda$0$Type();",
+        lambda);
+
+    JProgram program = compileSnippet("void", lambda, false);
+
+    assertNotNull(getMethod(program, "lambda$0"));
+
+    JClassType lambdaInnerClass = (JClassType) getType(program, "test.EntryPoint$lambda$0$Type");
+    assertNotNull(lambdaInnerClass);
+    assertEquals("java.lang.Object", lambdaInnerClass.getSuperClass().getName());
+    assertEquals(1, lambdaInnerClass.getImplements().size()); // only implements I2.
+    assertTrue(
+        lambdaInnerClass.getImplements().contains(program.getFromTypeMap("test.EntryPoint$I2")));
+    // should implement foo method
+    JMethod samMethod = findMethod(lambdaInnerClass, "foo");
+    assertEquals("public final void foo(){EntryPoint.lambda$0();}",
+        formatSource(samMethod.toSource()));
+  }
+
+  public void testIntersectionCastMultipleAbstractMethods() throws Exception {
+    addSnippetClassDecl("interface I1 { public void foo(); }");
+    addSnippetClassDecl("interface I2 { public void bar(); public void fun();}");
+    String lambda = "Object o = (I1 & I2) () -> {};";
+    assertEqualBlock("Object o=(EntryPoint$I1)(EntryPoint$I2)new EntryPoint$lambda$0$Type();",
+        lambda);
+
+    JProgram program = compileSnippet("void", lambda, false);
+
+    assertNotNull(getMethod(program, "lambda$0"));
+
+    JClassType lambdaInnerClass = (JClassType) getType(program, "test.EntryPoint$lambda$0$Type");
+    assertNotNull(lambdaInnerClass);
+    assertEquals("java.lang.Object", lambdaInnerClass.getSuperClass().getName());
+    assertEquals(1, lambdaInnerClass.getImplements().size());
+    assertTrue(
+        lambdaInnerClass.getImplements().contains(program.getFromTypeMap("test.EntryPoint$I1")));
+    // should implement foo method
+    JMethod samMethod = findMethod(lambdaInnerClass, "foo");
+    assertEquals("public final void foo(){EntryPoint.lambda$0();}",
+        formatSource(samMethod.toSource()));
+  }
+
   private static final MockJavaResource LAMBDA_METAFACTORY =
       JavaResourceBase.createMockJavaResource("java.lang.invoke.LambdaMetafactory",
           "package java.lang.invoke;",
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 391d52a..2dbad11 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
@@ -659,4 +659,119 @@
     assertEquals("B.n1;I.n;I.m;A.n;A.m;B.m;", outerClass.n1());
     assertEquals("B.n2;I.m;A.n;A.m;B.m;B.m;", outerClass.n2());
   }
+
+  class EmptyA { }
+  interface EmptyI { }
+  interface EmptyJ { }
+  class EmptyB extends EmptyA implements EmptyI { }
+  class EmptyC extends EmptyA implements EmptyI, EmptyJ { }
+  public void testBaseIntersectionCast() {
+    EmptyA localB = new EmptyB();
+    EmptyA localC = new EmptyC();
+    EmptyB b2BI = (EmptyB & EmptyI) localB;
+    EmptyC c2CIJ = (EmptyC & EmptyI & EmptyJ) localC;
+    EmptyI ii1 = (EmptyB & EmptyI) localB;
+    EmptyI ii2 = (EmptyC & EmptyI) localC;
+    EmptyI ii3 = (EmptyC & EmptyJ) localC;
+    EmptyI ii4 = (EmptyC & EmptyI & EmptyJ) localC;
+    EmptyJ jj1 = (EmptyC & EmptyI & EmptyJ) localC;
+    EmptyJ jj2 = (EmptyC & EmptyI) localC;
+    EmptyJ jj3 = (EmptyC & EmptyJ) localC;
+    EmptyJ jj4 = (EmptyI & EmptyJ) localC;
+
+    try {
+      EmptyC b2CIJ = (EmptyC & EmptyI & EmptyJ) localB;
+      fail("Should have thrown a ClassCastException");
+    } catch (ClassCastException e) {
+      // Expected.
+    }
+    try {
+      EmptyB c2BI = (EmptyB & EmptyI) localC;
+      fail("Should have thrown a ClassCastException");
+    } catch (ClassCastException e) {
+      // Expected.
+    }
+    try {
+      EmptyJ jj = (EmptyB & EmptyJ) localB;
+      fail("Should have thrown a ClassCastException");
+    } catch (ClassCastException e) {
+      // Expected.
+    }
+  }
+
+  interface SimpleI {
+    int fun();
+  }
+  interface SimpleJ {
+    int foo();
+    int bar();
+  }
+  interface SimpleK {
+  }
+  public void testIntersectionCastWithLambdaExpr() {
+    SimpleI simpleI1 = (SimpleI & EmptyI) () -> { return 11; };
+    assertEquals(11, simpleI1.fun());
+    SimpleI simpleI2 = (EmptyI & SimpleI) () -> { return 22; };
+    assertEquals(22, simpleI2.fun());
+    EmptyI emptyI = (EmptyI & SimpleI) () -> { return 33; };
+    try {
+      ((EmptyA & SimpleI) () -> { return 33; }).fun();
+      fail("Should have thrown a ClassCastException");
+    } catch (ClassCastException e) {
+      // expected.
+    }
+    try {
+      ((SimpleI & SimpleJ) () -> { return 44; }).fun();
+      fail("Should have thrown a ClassCastException");
+    } catch (ClassCastException e) {
+      // expected.
+    }
+    try {
+      ((SimpleI & SimpleJ) () -> { return 44; }).foo();
+      fail("Should have thrown a ClassCastException");
+    } catch (ClassCastException e) {
+      // expected.
+    }
+    try {
+      ((SimpleI & SimpleJ) () -> { return 44; }).bar();
+      fail("Should have thrown a ClassCastException");
+    } catch (ClassCastException e) {
+      // expected.
+    }
+    assertEquals(55, ((SimpleI & SimpleK) () -> { return 55; }).fun());
+  }
+
+  class SimpleA {
+    public int bar() {
+      return 11;
+    }
+  }
+
+  class SimpleB extends SimpleA implements SimpleI {
+    public int fun() {
+      return 22;
+    }
+  }
+
+  class SimpleC extends SimpleA implements SimpleI {
+    public int fun() {
+      return 33;
+    }
+
+    public int bar() {
+      return 44;
+    }
+  }
+
+  public void testIntersectionCastPolymorphism() {
+    SimpleA bb = new SimpleB();
+    assertEquals(22, ((SimpleB & SimpleI) bb).fun());
+    assertEquals(11, ((SimpleB & SimpleI) bb).bar());
+    SimpleA cc = new SimpleC();
+    assertEquals(33, ((SimpleC & SimpleI) cc).fun());
+    assertEquals(44, ((SimpleC & SimpleI) cc).bar());
+    assertEquals(33, ((SimpleA & SimpleI) cc).fun());
+    SimpleI ii = (SimpleC & SimpleI) cc;
+    assertEquals(33, ii.fun());
+  }
 }
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 d6428e0..a50dc0f 100644
--- a/user/test/com/google/gwt/dev/jjs/test/Java8Test.java
+++ b/user/test/com/google/gwt/dev/jjs/test/Java8Test.java
@@ -144,4 +144,13 @@
 
   public void testNestedInterfaceClass() {
   }
+
+  public void testBaseIntersectionCast() {
+  }
+
+  public void testIntersectionCastWithLambdaExpr() {
+  }
+
+  public void testIntersectionCastPolymorphism() {
+  }
 }
\ No newline at end of file