From 9be8eaaa3a8bd585a4448854b1f8a97644636ade Mon Sep 17 00:00:00 2001 From: Michael Williamson Date: Sat, 28 Nov 2015 18:05:13 +0000 Subject: Add variable capturing for anonymous classes --- .../org/zwobble/couscous/ast/ClassNodeBuilder.java | 6 ++- .../frontends/java/JavaExpressionReader.java | 18 +++++--- .../couscous/frontends/java/JavaReader.java | 49 +++++++++++++++------- .../zwobble/couscous/tests/JavaToPythonTests.java | 11 +++++ .../com/example/AnonymousClass.java | 16 +++++++ 5 files changed, 78 insertions(+), 22 deletions(-) create mode 100644 src/test/resources/java/anonymous-class-capture/com/example/AnonymousClass.java diff --git a/src/main/java/org/zwobble/couscous/ast/ClassNodeBuilder.java b/src/main/java/org/zwobble/couscous/ast/ClassNodeBuilder.java index 80e8eab..75aa5a3 100644 --- a/src/main/java/org/zwobble/couscous/ast/ClassNodeBuilder.java +++ b/src/main/java/org/zwobble/couscous/ast/ClassNodeBuilder.java @@ -22,7 +22,11 @@ public class ClassNodeBuilder { } public ClassNodeBuilder field(String name, TypeName type) { - this.fields.add(FieldDeclarationNode.field(name, type)); + return field(FieldDeclarationNode.field(name, type)); + } + + public ClassNodeBuilder field(FieldDeclarationNode field) { + this.fields.add(field); return this; } diff --git a/src/main/java/org/zwobble/couscous/frontends/java/JavaExpressionReader.java b/src/main/java/org/zwobble/couscous/frontends/java/JavaExpressionReader.java index 83e321b..c864371 100644 --- a/src/main/java/org/zwobble/couscous/frontends/java/JavaExpressionReader.java +++ b/src/main/java/org/zwobble/couscous/frontends/java/JavaExpressionReader.java @@ -17,6 +17,8 @@ import static org.zwobble.couscous.ast.VariableDeclaration.var; import static org.zwobble.couscous.ast.VariableReferenceNode.reference; import static org.zwobble.couscous.frontends.java.JavaOperators.readOperator; import static org.zwobble.couscous.frontends.java.JavaTypes.typeOf; +import static org.zwobble.couscous.util.ExtraLists.concat; +import static org.zwobble.couscous.util.ExtraLists.eagerMap; public class JavaExpressionReader { private final JavaReader javaReader; @@ -194,8 +196,11 @@ public class JavaExpressionReader { List arguments = readArguments(constructor, javaArguments); if (constructor.getDeclaringClass().isAnonymous()) { - TypeName anonymousClassName = javaReader.readAnonymousClass(expression.getAnonymousClassDeclaration()); - return constructorCall(anonymousClassName, arguments); + GeneratedClosure closure = + javaReader.readAnonymousClass(expression.getAnonymousClassDeclaration()); + return constructorCall( + closure.getType(), + concat(captureArguments(closure), arguments)); } else { return constructorCall(typeOf(expression), arguments); } @@ -205,10 +210,11 @@ public class JavaExpressionReader { GeneratedClosure closure = javaReader.readLambda(expression); return constructorCall( closure.getType(), - closure.getCaptures() - .stream() - .map(VariableReferenceNode::reference) - .collect(Collectors.toList())); + captureArguments(closure)); + } + + private List captureArguments(GeneratedClosure closure) { + return eagerMap(closure.getCaptures(), VariableReferenceNode::reference); } private List readArguments(IMethodBinding method, List javaArguments) { diff --git a/src/main/java/org/zwobble/couscous/frontends/java/JavaReader.java b/src/main/java/org/zwobble/couscous/frontends/java/JavaReader.java index 7d0e902..c8dc854 100644 --- a/src/main/java/org/zwobble/couscous/frontends/java/JavaReader.java +++ b/src/main/java/org/zwobble/couscous/frontends/java/JavaReader.java @@ -55,16 +55,7 @@ public class JavaReader { lambda.getFormalArguments().stream(), lambda.getBody().stream()).collect(Collectors.toList())); - // TODO: rewrite the AST to reference field directly rather than assigning - // This is especially bad since we use the same declaration in three places - // (the original declaration, the captured field, and the local to alias the field) - List restoreCaptures = eagerMap(freeVariables, freeVariable -> - localVariableDeclaration( - freeVariable, - fieldAccess( - thisReference(TypeName.of(className)), - freeVariable.getName(), - freeVariable.getType()))); + List restoreCaptures = generateCaptureRestoration(className, freeVariables); MethodNode method = MethodNode.method( Collections.emptyList(), @@ -82,6 +73,19 @@ public class JavaReader { return new GeneratedClosure(classNode.getName(), freeVariables); } + private List generateCaptureRestoration(String className, List freeVariables) { + // TODO: rewrite the AST to reference field directly rather than assigning + // This is especially bad since we use the same declaration in three places + // (the original declaration, the captured field, and the local to alias the field) + return eagerMap(freeVariables, freeVariable -> + localVariableDeclaration( + freeVariable, + fieldAccess( + thisReference(TypeName.of(className)), + freeVariable.getName(), + freeVariable.getType()))); + } + private ConstructorNode buildConstructor(TypeName type, List freeVariables) { // TODO: generate fresh variables for captures List arguments = freeVariables.stream() @@ -99,11 +103,26 @@ public class JavaReader { return generateClassName(expression.resolveMethodBinding().getDeclaringClass()); } - TypeName readAnonymousClass(AnonymousClassDeclaration declaration) { - String name = generateClassName(declaration); - ClassNode classNode = readTypeDeclarationBody(name, declaration.bodyDeclarations()); - classes.add(classNode); - return classNode.getName(); + GeneratedClosure readAnonymousClass(AnonymousClassDeclaration declaration) { + String className = generateClassName(declaration); + ClassNode classNode = readTypeDeclarationBody(className, declaration.bodyDeclarations()); + List freeVariables = findFreeVariables(asList(classNode)); + List restoreCaptures = generateCaptureRestoration(className, freeVariables); + + // TODO: map over methods rather than rebuilding entire class + ClassNodeBuilder classBuilder = new ClassNodeBuilder(className); + classBuilder.constructor(buildConstructor(TypeName.of(className), freeVariables)); + classNode.getFields().forEach(classBuilder::field); + classNode.getMethods().forEach(method -> + classBuilder.method(MethodNode.method( + method.getAnnotations(), + method.isStatic(), + method.getName(), + method.getArguments(), + concat(restoreCaptures, method.getBody())))); + + classes.add(classBuilder.build()); + return new GeneratedClosure(classNode.getName(), freeVariables); } private String generateClassName(AnonymousClassDeclaration declaration) { diff --git a/src/test/java/org/zwobble/couscous/tests/JavaToPythonTests.java b/src/test/java/org/zwobble/couscous/tests/JavaToPythonTests.java index bd2fe8f..3b9c490 100644 --- a/src/test/java/org/zwobble/couscous/tests/JavaToPythonTests.java +++ b/src/test/java/org/zwobble/couscous/tests/JavaToPythonTests.java @@ -92,6 +92,17 @@ public class JavaToPythonTests { asList())); } + @Test + public void anonymousClassesWithCapture() throws Exception { + assertEquals( + value(42), + execTestProgram( + "anonymous-class-capture", + TypeName.of("com.example.AnonymousClass"), + "value", + asList(value(42)))); + } + @Test public void lambdasWithoutCapture() throws Exception { assertEquals( diff --git a/src/test/resources/java/anonymous-class-capture/com/example/AnonymousClass.java b/src/test/resources/java/anonymous-class-capture/com/example/AnonymousClass.java new file mode 100644 index 0000000..65cd503 --- /dev/null +++ b/src/test/resources/java/anonymous-class-capture/com/example/AnonymousClass.java @@ -0,0 +1,16 @@ +package com.example; + +import java.util.function.Supplier; + +public class AnonymousClass { + public static int value(int x) { + Supplier supplier = new Supplier() { + @Override + public Integer get() { + return x; + } + }; + + return supplier.get(); + } +} -- cgit v1.2.3