From 7576a7a0afa1bbb6adf9c8be0ad422d99cab4293 Mon Sep 17 00:00:00 2001 From: Michael Williamson Date: Tue, 1 Mar 2016 23:10:04 +0000 Subject: Enable nested class generation with existing constructor --- .../org/zwobble/couscous/ast/ConstructorNode.java | 2 +- .../couscous/frontends/java/JavaReader.java | 34 ++++++++++++++++------ .../org/zwobble/couscous/tests/CompilerTests.java | 12 ++++++++ .../com/example/InnerClass.java | 30 +++++++++++++++++++ 4 files changed, 68 insertions(+), 10 deletions(-) create mode 100644 src/test/resources/java/inner-class-with-constructor-args/com/example/InnerClass.java diff --git a/src/main/java/org/zwobble/couscous/ast/ConstructorNode.java b/src/main/java/org/zwobble/couscous/ast/ConstructorNode.java index a25a993..1bd33d0 100644 --- a/src/main/java/org/zwobble/couscous/ast/ConstructorNode.java +++ b/src/main/java/org/zwobble/couscous/ast/ConstructorNode.java @@ -18,7 +18,7 @@ public class ConstructorNode implements Node { private final List arguments; private final List body; - private ConstructorNode( + public ConstructorNode( List arguments, List body) { this.arguments = arguments; 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 6772fdc..d3a763b 100644 --- a/src/main/java/org/zwobble/couscous/frontends/java/JavaReader.java +++ b/src/main/java/org/zwobble/couscous/frontends/java/JavaReader.java @@ -36,6 +36,7 @@ import static org.zwobble.couscous.util.ExtraLists.*; import static org.zwobble.couscous.util.ExtraMaps.lookup; public class JavaReader { + // TODO: de-dupe captures (multiple reference nodes may reference the same free variable) public static List readClassFromFile(List sourcePaths, Path sourcePath) throws IOException { CompilationUnit ast = new JavaParser().parseCompilationUnit(sourcePaths, sourcePath); @@ -73,8 +74,7 @@ public class JavaReader { .transformExpression(expression -> tryCast(ConstructorCallNode.class, expression) .flatMap(call -> lookup(nestedClasses, call.getType()).map(captures -> - // TODO: handle existing arguments - new ConstructorCallNode(call.getType(), captures)))) + new ConstructorCallNode(call.getType(), ExtraLists.concat(captures, call.getArguments()))))) .build(); return eagerMap(classes, classNode -> classNode.transform(transformer)); } @@ -146,7 +146,12 @@ public class JavaReader { return eagerMap(body, statement -> statement.transform(transformer)); } - private ConstructorNode buildConstructor(Scope outerScope, TypeName type, List freeVariables) { + private ConstructorNode buildConstructor( + Scope outerScope, + TypeName type, + List freeVariables, + ConstructorNode existing) + { Scope scope = outerScope.enterConstructor(); ImmutableList.Builder arguments = ImmutableList.builder(); @@ -158,7 +163,10 @@ public class JavaReader { body.add(assignStatement(captureAccess(type, variable), reference(argument))); } - return constructor(arguments.build(), body.build()); + arguments.addAll(existing.getArguments()); + body.addAll(existing.getBody()); + + return new ConstructorNode(arguments.build(), body.build()); } private FieldAccessNode captureAccess(TypeName type, CapturedVariable freeVariable) { @@ -197,9 +205,12 @@ public class JavaReader { Scope scope, ClassNode classNode ) { - List freeVariables = findFreeVariables(ExtraLists.concat( - classNode.getFields(), - classNode.getMethods())); + List freeVariables = eagerFilter( + findFreeVariables(ExtraLists.concat( + classNode.getFields(), + classNode.getMethods())), + // TODO: check this references work correctly in anonymous classes + variable -> !isThisReference(classNode.getName(), variable)); List capturedVariables = ImmutableList.copyOf(transform( freeVariables, freeVariable -> new CapturedVariable(freeVariable, fieldForCapture(freeVariable)))); @@ -215,13 +226,18 @@ public class JavaReader { fields, // TODO: handle static constructor (or codify that this is a case that never needs handling) list(), - // TODO: handle existing constructor - buildConstructor(scope, classNode.getName(), capturedVariables), + buildConstructor(scope, classNode.getName(), capturedVariables, classNode.getConstructor()), eagerMap(classNode.getMethods(), method -> method.mapBody(body -> replaceCaptureReferences(classNode.getName(), body, capturedVariables)))); return new GeneratedClosure(generatedClass, freeVariables); } + private boolean isThisReference(TypeName name, ReferenceNode reference) { + return tryCast(ThisReferenceNode.class, reference) + .map(node -> node.getType().equals(name)) + .orElse(false); + } + private GeneratedClosure classWithCapture( Scope scope, TypeName className, diff --git a/src/test/java/org/zwobble/couscous/tests/CompilerTests.java b/src/test/java/org/zwobble/couscous/tests/CompilerTests.java index 3641f33..2560695 100644 --- a/src/test/java/org/zwobble/couscous/tests/CompilerTests.java +++ b/src/test/java/org/zwobble/couscous/tests/CompilerTests.java @@ -110,6 +110,18 @@ public abstract class CompilerTests { IntegerValue.REF)); } + @Test + public void innerClassesWithConstructorArguments() throws Exception { + assertEquals( + value(42), + execTestProgram( + "inner-class-with-constructor-args", + TypeName.of("com.example.InnerClass"), + "run", + list(), + IntegerValue.REF)); + } + @Test public void canImplementUserDefinedInterface() throws Exception { assertEquals( diff --git a/src/test/resources/java/inner-class-with-constructor-args/com/example/InnerClass.java b/src/test/resources/java/inner-class-with-constructor-args/com/example/InnerClass.java new file mode 100644 index 0000000..a39a568 --- /dev/null +++ b/src/test/resources/java/inner-class-with-constructor-args/com/example/InnerClass.java @@ -0,0 +1,30 @@ +package com.example; + +public class InnerClass { + private final int stash; + + public static int run() { + return new InnerClass(21).value(); + } + + public InnerClass(int stash) { + this.stash = stash; + } + + public int value() { + IntSupplier supplier = new IntSupplier(2); + return supplier.get(); + } + + private class IntSupplier { + private final int factor; + + public IntSupplier(int factor) { + this.factor = factor; + } + + public int get() { + return stash * factor; + } + } +} -- cgit v1.2.3