From ed59ac47a8f2e04ab51c52fa2d6bd28fcfefe153 Mon Sep 17 00:00:00 2001 From: Colin Alworth Date: Tue, 7 Jan 2025 20:14:55 -0600 Subject: [PATCH] Add ast and compilation tests for lambdas in this()/super() calls --- .../google/gwt/dev/jjs/impl/Java8AstTest.java | 82 +++++++++++++++++++ .../google/gwt/dev/jjs/test/Java8Test.java | 33 ++++++++ 2 files changed, 115 insertions(+) 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 ac51d85ae8f..1db1a5b4895 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 @@ -257,6 +257,88 @@ public void testCompileLambdaCaptureLocalAndField() throws Exception { formatSource(samMethod.toSource())); } + public void testCompileLambdaOuterFieldCaptureInConstructor() throws Exception { + addSnippetClassDecl("int y = 22;"); + addSnippetClassDecl("class Foo {" + + "Foo(Runnable r) {}" + + "Foo() {" + + "this(() -> {y = 42;});" + + "}" + + "}"); + + JProgram program = compileSnippet("void", "new Foo();", false); + JDeclaredType entrypointType = program.getFromTypeMap("test.EntryPoint"); + + JMethod lambda = findMethod(program.getFromTypeMap("test.EntryPoint$Foo"), "lambda$0"); + assertNotNull(lambda); + assertEquals("{{this$0.y=42;}}", formatSource(lambda.getBody().toSource())); + + // Lambda closes over inner class's this$0 reference - lambda type constructor takes that + // parameter and assigns to a field. + JClassType lambdaInnerClass = (JClassType) getType(program, "test.EntryPoint$Foo$lambda$0$Type"); + assertNotNull(lambdaInnerClass); + + JMethod ctor = findMethod(lambdaInnerClass, "EntryPoint$Foo$lambda$0$Type"); + assertTrue(ctor instanceof JConstructor); + System.out.println(ctor.getOriginalParamTypes()); + assertEquals(1, ctor.getParams().size()); + assertEquals(entrypointType, ctor.getOriginalParamTypes().get(0)); + + assertEquals(1, lambdaInnerClass.getFields().size()); + assertEquals(entrypointType, lambdaInnerClass.getFields().get(0).getType()); + + // interface impl passes this.this$0 to the actual lambda method as a parameter + assertEquals("{this.this$0=this$0;}", formatSource(ctor.getBody().toSource())); + + // abstract method implementation passes field to lambda method + JMethod samMethod = findMethod(lambdaInnerClass, "run"); + assertNotNull(samMethod); + assertEquals("{EntryPoint$Foo.lambda$0(this.this$0);}", formatSource(samMethod.getBody().toSource())); + } + + public void testCompileLambdaOuterFieldCaptureInConstructorSuper() throws Exception { + addSnippetClassDecl("int y = 22;"); + addSnippetClassDecl("class Foo {" + + "Foo(Runnable r) {}" + + "}" + + "class Bar extends Foo {" + + "Bar() {" + + "super(() -> {y = 42;});" + + "}" + + "}"); + + JProgram program = compileSnippet("void", "new Bar();", false); + JDeclaredType entrypointType = program.getFromTypeMap("test.EntryPoint"); + + JMethod lambda = findMethod(program.getFromTypeMap("test.EntryPoint$Bar"), "lambda$0"); + assertNotNull(lambda); + assertEquals(1, lambda.getParams().size()); + assertEquals(entrypointType, lambda.getOriginalParamTypes().get(0)); + assertEquals("{{this$0.y=42;}}", formatSource(lambda.getBody().toSource())); + + // Lambda closes over inner class's this$0 reference - lambda type constructor takes that + // parameter and assigns to a field. + JClassType lambdaInnerClass = (JClassType) getType(program, "test.EntryPoint$Bar$lambda$0$Type"); + assertNotNull(lambdaInnerClass); + + JMethod ctor = findMethod(lambdaInnerClass, "EntryPoint$Bar$lambda$0$Type"); + assertTrue(ctor instanceof JConstructor); + System.out.println(ctor.getOriginalParamTypes()); + assertEquals(1, ctor.getParams().size()); + assertEquals(entrypointType, ctor.getOriginalParamTypes().get(0)); + + assertEquals(1, lambdaInnerClass.getFields().size()); + assertEquals(entrypointType, lambdaInnerClass.getFields().get(0).getType()); + + // interface impl passes this.this$0 to the actual lambda method as a parameter + assertEquals("{this.this$0=this$0;}", formatSource(ctor.getBody().toSource())); + + // abstract method implementation passes field to lambda method + JMethod samMethod = findMethod(lambdaInnerClass, "run"); + assertNotNull(samMethod); + assertEquals("{EntryPoint$Bar.lambda$0(this.this$0);}", formatSource(samMethod.getBody().toSource())); + } + // make sure nested scoping of identically named variables works public void testCompileLambdaCaptureOuterInnerField() throws Exception { addSnippetClassDecl("private int y = 22;"); 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 81ac455d8bb..e1253f1cb3d 100644 --- a/user/test/com/google/gwt/dev/jjs/test/Java8Test.java +++ b/user/test/com/google/gwt/dev/jjs/test/Java8Test.java @@ -252,6 +252,39 @@ public void testLambdaCaptureLocalAndFieldWithInnerClass() { assertEquals(82, new AcceptsLambda().accept(l).intValue()); } + class CtorAcceptsLambda { + CtorAcceptsLambda() { + this(() -> local = -1); + } + CtorAcceptsLambda(Runnable lambda) { + lambda.run(); + } + } + + public void testCompileLambdaOuterFieldCaptureInConstructor() { + assertEquals(42, local); + new CtorAcceptsLambda(); + assertEquals(-1, local); + } + + abstract class AbstractCtorAcceptsLambda { + AbstractCtorAcceptsLambda(Runnable lambda) { + lambda.run(); + } + } + + class CtorAcceptsLambdaSubtype extends AbstractCtorAcceptsLambda { + CtorAcceptsLambdaSubtype() { + super(() -> local = -1); + } + } + + public void testCompileLambdaOuterFieldCaptureInConstructorSuper() throws Exception { + assertEquals(42, local); + new CtorAcceptsLambdaSubtype(); + assertEquals(-1, local); + } + public void testCompileLambdaCaptureOuterInnerField() throws Exception { new Inner().run(); }