Fix scoping issue with lambda placed in local anonymous class.
GwtAstBuilder.replaceLambdaWithInnerClassAllocation now tries to
resolve labmda's synthetic arguments from enclosing anonymous class if
one exists and argument was not previously resolved from local scope
of the enclosing method.
E.g.:
{
int x = 1;
new Runnable() {
public void run(){
Lambda l = () -> x + 1;
}
}
}
Change-Id: I28d74e65a0d82b545778fa522b2fc06c004a52a1
Bug-Link: https://github.com/gwtproject/gwt/issues/8991
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 b6ad090..baaed7e 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
@@ -231,6 +231,7 @@
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
+import java.util.Map.Entry;
import java.util.Set;
/**
@@ -1362,7 +1363,7 @@
return lambdaMethod;
}
- private void replaceLambdaWithInnerClassAllocation(LambdaExpression x, SourceInfo info,
+ private void replaceLambdaWithInnerClassAllocation(LambdaExpression x, final SourceInfo info,
JClassType innerLambdaClass, JConstructor ctor, SyntheticArgumentBinding[] synthArgs) {
// Finally, we replace the LambdaExpression with
// new InnerLambdaClass(this, local1, local2, ...);
@@ -1373,23 +1374,47 @@
allocLambda.addArg(new JThisRef(info, innerLambdaClass.getEnclosingType()));
}
for (final SyntheticArgumentBinding sa : synthArgs) {
- MethodInfo method = methodStack.peek();
+ final MethodInfo method = methodStack.peek();
+ JExpression capturedLocalReference = null;
// Find the local variable in the current method context that is referred by the inner
// lambda.
- LocalVariableBinding argument = FluentIterable.from(method.locals.keySet()).firstMatch(
+ LocalVariableBinding localVariable = 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;
+ // Either the inner lambda refers directly 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));
+ }).orNull();
+ if (localVariable != null) {
+ // lambda is capturing a local from the immediate context
+ capturedLocalReference = makeLocalRef(info, localVariable, method);
+ } else {
+ // Local variable not found in current method context. Trying to find corresponding
+ // synthetic field in case if lambda is placed in anonymous/local class
+ // e.g. { int x = 1; new Outer(){ void m (){ Lambda l = () -> x+1;} }; }
+ Entry<SyntheticArgumentBinding, JField> capturedLocalInOuterClass = FluentIterable.from(
+ curClass.syntheticFields.entrySet()).firstMatch(
+ new Predicate<Entry<SyntheticArgumentBinding, JField>>() {
+ @Override
+ public boolean apply(Entry<SyntheticArgumentBinding, JField> entry) {
+ return entry.getKey().actualOuterLocalVariable == sa.actualOuterLocalVariable;
+ }
+ }).orNull();
+ if (capturedLocalInOuterClass != null) {
+ // local from outer scope has already been captured by enclosing class.
+ capturedLocalReference = makeInstanceFieldRef(info, capturedLocalInOuterClass
+ .getValue());
+ }
+ }
+
+ assert capturedLocalReference != null;
+ allocLambda.addArg(capturedLocalReference);
}
// put the result on the stack, and pop out synthetic method from the scope
push(allocLambda);
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 0a2cfde..584fab5 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
@@ -423,6 +423,122 @@
formatSource(lambdaMethod2.toSource()));
}
+ public void testLambdaNestingInAnonymousCaptureLocal() throws Exception {
+ String lambda =
+ "int x = 42;\n" +
+ "new Runnable() { public void run() { Lambda<Integer> l = (a, b) -> x + a + b; l.run(1, 2); } }.run();";
+ assertEqualBlock("int x=42;(new EntryPoint$1(this,x)).run();", lambda);
+ JProgram program = compileSnippet("void", lambda, false);
+ JClassType outerClass = (JClassType) getType(program, "test.EntryPoint$1");
+
+ // check that anonymous class implementation uses synthetic field val$x2 to initialize lambda
+ // synthetic class
+ assertEquals(
+ "public void run(){Lambda l=new EntryPoint$1$lambda$0$Type(this.val$x2);l.run(1,2);}",
+ formatSource(findMethod(outerClass, "run").toSource()));
+ }
+
+ public void testLambdaNestingInMultipleAnonymousCaptureLocal() throws Exception {
+ addSnippetClassDecl("interface I { int foo(Integer i); }");
+ // checks that lambda has access to local variable and arguments when placed in local anonymous
+ // class with multiple nesting
+ String snippet =
+ "int[] x = new int[] {42};\n" +
+ "int result = new I(){\n" +
+ " public int foo(Integer i1){\n" +
+ " return new I(){\n" +
+ " public int foo(Integer i2){\n" +
+ " return new I(){\n" +
+ " public int foo(Integer i3){\n" +
+ " Lambda<Integer> l = (a, b) -> x[0] = x[0] + a + b + i1 + i2 + i3;\n" +
+ " return l.run(1, 2);\n" +
+ " }\n" +
+ " }.foo(3);\n" +
+ " }\n" +
+ " }.foo(2);\n" +
+ " }\n" +
+ "}.foo(1);\n";
+
+ JProgram program = compileSnippet("void", snippet, false);
+ JClassType outer1 = (JClassType) getType(program, "test.EntryPoint$1");
+ JClassType outer2 = (JClassType) getType(program, "test.EntryPoint$1$1");
+ JClassType outer3 = (JClassType) getType(program, "test.EntryPoint$1$1$1");
+
+ assertNotNull(outer1);
+ assertNotNull(outer2);
+ assertNotNull(outer3);
+
+ JMethod outer1Method = findMethod(outer1, "foo");
+ JMethod outer2Method = findMethod(outer2, "foo");
+ JMethod outer3Method = findMethod(outer3, "foo");
+ assertNotNull(outer3Method);
+ assertEquals(
+ "public int foo(Integer i1){"
+ + "return(new EntryPoint$1$1(this,this.val$x2,i1)).foo(Integer.valueOf(2));"
+ + "}",
+ formatSource(outer1Method.toSource()));
+ assertEquals(
+ "public int foo(Integer i2){"
+ + "return(new EntryPoint$1$1$1(this,this.val$x2,this.val$i13,i2)).foo(Integer.valueOf(3));"
+ + "}",
+ formatSource(outer2Method.toSource()));
+ // checks that lambda scope initialized similar to anonymous class
+ assertEquals(
+ "public int foo(Integer i3){"
+ + "Lambda l=new EntryPoint$1$1$1$lambda$0$Type(this.val$x2,this.val$i13,this.val$i24,i3);"
+ + "return((Integer)l.run(1,2)).intValue();"
+ + "}",
+ formatSource(outer3Method.toSource()));
+ }
+
+ public void testLambdaNestingInMultipleMixedAnonymousCaptureLocal() throws Exception {
+ // checks that lambda has access to local variable and arguments when placed in mixed scopes
+ // Local Class -> Local Class -> Local Anonymous -> lambda -> Local Anonymous
+ addSnippetClassDecl("interface I { int foo(Integer i); }");
+ addSnippetClassDecl("class A {\n" +
+ "int a() {\n" +
+ " int[] x = new int[] {42};\n" +
+ " class B {\n" +
+ " void b() {\n" +
+ " I i = new I(){\n" +
+ " public int foo(Integer arg){\n" +
+ " Runnable r = () ->{\n" +
+ " new Runnable() {\n" +
+ " public void run() {\n" +
+ " Lambda<Integer> l = (a, b) -> x[0] = x[0] + a + b + arg;\n" +
+ " x[0] = l.run(1, 2);\n" +
+ " }\n" +
+ " }.run();\n" +
+ " };\n" +
+ " r.run();\n" +
+ " return x[0];\n" +
+ " }\n" +
+ " };\n" +
+ " i.foo(1);\n" +
+ " }\n" +
+ " }\n" +
+ " B b = new B();\n" +
+ " b.b();\n" +
+ " return x[0];\n" +
+ "}\n" +
+ "}\n");
+
+ String snippet = "A a = new A();";
+ JProgram program = compileSnippet("void", snippet, false);
+ JClassType lambdaOuter = (JClassType) getType(program, "test.EntryPoint$A$1B$1$1");
+ assertNotNull(lambdaOuter);
+
+ JMethod lambdaOuterMethod = findMethod(lambdaOuter, "run");
+ assertNotNull(lambdaOuterMethod);
+ // checks that lambda initialization properly uses synthetic fields from outer class
+ assertEquals(
+ "public void run(){"
+ + "Lambda l=new EntryPoint$A$1B$1$1$lambda$0$Type(this.val$x2,this.val$arg3);"
+ + "this.val$x2[0]=((Integer)l.run(1,2)).intValue();"
+ + "}",
+ formatSource(lambdaOuterMethod.toSource()));
+ }
+
public void testLambdaNestingCaptureField() throws Exception {
addSnippetClassDecl("interface Inner {\n" +
" void f();\n" +
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 e9774f8..5f51cea 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
@@ -804,6 +804,190 @@
assertEquals(10, success[0]);
}
+ public void testLambdaNestingInAnonymousCaptureLocal() {
+ int[] x = new int[] {42};
+ new Runnable() {
+ public void run() {
+ Lambda<Integer> l = (a, b) -> x[0] = x[0] + a + b;
+ l.run(1, 2);
+ }
+ }.run();
+ assertEquals(45, x[0]);
+ }
+
+ public void testLambdaNestingInMultipleMixedAnonymousCaptureLocal() {
+ // checks that lambda has access to local variable and arguments when placed in mixed scopes
+ // Local Class -> Local Class -> Local Anonymous -> lambda -> Local Anonymous
+ class A {
+ int a() {
+ int[] x = new int[] {42};
+ class B {
+ void b() {
+ I i = new I() {
+ public int foo(Integer arg) {
+ Runnable r = () -> {
+ new Runnable() {
+ public void run() {
+ Lambda<Integer> l = (a, b) -> x[0] = x[0] + a + b + arg;
+ l.run(1, 2);
+ }
+ }.run();
+ };
+ r.run();
+ return x[0];
+ }
+ };
+ i.foo(1);
+ }
+ }
+ B b = new B();
+ b.b();
+ return x[0];
+ }
+ }
+ A a = new A();
+ assertEquals(46, a.a());
+ }
+
+ public void testLambdaNestingInMultipleMixedAnonymousCaptureLocal_withInterference() {
+ // checks that lambda has access to NEAREST local variable and arguments when placed in mixed
+ // scopes Local Class -> Local Class -> Local Anonymous -> lambda -> Local Anonymous
+ class A {
+ int a() {
+ int[] x = new int[] {42};
+ class B {
+ int b() {
+ int[] x = new int[] {22};
+ I i = new I() {
+ public int foo(Integer arg) {
+ Runnable r = () -> {
+ new Runnable() {
+ public void run() {
+ Lambda<Integer> l = (a, b) -> x[0] = x[0] + a + b + arg;
+ l.run(1, 2);
+ }
+ }.run();
+ };
+ r.run();
+ return x[0];
+ }
+ };
+ return i.foo(1);
+ }
+ }
+ B b = new B();
+ return b.b();
+ }
+ }
+ A a = new A();
+ assertEquals(26, a.a());
+ }
+
+ public void testLambdaNestingInMultipleMixedAnonymousCaptureLocalAndField() {
+ // checks that lambda has access to local variable, field and arguments when placed in mixed
+ // scopes - Local Class -> Local Class -> Local Anonymous -> lambda -> Local Anonymous
+ class A {
+ int fA = 1;
+
+ int a() {
+ int[] x = new int[] {42};
+ class B {
+ int fB = 2;
+
+ int b() {
+ I i = new I() {
+ int fI = 3;
+
+ public int foo(Integer arg) {
+ Runnable r = () -> {
+ new Runnable() {
+ public void run() {
+ Lambda<Integer> l = (a, b) -> x[0] = x[0] + a + b + arg + fA + fB + fI;
+ l.run(1, 2);
+ }
+ }.run();
+ };
+ r.run();
+ return x[0];
+ }
+ };
+ return i.foo(1);
+ }
+ }
+ B b = new B();
+ return b.b();
+ }
+ }
+ A a = new A();
+ assertEquals(52, a.a());
+ }
+
+ public void testLambdaNestingInMultipleAnonymousCaptureLocal() {
+ // checks that lambda has access to local variable and arguments when placed in local anonymous
+ // class with multile nesting
+ int[] x = new int[] {42};
+ int result = new I() {
+ public int foo(Integer i1) {
+ return new I() {
+ public int foo(Integer i2) {
+ return new I() {
+ public int foo(Integer i3) {
+ Lambda<Integer> l = (a, b) -> x[0] = x[0] + a + b + i1 + i2 + i3;
+ return l.run(1, 2);
+ }
+ }.foo(3);
+ }
+ }.foo(2);
+ }
+ }.foo(1);
+ assertEquals(51, x[0]);
+ }
+
+ static class TestLambda_ClassA {
+ int[] f = new int[] {42};
+
+ class B {
+ void m() {
+ Runnable r = () -> f[0] = f[0] + 1;
+ r.run();
+ }
+ }
+
+ int a() {
+ B b = new B();
+ b.m();
+ return f[0];
+ }
+ }
+
+ public void testLambdaNestingCaptureField_InnerClassCapturingOuterClassVariable() {
+ TestLambda_ClassA a = new TestLambda_ClassA();
+ assertEquals(43, a.a());
+ }
+
+ public void testInnerClassCaptureLocalFromOuterLambda() {
+ int[] x = new int[] {42};
+ Lambda<Integer> l = (a, b) -> {
+ int[] x1 = new int[] {32};
+ Lambda<Integer> r = (rA, rB) -> {
+ int[] x2 = new int[] {22};
+ I i = new I() {
+ public int foo(Integer arg) {
+ x1[0] = x1[0] + 1;
+ x[0] = x[0] + 1;
+ return x2[0] = x2[0] + rA + rB + a + b;
+ }
+ };
+ return i.foo(1);
+ };
+ return r.run(3, 4) + x1[0];
+ };
+
+ // x1[0](32) + 1 + x2[0](22) + rA(3) + rB(4) + a(1) + b(2)
+ assertEquals(65, l.run(1, 2).intValue());
+ assertEquals(43, x[0]);
+ }
+
static class TestLambda_Class {
public int[] s = new int[] {0};
public void call(TestLambda_Outer a) {
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 3731e42..e2c8084 100644
--- a/user/test/com/google/gwt/dev/jjs/test/Java8Test.java
+++ b/user/test/com/google/gwt/dev/jjs/test/Java8Test.java
@@ -158,6 +158,27 @@
public void testLambdaNestingCaptureLocal() {
}
+ public void testLambdaNestingInAnonymousCaptureLocal() {
+ }
+
+ public void testLambdaNestingInMultipleMixedAnonymousCaptureLocal() {
+ }
+
+ public void testLambdaNestingInMultipleMixedAnonymousCaptureLocal_withInterference() {
+ }
+
+ public void testLambdaNestingInMultipleMixedAnonymousCaptureLocalAndField() {
+ }
+
+ public void testLambdaNestingInMultipleAnonymousCaptureLocal() {
+ }
+
+ public void testLambdaNestingCaptureField_InnerClassCapturingOuterClassVariable() {
+ }
+
+ public void testInnerClassCaptureLocalFromOuterLambda() {
+ }
+
public void testLambdaNestingCaptureField() {
}