Skip to content

Commit f1c4578

Browse files
authored
Allow lambdas in this()/super() to close over outer types (#10078)
Prior to JDT 3.32, passing a lambda that closes over outer types to this()/super() constructors would result in a compiler error. As of around 3.32 this is now allowed, but the closed-over instance is passed as an item in a "synthetic outer local variable" collection. Each instance in that collection has a field representing the "actual outer local variable", and in this case, that field is null. Added tests to confirm that the generated code seems consistent, and that running such a lambda affects the expected instance. Fixes #10065
1 parent 843f62a commit f1c4578

File tree

3 files changed

+121
-1
lines changed

3 files changed

+121
-1
lines changed

dev/core/src/com/google/gwt/dev/jjs/impl/GwtAstBuilder.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1267,7 +1267,12 @@ private void pushLambdaExpressionLocalsIntoMethodScope(LambdaExpression x,
12671267
if (syntheticArguments != null) {
12681268
MethodScope scope = x.getScope();
12691269
for (SyntheticArgumentBinding sa : syntheticArguments) {
1270-
VariableBinding[] path = scope.getEmulationPath(sa.actualOuterLocalVariable);
1270+
VariableBinding[] path;
1271+
if (sa.actualOuterLocalVariable == null) {
1272+
path = scope.getEmulationPath(sa);
1273+
} else {
1274+
path = scope.getEmulationPath(sa.actualOuterLocalVariable);
1275+
}
12711276
assert path.length == 1 && path[0] instanceof LocalVariableBinding;
12721277
JParameter param = it.next();
12731278
curMethod.locals.put((LocalVariableBinding) path[0], param);

dev/core/test/com/google/gwt/dev/jjs/impl/Java8AstTest.java

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,88 @@ public void testCompileLambdaCaptureLocalAndField() throws Exception {
257257
formatSource(samMethod.toSource()));
258258
}
259259

260+
public void testCompileLambdaOuterFieldCaptureInConstructor() throws Exception {
261+
addSnippetClassDecl("int y = 22;");
262+
addSnippetClassDecl("class Foo {" +
263+
"Foo(Runnable r) {}" +
264+
"Foo() {" +
265+
"this(() -> {y = 42;});" +
266+
"}" +
267+
"}");
268+
269+
JProgram program = compileSnippet("void", "new Foo();", false);
270+
JDeclaredType entrypointType = program.getFromTypeMap("test.EntryPoint");
271+
272+
JMethod lambda = findMethod(program.getFromTypeMap("test.EntryPoint$Foo"), "lambda$0");
273+
assertNotNull(lambda);
274+
assertEquals("{{this$0.y=42;}}", formatSource(lambda.getBody().toSource()));
275+
276+
// Lambda closes over inner class's this$0 reference - lambda type constructor takes that
277+
// parameter and assigns to a field.
278+
JClassType lambdaInnerClass = (JClassType) getType(program, "test.EntryPoint$Foo$lambda$0$Type");
279+
assertNotNull(lambdaInnerClass);
280+
281+
JMethod ctor = findMethod(lambdaInnerClass, "EntryPoint$Foo$lambda$0$Type");
282+
assertTrue(ctor instanceof JConstructor);
283+
System.out.println(ctor.getOriginalParamTypes());
284+
assertEquals(1, ctor.getParams().size());
285+
assertEquals(entrypointType, ctor.getOriginalParamTypes().get(0));
286+
287+
assertEquals(1, lambdaInnerClass.getFields().size());
288+
assertEquals(entrypointType, lambdaInnerClass.getFields().get(0).getType());
289+
290+
// interface impl passes this.this$0 to the actual lambda method as a parameter
291+
assertEquals("{this.this$0=this$0;}", formatSource(ctor.getBody().toSource()));
292+
293+
// abstract method implementation passes field to lambda method
294+
JMethod samMethod = findMethod(lambdaInnerClass, "run");
295+
assertNotNull(samMethod);
296+
assertEquals("{EntryPoint$Foo.lambda$0(this.this$0);}", formatSource(samMethod.getBody().toSource()));
297+
}
298+
299+
public void testCompileLambdaOuterFieldCaptureInConstructorSuper() throws Exception {
300+
addSnippetClassDecl("int y = 22;");
301+
addSnippetClassDecl("class Foo {" +
302+
"Foo(Runnable r) {}" +
303+
"}" +
304+
"class Bar extends Foo {" +
305+
"Bar() {" +
306+
"super(() -> {y = 42;});" +
307+
"}" +
308+
"}");
309+
310+
JProgram program = compileSnippet("void", "new Bar();", false);
311+
JDeclaredType entrypointType = program.getFromTypeMap("test.EntryPoint");
312+
313+
JMethod lambda = findMethod(program.getFromTypeMap("test.EntryPoint$Bar"), "lambda$0");
314+
assertNotNull(lambda);
315+
assertEquals(1, lambda.getParams().size());
316+
assertEquals(entrypointType, lambda.getOriginalParamTypes().get(0));
317+
assertEquals("{{this$0.y=42;}}", formatSource(lambda.getBody().toSource()));
318+
319+
// Lambda closes over inner class's this$0 reference - lambda type constructor takes that
320+
// parameter and assigns to a field.
321+
JClassType lambdaInnerClass = (JClassType) getType(program, "test.EntryPoint$Bar$lambda$0$Type");
322+
assertNotNull(lambdaInnerClass);
323+
324+
JMethod ctor = findMethod(lambdaInnerClass, "EntryPoint$Bar$lambda$0$Type");
325+
assertTrue(ctor instanceof JConstructor);
326+
System.out.println(ctor.getOriginalParamTypes());
327+
assertEquals(1, ctor.getParams().size());
328+
assertEquals(entrypointType, ctor.getOriginalParamTypes().get(0));
329+
330+
assertEquals(1, lambdaInnerClass.getFields().size());
331+
assertEquals(entrypointType, lambdaInnerClass.getFields().get(0).getType());
332+
333+
// interface impl passes this.this$0 to the actual lambda method as a parameter
334+
assertEquals("{this.this$0=this$0;}", formatSource(ctor.getBody().toSource()));
335+
336+
// abstract method implementation passes field to lambda method
337+
JMethod samMethod = findMethod(lambdaInnerClass, "run");
338+
assertNotNull(samMethod);
339+
assertEquals("{EntryPoint$Bar.lambda$0(this.this$0);}", formatSource(samMethod.getBody().toSource()));
340+
}
341+
260342
// make sure nested scoping of identically named variables works
261343
public void testCompileLambdaCaptureOuterInnerField() throws Exception {
262344
addSnippetClassDecl("private int y = 22;");

user/test/com/google/gwt/dev/jjs/test/Java8Test.java

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -252,6 +252,39 @@ public void testLambdaCaptureLocalAndFieldWithInnerClass() {
252252
assertEquals(82, new AcceptsLambda<Integer>().accept(l).intValue());
253253
}
254254

255+
class CtorAcceptsLambda {
256+
CtorAcceptsLambda() {
257+
this(() -> local = -1);
258+
}
259+
CtorAcceptsLambda(Runnable lambda) {
260+
lambda.run();
261+
}
262+
}
263+
264+
public void testCompileLambdaOuterFieldCaptureInConstructor() {
265+
assertEquals(42, local);
266+
new CtorAcceptsLambda();
267+
assertEquals(-1, local);
268+
}
269+
270+
abstract class AbstractCtorAcceptsLambda {
271+
AbstractCtorAcceptsLambda(Runnable lambda) {
272+
lambda.run();
273+
}
274+
}
275+
276+
class CtorAcceptsLambdaSubtype extends AbstractCtorAcceptsLambda {
277+
CtorAcceptsLambdaSubtype() {
278+
super(() -> local = -1);
279+
}
280+
}
281+
282+
public void testCompileLambdaOuterFieldCaptureInConstructorSuper() throws Exception {
283+
assertEquals(42, local);
284+
new CtorAcceptsLambdaSubtype();
285+
assertEquals(-1, local);
286+
}
287+
255288
public void testCompileLambdaCaptureOuterInnerField() throws Exception {
256289
new Inner().run();
257290
}

0 commit comments

Comments
 (0)