Fix bugs in Lambda with nested scopes.

There are two places where we tried to find outer variable bindings
of a lambda in current method context but we did it wrong.

1). In GwtAstBuilder.resolveNameReference,
When a lambda expression has a block method body,
For example, {int x = 0; () -> {int t = x; return t;}; },
a wrong path was executed. The reason is that for the condition
scope.isLambdaScope(), only MethodScope may return true,
and BlockScope (here {int t = x; return t;}) always returns false.

2). In GwtAstBuilder.replaceLambdaWithInnerClassAllocation,
For nested lambdas, for example,
x = 0;
call(sam1 -> {call(sam2 -> {x;});});
LocalVariableBinding of x in the inner lambda (sam2->{x;})
is different from the binding of x in the outer lambda.
So we cannot get the JVariable from the outer lambda. But they
refer to the same actual outer variable. So to fix it, we compare
the actual outer variable to get the corresponding variable.

Bug: issue 9038.
Change-Id: I974d7fa0e40bff08493195c7293b76d2668dcbb8
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 853c8f0..ca13e5e 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
@@ -113,6 +113,8 @@
 import com.google.gwt.dev.util.collect.Stack;
 import com.google.gwt.thirdparty.guava.common.base.Function;
 import com.google.gwt.thirdparty.guava.common.base.Preconditions;
+import com.google.gwt.thirdparty.guava.common.base.Predicate;
+import com.google.gwt.thirdparty.guava.common.collect.FluentIterable;
 import com.google.gwt.thirdparty.guava.common.collect.Interner;
 import com.google.gwt.thirdparty.guava.common.collect.Iterables;
 import com.google.gwt.thirdparty.guava.common.collect.Lists;
@@ -1371,8 +1373,24 @@
       if (x.shouldCaptureInstance) {
         allocLambda.addArg(new JThisRef(info, innerLambdaClass.getEnclosingType()));
       }
-      for (SyntheticArgumentBinding sa : synthArgs) {
-        allocLambda.addArg(makeLocalRef(info, sa.actualOuterLocalVariable, methodStack.peek()));
+      for (final SyntheticArgumentBinding sa : synthArgs) {
+        MethodInfo method = methodStack.peek();
+        // Find the local variable in the current method context that is referred by the inner
+        // lambda.
+        LocalVariableBinding argument = FluentIterable.from(method.locals.keySet()).firstMatch(
+            new Predicate<LocalVariableBinding>() {
+              @Override
+              public boolean apply(LocalVariableBinding enclosingLocal) {
+                // Either the inner lambda refers direcly to the enclosing scope variable, or
+                // it is a capture from an enclosing scope, in which case both synthetic arguments
+                // point to the same outer local variable.
+                return enclosingLocal == sa.actualOuterLocalVariable ||
+                    (enclosingLocal instanceof SyntheticArgumentBinding) &&
+                        ((SyntheticArgumentBinding) enclosingLocal)
+                            .actualOuterLocalVariable == sa.actualOuterLocalVariable;
+              }
+            }).get();
+        allocLambda.addArg(makeLocalRef(info, argument, method));
       }
       // put the result on the stack, and pop out synthetic method from the scope
       push(allocLambda);
@@ -3291,7 +3309,9 @@
       JExpression result = null;
       if (binding instanceof LocalVariableBinding) {
         LocalVariableBinding b = (LocalVariableBinding) binding;
-        if ((x.bits & ASTNode.DepthMASK) != 0 || scope.isLambdaScope()) {
+        MethodScope nearestMethodScope =
+            scope instanceof MethodScope ? (MethodScope) scope : scope.enclosingMethodScope();
+        if ((x.bits & ASTNode.DepthMASK) != 0 || nearestMethodScope.isLambdaScope()) {
           VariableBinding[] path = scope.getEmulationPath(b);
           if (path == null) {
             /*
diff --git a/dev/core/test/com/google/gwt/dev/jjs/impl/Java8AstTest.java b/dev/core/test/com/google/gwt/dev/jjs/impl/Java8AstTest.java
index 069703b..695c224 100644
--- a/dev/core/test/com/google/gwt/dev/jjs/impl/Java8AstTest.java
+++ b/dev/core/test/com/google/gwt/dev/jjs/impl/Java8AstTest.java
@@ -172,6 +172,45 @@
         formatSource(samMethod.toSource()));
   }
 
+  public void testCompileLambdaCaptureLocalWithBlockInLambda() throws Exception {
+    String lambda =
+        "int x = 42; "
+        + "new AcceptsLambda<Integer>().accept((a,b) -> { int temp = x; return temp + 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;");
@@ -261,6 +300,203 @@
         formatSource(samMethod.toSource()));
   }
 
+  public void testLambdaCaptureParameter() throws Exception {
+    addSnippetClassDecl("interface ClickHandler {\n" +
+        "    int onClick(int a);\n" +
+        "  }\n" +
+        "  private int addClickHandler(ClickHandler clickHandler) {\n" +
+        "    return clickHandler.onClick(1);\n" +
+        "  }\n" +
+        "  private int addClickHandler(int a) {\n" +
+        "    return addClickHandler(x->{int temp = a; return temp;});\n" +
+        "  }\n");
+    JProgram program = compileSnippet("int", "return addClickHandler(2);", false);
+    JClassType lambdaInnerClass = (JClassType) getType(program, "test.EntryPoint$lambda$0$Type");
+    assertNotNull(lambdaInnerClass);
+
+    // should have constructor taking the outer variable from parameter
+    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 outer
+    assertEquals(1, lambdaInnerClass.getFields().size());
+    assertEquals(JPrimitiveType.INT,
+        lambdaInnerClass.getFields().get(0).getType());
+
+    // should contain assignment statement of ctor params to field
+    assertEquals("{this.a_0=a_0;}", formatSource(ctor.getBody().toSource()));
+    // should extends test.Lambda
+    assertTrue(lambdaInnerClass.getImplements().contains(
+        program.getFromTypeMap("test.EntryPoint$ClickHandler")));
+
+    JMethod samMethod = findMethod(lambdaInnerClass, "onClick");
+    assertEquals("public final int onClick(int a){return EntryPoint.lambda$0(this.a_0,a);}",
+        formatSource(samMethod.toSource()));
+  }
+
+  public void testLambdaNestingCaptureLocal() throws Exception {
+    addSnippetClassDecl("interface Inner {\n" +
+        "    void f();\n" +
+        "  }\n");
+    addSnippetClassDecl(
+        "  interface Outer {\n" +
+        "     void accept(Inner t);\n" +
+        "   }\n");
+    addSnippetClassDecl(
+        "  public static void call(Outer a) {\n" +
+        "    a.accept(() -> {});\n" +
+        "  }\n");
+    String nestedLambda = "boolean[] success = new boolean[] {false};\n"
+        + "call( sam1 -> { call(sam2 -> {success[0] = true;}); });";
+    assertEqualBlock("boolean[]success=new boolean[]{false};"
+        + "EntryPoint.call(new EntryPoint$lambda$1$Type(success));", nestedLambda);
+    JProgram program = compileSnippet("void", nestedLambda, false);
+    JClassType lambdaInnerClass1 = (JClassType) getType(program, "test.EntryPoint$lambda$0$Type");
+    JClassType lambdaInnerClass2 = (JClassType) getType(program, "test.EntryPoint$lambda$1$Type");
+    JClassType lambdaInnerClass3 = (JClassType) getType(program, "test.EntryPoint$lambda$2$Type");
+    assertNotNull(lambdaInnerClass1);
+    assertNotNull(lambdaInnerClass2);
+    assertNotNull(lambdaInnerClass3);
+
+    // check constructors
+    JMethod ctor1 = findMethod(lambdaInnerClass1, "EntryPoint$lambda$0$Type");
+    assertTrue(ctor1 instanceof JConstructor);
+    assertEquals(0, ctor1.getParams().size());
+
+    JMethod ctor2 = findMethod(lambdaInnerClass2, "EntryPoint$lambda$1$Type");
+    assertTrue(ctor2 instanceof JConstructor);
+    assertEquals(1, ctor2.getParams().size());
+    assertEquals("boolean[]", ctor2.getOriginalParamTypes().get(0).getName());
+
+    JMethod ctor3 = findMethod(lambdaInnerClass3, "EntryPoint$lambda$2$Type");
+    assertTrue(ctor3 instanceof JConstructor);
+    assertEquals(1, ctor3.getParams().size());
+    assertEquals("boolean[]", ctor3.getOriginalParamTypes().get(0).getName());
+
+    // check fields
+    assertEquals(0, lambdaInnerClass1.getFields().size());
+
+    assertEquals(1, lambdaInnerClass2.getFields().size());
+    assertEquals("boolean[]",
+        lambdaInnerClass2.getFields().get(0).getType().getName());
+
+    assertEquals(1, lambdaInnerClass3.getFields().size());
+    assertEquals("boolean[]",
+        lambdaInnerClass3.getFields().get(0).getType().getName());
+
+    // check constructor body
+    assertEquals("{this.success_0=success_0;}", formatSource(ctor2.getBody().toSource()));
+    assertEquals("{this.success_0=success_0;}", formatSource(ctor3.getBody().toSource()));
+
+    // check super interface
+    assertTrue(lambdaInnerClass1.getImplements().contains(
+        program.getFromTypeMap("test.EntryPoint$Inner")));
+    assertTrue(lambdaInnerClass2.getImplements().contains(
+        program.getFromTypeMap("test.EntryPoint$Outer")));
+    assertTrue(lambdaInnerClass3.getImplements().contains(
+        program.getFromTypeMap("test.EntryPoint$Outer")));
+
+    // check samMethod
+    JMethod samMethod1 = findMethod(lambdaInnerClass2, "accept");
+    JMethod samMethod2 = findMethod(lambdaInnerClass3, "accept");
+    assertEquals(
+        "public final void accept(EntryPoint$Inner t){EntryPoint.lambda$1(this.success_0,t);}",
+        formatSource(samMethod1.toSource()));
+    assertEquals(
+        "public final void accept(EntryPoint$Inner t){EntryPoint.lambda$2(this.success_0,t);}",
+        formatSource(samMethod2.toSource()));
+
+    // check lambda method
+    JMethod lambdaMethod1 = findMethod(program, "lambda$1");
+    JMethod lambdaMethod2 = findMethod(program, "lambda$2");
+    assertEquals(
+        "private static void lambda$1(boolean[]success_0,EntryPoint$Inner sam1_1)"
+        + "{{EntryPoint.call(new EntryPoint$lambda$2$Type(success_0));}}",
+        formatSource(lambdaMethod1.toSource()));
+    assertEquals(
+        "private static void lambda$2(boolean[]success_0,EntryPoint$Inner sam2_1)"
+        + "{{success_0[0]=true;}}",
+        formatSource(lambdaMethod2.toSource()));
+  }
+
+  public void testLambdaNestingCaptureField() throws Exception {
+    addSnippetClassDecl("interface Inner {\n" +
+        "    void f();\n" +
+        "  }\n");
+    addSnippetClassDecl(
+        "  interface Outer {\n" +
+        "     void accept(Inner t);\n" +
+        "   }\n");
+    addSnippetClassDecl(
+        "  static class A {\n" +
+        "    public boolean[] success = new boolean[] {false};\n" +
+        "    public void call(Outer a) {\n" +
+        "      a.accept(() -> {});\n" +
+        "    }\n" +
+        "  }\n");
+    String nestedLambda = "A a = new A();\n"
+        + "a.call( sam1 -> { a.call(sam2 -> {a.success[0] = true;}); });";
+    assertEqualBlock("EntryPoint$A a=new EntryPoint$A();a.call(new EntryPoint$lambda$0$Type(a));",
+        nestedLambda);
+    JProgram program = compileSnippet("void", nestedLambda, false);
+    JClassType lambdaInnerClass1 = (JClassType) getType(program, "test.EntryPoint$lambda$0$Type");
+    JClassType lambdaInnerClass2 = (JClassType) getType(program, "test.EntryPoint$lambda$1$Type");
+    assertNotNull(lambdaInnerClass1);
+    assertNotNull(lambdaInnerClass2);
+
+    // check constructors
+    JMethod ctor1 = findMethod(lambdaInnerClass1, "EntryPoint$lambda$0$Type");
+    assertTrue(ctor1 instanceof JConstructor);
+    assertEquals(1, ctor1.getParams().size());
+    assertEquals("test.EntryPoint$A", ctor1.getOriginalParamTypes().get(0).getName());
+
+    JMethod ctor2 = findMethod(lambdaInnerClass2, "EntryPoint$lambda$1$Type");
+    assertTrue(ctor2 instanceof JConstructor);
+    assertEquals(1, ctor2.getParams().size());
+    assertEquals("test.EntryPoint$A", ctor2.getOriginalParamTypes().get(0).getName());
+
+    // check fields
+    assertEquals(1, lambdaInnerClass1.getFields().size());
+    assertEquals("test.EntryPoint$A", lambdaInnerClass2.getFields().get(0).getType().getName());
+
+    assertEquals(1, lambdaInnerClass2.getFields().size());
+    assertEquals("test.EntryPoint$A", lambdaInnerClass2.getFields().get(0).getType().getName());
+
+    // check constructor body
+    assertEquals("{this.a_0=a_0;}", formatSource(ctor2.getBody().toSource()));
+    assertEquals("{this.a_0=a_0;}", formatSource(ctor2.getBody().toSource()));
+
+    // check super interface
+    assertTrue(lambdaInnerClass1.getImplements().contains(
+        program.getFromTypeMap("test.EntryPoint$Outer")));
+    assertTrue(lambdaInnerClass2.getImplements().contains(
+        program.getFromTypeMap("test.EntryPoint$Outer")));
+
+    // check samMethod
+    JMethod samMethod1 = findMethod(lambdaInnerClass1, "accept");
+    JMethod samMethod2 = findMethod(lambdaInnerClass2, "accept");
+    assertEquals(
+        "public final void accept(EntryPoint$Inner t){EntryPoint.lambda$0(this.a_0,t);}",
+        formatSource(samMethod1.toSource()));
+    assertEquals(
+        "public final void accept(EntryPoint$Inner t){EntryPoint.lambda$1(this.a_0,t);}",
+        formatSource(samMethod2.toSource()));
+
+    // check lambda method
+    JMethod lambdaMethod1 = findMethod(program, "lambda$0");
+    JMethod lambdaMethod2 = findMethod(program, "lambda$1");
+    assertEquals(
+        "private static void lambda$0(EntryPoint$A a_0,EntryPoint$Inner sam1_1)"
+        + "{{a_0.call(new EntryPoint$lambda$1$Type(a_0));}}",
+        formatSource(lambdaMethod1.toSource()));
+    assertEquals(
+        "private static void lambda$1(EntryPoint$A a_0,EntryPoint$Inner sam2_1)"
+        + "{{a_0.success[0]=true;}}",
+        formatSource(lambdaMethod2.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);";
diff --git a/user/test-super/com/google/gwt/dev/jjs/super/com/google/gwt/dev/jjs/test/Java8Test.java b/user/test-super/com/google/gwt/dev/jjs/super/com/google/gwt/dev/jjs/test/Java8Test.java
index cfdb77f..ad5ed5a 100644
--- a/user/test-super/com/google/gwt/dev/jjs/super/com/google/gwt/dev/jjs/test/Java8Test.java
+++ b/user/test-super/com/google/gwt/dev/jjs/super/com/google/gwt/dev/jjs/test/Java8Test.java
@@ -774,4 +774,81 @@
     SimpleI ii = (SimpleC & SimpleI) cc;
     assertEquals(33, ii.fun());
   }
+
+  interface ClickHandler {
+    int onClick(int a);
+  }
+  private int addClickHandler(ClickHandler clickHandler) {
+    return clickHandler.onClick(1);
+  }
+  private int addClickHandler(int a) {
+    return addClickHandler(x -> { int temp = a; return temp; });
+  }
+  public void testLambdaCaptureParameter() {
+    assertEquals(2, addClickHandler(2));
+  }
+
+  interface TestLambda_Inner {
+    void f();
+  }
+  interface TestLambda_Outer {
+    void accept(TestLambda_Inner t);
+  }
+  public void testLambda_call(TestLambda_Outer a) {
+    a.accept(() -> { });
+  }
+  public void testLambdaNestingCaptureLocal() {
+    int[] success = new int[] {0};
+    testLambda_call(sam1 -> { testLambda_call(sam2 -> { success[0] = 10; }); });
+    assertEquals(10, success[0]);
+  }
+
+  static class TestLambda_Class {
+    public int[] s = new int[] {0};
+    public void call(TestLambda_Outer a) {
+      a.accept(() -> { });
+    }
+    class TestLambda_InnerClass {
+      public int[] s = new int[] {0};
+      public int test() {
+        int[] s = new int[] {0};
+        TestLambda_Class.this.call(
+            sam0 -> TestLambda_Class.this.call(
+                sam1 -> {
+                  TestLambda_Class.this.call(
+                    sam2 -> {
+                      TestLambda_Class.this.s[0] = 10;
+                      this.s[0] = 20;
+                      s[0] = 30;
+                    });
+                  }));
+        return s[0];
+      }
+    }
+  }
+
+  public void testLambdaNestingCaptureField() {
+    TestLambda_Class a = new TestLambda_Class();
+    a.call(sam1 -> { a.call(sam2 -> { a.s[0] = 20; }); });
+    assertEquals(20, a.s[0]);
+  }
+
+  public void testLambdaMultipleNestingCaptureFieldAndLocal() {
+    TestLambda_Class a = new TestLambda_Class();
+    TestLambda_Class b = new TestLambda_Class();
+    int [] s = new int [] {0};
+    b.call(sam0 -> a.call(sam1 -> { a.call(sam2 -> { a.s[0] = 20; b.s[0] = 30; s[0] = 40; }); }));
+    assertEquals(20, a.s[0]);
+    assertEquals(30, b.s[0]);
+    assertEquals(40, s[0]);
+  }
+
+  public void testLambdaMultipleNestingCaptureFieldAndLocalInnerClass() {
+    TestLambda_Class a = new TestLambda_Class();
+    TestLambda_Class.TestLambda_InnerClass b = a.new TestLambda_InnerClass();
+    int result = b.test();
+    assertEquals(10, a.s[0]);
+    assertEquals(20, b.s[0]);
+    assertEquals(30, result);
+  }
 }
diff --git a/user/test/com/google/gwt/dev/jjs/test/Java8Test.java b/user/test/com/google/gwt/dev/jjs/test/Java8Test.java
index a50dc0f..05dca4c 100644
--- a/user/test/com/google/gwt/dev/jjs/test/Java8Test.java
+++ b/user/test/com/google/gwt/dev/jjs/test/Java8Test.java
@@ -153,4 +153,19 @@
 
   public void testIntersectionCastPolymorphism() {
   }
+
+  public void testLambdaCaptureParameter() {
+  }
+
+  public void testLambdaNestingCaptureLocal() {
+  }
+
+  public void testLambdaNestingCaptureField() {
+  }
+
+  public void testLambdaMultipleNestingCaptureFieldAndLocal() {
+  }
+
+  public void testLambdaMultipleNestingCaptureFieldAndLocalInnerClass() {
+  }
 }
\ No newline at end of file