Java8 lambda/method reference language support

Includes James Nelson's upstream

Fix lambdas to appear as the expected type

Previous implementation created lambdas as the type of the
single abstract method being implemented, rather than expected
type in method signature.  To fix this, we correctly type the
anonymous type (and return the correct class from .getClass())

Add java8 emulation for common functional interfaces

Change-Id: I0b8c941f99a2e6869c5206bbbde0450ca6a80f64
diff --git a/dev/core/src/com/google/gwt/dev/javac/JavaSourceParser.java b/dev/core/src/com/google/gwt/dev/javac/JavaSourceParser.java
index ae0a16d..600e1fa 100644
--- a/dev/core/src/com/google/gwt/dev/javac/JavaSourceParser.java
+++ b/dev/core/src/com/google/gwt/dev/javac/JavaSourceParser.java
@@ -180,9 +180,9 @@
   private static CompilationUnitDeclaration parseJava(String javaSource) {
     CodeSnippetParsingUtil parsingUtil = new CodeSnippetParsingUtil(true);
     CompilerOptions options = new CompilerOptions();
-    options.complianceLevel = ClassFileConstants.JDK1_6;
-    options.originalSourceLevel = ClassFileConstants.JDK1_6;
-    options.sourceLevel = ClassFileConstants.JDK1_6;
+    options.complianceLevel = ClassFileConstants.JDK1_8;
+    options.originalSourceLevel = ClassFileConstants.JDK1_8;
+    options.sourceLevel = ClassFileConstants.JDK1_8;
     CompilationUnitDeclaration unit = parsingUtil.parseCompilationUnit(
         javaSource.toString().toCharArray(), options.getMap(), true);
     if (unit.compilationResult().hasProblems()) {
diff --git a/dev/core/src/com/google/gwt/dev/javac/JdtCompiler.java b/dev/core/src/com/google/gwt/dev/javac/JdtCompiler.java
index 12144c9..fb78490 100644
--- a/dev/core/src/com/google/gwt/dev/javac/JdtCompiler.java
+++ b/dev/core/src/com/google/gwt/dev/javac/JdtCompiler.java
@@ -401,6 +401,7 @@
       }
       String internalName = CharOperation.charToString(classFile.fileName());
       String sourceName = JdtUtil.getSourceName(classFile.referenceBinding);
+      // TODO(cromwellian) implement Retrolambda on output?
       CompiledClass result = new CompiledClass(classFile.getBytes(), enclosingClass,
           isLocalType(classFile), internalName, sourceName);
       results.put(classFile, result);
@@ -607,7 +608,6 @@
     };
 
     long jdtSourceLevel = jdtLevelByGwtLevel.get(SourceLevel.DEFAULT_SOURCE_LEVEL);
-
     options.originalSourceLevel = jdtSourceLevel;
     options.complianceLevel = jdtSourceLevel;
     options.sourceLevel = jdtSourceLevel;
@@ -758,7 +758,8 @@
   private static final Map<SourceLevel, Long> jdtLevelByGwtLevel =
       ImmutableMap.<SourceLevel, Long>of(
           SourceLevel.JAVA6, ClassFileConstants.JDK1_6,
-          SourceLevel.JAVA7, ClassFileConstants.JDK1_7);
+          SourceLevel.JAVA7, ClassFileConstants.JDK1_7,
+          SourceLevel.JAVA8, ClassFileConstants.JDK1_8);
 
   public JdtCompiler(CompilerContext compilerContext, UnitProcessor processor) {
     this.compilerContext = compilerContext;
diff --git a/dev/core/src/com/google/gwt/dev/javac/testing/impl/JavaResourceBase.java b/dev/core/src/com/google/gwt/dev/javac/testing/impl/JavaResourceBase.java
index 3f7f7c9..b4330db 100644
--- a/dev/core/src/com/google/gwt/dev/javac/testing/impl/JavaResourceBase.java
+++ b/dev/core/src/com/google/gwt/dev/javac/testing/impl/JavaResourceBase.java
@@ -176,6 +176,12 @@
           "  public String value() { return \"Foo\"; }",
           "}");
 
+  public static final MockJavaResource FUNCTIONALINTERFACE =
+      createMockJavaResource("java.lang.FunctionalInterface",
+          "package java.lang;",
+          "public @interface FunctionalInterface {",
+          "}");
+
   public static final MockJavaResource INTEGER =
       createMockJavaResource("java.lang.Integer",
           "package java.lang;",
@@ -382,7 +388,7 @@
     return new MockJavaResource[] {
         AUTOCLOSEABLE, ANNOTATION, ARRAY_LIST, BYTE, BOOLEAN, CHARACTER, CHAR_SEQUENCE, CLASS,
         CLASS_NOT_FOUND_EXCEPTION, CLONEABLE, COLLECTION, COMPARABLE, DOUBLE, ENUM, EXCEPTION,
-        ERROR, FLOAT, INTEGER, IS_SERIALIZABLE, JAVASCRIPTOBJECT, LIST, LONG, MAP,
+        ERROR, FUNCTIONALINTERFACE, FLOAT, INTEGER, IS_SERIALIZABLE, JAVASCRIPTOBJECT, LIST, LONG, MAP,
         NO_CLASS_DEF_FOUND_ERROR, NUMBER, OBJECT, RUNTIME_EXCEPTION, SERIALIZABLE, SHORT, STRING,
         STRING_BUILDER, SUPPRESS_WARNINGS, THROWABLE, SPECIALIZE_METHOD, JSTYPE, JSTYPEPROTOTYPE,
         JSEXPORT, JSPROPERTY};
diff --git a/dev/core/src/com/google/gwt/dev/jjs/ast/JDeclaredType.java b/dev/core/src/com/google/gwt/dev/jjs/ast/JDeclaredType.java
index 39ef5d0..49f1d7f 100755
--- a/dev/core/src/com/google/gwt/dev/jjs/ast/JDeclaredType.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/ast/JDeclaredType.java
@@ -16,9 +16,11 @@
 package com.google.gwt.dev.jjs.ast;
 
 import com.google.gwt.dev.jjs.SourceInfo;
+import com.google.gwt.dev.jjs.impl.GwtAstBuilder;
 import com.google.gwt.dev.util.StringInterner;
 import com.google.gwt.dev.util.collect.Lists;
 import com.google.gwt.thirdparty.guava.common.base.Preconditions;
+import com.google.gwt.thirdparty.guava.common.base.Strings;
 
 import java.io.IOException;
 import java.io.ObjectInputStream;
@@ -117,13 +119,17 @@
   /**
    * Adds a method to this type.
    */
-  public final void addMethod(JMethod method) {
+  public final void addMethod(int index, JMethod method) {
     assert method.getEnclosingType() == this;
-    assert !method.getName().equals("$clinit") || getMethods().size() == 0 : "Attempted adding "
+    assert !method.getName().equals(GwtAstBuilder.CLINIT_NAME) || getMethods().size() == 0 : "Attempted adding "
         + "$clinit method with index != 0";
-    assert !method.getName().equals("$init") || getMethods().size() == 1 : "Attempted adding $init "
-        + "method with index != 1";
-    methods = Lists.add(methods, method);
+    assert !method.getName().equals(GwtAstBuilder.INIT_NAME) || method.getParams().size() != 0 ||
+        getMethods().size() == 1 : "Attempted adding $init method with index != 1";
+    methods = Lists.add(methods, index, method);
+  }
+
+  public void addMethod(JMethod newMethod) {
+    addMethod(methods.size(), newMethod);
   }
 
   /**
@@ -204,7 +210,7 @@
     JMethod clinit = this.getMethods().get(0);
 
     assert clinit != null;
-    assert clinit.getName().equals("$clinit");
+    assert clinit.getName().equals(GwtAstBuilder.CLINIT_NAME);
     return clinit;
   }
 
@@ -270,7 +276,7 @@
     JMethod init = this.getMethods().get(1);
 
     assert init != null;
-    assert init.getName().equals("$init");
+    assert init.getName().equals(GwtAstBuilder.INIT_NAME);
     return init;
   }
 
@@ -360,7 +366,7 @@
   public void resolve(List<JInterfaceType> resolvedInterfaces, String jsNamespace) {
     assert JType.replaces(resolvedInterfaces, superInterfaces);
     superInterfaces = Lists.normalize(resolvedInterfaces);
-    if (this.jsNamespace.isEmpty()) {
+    if (Strings.isNullOrEmpty(this.jsNamespace)) {
       this.jsNamespace = jsNamespace;
     }
   }
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 cdc01cf..f38950a 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
@@ -20,6 +20,7 @@
 import com.google.gwt.dev.jjs.InternalCompilerException;
 import com.google.gwt.dev.jjs.SourceInfo;
 import com.google.gwt.dev.jjs.SourceOrigin;
+import com.google.gwt.dev.jjs.impl.GwtAstBuilder;
 import com.google.gwt.dev.jjs.impl.codesplitter.FragmentPartitioningResult;
 import com.google.gwt.dev.util.StringInterner;
 import com.google.gwt.dev.util.log.speedtracer.CompilerEventType;
@@ -241,7 +242,7 @@
     public static boolean isClinit(JMethod method) {
     JDeclaredType enclosingType = method.getEnclosingType();
     if ((enclosingType != null) && (method == enclosingType.getClinitMethod())) {
-      assert (method.getName().equals("$clinit"));
+      assert (method.getName().equals(GwtAstBuilder.CLINIT_NAME));
       return true;
     } else {
       return false;
diff --git a/dev/core/src/com/google/gwt/dev/jjs/ast/JThisRef.java b/dev/core/src/com/google/gwt/dev/jjs/ast/JThisRef.java
index 0778d07..659cbf9 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/ast/JThisRef.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/ast/JThisRef.java
@@ -22,14 +22,14 @@
  */
 public class JThisRef extends JExpression {
 
-  private final JClassType type;
+  private final JDeclaredType type;
 
-  public JThisRef(SourceInfo info, JClassType type) {
+  public JThisRef(SourceInfo info, JDeclaredType type) {
     super(info);
     this.type = type;
   }
 
-  public JClassType getClassType() {
+  public JDeclaredType getClassType() {
     return type;
   }
 
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/ComputePotentiallyObservableUninitializedValues.java b/dev/core/src/com/google/gwt/dev/jjs/impl/ComputePotentiallyObservableUninitializedValues.java
index 556415e..5cd2f0a 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/ComputePotentiallyObservableUninitializedValues.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/ComputePotentiallyObservableUninitializedValues.java
@@ -232,12 +232,12 @@
     }
 
     private boolean isDevirtualizedInitMethod(JMethod method) {
-      return method.isStatic() && method.getName().equals("$$init") &&
+      return method.isStatic() && method.getName().equals(GwtAstBuilder.STATIC_INIT_NAME) &&
           method.getEnclosingType() == currentClass;
     }
 
     private boolean isInitMethod(JMethod method) {
-      return !method.isStatic() && method.getName().equals("$init") &&
+      return !method.isStatic() && method.getName().equals(GwtAstBuilder.INIT_NAME) &&
           method.getEnclosingType() == currentClass;
     }
 
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 8f3fcb1..016e301 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
@@ -1170,6 +1170,16 @@
         globalStmts.add(clinitFunc.makeStmt());
       }
 
+      assert jsFuncs.get(0).getName().getShortIdent().startsWith(GwtAstBuilder.CLINIT_NAME + "_");
+      jsFuncs.remove(0);
+
+      // declare all static methods (Java8) into the global scope
+      for (JsFunction func : jsFuncs) {
+        if (!polymorphicJsFunctions.contains(func)) {
+          globalStmts.add(func.makeStmt());
+        }
+      }
+
       // setup fields
       JsVars vars = new JsVars(x.getSourceInfo());
       for (int i = 0; i < jsFields.size(); ++i) {
@@ -3430,7 +3440,7 @@
     }
   }
 
-  String getNameString(HasName hasName) {
+  static String getNameString(HasName hasName) {
     String s = hasName.getName().replaceAll("_", "_1").replace('.', '_');
     return s;
   }
@@ -3478,6 +3488,26 @@
     return StringInterner.get().intern(sb.toString());
   }
 
+  /**
+   * Java8 Method References such as String::equalsIgnoreCase should produce inner class names
+   * that are a function of the samInterface (e.g. Runnable), the method being referred to,
+   * and the qualifying disposition (this::foo vs Class::foo if foo is an instance method)
+   */
+  static String classNameForMethodReference(JInterfaceType samInterface, JMethod referredMethod,
+      boolean haveReceiver) {
+    StringBuilder sb = new StringBuilder();
+    sb.append(samInterface.getPackageName());
+    sb.append('.');
+    sb.append(samInterface.getShortName());
+    sb.append("$");
+    if (!haveReceiver) {
+      sb.append("$");
+    }
+    sb.append(getNameString(referredMethod));
+    constructManglingSignature(referredMethod, sb);
+    return StringInterner.get().intern(sb.toString());
+  }
+
   String mangleNameForPoly(JMethod x) {
     assert !x.isPrivate() && !x.isStatic();
     StringBuilder sb = new StringBuilder();
@@ -3502,7 +3532,7 @@
     return StringInterner.get().intern(sb.toString());
   }
 
-  private void constructManglingSignature(JMethod x, StringBuilder partialSignature) {
+  private static void constructManglingSignature(JMethod x, StringBuilder partialSignature) {
     partialSignature.append("__");
     for (int i = 0; i < x.getOriginalParamTypes().size(); ++i) {
       JType type = x.getOriginalParamTypes().get(i);
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 d575f05..c1e29a8 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
@@ -155,11 +155,13 @@
 import org.eclipse.jdt.internal.compiler.ast.FloatLiteral;
 import org.eclipse.jdt.internal.compiler.ast.ForStatement;
 import org.eclipse.jdt.internal.compiler.ast.ForeachStatement;
+import org.eclipse.jdt.internal.compiler.ast.FunctionalExpression;
 import org.eclipse.jdt.internal.compiler.ast.IfStatement;
 import org.eclipse.jdt.internal.compiler.ast.Initializer;
 import org.eclipse.jdt.internal.compiler.ast.InstanceOfExpression;
 import org.eclipse.jdt.internal.compiler.ast.IntLiteral;
 import org.eclipse.jdt.internal.compiler.ast.LabeledStatement;
+import org.eclipse.jdt.internal.compiler.ast.LambdaExpression;
 import org.eclipse.jdt.internal.compiler.ast.LocalDeclaration;
 import org.eclipse.jdt.internal.compiler.ast.LongLiteral;
 import org.eclipse.jdt.internal.compiler.ast.MarkerAnnotation;
@@ -176,6 +178,7 @@
 import org.eclipse.jdt.internal.compiler.ast.QualifiedNameReference;
 import org.eclipse.jdt.internal.compiler.ast.QualifiedSuperReference;
 import org.eclipse.jdt.internal.compiler.ast.QualifiedThisReference;
+import org.eclipse.jdt.internal.compiler.ast.ReferenceExpression;
 import org.eclipse.jdt.internal.compiler.ast.ReturnStatement;
 import org.eclipse.jdt.internal.compiler.ast.SingleMemberAnnotation;
 import org.eclipse.jdt.internal.compiler.ast.SingleNameReference;
@@ -234,6 +237,11 @@
  */
 public class GwtAstBuilder {
 
+  public static final String CLINIT_NAME = "$clinit";
+  public static final String INIT_NAME = "$init";
+  public static final String STATIC_INIT_NAME =  "$" + INIT_NAME;
+  public static final String OUTER_LAMBDA_PARAM_NAME = "$$outer_0";
+
   /**
    * Visit the JDT AST and produce our own AST. By the end of this pass, the
    * produced AST should contain every piece of information we'll ever need
@@ -1077,6 +1085,333 @@
     }
 
     @Override
+    public boolean visit(ReferenceExpression x, BlockScope blockScope) {
+      // T[][][]::new => lambda$n(int x) { return new T[int x][][]; }
+      if (x.isArrayConstructorReference()) {
+        // ensure array[]::new synthetic method (created by JDT) has an associated JMethod
+        JMethod synthMethod = typeMap.get(x.binding);
+        if (synthMethod.getBody() == null) {
+          JMethodBody body = new JMethodBody(synthMethod.getSourceInfo());
+          List<JExpression> dims = new ArrayList<JExpression>();
+          JArrayType arrayType = (JArrayType) synthMethod.getType();
+          JParameter dimParam = synthMethod.getParams().get(0);
+          JExpression dimArgExpr = new JParameterRef(dimParam.getSourceInfo(), dimParam);
+          dims.add(dimArgExpr);
+          for (int i = 1; i < arrayType.getDims(); i++) {
+            dims.add(JAbsentArrayDimension.INSTANCE);
+          }
+          JNewArray newArray = JNewArray.createDims(synthMethod.getSourceInfo(), arrayType, dims);
+          JReturnStatement retArray = new JReturnStatement(synthMethod.getSourceInfo(), newArray);
+          body.getBlock().addStmt(retArray);
+          synthMethod.setBody(body);
+        }
+        push(null); // no qualifier
+      }
+      return true;
+    }
+
+    @Override
+    public boolean visit(LambdaExpression x, BlockScope blockScope) {
+      // Fetch the variables 'captured' by this lambda
+      SyntheticArgumentBinding[] synthArgs = x.outerLocalVariables;
+      // Get the parameter names, captured locals + lambda arguments
+      String paramNames[] = computeCombinedParamNames(x, synthArgs);
+      SourceInfo info = makeSourceInfo(x);
+      // JDT synthesizes a method lambda$n(capture1, capture2, ..., lambda_arg1, lambda_arg2, ...)
+      // Here we create a JMethod from this
+      JMethod lambdaMethod = createSyntheticMethodFromBinding(info, x.binding,
+          paramNames);
+      JMethodBody methodBody = new JMethodBody(info);
+      lambdaMethod.setBody(methodBody);
+      // We need to push this method  on the stack as it introduces a scope, and
+      // expressions in the body need to lookup variable refs like parameters from it
+      pushMethodInfo(new MethodInfo(lambdaMethod, methodBody, x.scope));
+      pushLambdaExpressionLocalsIntoMethodScope(x, synthArgs, lambdaMethod);
+      // now the body of the lambda is processed
+      return true;
+    }
+
+    private void pushLambdaExpressionLocalsIntoMethodScope(LambdaExpression x, SyntheticArgumentBinding[] synthArgs,
+        JMethod lambdaMethod) {
+      Iterator<JParameter> it = lambdaMethod.getParams().iterator();
+      if (synthArgs != null) {
+        for (SyntheticArgumentBinding sa : synthArgs) {
+          curMethod.locals.put(sa.actualOuterLocalVariable, it.next());
+        }
+        for (Argument a : x.arguments) {
+          curMethod.locals.put(a.binding, it.next());
+        }
+      }
+    }
+
+    /**
+     * Calculate the names of all the parameters a lambda method will need, that is, the combination of all
+     * captured locals plus all arguments to the lambda expression.
+     */
+    private String[] computeCombinedParamNames(LambdaExpression x, SyntheticArgumentBinding[] synthArgs) {
+      String[] paramNames;
+      paramNames = new String[x.binding.parameters.length];
+      int numSynthArgs = synthArgs != null ? synthArgs.length : 0;
+      for (int i = 0; i < paramNames.length; i++) {
+        if (i < numSynthArgs) {
+          paramNames[i] = nameForSyntheticArgument(synthArgs[i]);
+        } else {
+          paramNames[i] = nameForArgument(x.arguments, i - numSynthArgs, i);
+        }
+      }
+      return paramNames;
+    }
+
+    private String nameForArgument(Argument[] arguments, int argIndex, int argPosition) {
+      return new String(arguments[argIndex].name) + "_" + argPosition;
+    }
+
+    private String nameForSyntheticArgument(SyntheticArgumentBinding synthArg) {
+      return synthArg.actualOuterLocalVariable != null ?
+          intern(intern(synthArg.actualOuterLocalVariable.name) + "_" + synthArg.resolvedPosition) :
+          intern(synthArg.name);
+    }
+
+    @Override
+    public void endVisit(LambdaExpression x, BlockScope blockScope) {
+
+      /**
+       * Our output of a (args) -> expression_using_locals(locals) looks like this.
+       *
+       * class Enclosing {
+       *
+       *   T lambda$0(locals, args) {...lambda expr }
+       *
+       *   class lambda$0$type implements I {
+       *       ctor([outer], locals) { ... }
+       *       R <SAM lambdaMethod>(args) { return [outer].lambda$0(locals, args); }
+       *   }
+       * }
+       *
+       * And replaces the lambda with new lambda$0$Type([outer this], captured locals...).
+       */
+
+      // The target accepting this lambda is looking for which type? (e.g. ClickHandler, Runnable, etc)
+      TypeBinding binding = x.expectedType();
+      // Find the single abstract method of this interface
+      MethodBinding samBinding = binding.getSingleAbstractMethod(blockScope, false);
+
+      // Lookup the JMethod version
+      JMethod interfaceMethod = typeMap.get(samBinding);
+      // And its JInterface container we must implement
+      JInterfaceType funcType = (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);
+      JConstructor ctor = new JConstructor(info, innerLambdaClass);
+
+      // locals captured by the lambda and saved as fields on the anonymous inner class
+      List<JField> locals = new ArrayList<JField>();
+      SyntheticArgumentBinding[] synthArgs = x.outerLocalVariables;
+
+      // create the constructor for the anonymous inner and return the field used to store the enclosing 'this'
+      // which is needed by the SAM method implementation later
+      JField outerField =
+          createLambdaConstructor(x, info, innerLambdaClass, ctor, locals, synthArgs);
+
+      // the method containing the lambda expression that the anonymous inner class delegates to,
+      // it corresponds directly to the lambda expression itself, produced by JDT as a helper method
+      JMethod lambdaMethod = createLambdaMethod(x);
+
+      // Now that we've added an implementation method for the lambda, we must create the inner class method
+      // that implements the target interface type that delegates to the target lambda method
+      JMethod samMethod = new JMethod(info, interfaceMethod.getName(), innerLambdaClass, interfaceMethod.getType(),
+          false, false, true, interfaceMethod.getAccess());
+      
+      // implements the SAM, e.g. Callback.onCallback(), Runnable.run(), etc
+      createLambdaSamMethod(x, interfaceMethod, info, innerLambdaClass, locals, outerField,
+          lambdaMethod,
+          samMethod);
+
+      ctor.freezeParamTypes();
+      samMethod.freezeParamTypes();
+
+      // replace (x,y,z) -> expr with 'new Lambda(args)'
+      replaceLambdaWithInnerClassAllocation(x, info, innerLambdaClass, ctor, synthArgs);
+      popMethodInfo();
+      // Add the newly generated type
+      newTypes.add(innerLambdaClass);
+    }
+
+    private void createLambdaSamMethod(LambdaExpression x, JMethod interfaceMethod, SourceInfo info,
+        JClassType innerLambdaClass, List<JField> locals, JField outerField, JMethod lambdaMethod,
+        JMethod samMethod) {
+      // The parameters to this method will be the same as the Java interface that must be implemented
+      for (JParameter origParam : interfaceMethod.getParams()) {
+        JType origType = origParam.getType();
+        samMethod.addParam(new JParameter(origParam.getSourceInfo(),
+            origParam.getName(), origType,
+            origParam.isFinal(), origParam.isThis(),
+            samMethod));
+      }
+      // Create a body like void onClick(ClickEvent e) { OuterClass.lambdaMethod(locals, e); }
+      JMethodBody samMethodBody = new JMethodBody(info);
+      // First we create the method call to the outer lambda method
+      JMethodCall samCall = new JMethodCall(info, x.shouldCaptureInstance ?
+          new JFieldRef(info, new JThisRef(info, innerLambdaClass), outerField, innerLambdaClass) :
+          null, lambdaMethod);
+
+      // and add any locals that were storing captured outer variables as arguments to the call first
+      for (JField localField : locals) {
+        samCall.addArg(new JFieldRef(info, new JThisRef(info, innerLambdaClass),
+            localField, innerLambdaClass));
+      }
+
+      // and now we propagate the rest of the actual interface method parameters on the end (e.g. ClickEvent e)
+      for (JParameter param : samMethod.getParams()) {
+        samCall.addArg(new JParameterRef(info, param));
+      }
+
+      // we either add a return statement, or don't, depending on what the interface wants
+      if (samMethod.getType() != JPrimitiveType.VOID) {
+        samMethodBody.getBlock().addStmt(new JReturnStatement(info, samCall));
+      } else {
+        samMethodBody.getBlock().addStmt(samCall.makeStatement());
+      }
+      samMethod.setBody(samMethodBody);
+      innerLambdaClass.addMethod(samMethod);
+    }
+
+    private JField createLambdaConstructor(LambdaExpression x, SourceInfo info,
+        JClassType innerLambdaClass, JConstructor ctor, List<JField> locals,
+        SyntheticArgumentBinding[] synthArgs) {
+      // Create a constructor to accept all "captured" locals
+      // CTor([OuterClassRef ref], capture1, capture2) { }
+      JMethodBody ctorBody = new JMethodBody(info);
+      JField outerField = null;
+      // if this lambda refers to fields on the enclosing instance
+      if (x.shouldCaptureInstance) {
+        // ctor($$outer) { this.$$outer = $$outer; }
+        outerField = createAndBindCapturedLambdaParameter(info, OUTER_LAMBDA_PARAM_NAME,
+            innerLambdaClass.getEnclosingType(),
+            ctor, ctorBody);
+      }
+
+      // Now we add parameters to the ctor
+      // this is the outer instance (if needed), plus any method local variables captured
+      String paramNames[] = new String[x.binding.parameters.length];
+      int numSynthArgs = synthArgs != null ? synthArgs.length : 0;
+
+      for (int i = 0; i < paramNames.length; i++) {
+        // Setup params, fields, and ctor assignments for the outer captured vars
+        if (i < numSynthArgs) {
+          paramNames[i] = nameForSyntheticArgument(synthArgs[i]);
+          JType captureType = typeMap.get(synthArgs[i].type);
+          // adds ctor(..., param, ...) { ...this.param = param }
+          JField captureField = createAndBindCapturedLambdaParameter(info, paramNames[i], captureType, ctor, ctorBody);
+          locals.add(captureField);
+        } else {
+          // Record the names of the actual closure arguments, e.g. (ClickEvent x) -> expr will be 'x'
+          paramNames[i] = nameForArgument(x.arguments, i - numSynthArgs, i);
+        }
+      }
+
+      ctor.setBody(ctorBody);
+      innerLambdaClass.addMethod(ctor);
+      return outerField;
+    }
+
+    private JMethod createLambdaMethod(LambdaExpression x) {
+      // First let's get that synthetic method we created in the visit() call on the containing class?
+      JMethod lambdaMethod = curMethod.method;
+
+      // And pop off the body nodes of the LambdaExpression that was processed as children
+      // Deal with any boxing/unboxing needed
+      JNode node = pop();
+      if (node instanceof JExpression) {
+        node = simplify((JExpression) node, (Expression) x.body);
+      }
+
+      JMethodBody body = (JMethodBody) curMethod.method.getBody();
+      // and copy those nodes into the body of our synthetic method
+      JStatement lambdaStatement = node instanceof JExpression ?
+          (((JExpression) node).getType() == JPrimitiveType.VOID ? ((JExpression) node).makeStatement() :
+              new JReturnStatement(node.getSourceInfo(), (JExpression) node)) : (JStatement) node;
+      body.getBlock().addStmt(lambdaStatement);
+      lambdaMethod.setBody(body);
+      return lambdaMethod;
+    }
+
+    private void replaceLambdaWithInnerClassAllocation(LambdaExpression x, SourceInfo info,
+        JClassType innerLambdaClass, JConstructor ctor, SyntheticArgumentBinding[] synthArgs) {
+      // Finally, we replace the LambdaExpression with new InnerLambdaClass(this, local1, local2, ...);
+      JNewInstance allocLambda = new JNewInstance(info, ctor, innerLambdaClass);
+      // only pass 'this' if lambda refers to fields on outer class
+      if (x.shouldCaptureInstance) {
+        allocLambda.addArg(new JThisRef(info, innerLambdaClass.getEnclosingType()));
+      }
+      for (SyntheticArgumentBinding sa : synthArgs) {
+        allocLambda.addArg(makeLocalRef(info, sa.actualOuterLocalVariable, methodStack.peek()));
+      }
+      // put the result on the stack, and pop out synthetic method from the scope
+      push(allocLambda);
+    }
+
+    private JField createAndBindCapturedLambdaParameter(SourceInfo info,
+        String paramName, JType captureType,
+        JConstructor ctor, JMethodBody ctorBody) {
+      JField paramField;
+      JParameter param = createLambdaParameter(info, paramName, captureType, ctor);
+
+      // Plus a field to store it
+      paramField = createLambdaField(info, paramName, captureType, ctor.getEnclosingType());
+
+      // Now add the initializers to bind the param to field
+      // this.paramField = param
+      JThisRef thisRef = new JThisRef(info, ctor.getEnclosingType());
+      JFieldRef paramFieldRef = new JFieldRef(info, thisRef, paramField, ctor.getEnclosingType());
+      JParameterRef paramRef = new JParameterRef(info, param);
+      ctorBody.getBlock().addStmt(
+          new JBinaryOperation(info, paramFieldRef.getType(),
+              JBinaryOperator.ASG,
+              paramFieldRef, paramRef).makeStatement());
+      return paramField;
+    }
+
+    private JField createLambdaField(SourceInfo info, String fieldName, JType fieldType,
+        JClassType enclosingType) {
+      JField outerField;
+      outerField = new JField(info, fieldName, enclosingType, fieldType, false, Disposition.NONE);
+      enclosingType.addField(outerField);
+      return outerField;
+    }
+
+    private JParameter createLambdaParameter(SourceInfo info, String paramName, JType paramType,
+        JConstructor ctor) {
+      JParameter outerParam = new JParameter(info, paramName, paramType,
+          true, false, ctor);
+      ctor.addParam(outerParam);
+      return outerParam;
+    }
+
+    private JClassType createInnerClass(String name, FunctionalExpression x, JInterfaceType funcType, SourceInfo info) {
+      JClassType innerLambdaClass = new JClassType(info, name + "$Type", false, true);
+      innerLambdaClass.setEnclosingType((JDeclaredType) typeMap.get(x.binding.declaringClass));
+      innerLambdaClass.addImplements(funcType);
+      innerLambdaClass.setSuperClass(javaLangObject);
+
+      createSyntheticMethod(info, CLINIT_NAME, innerLambdaClass, JPrimitiveType.VOID, false, true, true,
+          AccessModifier.PRIVATE);
+
+      createSyntheticMethod(info, INIT_NAME, innerLambdaClass, JPrimitiveType.VOID, false, false, true,
+          AccessModifier.PRIVATE);
+
+      // Add a getClass() implementation for all non-Object classes.
+      createSyntheticMethod(info, "getClass", innerLambdaClass, javaLangClass, false, false, false,
+          AccessModifier.PUBLIC, new JReturnStatement(info, new JClassLiteral(info, innerLambdaClass)));
+
+      return innerLambdaClass;
+    }
+
+      @Override
     public void endVisit(LocalDeclaration x, BlockScope scope) {
       try {
         SourceInfo info = makeSourceInfo(x);
@@ -1301,6 +1636,253 @@
       }
     }
 
+    private Map<String, JClassType> lambdaNameToInnerLambdaType = Maps.newHashMap();
+
+    @Override
+    public void endVisit(ReferenceExpression x, BlockScope blockScope) {
+      /**
+       * Converts an expression like foo(qualifier::someMethod) into
+       *
+       * class Enclosing {
+       *
+       *   [static] T someMethod(locals, args) {...lambda expr }
+       *
+       *   class lambda$someMethodType implements I {
+       *       ctor([qualifier]) { ... }
+       *       R <SAM lambdaMethod>(args) { return [outer]someMethod(args); }
+       *   }
+       * }
+       *
+       * and replaces qualifier::someMethod with new lambda$someMethodType([outer this])
+       *
+       * [x] denotes optional, depending on context of whether outer this scope is needed.
+       */
+
+      // Calculate what type this reference is going to bind to, and what single abstract method
+      TypeBinding binding = x.expectedType();
+      MethodBinding samBinding = binding.getSingleAbstractMethod(blockScope, false);
+
+      // Get the interface method is binds to
+      JMethod interfaceMethod = typeMap.get(samBinding);
+      JInterfaceType funcType = (JInterfaceType) typeMap.get(binding);
+      SourceInfo info = makeSourceInfo(x);
+
+      // Get the method that the Type::method is actually referring to
+      MethodBinding referredMethodBinding = x.binding;
+      if (referredMethodBinding instanceof SyntheticMethodBinding) {
+        SyntheticMethodBinding synthRefMethodBinding = (SyntheticMethodBinding) referredMethodBinding;
+        if (synthRefMethodBinding.targetMethod != null) {
+          // generated in cases were a private method in an outer class needed to be called
+          // e.g. outer.access$0 calls some outer.private_method
+          referredMethodBinding = synthRefMethodBinding.targetMethod;
+          // privateCtor::new generates overloaded <init> references with fake args that delegate
+          // to the real ctor (JDT WTF!). Will we ever need to go deeper?
+          if (synthRefMethodBinding.fakePaddedParameters != 0
+              && synthRefMethodBinding.targetMethod instanceof SyntheticMethodBinding) {
+            referredMethodBinding = ((SyntheticMethodBinding) referredMethodBinding).targetMethod;
+          }
+        }
+      }
+      JMethod referredMethod = typeMap.get(referredMethodBinding);
+      boolean haveReceiver = false;
+      try {
+        haveReceiver = (Boolean) haveReceiverField.get(x);
+      } catch (IllegalAccessException e) {
+        throw translateException(x, e);
+      }
+
+      // Constructors and overloading mean we need generate unique names
+      String lambdaName = GenerateJavaScriptAST.classNameForMethodReference(funcType,
+          referredMethod,
+          haveReceiver);
+
+      // Create an inner class to hold the implementation of the interface
+      JClassType innerLambdaClass = lambdaNameToInnerLambdaType.get(lambdaName);
+      List<JExpression> enclosingThisRefs = new ArrayList<JExpression>();
+
+      if (innerLambdaClass == null) {
+        innerLambdaClass = createInnerClass(lambdaName, x, funcType, info);
+        lambdaNameToInnerLambdaType.put(lambdaName, innerLambdaClass);
+
+        JConstructor ctor = new JConstructor(info, innerLambdaClass);
+
+        JMethodBody ctorBody = new JMethodBody(info);
+        JThisRef thisRef = new JThisRef(info, innerLambdaClass);
+        JExpression instance = null;
+
+        List<JField> enclosingInstanceFields = new ArrayList<JField>();
+        // If we have a qualifier instance, we have to stash it in the constructor
+        if (haveReceiver) {
+          // this.$$outer = $$outer
+          JField outerField = createAndBindCapturedLambdaParameter(info, OUTER_LAMBDA_PARAM_NAME,
+              innerLambdaClass.getEnclosingType(), ctor, ctorBody);
+          instance = new JFieldRef(info,
+              new JThisRef(info, innerLambdaClass), outerField, innerLambdaClass);
+        } else if (referredMethod instanceof JConstructor) {
+          // the method we are invoking is a constructor and may need enclosing instances passed to it
+          // For example, an class Foo { class Inner { Inner(int x) { } } } needs
+          // it's constructor invoked with an enclosing instance, Inner::new
+          // Java8 doesn't allow the qualifified case, e.g. x.new Foo() -> x.Foo::new
+          ReferenceBinding targetBinding = referredMethodBinding.declaringClass;
+          if (JdtUtil.isInnerClass(targetBinding)) {
+            for (ReferenceBinding argType : targetBinding.syntheticEnclosingInstanceTypes()) {
+              argType = (ReferenceBinding) argType.erasure();
+              JExpression enclosingThisRef = makeThisReference(info, argType, false, blockScope);
+              JField enclosingInstance = createAndBindCapturedLambdaParameter(info,
+                  String.valueOf(argType.readableName()).replace('.', '_'),
+                  enclosingThisRef.getType(), ctor, ctorBody);
+              enclosingInstanceFields.add(enclosingInstance);
+              enclosingThisRefs.add(enclosingThisRef);
+            }
+          }
+        }
+        ctor.setBody(ctorBody);
+        innerLambdaClass.addMethod(ctor);
+
+        // Create an implementation of the target interface that invokes the method referred to
+        // void onClick(ClickEvent e) { outer.referredMethod(e); }
+        JMethod samMethod = new JMethod(info, interfaceMethod.getName(),
+            innerLambdaClass, interfaceMethod.getType(),
+            false, false, true, interfaceMethod.getAccess());
+        for (JParameter origParam : interfaceMethod.getParams()) {
+          JType origType = origParam.getType();
+          samMethod.addParam(new JParameter(origParam.getSourceInfo(),
+              origParam.getName(), origType,
+              origParam.isFinal(), origParam.isThis(),
+              samMethod));
+        }
+        JMethodBody samMethodBody = new JMethodBody(info);
+
+        Iterator<JParameter> paramIt = samMethod.getParams().iterator();
+        // here's where it gets tricky. A method can have an implicit qualifier, e.g.
+        // String::compareToIgnoreCase, it's non-static, it only has one argument, but it binds to Comparator<T>
+        // The first argument serves as the qualifier, so for example, the method dispatch looks like this
+        // int compare(T a, T b) { a.compareTo(b); }
+        if (!haveReceiver && !referredMethod.isStatic() && instance == null &&
+            samMethod.getParams().size() == referredMethod.getParams().size() + 1) {
+          // the instance qualifier is the first parameter in this case.
+          instance = new JParameterRef(info, paramIt.next());
+        }
+        JMethodCall samCall = null;
+
+        if (referredMethod.isConstructor()) {
+          // Constructors must be invoked with JNewInstance
+          samCall = new JNewInstance(info, (JConstructor) referredMethod,
+              referredMethod.getEnclosingType());
+          for (JField enclosingInstance : enclosingInstanceFields) {
+            samCall.addArg(new JFieldRef(enclosingInstance.getSourceInfo(), thisRef,
+                enclosingInstance, innerLambdaClass));
+          }
+        } else {
+          // For static methods, instance will be null
+          samCall = new JMethodCall(info, instance, referredMethod);
+          // if super::method, we need static dispatch
+          if (x.lhs instanceof SuperReference) {
+            samCall.setStaticDispatchOnly();
+          }
+        }
+
+        // Add the rest of the parameters from the interface method to methodcall
+        // boxing or unboxing and dealing with varargs
+        int paramNumber = 0;
+
+        // need to build up an array of passed parameters if we have varargs
+        List<JExpression> varArgInitializers = null;
+        int varArg = x.binding.parameters.length - 1;
+
+        // interface Foo { m(int x, int y); } bound to reference foo(int... args)
+        // if varargs and incoming param is not already a var-arg, we'll need to convert
+        // trailing args of the target interface into an array
+        if (x.binding.isVarargs() && !samBinding.parameters[varArg].isArrayType()) {
+          varArgInitializers = new ArrayList<JExpression>();
+        }
+
+        while (paramIt.hasNext()) {
+          JParameter param = paramIt.next();
+          JExpression paramExpr = new JParameterRef(info, param);
+          // params may need to be boxed or unboxed
+          TypeBinding destParam = null;
+          // if it is not the trailing param or varargs, or interface method is already varargs
+          if (varArgInitializers == null || !x.binding.isVarargs() || (paramNumber < varArg)) {
+            destParam = x.binding.parameters[paramNumber];
+            paramExpr = boxOrUnboxExpression(paramExpr, samBinding.parameters[paramNumber],
+                destParam);
+            samCall.addArg(paramExpr);
+          } else if (!samBinding.parameters[paramNumber].isArrayType()) {
+            // else add trailing parameters to var-args initializer list for an array
+            destParam = x.binding.parameters[varArg].leafComponentType();
+            paramExpr = boxOrUnboxExpression(paramExpr, samBinding.parameters[paramNumber],
+                destParam);
+            varArgInitializers.add(paramExpr);
+          }
+          paramNumber++;
+        }
+
+        // add trailing new T[] { initializers } var-arg array
+        if (varArgInitializers != null) {
+          JArrayType lastParamType = (JArrayType) typeMap.get(x.binding.parameters[x.binding.parameters.length - 1]);
+          JNewArray newArray = JNewArray.createInitializers(info, lastParamType, varArgInitializers);
+          samCall.addArg(newArray);
+        }
+
+        if (samMethod.getType() != JPrimitiveType.VOID) {
+          JExpression samExpression = boxOrUnboxExpression(samCall, x.binding.returnType, samBinding.returnType);
+          samMethodBody.getBlock().addStmt(new JReturnStatement(info, simplify(samExpression, x)));
+        } else {
+          samMethodBody.getBlock().addStmt(samCall.makeStatement());
+        }
+        samMethod.setBody(samMethodBody);
+        innerLambdaClass.addMethod(samMethod);
+        ctor.freezeParamTypes();
+        samMethod.freezeParamTypes();
+      }
+
+      JConstructor lambdaCtor = null;
+      for (JMethod method : innerLambdaClass.getMethods()) {
+        if (method instanceof JConstructor) {
+          lambdaCtor = (JConstructor) method;
+          break;
+        }
+      }
+
+      assert lambdaCtor != null;
+
+      // Replace the ReferenceExpression qualifier::method with new lambdaType(qualifier)
+      JNewInstance allocLambda = new JNewInstance(info, lambdaCtor, innerLambdaClass);
+      JExpression qualifier = (JExpression) pop();
+      if (haveReceiver) {
+        // pop qualifier from stack
+        allocLambda.addArg(qualifier);
+      } else {
+        // you can't simultaneously have a qualifier, and have enclosing inner class refs
+        // because Java8 won't allow a qualified constructor method reference, e.g. x.Foo::new
+        for (JExpression enclosingRef : enclosingThisRefs) {
+          allocLambda.addArg(enclosingRef);
+        }
+      }
+
+      push(allocLambda);
+      newTypes.add(innerLambdaClass);
+    }
+
+    private JExpression boxOrUnboxExpression(JExpression expr, TypeBinding fromType,
+        TypeBinding toType) {
+      if (fromType == TypeBinding.VOID || toType == TypeBinding.VOID) {
+        return expr;
+      }
+      int implicitConversion;
+      if (fromType.isBaseType() && !toType.isBaseType()) {
+        implicitConversion = (fromType.id & TypeIds.IMPLICIT_CONVERSION_MASK) << 4;
+        implicitConversion = implicitConversion | TypeIds.BOXING;
+        expr = box(expr, implicitConversion);
+      } else if (!fromType.isBaseType() && toType.isBaseType()) {
+        implicitConversion = (toType.id & TypeIds.IMPLICIT_CONVERSION_MASK) << 4;
+        implicitConversion = implicitConversion | TypeIds.UNBOXING;
+        expr = unbox(expr, implicitConversion);
+      }
+      return expr;
+    }
+
     @Override
     public void endVisit(ReturnStatement x, BlockScope scope) {
       try {
@@ -1353,7 +1935,7 @@
     @Override
     public void endVisit(SuperReference x, BlockScope scope) {
       try {
-        assert (typeMap.get(x.resolvedType) == curClass.classType.getSuperClass());
+        assert (typeMap.get(x.resolvedType) == curClass.getClassOrInterface().getSuperClass());
         // Super refs can be modeled as a this ref.
         push(makeThisRef(makeSourceInfo(x)));
       } catch (Throwable e) {
@@ -1394,7 +1976,7 @@
     @Override
     public void endVisit(ThisReference x, BlockScope scope) {
       try {
-        assert (typeMap.get(x.resolvedType) == curClass.classType);
+        assert typeMap.get(x.resolvedType) == curClass.getClassOrInterface();
         push(makeThisRef(makeSourceInfo(x)));
       } catch (Throwable e) {
         throw translateException(x, e);
@@ -2285,8 +2867,8 @@
       return new JFieldRef(info, makeThisRef(info), field, curClass.classType);
     }
 
-    private JExpression makeLocalRef(SourceInfo info, LocalVariableBinding b) {
-      JVariable variable = curMethod.locals.get(b);
+    private JExpression makeLocalRef(SourceInfo info, LocalVariableBinding b, MethodInfo cur) {
+      JVariable variable = cur.locals.get(b);
       assert variable != null;
       if (variable instanceof JLocal) {
         return new JLocalRef(info, (JLocal) variable);
@@ -2295,8 +2877,12 @@
       }
     }
 
+    private JExpression makeLocalRef(SourceInfo info, LocalVariableBinding b) {
+      return makeLocalRef(info, b, curMethod);
+    }
+
     private JThisRef makeThisRef(SourceInfo info) {
-      return new JThisRef(info, curClass.classType);
+      return new JThisRef(info, curClass.getClassOrInterface());
     }
 
     private JExpression makeThisReference(SourceInfo info, ReferenceBinding targetType,
@@ -2846,6 +3432,10 @@
       this.typeDecl = x;
       this.scope = x.scope;
     }
+
+    public JDeclaredType getClassOrInterface() {
+      return classType == null ? type : classType;
+    }
   }
 
   static class CudInfo {
@@ -2887,6 +3477,7 @@
    * Reflective access to {@link ForeachStatement#collectionElementType}.
    */
   private static final Field collectionElementTypeField;
+  private static final Field haveReceiverField;
 
   private static final char[] CREATE_VALUE_OF_MAP = "createValueOfMap".toCharArray();
   private static final char[] HAS_NEXT = "hasNext".toCharArray();
@@ -2908,6 +3499,14 @@
       throw new RuntimeException(
           "Unexpectedly unable to access ForeachStatement.collectionElementType via reflection", e);
     }
+
+    try {
+      haveReceiverField = ReferenceExpression.class.getDeclaredField("haveReceiver");
+      haveReceiverField.setAccessible(true);
+    } catch (Exception e) {
+      throw new RuntimeException(
+          "Unexpectedly unable to access ReferenceExpression.haveReceiver via reflection", e);
+    }
   }
 
   /**
@@ -3011,7 +3610,6 @@
   static JMethod SAFE_CLOSE_METHOD = JMethod.getExternalizedMethod("com.google.gwt.lang.Exceptions",
       "safeClose(Ljava/lang/AutoCloseable;Ljava/lang/Throwable;)Ljava/lang/Throwable;", true);
 
-
   /**
    * Builds all the GWT AST nodes that correspond to one Java source file.
    *
@@ -3141,12 +3739,12 @@
        * is always in slot 1.
        */
       assert type.getMethods().size() == 0;
-      createSyntheticMethod(info, "$clinit", type, JPrimitiveType.VOID, false, true, true,
+      createSyntheticMethod(info, CLINIT_NAME, type, JPrimitiveType.VOID, false, true, true,
           AccessModifier.PRIVATE);
 
       if (type instanceof JClassType) {
         assert type.getMethods().size() == 1;
-        createSyntheticMethod(info, "$init", type, JPrimitiveType.VOID, false, false, true,
+        createSyntheticMethod(info, INIT_NAME, type, JPrimitiveType.VOID, false, false, true,
             AccessModifier.PRIVATE);
 
         // Add a getClass() implementation for all non-Object, non-String classes.
@@ -3349,12 +3947,16 @@
   }
 
   private JMethod createSyntheticMethod(SourceInfo info, String name, JDeclaredType enclosingType,
-      JType returnType, boolean isAbstract, boolean isStatic, boolean isFinal, AccessModifier access) {
+      JType returnType, boolean isAbstract, boolean isStatic, boolean isFinal, AccessModifier access, JStatement ... statements) {
     JMethod method =
         new JMethod(info, name, enclosingType, returnType, isAbstract, isStatic, isFinal, access);
     method.freezeParamTypes();
     method.setSynthetic();
-    method.setBody(new JMethodBody(info));
+    JMethodBody body = new JMethodBody(info);
+    for (JStatement statement : statements) {
+      body.getBlock().addStmt(statement);
+    }
+    method.setBody(body);
     enclosingType.addMethod(method);
     return method;
   }
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/MakeCallsStatic.java b/dev/core/src/com/google/gwt/dev/jjs/impl/MakeCallsStatic.java
index b9d7372..f3b46da 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/MakeCallsStatic.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/MakeCallsStatic.java
@@ -19,7 +19,6 @@
 import com.google.gwt.dev.jjs.SourceInfo;
 import com.google.gwt.dev.jjs.ast.Context;
 import com.google.gwt.dev.jjs.ast.JAbstractMethodBody;
-import com.google.gwt.dev.jjs.ast.JClassType;
 import com.google.gwt.dev.jjs.ast.JConstructor;
 import com.google.gwt.dev.jjs.ast.JDeclaredType;
 import com.google.gwt.dev.jjs.ast.JExpression;
@@ -151,14 +150,14 @@
     @Override
     public boolean visit(JMethod x, Context ctx) {
       // Let's do it!
-      JClassType enclosingType = (JClassType) x.getEnclosingType();
+      JDeclaredType enclosingType = (JDeclaredType) x.getEnclosingType();
       JType returnType = x.getType();
       SourceInfo sourceInfo = x.getSourceInfo().makeChild();
       int myIndexInClass = enclosingType.getMethods().indexOf(x);
       assert (myIndexInClass > 0);
 
       // Create the new static method
-      String newName = "$" + x.getName();
+      String newName = getStaticMethodName(x);
 
       /*
        * Don't use the JProgram helper because it auto-adds the new method to
@@ -241,6 +240,10 @@
     }
   }
 
+  private static String getStaticMethodName(JMethod x) {
+    return "$" + x.getName();
+  }
+
   /**
    * Look for any places where instance methods are called in a static manner.
    * Record this fact so we can create static dispatch implementations.
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/Pruner.java b/dev/core/src/com/google/gwt/dev/jjs/impl/Pruner.java
index 8f41e26..5dddc53 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/Pruner.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/Pruner.java
@@ -409,7 +409,7 @@
         if (!isInstantiated || !referencedNonTypes.contains(method)) {
           methodWasRemoved(method);
           type.removeMethod(i);
-          assert program.instanceMethodForStaticImpl(method) == null;
+          program.removeStaticImplMapping(method);
           madeChanges();
           --i;
         }
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 1292edc..59b38cb 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
@@ -44,6 +44,7 @@
 import org.eclipse.jdt.internal.compiler.lookup.ReferenceBinding;
 import org.eclipse.jdt.internal.compiler.lookup.SourceTypeBinding;
 import org.eclipse.jdt.internal.compiler.lookup.SyntheticArgumentBinding;
+import org.eclipse.jdt.internal.compiler.lookup.SyntheticMethodBinding;
 import org.eclipse.jdt.internal.compiler.lookup.TypeBinding;
 import org.eclipse.jdt.internal.compiler.problem.AbortCompilation;
 
@@ -110,7 +111,7 @@
       } else {
         method = createMethod(SourceOrigin.UNKNOWN, binding, null);
       }
-      assert method.isExternal();
+      assert binding instanceof SyntheticMethodBinding || method.isExternal();
       methods.put(key, method);
     }
     return method;
@@ -120,6 +121,7 @@
     binding = binding.erasure();
     String key = signature(binding);
     JReferenceType sourceType = sourceTypes.get(key);
+
     if (sourceType != null) {
       assert !sourceType.isExternal();
       return sourceType;
@@ -170,9 +172,8 @@
          */
       }
       // Emulate clinit method for super clinit calls.
-      JMethod clinit =
-          new JMethod(SourceOrigin.UNKNOWN, "$clinit", declType, JPrimitiveType.VOID, false, true,
-              true, AccessModifier.PRIVATE);
+      JMethod clinit = new JMethod(SourceOrigin.UNKNOWN, GwtAstBuilder.CLINIT_NAME, declType,
+          JPrimitiveType.VOID, false, true, true, AccessModifier.PRIVATE);
       clinit.freezeParamTypes();
       clinit.setSynthetic();
       declType.addMethod(clinit);
diff --git a/dev/core/src/com/google/gwt/dev/shell/rewrite/ForceClassVersion15.java b/dev/core/src/com/google/gwt/dev/shell/rewrite/ForceClassVersion15.java
index 084a751..5a0f0f4 100644
--- a/dev/core/src/com/google/gwt/dev/shell/rewrite/ForceClassVersion15.java
+++ b/dev/core/src/com/google/gwt/dev/shell/rewrite/ForceClassVersion15.java
@@ -31,7 +31,7 @@
   @Override
   public void visit(final int version, final int access, final String name,
       final String signature, final String superName, final String[] interfaces) {
-    assert (version >= Opcodes.V1_5 && version <= Opcodes.V1_6);
+    assert (version >= Opcodes.V1_5 && version <= Opcodes.V1_8);
     super.visit(Opcodes.V1_5, access, name, signature, superName, interfaces);
   }
 }
diff --git a/dev/core/src/com/google/gwt/dev/shell/rewrite/HostedModeClassRewriter.java b/dev/core/src/com/google/gwt/dev/shell/rewrite/HostedModeClassRewriter.java
index 66e3712..65c50d4 100644
--- a/dev/core/src/com/google/gwt/dev/shell/rewrite/HostedModeClassRewriter.java
+++ b/dev/core/src/com/google/gwt/dev/shell/rewrite/HostedModeClassRewriter.java
@@ -245,7 +245,8 @@
 
     v = new RewriteJsniMethods(v, anonymousClassMap);
 
-    if (Double.parseDouble(System.getProperty("java.class.version")) < Opcodes.V1_6) {
+    if (Double.parseDouble(System.getProperty("java.class.version")) < Opcodes.V1_8) {
+      // TODO(cromwellian) implement Retrolambda?
       v = new ForceClassVersion15(v);
     }
 
diff --git a/dev/core/src/com/google/gwt/dev/util/arg/SourceLevel.java b/dev/core/src/com/google/gwt/dev/util/arg/SourceLevel.java
index 7c82554..ca3a955 100644
--- a/dev/core/src/com/google/gwt/dev/util/arg/SourceLevel.java
+++ b/dev/core/src/com/google/gwt/dev/util/arg/SourceLevel.java
@@ -27,7 +27,8 @@
 public enum SourceLevel {
   // Source levels must appear in ascending order for the default setting logic to work.
   JAVA6("1.6", "6"),
-  JAVA7("1.7", "7");
+  JAVA7("1.7", "7"),
+  JAVA8("1.8", "8");
 
   /**
    * The default java sourceLevel.
diff --git a/dev/core/test/com/google/gwt/dev/jjs/impl/AdditionalTypeProviderDelegateTest.java b/dev/core/test/com/google/gwt/dev/jjs/impl/AdditionalTypeProviderDelegateTest.java
index c665cab..a553430 100644
--- a/dev/core/test/com/google/gwt/dev/jjs/impl/AdditionalTypeProviderDelegateTest.java
+++ b/dev/core/test/com/google/gwt/dev/jjs/impl/AdditionalTypeProviderDelegateTest.java
@@ -137,7 +137,7 @@
   }
 
   public void testInsertedClass() throws UnableToCompleteException {
-    JProgram program = compileSnippet("void", "new test.B().func();");
+    JProgram program = compileSnippet("void", "new test.B().func();", true);
 
     // Make sure the compiled classes appeared.
     JDeclaredType bType = findDeclaredType(program, "test.B");
@@ -147,7 +147,7 @@
   }
 
   public void testInsertedClass2() throws UnableToCompleteException {
-    JProgram program = compileSnippet("void", "new test.B1().func();");
+    JProgram program = compileSnippet("void", "new test.B1().func();", true);
 
     // Make sure the compiled classes appeared.
     JDeclaredType bType = findDeclaredType(program, "test.B1");
@@ -158,7 +158,7 @@
 
   // Make sure regular code not using the AdditionalTypeProviderDelegate still works.
   public void testSimpleParse() throws UnableToCompleteException {
-    JProgram program = compileSnippet("void", "new test.A();");
+    JProgram program = compileSnippet("void", "new test.A();", true);
     JDeclaredType goodClassType = findDeclaredType(program, "test.A");
     assertNotNull("Unknown class A", goodClassType);
   }
@@ -181,7 +181,7 @@
       }
     });
     try {
-      compileSnippet("void", "new test.C();");
+      compileSnippet("void", "new test.C();", true);
       fail("Shouldn't have compiled");
     } catch (UnableToCompleteException expected) {
     }
@@ -205,7 +205,7 @@
       }
     });
     try {
-      compileSnippet("void", "new test.D();");
+      compileSnippet("void", "new test.D();", true);
       fail("Shouldn't have compiled");
     } catch (UnableToCompleteException expected) {
     }
diff --git a/dev/core/test/com/google/gwt/dev/jjs/impl/ComputeExhaustiveCastabilityInformationTest.java b/dev/core/test/com/google/gwt/dev/jjs/impl/ComputeExhaustiveCastabilityInformationTest.java
index 89194b1..0d36e24 100644
--- a/dev/core/test/com/google/gwt/dev/jjs/impl/ComputeExhaustiveCastabilityInformationTest.java
+++ b/dev/core/test/com/google/gwt/dev/jjs/impl/ComputeExhaustiveCastabilityInformationTest.java
@@ -40,7 +40,7 @@
     registerCompilableResources();
 
     // Compiles and gets a reference to the String[] type.
-    JProgram program = compileSnippet("void", "", "", false);
+    JProgram program = compileSnippet("void", "", "", false, true);
     ComputeExhaustiveCastabilityInformation.exec(program, false);
     JDeclaredType stringType = program.getIndexedType("String");
     JArrayType stringArrayType = program.getTypeArray(stringType);
@@ -84,7 +84,7 @@
 
   private void assertSourceCastsToTargets(String sourceTypeName,
       Set<String> expectedTargetTypeNames) throws UnableToCompleteException {
-    JProgram program = compileSnippet("void", "", "", false);
+    JProgram program = compileSnippet("void", "", "", false, true);
     ComputeExhaustiveCastabilityInformation.exec(program, false);
     JDeclaredType sourceType = program.getIndexedType(sourceTypeName);
     assertSourceCastsToTargets(program, sourceType, expectedTargetTypeNames);
diff --git a/dev/core/test/com/google/gwt/dev/jjs/impl/ControlFlowAnalyzerTest.java b/dev/core/test/com/google/gwt/dev/jjs/impl/ControlFlowAnalyzerTest.java
index 3c93fab..b320b66 100644
--- a/dev/core/test/com/google/gwt/dev/jjs/impl/ControlFlowAnalyzerTest.java
+++ b/dev/core/test/com/google/gwt/dev/jjs/impl/ControlFlowAnalyzerTest.java
@@ -261,7 +261,7 @@
 
   private Result analyzeSnippet(String codeSnippet)
       throws UnableToCompleteException {
-    JProgram program = compileSnippet("void", codeSnippet);
+    JProgram program = compileSnippet("void", codeSnippet, true);
     ControlFlowAnalyzer cfa = new ControlFlowAnalyzer(program);
     cfa.traverseFrom(findMainMethod(program));
     return new Result(program, cfa);
diff --git a/dev/core/test/com/google/gwt/dev/jjs/impl/ExpressionAnalyzerTest.java b/dev/core/test/com/google/gwt/dev/jjs/impl/ExpressionAnalyzerTest.java
index c0498fc..0372b81 100644
--- a/dev/core/test/com/google/gwt/dev/jjs/impl/ExpressionAnalyzerTest.java
+++ b/dev/core/test/com/google/gwt/dev/jjs/impl/ExpressionAnalyzerTest.java
@@ -174,7 +174,7 @@
 
   private Result analyzeExpression(String type, String expression)
       throws UnableToCompleteException {
-    JProgram program = compileSnippet(type, "return " + expression + ";");
+    JProgram program = compileSnippet(type, "return " + expression + ";", true);
     ExpressionAnalyzer ea = new ExpressionAnalyzer();
     JMethod mainMethod = findMainMethod(program);
     JMethodBody body = (JMethodBody) mainMethod.getBody();
diff --git a/dev/core/test/com/google/gwt/dev/jjs/impl/JEnumTest.java b/dev/core/test/com/google/gwt/dev/jjs/impl/JEnumTest.java
index f35b727..65859fc 100644
--- a/dev/core/test/com/google/gwt/dev/jjs/impl/JEnumTest.java
+++ b/dev/core/test/com/google/gwt/dev/jjs/impl/JEnumTest.java
@@ -46,7 +46,7 @@
   }
 
   public void testBasic() throws UnableToCompleteException {
-    JProgram program = compileSnippet("void", "test.Simple.FOO.toString();");
+    JProgram program = compileSnippet("void", "test.Simple.FOO.toString();", true);
 
     JDeclaredType simple = findDeclaredType(program, "test.Simple");
     Set<JEnumField> enumFields = new HashSet<JEnumField>();
diff --git a/dev/core/test/com/google/gwt/dev/jjs/impl/JJSTestBase.java b/dev/core/test/com/google/gwt/dev/jjs/impl/JJSTestBase.java
index 615fa0f..c3291ac 100644
--- a/dev/core/test/com/google/gwt/dev/jjs/impl/JJSTestBase.java
+++ b/dev/core/test/com/google/gwt/dev/jjs/impl/JJSTestBase.java
@@ -26,14 +26,20 @@
 import com.google.gwt.dev.javac.testing.impl.MockResourceOracle;
 import com.google.gwt.dev.jjs.JavaAstConstructor;
 import com.google.gwt.dev.jjs.ast.Context;
+import com.google.gwt.dev.jjs.ast.JBlock;
 import com.google.gwt.dev.jjs.ast.JDeclaredType;
+import com.google.gwt.dev.jjs.ast.JExpression;
 import com.google.gwt.dev.jjs.ast.JField;
 import com.google.gwt.dev.jjs.ast.JLocal;
 import com.google.gwt.dev.jjs.ast.JMethod;
+import com.google.gwt.dev.jjs.ast.JMethodBody;
 import com.google.gwt.dev.jjs.ast.JPrimitiveType;
 import com.google.gwt.dev.jjs.ast.JProgram;
+import com.google.gwt.dev.jjs.ast.JReferenceType;
+import com.google.gwt.dev.jjs.ast.JReturnStatement;
 import com.google.gwt.dev.jjs.ast.JType;
 import com.google.gwt.dev.jjs.ast.JVisitor;
+import com.google.gwt.dev.resource.Resource;
 import com.google.gwt.dev.util.arg.SourceLevel;
 import com.google.gwt.dev.util.log.AbstractTreeLogger;
 import com.google.gwt.dev.util.log.PrintWriterTreeLogger;
@@ -209,7 +215,7 @@
   /**
    * Adds a snippet of code, for example a field declaration, to the class that
    * encloses the snippet subsequently passed to
-   * {@link #compileSnippet(String, String)}.
+   * {@link #compileSnippet(String, String, boolean)}.
    */
   protected void addSnippetClassDecl(String...decl) {
     snippetClassDecls.add(Joiner.on("\n").join(decl));
@@ -217,7 +223,7 @@
 
   /**
    * Adds an import statement for any code subsequently passed to
-   * {@link #compileSnippet(String, String)}.
+   * {@link #compileSnippet(String, String, boolean)}.
    */
   protected void addSnippetImport(String typeName) {
     snippetImports.add(typeName);
@@ -226,28 +232,41 @@
   /**
    * Returns the program that results from compiling the specified code snippet
    * as the body of an entry point method.
-   *
-   * @param returnType the return type of the method to compile; use "void" if
+   *  @param returnType the return type of the method to compile; use "void" if
    *          the code snippet has no return statement
    * @param codeSnippet the body of the entry method
    */
   protected JProgram compileSnippet(final String returnType,
       final String codeSnippet) throws UnableToCompleteException {
-    return compileSnippet(returnType, "", codeSnippet, true);
+    return compileSnippet(returnType, "", codeSnippet, true, false);
   }
 
   /**
    * Returns the program that results from compiling the specified code snippet
    * as the body of an entry point method.
-   *
+   *  @param returnType the return type of the method to compile; use "void" if
+   *          the code snippet has no return statement
+   * @param codeSnippet the body of the entry method
+   * @param staticMethod whether to make the method static
+   */
+  protected JProgram compileSnippet(final String returnType,
+      final String codeSnippet, boolean staticMethod) throws UnableToCompleteException {
+    return compileSnippet(returnType, "", codeSnippet, true, staticMethod);
+  }
+
+  /**
+   * Returns the program that results from compiling the specified code snippet
+   * as the body of an entry point method.
    * @param returnType the return type of the method to compile; use "void" if
    *          the code snippet has no return statement
    * @param params the parameter list of the method to compile
    * @param codeSnippet the body of the entry method
    * @param compileMonolithic whether the compile is monolithic
+   * @param staticMethod whether the entryPoint should be static
    */
   protected JProgram compileSnippet(final String returnType,
-      final String params, final String codeSnippet, boolean compileMonolithic)
+      final String params, final String codeSnippet, boolean compileMonolithic,
+      final boolean staticMethod)
       throws UnableToCompleteException {
     sourceOracle.addOrReplace(new MockJavaResource("test.EntryPoint") {
       @Override
@@ -261,8 +280,8 @@
         for (String snippetClassDecl : snippetClassDecls) {
           code.append(snippetClassDecl + ";\n");
         }
-        code.append("  public static " + returnType + " onModuleLoad(" + params
-            + ") {\n");
+        code.append("  public " + (staticMethod ? "static " : "") + returnType + " onModuleLoad(" +
+            params + ") {\n");
         code.append(codeSnippet);
         code.append("  }\n");
         code.append("}\n");
@@ -302,12 +321,63 @@
 
   public Result assertTransform(String codeSnippet, JVisitor visitor)
       throws UnableToCompleteException {
-    JProgram program = compileSnippet("void", codeSnippet);
+    JProgram program = compileSnippet("void", codeSnippet, true);
     JMethod mainMethod = findMainMethod(program);
     visitor.accept(mainMethod);
     return new Result("void", codeSnippet, mainMethod.getBody().toSource());
   }
 
+  protected JMethod getMethod(JProgram program, String name) {
+    return findMethod(program, name);
+  }
+
+  protected JReferenceType getType(JProgram program, String name) {
+    return program.getFromTypeMap(name);
+  }
+
+  protected JBlock getStatement(String statement)
+      throws UnableToCompleteException {
+    JProgram program = compileSnippet("void", statement, false);
+    JMethod mainMethod = findMainMethod(program);
+    JMethodBody body = (JMethodBody) mainMethod.getBody();
+    return body.getBlock();
+  }
+
+  /**
+   * Removes most whitespace while still leaving one space separating words.
+   *
+   * Used to make the assertEquals ignore whitespace (mostly) while still retaining meaningful
+   * output when the test fails.
+   */
+  protected String formatSource(String source) {
+    return source.replaceAll("\\s+", " ") // substitutes multiple whitespaces into one.
+      .replaceAll("\\s([\\p{Punct}&&[^$]])", "$1")  // removes whitespace preceding symbols
+                                                    // (except $ which can be part of an identifier)
+      .replaceAll("([\\p{Punct}&&[^$]])\\s", "$1"); // removes whitespace succeeding symbols.
+  }
+
+  protected void assertEqualBlock(String expected, String input)
+      throws UnableToCompleteException {
+    JBlock testExpression = getStatement(input);
+    assertEquals(formatSource("{ " + expected + "}"),
+        formatSource(testExpression.toSource()));
+  }
+
+  protected void addAll(Resource... sourceFiles) {
+    for (Resource sourceFile : sourceFiles) {
+      sourceOracle.addOrReplace(sourceFile);
+    }
+  }
+
+  protected JExpression getExpression(String type, String expression)
+      throws UnableToCompleteException {
+    JProgram program = compileSnippet(type, "return " + expression + ";", false);
+    JMethod mainMethod = findMainMethod(program);
+    JMethodBody body = (JMethodBody) mainMethod.getBody();
+    JReturnStatement returnStmt = (JReturnStatement) body.getStatements().get(0);
+    return returnStmt.getExpr();
+  }
+
   /**
    * Holds the result of a optimizations to compare with expected results.
    */
@@ -323,7 +393,7 @@
     }
 
     public void into(String expected) throws UnableToCompleteException {
-      JProgram program = compileSnippet(returnType, expected);
+      JProgram program = compileSnippet(returnType, expected, true);
       expected = getMainMethodSource(program);
       assertEquals(userCode, expected, optimized);
     }
diff --git a/dev/core/test/com/google/gwt/dev/jjs/impl/Java7AstTest.java b/dev/core/test/com/google/gwt/dev/jjs/impl/Java7AstTest.java
index f0b8481..ae9de11 100644
--- a/dev/core/test/com/google/gwt/dev/jjs/impl/Java7AstTest.java
+++ b/dev/core/test/com/google/gwt/dev/jjs/impl/Java7AstTest.java
@@ -18,13 +18,7 @@
 import com.google.gwt.core.ext.UnableToCompleteException;
 import com.google.gwt.dev.javac.testing.impl.Java7MockResources;
 import com.google.gwt.dev.javac.testing.impl.JavaResourceBase;
-import com.google.gwt.dev.jjs.ast.JBlock;
 import com.google.gwt.dev.jjs.ast.JExpression;
-import com.google.gwt.dev.jjs.ast.JMethod;
-import com.google.gwt.dev.jjs.ast.JMethodBody;
-import com.google.gwt.dev.jjs.ast.JProgram;
-import com.google.gwt.dev.jjs.ast.JReturnStatement;
-import com.google.gwt.dev.resource.Resource;
 import com.google.gwt.dev.util.arg.SourceLevel;
 
 /**
@@ -109,52 +103,9 @@
     //    "Number o= 1 ; int s = (int) o;");
   }
 
-  private void addAll(Resource... sourceFiles) {
-    for (Resource sourceFile : sourceFiles) {
-      sourceOracle.addOrReplace(sourceFile);
-    }
-  }
-
   private void assertEqualExpression(String type, String expected, String expression)
       throws UnableToCompleteException {
     JExpression testExpresssion = getExpression(type, expression);
     assertEquals(expected, testExpresssion.toSource());
   }
-
-  private JExpression getExpression(String type, String expression)
-      throws UnableToCompleteException {
-    JProgram program = compileSnippet(type, "return " + expression + ";");
-    JMethod mainMethod = findMainMethod(program);
-    JMethodBody body = (JMethodBody) mainMethod.getBody();
-    JReturnStatement returnStmt = (JReturnStatement) body.getStatements().get(0);
-    return returnStmt.getExpr();
-  }
-
-  private void assertEqualBlock(String expected, String input)
-      throws UnableToCompleteException {
-    JBlock testExpression = getStatement(input);
-    assertEquals(formatSource("{ " + expected + "}"),
-        formatSource(testExpression.toSource()));
-  }
-
-  /**
-   * Removes most whitespace while still leaving one space separating words.
-   *
-   * Used to make the assertEquals ignore whitespace (mostly) while still retaining meaningful
-   * output when the test fails.
-   */
-  private String formatSource(String source) {
-    return source.replaceAll("\\s+", " ") // substitutes multiple whitespaces into one.
-      .replaceAll("\\s([\\p{Punct}&&[^$]])", "$1")  // removes whitespace preceding symbols
-                                                    // (except $ which can be part of an identifier)
-      .replaceAll("([\\p{Punct}&&[^$]])\\s", "$1"); // removes whitespace succeeding symbols.
-  }
-
-  private JBlock getStatement(String statement)
-      throws UnableToCompleteException {
-    JProgram program = compileSnippet("void", statement);
-    JMethod mainMethod = findMainMethod(program);
-    JMethodBody body = (JMethodBody) mainMethod.getBody();
-    return body.getBlock();
-  }
 }
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
new file mode 100644
index 0000000..ff40350
--- /dev/null
+++ b/dev/core/test/com/google/gwt/dev/jjs/impl/Java8AstTest.java
@@ -0,0 +1,398 @@
+/*
+ * Copyright 2014 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.dev.jjs.impl;
+
+import com.google.gwt.dev.javac.testing.impl.JavaResourceBase;
+import com.google.gwt.dev.jjs.ast.JClassType;
+import com.google.gwt.dev.jjs.ast.JConstructor;
+import com.google.gwt.dev.jjs.ast.JMethod;
+import com.google.gwt.dev.jjs.ast.JPrimitiveType;
+import com.google.gwt.dev.jjs.ast.JProgram;
+import com.google.gwt.dev.util.arg.SourceLevel;
+
+/**
+ * Tests that {@link com.google.gwt.dev.jjs.impl.GwtAstBuilder} correctly builds the AST for features introduced in Java 8.
+ */
+public class Java8AstTest extends JJSTestBase {
+
+  @Override
+  public void setUp() {
+    sourceLevel = SourceLevel.JAVA8;
+    addAll(JavaResourceBase.FUNCTIONALINTERFACE);
+    addAll(JavaResourceBase.createMockJavaResource("test.Runnable",
+        "package test;",
+        "public interface Runnable {",
+        "  void run();",
+        "}"
+    ));
+    addAll(JavaResourceBase.createMockJavaResource("test.Lambda",
+        "package test;",
+        "public interface Lambda<T> {",
+        "  T run(int a, int b);",
+        "}"
+    ));
+    addAll(JavaResourceBase.createMockJavaResource("test.Lambda2",
+        "package test;",
+        "public interface Lambda2<String> {",
+        "  boolean run(String a, String b);",
+        "}"
+    ));
+    addAll(JavaResourceBase.createMockJavaResource("test.AcceptsLambda",
+        "package test;",
+        "public class AcceptsLambda<T> {",
+        "  public T accept(Lambda<T> foo) {",
+        "    return foo.run(10, 20);",
+        "  }",
+        "  public boolean accept2(Lambda2<String> foo) {",
+        "    return foo.run(\"a\", \"b\");",
+        "  }",
+        "}"
+    ));
+
+    addAll(JavaResourceBase.createMockJavaResource("test.Pojo",
+        "package test;",
+        "public class Pojo {",
+        "  public Pojo(int x, int y) {",
+        "  }",
+        "}"
+    ));
+  }
+
+  public void testCompileLambdaNoCapture() throws Exception {
+    String lambda = "new AcceptsLambda<Integer>().accept((a,b) -> a + b);";
+    assertEqualBlock(
+        "(new AcceptsLambda()).accept(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 test.Lambda
+    assertTrue(lambdaInnerClass.getImplements().contains(program.getFromTypeMap("test.Lambda")));
+    // should implement run method
+    JMethod samMethod = findMethod(lambdaInnerClass, "run");
+    assertEquals(
+        "public final Object run(int arg0,int arg1){return EntryPoint.lambda$0(arg0,arg1);}",
+        formatSource(samMethod.toSource()));
+  }
+
+  public void testCompileLambdaCaptureLocal() throws Exception {
+    String lambda = "int x = 42; new AcceptsLambda<Integer>().accept((a,b) -> x + a + b);";
+    assertEqualBlock(
+        "int x=42;(new AcceptsLambda()).accept(new EntryPoint$lambda$0$Type(x));",
+        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);
+
+    // should have constructor taking x
+    JMethod ctor = findMethod(lambdaInnerClass, "EntryPoint$lambda$0$Type");
+    assertTrue(ctor instanceof JConstructor);
+    assertEquals(1, ctor.getParams().size());
+    assertEquals(JPrimitiveType.INT, ctor.getOriginalParamTypes().get(0));
+
+    // should have 1 field to store the local
+    assertEquals(1, lambdaInnerClass.getFields().size());
+    assertEquals(JPrimitiveType.INT, lambdaInnerClass.getFields().get(0).getType());
+
+    // should contain assignment statement of ctor param to field
+    assertEquals("{this.x_0=x_0;}", formatSource(ctor.getBody().toSource()));
+    // should extends test.Lambda
+    assertTrue(lambdaInnerClass.getImplements().contains(program.getFromTypeMap("test.Lambda")));
+
+    // should implement run method and invoke lambda as static function
+    JMethod samMethod = findMethod(lambdaInnerClass, "run");
+    assertEquals(
+        "public final Object run(int arg0,int arg1){return EntryPoint.lambda$0(this.x_0,arg0,arg1);}",
+        formatSource(samMethod.toSource()));
+  }
+
+  // test whether local capture and outer scope capture work together
+  public void testCompileLambdaCaptureLocalAndField() throws Exception {
+    addSnippetClassDecl("private int y = 22;");
+    String lambda = "int x = 42; new AcceptsLambda<Integer>().accept((a,b) -> x + y + a + b);";
+    assertEqualBlock(
+        "int x=42;(new AcceptsLambda()).accept(new EntryPoint$lambda$0$Type(this,x));",
+        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);
+
+    // should have constructor taking this and x
+    JMethod ctor = findMethod(lambdaInnerClass, "EntryPoint$lambda$0$Type");
+    assertTrue(ctor instanceof JConstructor);
+    assertEquals(2, ctor.getParams().size());
+    assertEquals(lambdaInnerClass.getEnclosingType(), ctor.getOriginalParamTypes().get(0));
+    assertEquals(JPrimitiveType.INT, ctor.getOriginalParamTypes().get(1));
+
+    // should have 2 field to store the outer and local
+    assertEquals(2, lambdaInnerClass.getFields().size());
+    assertEquals(lambdaInnerClass.getEnclosingType(), lambdaInnerClass.getFields().get(0).getType());
+    assertEquals(JPrimitiveType.INT, lambdaInnerClass.getFields().get(1).getType());
+
+    // should contain assignment statement of ctor params to field
+    assertEquals("{this.$$outer_0=$$outer_0;this.x_1=x_1;}", formatSource(ctor.getBody().toSource()));
+    // should extends test.Lambda
+    assertTrue(lambdaInnerClass.getImplements().contains(program.getFromTypeMap("test.Lambda")));
+
+    // should implement run method and invoke lambda via captured instance
+    JMethod samMethod = findMethod(lambdaInnerClass, "run");
+    assertEquals(
+        "public final Object run(int arg0,int arg1){return this.$$outer_0.lambda$0(this.x_1,arg0,arg1);}",
+        formatSource(samMethod.toSource()));
+  }
+
+  // make sure nested scoping of identically named variables works
+  public void testCompileLambdaCaptureOuterInnerField() throws Exception {
+    addSnippetClassDecl("private int y = 22;");
+    addSnippetClassDecl("class Foo { " +
+          "int y = 42;" +
+          "void m() { new AcceptsLambda<Integer>().accept((a,b) -> EntryPoint.this.y + y + a + b); }" +
+        " }");
+    String lambda = "new Foo().m();";
+    assertEqualBlock(
+        "(new EntryPoint$Foo(this)).m();",
+        lambda
+    );
+    JProgram program = compileSnippet("void", lambda, false);
+    // created by JDT, should exist
+    JMethod lambdaMethod = findMethod(program.getFromTypeMap("test.EntryPoint$Foo"), "lambda$0");
+    assertNotNull(lambdaMethod);
+    assertEquals("{return Integer.valueOf(this.this$0.y+this.y+a_0+b_1);}", formatSource(lambdaMethod.getBody().toSource()));
+    // created by GwtAstBuilder
+    JClassType lambdaInnerClass = (JClassType) getType(program, "test.EntryPoint$Foo$lambda$0$Type");
+    assertNotNull(lambdaInnerClass);
+
+    // should have constructor taking this
+    JMethod ctor = findMethod(lambdaInnerClass, "EntryPoint$Foo$lambda$0$Type");
+    assertTrue(ctor instanceof JConstructor);
+    assertEquals(1, ctor.getParams().size());
+    assertEquals(lambdaInnerClass.getEnclosingType(), ctor.getOriginalParamTypes().get(0));
+
+    // should have 1 field to store the outer
+    assertEquals(1, lambdaInnerClass.getFields().size());
+    assertEquals(lambdaInnerClass.getEnclosingType(), lambdaInnerClass.getFields().get(0).getType());
+
+    // should contain assignment statement of ctor params to field
+    assertEquals("{this.$$outer_0=$$outer_0;}", formatSource(ctor.getBody().toSource()));
+    // should extends test.Lambda
+    assertTrue(lambdaInnerClass.getImplements().contains(program.getFromTypeMap("test.Lambda")));
+
+    // should implement run method and invoke lambda via captured instance
+    JMethod samMethod = findMethod(lambdaInnerClass, "run");
+    assertEquals(
+        "public final Object run(int arg0,int arg1){return this.$$outer_0.lambda$0(arg0,arg1);}",
+        formatSource(samMethod.toSource()));
+  }
+
+  public void testCompileStaticReferenceBinding() throws Exception {
+    addSnippetClassDecl("public static Integer foo(int x, int y) { return x + y; }");
+    String lambda = "new AcceptsLambda<Integer>().accept(EntryPoint::foo);";
+    assertEqualBlock(
+        "(new AcceptsLambda()).accept(new Lambda$$foo__IILjava_lang_Integer_2$Type());",
+        lambda
+    );
+    JProgram program = compileSnippet("void", lambda, false);
+    // created by JDT, should exist
+    assertNotNull(getMethod(program, "foo"));
+
+    // created by GwtAstBuilder
+    JClassType lambdaInnerClass = (JClassType) getType(program, "test.Lambda$$foo__IILjava_lang_Integer_2$Type");
+    assertNotNull(lambdaInnerClass);
+
+    // should have constructor taking this and x
+    JMethod ctor = findMethod(lambdaInnerClass, "Lambda$$foo__IILjava_lang_Integer_2$Type");
+    assertTrue(ctor instanceof JConstructor);
+    // no ctor args
+    assertEquals(0, ctor.getParams().size());
+
+    // no fields
+    assertEquals(0, lambdaInnerClass.getFields().size());
+
+    // should extends test.Lambda
+    assertTrue(lambdaInnerClass.getImplements().contains(program.getFromTypeMap("test.Lambda")));
+
+    // should implement run method and invoke lambda via captured instance
+    JMethod samMethod = findMethod(lambdaInnerClass, "run");
+    assertEquals(
+        "public final Object run(int arg0,int arg1){return EntryPoint.foo(arg0,arg1);}",
+        formatSource(samMethod.toSource()));
+  }
+
+  public void testCompileInstanceReferenceBinding() throws Exception {
+    addSnippetClassDecl("public Integer foo(int x, int y) { return x + y; }");
+    String lambda = "new AcceptsLambda<Integer>().accept(this::foo);";
+    assertEqualBlock(
+        "(new AcceptsLambda()).accept(new Lambda$foo__IILjava_lang_Integer_2$Type(this));",
+        lambda
+    );
+    JProgram program = compileSnippet("void", lambda, false);
+    // created by JDT, should exist
+    assertNotNull(getMethod(program, "foo"));
+
+    // created by GwtAstBuilder
+    JClassType lambdaInnerClass = (JClassType) getType(program, "test.Lambda$foo__IILjava_lang_Integer_2$Type");
+    assertNotNull(lambdaInnerClass);
+
+    // should have constructor taking this and x
+    JMethod ctor = findMethod(lambdaInnerClass, "Lambda$foo__IILjava_lang_Integer_2$Type");
+    assertTrue(ctor instanceof JConstructor);
+    // instance capture
+    assertEquals(1, ctor.getParams().size());
+    assertEquals(lambdaInnerClass.getEnclosingType(), ctor.getOriginalParamTypes().get(0));
+
+    // should have 1 field to store the captured instance
+    assertEquals(1, lambdaInnerClass.getFields().size());
+    assertEquals(lambdaInnerClass.getEnclosingType(), lambdaInnerClass.getFields().get(0).getType());
+
+    // should extends test.Lambda
+    assertTrue(lambdaInnerClass.getImplements().contains(program.getFromTypeMap("test.Lambda")));
+
+    // should implement run method and invoke lambda via captured instance
+    JMethod samMethod = findMethod(lambdaInnerClass, "run");
+    assertEquals(
+        "public final Object run(int arg0,int arg1){return this.$$outer_0.foo(arg0,arg1);}",
+        formatSource(samMethod.toSource()));
+  }
+
+  public void testCompileImplicitQualifierReferenceBinding() throws Exception {
+    String lambda = "new AcceptsLambda<String>().accept2(String::equalsIgnoreCase);";
+    assertEqualBlock(
+        "(new AcceptsLambda()).accept2(new Lambda2$$equalsIgnoreCase__Ljava_lang_String_2Z$Type());",
+        lambda
+    );
+    JProgram program = compileSnippet("void", lambda, false);
+
+    // created by GwtAstBuilder
+    JClassType lambdaInnerClass = (JClassType) getType(program, "test.Lambda2$$equalsIgnoreCase__Ljava_lang_String_2Z$Type");
+    assertNotNull(lambdaInnerClass);
+
+    // should have constructor taking this and x
+    JMethod ctor = findMethod(lambdaInnerClass, "Lambda2$$equalsIgnoreCase__Ljava_lang_String_2Z$Type");
+    assertTrue(ctor instanceof JConstructor);
+    // no instance capture
+    assertEquals(0, ctor.getParams().size());
+
+    // no fields
+    assertEquals(0, lambdaInnerClass.getFields().size());
+
+    // should extends test.Lambda
+    assertTrue(lambdaInnerClass.getImplements().contains(program.getFromTypeMap("test.Lambda2")));
+
+    // should implement run method and invoke lambda via captured instance
+    JMethod samMethod = findMethod(lambdaInnerClass, "run");
+    assertEquals(
+        "public final boolean run(Object arg0,Object arg1){return arg0.equalsIgnoreCase(arg1);}",
+        formatSource(samMethod.toSource()));
+  }
+
+  public void testCompileConstructorReferenceBinding() throws Exception {
+    String lambda = "new AcceptsLambda<Pojo>().accept(Pojo::new);";
+    assertEqualBlock(
+        "(new AcceptsLambda()).accept(new Lambda$$Pojo__IIV$Type());",
+        lambda
+    );
+    JProgram program = compileSnippet("void", lambda, false);
+
+    // created by GwtAstBuilder
+    JClassType lambdaInnerClass = (JClassType) getType(program, "test.Lambda$$Pojo__IIV$Type");
+    assertNotNull(lambdaInnerClass);
+
+    // should have constructor taking this and x
+    JMethod ctor = findMethod(lambdaInnerClass, "Lambda$$Pojo__IIV$Type");
+    assertTrue(ctor instanceof JConstructor);
+    // no instance capture
+    assertEquals(0, ctor.getParams().size());
+
+    // no fields
+    assertEquals(0, lambdaInnerClass.getFields().size());
+
+    // should extends test.Lambda
+    assertTrue(lambdaInnerClass.getImplements().contains(program.getFromTypeMap("test.Lambda")));
+
+    // should implement run method and invoke lambda via captured instance
+    JMethod samMethod = findMethod(lambdaInnerClass, "run");
+    assertEquals(
+        "public final Object run(int arg0,int arg1){return new Pojo(arg0,arg1);}",
+        formatSource(samMethod.toSource()));
+  }
+
+  public void testCompileConstructorReferenceBindingWithEnclosingInstanceCapture() throws Exception {
+    addSnippetClassDecl("int field1, field2;");
+    addSnippetClassDecl(
+        "class Pojo2 {",
+        "  public Pojo2(int x, int y) {",
+        "  }",
+        "  public int someMethod() { ",
+        "    return field1 + field2; ",
+        "  }",
+        "}"
+    );
+
+    String lambda = "new AcceptsLambda<Pojo2>().accept(Pojo2::new);";
+    assertEqualBlock(
+        "(new AcceptsLambda()).accept(new Lambda$$EntryPoint$Pojo2__Ltest_EntryPoint_2IIV$Type(this));",
+        lambda
+    );
+    JProgram program = compileSnippet("void", lambda, false);
+
+    // created by GwtAstBuilder
+    JClassType lambdaInnerClass = (JClassType) getType(program, "test.Lambda$$EntryPoint$Pojo2__Ltest_EntryPoint_2IIV$Type");
+    assertNotNull(lambdaInnerClass);
+
+    // should have constructor taking this and x
+    JMethod ctor = findMethod(lambdaInnerClass, "Lambda$$EntryPoint$Pojo2__Ltest_EntryPoint_2IIV$Type");
+    assertTrue(ctor instanceof JConstructor);
+    // one instance capture
+    assertEquals(1, ctor.getParams().size());
+
+    // one field for instance
+    assertEquals(1, lambdaInnerClass.getFields().size());
+
+    // should extends test.Lambda
+    assertTrue(lambdaInnerClass.getImplements().contains(program.getFromTypeMap("test.Lambda")));
+
+    // should implement run method and invoke lambda via captured instance
+    JMethod samMethod = findMethod(lambdaInnerClass, "run");
+    assertEquals(
+        "public final Object run(int arg0,int arg1){return new EntryPoint$Pojo2(this.test.EntryPoint,arg0,arg1);}",
+        formatSource(samMethod.toSource()));
+  }
+
+}
diff --git a/dev/core/test/com/google/gwt/dev/jjs/impl/JsniRefLookupTest.java b/dev/core/test/com/google/gwt/dev/jjs/impl/JsniRefLookupTest.java
index 6c428f2..aa59cb6 100644
--- a/dev/core/test/com/google/gwt/dev/jjs/impl/JsniRefLookupTest.java
+++ b/dev/core/test/com/google/gwt/dev/jjs/impl/JsniRefLookupTest.java
@@ -183,7 +183,7 @@
     try {
       // The snippet must reference the classes so they will be compiled in
       program = compileSnippet("void",
-          "new test.Foo(); new test.Bar(); new ClassWithBridge(); new PrivateSub();");
+          "new test.Foo(); new test.Bar(); new ClassWithBridge(); new PrivateSub();", true);
     } catch (UnableToCompleteException e) {
       throw new RuntimeException(e);
     }
diff --git a/dev/core/test/com/google/gwt/dev/jjs/impl/OptimizerTestBase.java b/dev/core/test/com/google/gwt/dev/jjs/impl/OptimizerTestBase.java
index 7945681..3f089fc 100644
--- a/dev/core/test/com/google/gwt/dev/jjs/impl/OptimizerTestBase.java
+++ b/dev/core/test/com/google/gwt/dev/jjs/impl/OptimizerTestBase.java
@@ -92,7 +92,7 @@
     public void into(String... expected) throws UnableToCompleteException {
       // We can't compile expected code into non-main method.
       Preconditions.checkState(methodName.equals(MAIN_METHOD_NAME));
-      JProgram program = compileSnippet(returnType, Joiner.on("\n").join(expected));
+      JProgram program = compileSnippet(returnType, Joiner.on("\n").join(expected), true);
       String expectedSource =
         OptimizerTestBase.findMethod(program, methodName).getBody().toSource();
       String actualSource =
@@ -239,7 +239,7 @@
           "return " + expressionSnippets[expressionSnippets.length - 1];
     }
     String snippet = Joiner.on(";\n").join(expressionSnippets) + ";\n";
-    final JProgram program = compileSnippet(returnType, snippet);
+    final JProgram program = compileSnippet(returnType, snippet, true);
     JMethod method = findMethod(program, MAIN_METHOD_NAME);
     JMethodBody body = (JMethodBody) method.getBody();
     JMultiExpression multi = new JMultiExpression(body.getSourceInfo());
@@ -293,7 +293,7 @@
       final String mainMethodReturnType, final String... mainMethodSnippet)
       throws UnableToCompleteException {
     String snippet = Joiner.on("\n").join(mainMethodSnippet);
-    JProgram program = compileSnippet(mainMethodReturnType, snippet);
+    JProgram program = compileSnippet(mainMethodReturnType, snippet, true);
     JMethod method = findMethod(program, methodName);
     boolean madeChanges = optimizeMethod(program, method);
     if (madeChanges && runDeadCodeElimination) {
diff --git a/dev/core/test/com/google/gwt/dev/jjs/impl/RunAsyncNameTest.java b/dev/core/test/com/google/gwt/dev/jjs/impl/RunAsyncNameTest.java
index a1c961a..1019ca9 100644
--- a/dev/core/test/com/google/gwt/dev/jjs/impl/RunAsyncNameTest.java
+++ b/dev/core/test/com/google/gwt/dev/jjs/impl/RunAsyncNameTest.java
@@ -69,7 +69,8 @@
 
     addSnippetImport("com.google.gwt.core.client.GWT");
     try {
-      JProgram program = compileSnippet("void", "GWT.runAsync((new Object()).getClass(), null);");
+      JProgram program = compileSnippet("void", "GWT.runAsync((new Object()).getClass(), null);",
+          true);
       ReplaceRunAsyncs.exec(logger, program);
       fail("Expected compilation to fail");
     } catch (UnableToCompleteException e) {
diff --git a/dev/core/test/com/google/gwt/dev/jjs/impl/codesplitter/ReplaceRunAsyncsErrorMessagesTest.java b/dev/core/test/com/google/gwt/dev/jjs/impl/codesplitter/ReplaceRunAsyncsErrorMessagesTest.java
index 0ac94ff..f466cb8 100644
--- a/dev/core/test/com/google/gwt/dev/jjs/impl/codesplitter/ReplaceRunAsyncsErrorMessagesTest.java
+++ b/dev/core/test/com/google/gwt/dev/jjs/impl/codesplitter/ReplaceRunAsyncsErrorMessagesTest.java
@@ -149,7 +149,7 @@
     logger = testLogger;
 
     try {
-      JProgram program = compileSnippet("void", codeSnippet);
+      JProgram program = compileSnippet("void", codeSnippet, true);
       ReplaceRunAsyncs.exec(logger, program);
       fail("Expected a compile error");
     } catch (UnableToCompleteException e) {
diff --git a/dev/core/test/com/google/gwt/dev/jjs/impl/gflow/CfgAnalysisTestBase.java b/dev/core/test/com/google/gwt/dev/jjs/impl/gflow/CfgAnalysisTestBase.java
index bc1c134..8ba8fd2 100644
--- a/dev/core/test/com/google/gwt/dev/jjs/impl/gflow/CfgAnalysisTestBase.java
+++ b/dev/core/test/com/google/gwt/dev/jjs/impl/gflow/CfgAnalysisTestBase.java
@@ -42,7 +42,8 @@
 
   protected AnalysisResult analyzeWithParams(String returnType, String params,
       String... codeSnippet) throws UnableToCompleteException {
-    JProgram program = compileSnippet(returnType, params, Joiner.on("\n").join(codeSnippet), true);
+    JProgram program = compileSnippet(returnType, params, Joiner.on("\n").join(codeSnippet), true,
+        true);
     JMethodBody body = (JMethodBody) findMainMethod(program).getBody();
     Cfg cfgGraph = CfgBuilder.build(program, body.getBlock());
 
diff --git a/dev/core/test/com/google/gwt/dev/jjs/impl/gflow/cfg/CfgBuilderTest.java b/dev/core/test/com/google/gwt/dev/jjs/impl/gflow/cfg/CfgBuilderTest.java
index 421531c..35330af 100644
--- a/dev/core/test/com/google/gwt/dev/jjs/impl/gflow/cfg/CfgBuilderTest.java
+++ b/dev/core/test/com/google/gwt/dev/jjs/impl/gflow/cfg/CfgBuilderTest.java
@@ -1686,7 +1686,7 @@
 
   private CfgBuilderResult assertCfg(String returnType, String ...codeSnippet)
       throws UnableToCompleteException {
-    JProgram program = compileSnippet(returnType, Joiner.on("\n").join(codeSnippet));
+    JProgram program = compileSnippet(returnType, Joiner.on("\n").join(codeSnippet), true);
     JMethodBody body = (JMethodBody) findMainMethod(program).getBody();
     Cfg cfgGraph = CfgBuilder.build(program, body.getBlock());
     return new CfgBuilderResult(cfgGraph);
diff --git a/dev/core/test/com/google/gwt/dev/jjs/impl/gflow/constants/AssumptionsDeducerTest.java b/dev/core/test/com/google/gwt/dev/jjs/impl/gflow/constants/AssumptionsDeducerTest.java
index d5d5d2d..bc1762a 100644
--- a/dev/core/test/com/google/gwt/dev/jjs/impl/gflow/constants/AssumptionsDeducerTest.java
+++ b/dev/core/test/com/google/gwt/dev/jjs/impl/gflow/constants/AssumptionsDeducerTest.java
@@ -104,7 +104,7 @@
   }
 
   private Result from(String decls, String expr, boolean b) throws UnableToCompleteException {
-    JProgram program = compileSnippet("void", decls + "\n if(" + expr + ") return;");
+    JProgram program = compileSnippet("void", decls + "\n if(" + expr + ") return;", true);
     JMethod mainMethod = findMainMethod(program);
     JBlock block = ((JMethodBody) mainMethod.getBody()).getBlock();
     List<JStatement> statements = block.getStatements();
diff --git a/dev/core/test/com/google/gwt/dev/jjs/impl/gflow/constants/ExpressionEvaluatorTest.java b/dev/core/test/com/google/gwt/dev/jjs/impl/gflow/constants/ExpressionEvaluatorTest.java
index b1fd7dc..86f87e2 100644
--- a/dev/core/test/com/google/gwt/dev/jjs/impl/gflow/constants/ExpressionEvaluatorTest.java
+++ b/dev/core/test/com/google/gwt/dev/jjs/impl/gflow/constants/ExpressionEvaluatorTest.java
@@ -92,7 +92,7 @@
 
     String codeSnippet = decls;
     codeSnippet += "return " + expr + ";";
-    JProgram program = compileSnippet(type, codeSnippet);
+    JProgram program = compileSnippet(type, codeSnippet, true);
     JMethod mainMethod = findMainMethod(program);
     JBlock block = ((JMethodBody) mainMethod.getBody()).getBlock();
 
diff --git a/user/super/com/google/gwt/emul/java/lang/annotation/FunctionalInterface.java b/user/super/com/google/gwt/emul/java/lang/annotation/FunctionalInterface.java
new file mode 100644
index 0000000..82df932
--- /dev/null
+++ b/user/super/com/google/gwt/emul/java/lang/annotation/FunctionalInterface.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2014 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package java.lang.annotation;
+
+/**
+ * Used to declare interfaces which must have a single abstract method.
+ */
+@Documented
+@Retention(value = RetentionPolicy.RUNTIME)
+@Target(value = ElementType.TYPE)
+public @interface FunctionalInterface {
+}
\ No newline at end of file
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
new file mode 100644
index 0000000..6f7655b
--- /dev/null
+++ b/user/test-super/com/google/gwt/dev/jjs/super/com/google/gwt/dev/jjs/test/Java8Test.java
@@ -0,0 +1,302 @@
+/*
+ * Copyright 2014 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.dev.jjs.test;
+
+import com.google.gwt.core.client.GwtScriptOnly;
+import com.google.gwt.junit.client.GWTTestCase;
+
+/**
+ * Tests Java 8 features. It is super sourced so that gwt can be compiles under Java 6.
+ *
+ * IMPORTANT: For each test here there must exist the corresponding method in the non super sourced
+ * version.
+ *
+ * Eventually this test will graduate and not be super sourced.
+ */
+@GwtScriptOnly
+public class Java8Test extends GWTTestCase {
+  int local = 42;
+
+  static abstract class SameClass {
+    public int method1() { return 10; }
+    public abstract int method2();
+  }
+
+  interface Lambda<T> {
+    T run(int a, int b);
+  }
+
+  interface Lambda2<String> {
+    boolean run(String a, String b);
+  }
+
+  class AcceptsLambda<T> {
+    public T accept(Lambda<T> foo) {
+      return foo.run(10, 20);
+    }
+    public boolean accept2(Lambda2<String> foo) {
+      return foo.run("a", "b");
+    }
+  }
+
+  class Pojo {
+    private final int x;
+    private final int y;
+
+    public Pojo(int x, int y) {
+      this.x = x;
+      this.y = y;
+    }
+
+    public int fooInstance(int a, int b) {
+      return a + b + x + y;
+    }
+  }
+
+  class Inner {
+    int local = 22;
+    public void run() {
+      assertEquals(94, new AcceptsLambda<Integer>().accept((a,b) -> Java8Test.this.local +  local + a + b).intValue());
+    }
+  }
+
+  static class Static {
+    static int staticField;
+    static {
+      staticField = 99;
+    }
+    static Integer staticMethod(int x, int y) { return x + y + staticField; }
+  }
+
+  static class StaticFailIfClinitRuns {
+    static {
+      fail("clinit() shouldn't run from just taking a reference to a method");
+    }
+
+    public static Integer staticMethod(int x, int y) {
+      return null;
+    }
+  }
+
+  @Override
+  public String getModuleName() {
+    return "com.google.gwt.dev.jjs.Java8Test";
+  }
+
+  public void testLambdaNoCapture() {
+    assertEquals(30, new AcceptsLambda<Integer>().accept((a, b) -> a + b).intValue());
+  }
+
+  public void testLambdaCaptureLocal() {
+    int x = 10;
+    assertEquals(40, new AcceptsLambda<Integer>().accept((a,b) -> x + a + b).intValue());
+  }
+
+  public void testLambdaCaptureLocalAndField() {
+    int x = 10;
+    assertEquals(82, new AcceptsLambda<Integer>().accept((a,b) -> x + local + a + b).intValue());
+  }
+
+  public void testCompileLambdaCaptureOuterInnerField() throws Exception {
+    new Inner().run();
+  }
+
+  public void testStaticReferenceBinding() throws Exception {
+    assertEquals(129, new AcceptsLambda<Integer>().accept(Static::staticMethod).intValue());
+    // if this next line runs a clinit, it fails
+    Lambda l = dummyMethodToMakeCheckStyleHappy(StaticFailIfClinitRuns::staticMethod);
+    try {
+      // but now it should fail
+      l.run(1,2);
+      fail("Clinit should have run for the first time");
+    } catch (AssertionError ae) {
+      // success, it was supposed to throw!
+    }
+  }
+
+  private static Lambda<Integer> dummyMethodToMakeCheckStyleHappy(Lambda<Integer> l) {
+    return l;
+  }
+
+  public void testInstanceReferenceBinding() throws Exception {
+    Pojo instance1 = new Pojo(1, 2);
+    Pojo instance2 = new Pojo(3, 4);
+    assertEquals(33, new AcceptsLambda<Integer>().accept(instance1::fooInstance).intValue());
+    assertEquals(37, new AcceptsLambda<Integer>().accept(instance2::fooInstance).intValue());
+  }
+
+  public void testImplicitQualifierReferenceBinding() throws Exception {
+    assertFalse(new AcceptsLambda<String>().accept2(String::equalsIgnoreCase));
+  }
+
+  public void testConstructorReferenceBinding() {
+    assertEquals(30, new AcceptsLambda<Pojo>().accept(Pojo::new).fooInstance(0, 0));
+  }
+
+  public void testStaticInterfaceMethod() {
+    assertEquals(99, (int) Static.staticMethod(0, 0));
+  }
+
+  interface ArrayCtor {
+    ArrayElem [][][] copy(int i);
+  }
+
+  interface ArrayCtorBoxed {
+    ArrayElem [][][] copy(Integer i);
+  }
+
+  static class ArrayElem {
+  }
+
+  public void testArrayConstructorReference() {
+    ArrayCtor ctor = ArrayElem[][][]::new;
+    ArrayElem[][][] array = ctor.copy(100);
+    assertEquals(100, array.length);
+  }
+
+  public void testArrayConstructorReferenceBoxed() {
+    ArrayCtorBoxed ctor = ArrayElem[][][]::new;
+    ArrayElem[][][] array = ctor.copy(100);
+    assertEquals(100, array.length);
+  }
+
+  interface ThreeArgs {
+    int foo(int x, int y, int z);
+  }
+
+  interface ThreeVarArgs {
+    int foo(int x, int y, int... z);
+  }
+
+  public static int addMany(int x, int y, int... nums) {
+    int sum = x + y;
+    for (int num : nums) {
+      sum += num;
+    }
+    return sum;
+  }
+
+  public void testVarArgsReferenceBinding() {
+    ThreeArgs t = Java8Test::addMany;
+    assertEquals(6, t.foo(1,2,3));
+  }
+
+  public void testVarArgsPassthroughReferenceBinding() {
+    ThreeVarArgs t = Java8Test::addMany;
+    assertEquals(6, t.foo(1,2,3));
+  }
+
+  public void testVarArgsPassthroughReferenceBindingProvidedArray() {
+    ThreeVarArgs t = Java8Test::addMany;
+    assertEquals(6, t.foo(1,2, new int[] {3}));
+  }
+
+  interface I {
+    int foo(Integer i);
+  }
+
+  public void testSuperReferenceExpression() {
+    class Y {
+      int foo(Integer i) {
+        return 42;
+      }
+    }
+
+    class X extends Y {
+      int foo(Integer i) {
+        return 23;
+      }
+
+      int goo() {
+        I i = super::foo;
+        return i.foo(0);
+      }
+    }
+
+    assertEquals(42, new X().goo());
+  }
+
+  static class X2 {
+    protected int field;
+    void foo() {
+      int local;
+      class Y extends X2 {
+        class Z extends X2 {
+          void f() {
+            Ctor c = X2::new;
+            X2 x = c.makeX(123456);
+            assertEquals(123456, x.field);
+            c = Y::new;
+            x = c.makeX(987654);
+            x = new Y(987654);
+            assertEquals(987655, x.field);
+            c = Z::new;
+            x = c.makeX(456789);
+            x = new Z(456789);
+            assertEquals(456791, x.field);
+          }
+          private Z(int z) {
+            super(z + 2);
+          }
+          Z() {
+          }
+        }
+
+        private Y(int y) {
+          super(y + 1);
+        }
+
+        private Y() {
+        }
+      }
+      new Y().new Z().f();
+    }
+
+    private X2(int x) {
+      this.field = x;
+    }
+    X2() {
+    }
+  }
+
+  public void testSuperReferenceExpressionWithVarArgs() {
+    class Base {
+      int foo(Object... objects) {
+        return 0;
+      }
+    }
+
+    class X extends Base {
+      int foo(Object... objects) {
+        throw new AssertionError();
+      }
+
+      void goo() {
+        I i = super::foo;
+        i.foo(10);
+      }
+    }
+    new X().goo();
+  }
+
+  interface Ctor {
+    X2 makeX(int x);
+  }
+
+  public void testPrivateConstructorReference() {
+    new X2().foo();
+  }
+}
diff --git a/user/test/com/google/gwt/dev/jjs/CompilerSuite.java b/user/test/com/google/gwt/dev/jjs/CompilerSuite.java
index 5040e9c..c01f7cf 100644
--- a/user/test/com/google/gwt/dev/jjs/CompilerSuite.java
+++ b/user/test/com/google/gwt/dev/jjs/CompilerSuite.java
@@ -38,6 +38,7 @@
 import com.google.gwt.dev.jjs.test.InnerOuterSuperTest;
 import com.google.gwt.dev.jjs.test.JStaticEvalTest;
 import com.google.gwt.dev.jjs.test.Java7Test;
+import com.google.gwt.dev.jjs.test.Java8Test;
 import com.google.gwt.dev.jjs.test.JavaAccessFromJavaScriptTest;
 import com.google.gwt.dev.jjs.test.JsStaticEvalTest;
 import com.google.gwt.dev.jjs.test.JsniConstructorTest;
@@ -90,6 +91,7 @@
     // Java7Test cannot be the first one in a suite. It uses a hack
     // to avoid executing if not in a Java 7+ environment.
     suite.addTestSuite(Java7Test.class);
+    suite.addTestSuite(Java8Test.class);
     suite.addTestSuite(JavaAccessFromJavaScriptTest.class);
     suite.addTestSuite(JsniConstructorTest.class);
     suite.addTestSuite(JsniDispatchTest.class);
diff --git a/user/test/com/google/gwt/dev/jjs/Java8Test.gwt.xml b/user/test/com/google/gwt/dev/jjs/Java8Test.gwt.xml
new file mode 100644
index 0000000..e4016ac
--- /dev/null
+++ b/user/test/com/google/gwt/dev/jjs/Java8Test.gwt.xml
@@ -0,0 +1,19 @@
+<!--                                                                        -->
+<!-- Copyright 2014 Google Inc.                                             -->
+<!-- Licensed under the Apache License, Version 2.0 (the "License"); you    -->
+<!-- may not use this file except in compliance with the License. You may   -->
+<!-- may obtain a copy of the License at                                    -->
+<!--                                                                        -->
+<!-- http://www.apache.org/licenses/LICENSE-2.0                             -->
+<!--                                                                        -->
+<!-- Unless required by applicable law or agreed to in writing, software    -->
+<!-- distributed under the License is distributed on an "AS IS" BASIS,      -->
+<!-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or        -->
+<!-- implied. License for the specific language governing permissions and   -->
+<!-- limitations under the License.                                         -->
+
+<!-- Contains tests for breaking out of the client source path  -->
+<module type="fileset">
+  <inherits name="com.google.gwt.core.Core" />
+  <super-source path='super' />
+</module>
\ No newline at end of file
diff --git a/user/test/com/google/gwt/dev/jjs/test/Java8Test.java b/user/test/com/google/gwt/dev/jjs/test/Java8Test.java
new file mode 100644
index 0000000..d34d7e4
--- /dev/null
+++ b/user/test/com/google/gwt/dev/jjs/test/Java8Test.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright 2014 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.dev.jjs.test;
+
+import com.google.gwt.dev.util.arg.SourceLevel;
+import com.google.gwt.junit.DoNotRunWith;
+import com.google.gwt.junit.JUnitShell;
+import com.google.gwt.junit.Platform;
+import com.google.gwt.junit.client.GWTTestCase;
+
+/**
+ * Dummy test case. Java8Test is super sourced so that GWT can be compiled by Java 6.
+ *
+ * NOTE: Make sure this class has the same test methods of its supersourced variant.
+ */
+@DoNotRunWith(Platform.Devel)
+public class Java8Test extends GWTTestCase {
+
+  @Override
+  public String getModuleName() {
+    return "com.google.gwt.dev.jjs.Java8Test";
+  }
+
+  @Override
+  public void runTest() throws Throwable {
+    // Only run these tests if -sourceLevel 8 (or greater) is enabled.
+    if (JUnitShell.getCompilerOptions().getSourceLevel()
+        .compareTo(SourceLevel.JAVA8) >= 0 && System.getProperty("java.version").startsWith("1.8")) {
+      super.runTest();
+    }
+  }
+
+  public void testLambdaNoCapture() {
+    // Make sure we are using the right Java8Test if the source compatibility level is set to Java 8
+    // or above.
+    assertFalse((JUnitShell.getCompilerOptions().getSourceLevel()
+        .compareTo(SourceLevel.JAVA8) >= 0));
+  }
+
+  public void testLambdaCaptureLocal() {
+  }
+
+  public void testLambdaCaptureLocalAndField() {
+  }
+
+  public void testCompileLambdaCaptureOuterInnerField() throws Exception {
+  }
+
+  public void testStaticReferenceBinding() throws Exception {
+  }
+
+  public static Integer foo(int x, int y) {
+    return x + y;
+  }
+
+  public Integer fooInstance(int x, int y) {
+    return x + y + 1;
+  }
+
+  public void testInstanceReferenceBinding() throws Exception {
+  }
+
+  public void testImplicitQualifierReferenceBinding() throws Exception {
+  }
+
+  public void testConstructorReferenceBinding() {
+  }
+
+  public void testStaticInterfaceMethod() {
+  }
+
+  public void testArrayConstructorReference() {
+  }
+
+  public void testArrayConstructorReferenceBoxed() {
+  }
+
+  public void testVarArgsReferenceBinding() {
+  }
+
+  public void testVarArgsPassthroughReferenceBinding() {
+  }
+
+  public void testVarArgsPassthroughReferenceBindingProvidedArray() {
+  }
+
+  public void testSuperReferenceExpression() {
+  }
+
+  public void testSuperReferenceExpressionWithVarArgs() {
+  }
+
+  public void testPrivateConstructorReference() {
+  }
+}
\ No newline at end of file