diff options
| author | Michael Williamson <mike@zwobble.org> | 2016-10-30 17:21:10 +0000 |
|---|---|---|
| committer | Michael Williamson <mike@zwobble.org> | 2016-10-30 17:21:10 +0000 |
| commit | d58b27ee89a014aa294b25baffd53c7a96a64be3 (patch) | |
| tree | 6dcd100dd36c54b481dd49d07fab76bd747dc851 /src | |
| parent | befd7c344ba8a7c3402d4c678f1b81017fd65947 (diff) | |
Extract inner type hoisting to separate step
Diffstat (limited to 'src')
27 files changed, 577 insertions, 217 deletions
diff --git a/src/main/java/org/zwobble/couscous/ast/ClassNode.java b/src/main/java/org/zwobble/couscous/ast/ClassNode.java index 3a53cfd..322a32a 100644 --- a/src/main/java/org/zwobble/couscous/ast/ClassNode.java +++ b/src/main/java/org/zwobble/couscous/ast/ClassNode.java @@ -9,6 +9,9 @@ import org.zwobble.couscous.util.ExtraIterables; import java.util.List; import java.util.Set; +import static org.zwobble.couscous.util.ExtraLists.eagerFlatMap; +import static org.zwobble.couscous.util.ExtraLists.list; + public class ClassNode implements TypeNode { public static ClassNodeBuilder builder(ScalarType name) { return new ClassNodeBuilder(name); @@ -24,9 +27,11 @@ public class ClassNode implements TypeNode { List<FieldDeclarationNode> fields, List<StatementNode> staticConstructor, ConstructorNode constructor, - List<MethodNode> methods) + List<MethodNode> methods, + List<TypeNode> innerTypes + ) { - return new ClassNode(name, typeParameters, superTypes, fields, staticConstructor, constructor, methods); + return new ClassNode(name, typeParameters, superTypes, fields, staticConstructor, constructor, methods, innerTypes); } private final ScalarType name; @@ -36,7 +41,8 @@ public class ClassNode implements TypeNode { private final List<StatementNode> staticConstructor; private final ConstructorNode constructor; private final List<MethodNode> methods; - + private final List<TypeNode> innerTypes; + public ClassNode( ScalarType name, List<FormalTypeParameterNode> typeParameters, @@ -44,7 +50,9 @@ public class ClassNode implements TypeNode { List<FieldDeclarationNode> fields, List<StatementNode> staticConstructor, ConstructorNode constructor, - List<MethodNode> methodNodes) + List<MethodNode> methodNodes, + List<TypeNode> innerTypes + ) { this.name = name; this.typeParameters = typeParameters; @@ -53,6 +61,7 @@ public class ClassNode implements TypeNode { this.staticConstructor = staticConstructor; this.constructor = constructor; methods = methodNodes; + this.innerTypes = innerTypes; } @Override @@ -86,7 +95,12 @@ public class ClassNode implements TypeNode { public List<MethodNode> getMethods() { return methods; } - + + @Override + public List<TypeNode> getInnerTypes() { + return innerTypes; + } + public String getSimpleName() { return name.getSimpleName(); } @@ -114,7 +128,23 @@ public class ClassNode implements TypeNode { transformer.transformFields(fields), transformer.transformStatements(staticConstructor), transformer.transformConstructor(constructor), - transformer.transformMethods(methods)); + transformer.transformMethods(methods), + eagerFlatMap(innerTypes, transformer::transformTypeDeclaration) + ); + } + + @Override + public TypeNode stripInnerTypes() { + return new ClassNode( + name, + typeParameters, + superTypes, + fields, + staticConstructor, + constructor, + methods, + list() + ); } @Override @@ -127,6 +157,7 @@ public class ClassNode implements TypeNode { ", staticConstructor=" + staticConstructor + ", constructor=" + constructor + ", methods=" + methods + + ", innerTypes=" + innerTypes + ')'; } @@ -143,7 +174,8 @@ public class ClassNode implements TypeNode { if (!fields.equals(classNode.fields)) return false; if (!staticConstructor.equals(classNode.staticConstructor)) return false; if (!constructor.equals(classNode.constructor)) return false; - return methods.equals(classNode.methods); + if (!methods.equals(classNode.methods)) return false; + return innerTypes.equals(classNode.innerTypes); } @@ -156,6 +188,7 @@ public class ClassNode implements TypeNode { result = 31 * result + staticConstructor.hashCode(); result = 31 * result + constructor.hashCode(); result = 31 * result + methods.hashCode(); + result = 31 * result + innerTypes.hashCode(); return result; } } diff --git a/src/main/java/org/zwobble/couscous/ast/ClassNodeBuilder.java b/src/main/java/org/zwobble/couscous/ast/ClassNodeBuilder.java index 4735a2e..ec17c9b 100644 --- a/src/main/java/org/zwobble/couscous/ast/ClassNodeBuilder.java +++ b/src/main/java/org/zwobble/couscous/ast/ClassNodeBuilder.java @@ -24,6 +24,7 @@ public class ClassNodeBuilder { private List<StatementNode> staticConstructor; private Optional<ConstructorNode> constructor; private final ImmutableList.Builder<MethodNode> methods; + private final ImmutableList.Builder<TypeNode> innerTypes; public ClassNodeBuilder(ScalarType name) { this.name = name; @@ -33,6 +34,7 @@ public class ClassNodeBuilder { staticConstructor = list(); this.constructor = Optional.empty(); this.methods = ImmutableList.builder(); + this.innerTypes = ImmutableList.builder(); } public ClassNodeBuilder(String name) { @@ -119,7 +121,9 @@ public class ClassNodeBuilder { fields.build(), staticConstructor, constructor.orElse(ConstructorNode.DEFAULT), - methods.build()); + methods.build(), + innerTypes.build() + ); } public InterfaceNode buildInterface() { @@ -129,7 +133,8 @@ public class ClassNodeBuilder { superTypes.build(), fields.build(), staticConstructor, - methods.build() + methods.build(), + innerTypes.build() ); } diff --git a/src/main/java/org/zwobble/couscous/ast/EnumNode.java b/src/main/java/org/zwobble/couscous/ast/EnumNode.java index 92084dd..4a41a03 100644 --- a/src/main/java/org/zwobble/couscous/ast/EnumNode.java +++ b/src/main/java/org/zwobble/couscous/ast/EnumNode.java @@ -50,6 +50,11 @@ public class EnumNode implements TypeNode { } @Override + public List<TypeNode> getInnerTypes() { + return list(); + } + + @Override public int nodeType() { return NodeTypes.ENUM; } @@ -67,6 +72,11 @@ public class EnumNode implements TypeNode { } @Override + public TypeNode stripInnerTypes() { + return this; + } + + @Override public String toString() { return "EnumNode(" + "name=" + name + diff --git a/src/main/java/org/zwobble/couscous/ast/InterfaceNode.java b/src/main/java/org/zwobble/couscous/ast/InterfaceNode.java index c0444a4..3159d03 100644 --- a/src/main/java/org/zwobble/couscous/ast/InterfaceNode.java +++ b/src/main/java/org/zwobble/couscous/ast/InterfaceNode.java @@ -7,6 +7,9 @@ import org.zwobble.couscous.types.Type; import java.util.List; import java.util.Set; +import static org.zwobble.couscous.util.ExtraLists.eagerFlatMap; +import static org.zwobble.couscous.util.ExtraLists.list; + public class InterfaceNode implements TypeNode { public static InterfaceNode declareInterface( ScalarType name, @@ -14,9 +17,11 @@ public class InterfaceNode implements TypeNode { Set<Type> superTypes, List<FieldDeclarationNode> fields, List<StatementNode> staticConstructor, - List<MethodNode> methods) + List<MethodNode> methods, + List<TypeNode> innerTypes + ) { - return new InterfaceNode(name, typeParameters, superTypes, fields, staticConstructor, methods); + return new InterfaceNode(name, typeParameters, superTypes, fields, staticConstructor, methods, innerTypes); } private final ScalarType name; @@ -25,6 +30,7 @@ public class InterfaceNode implements TypeNode { private final List<FieldDeclarationNode> fields; private final List<StatementNode> staticConstructor; private final List<MethodNode> methods; + private final List<TypeNode> innerTypes; private InterfaceNode( ScalarType name, @@ -32,7 +38,9 @@ public class InterfaceNode implements TypeNode { Set<Type> superTypes, List<FieldDeclarationNode> fields, List<StatementNode> staticConstructor, - List<MethodNode> methods) + List<MethodNode> methods, + List<TypeNode> innerTypes + ) { this.name = name; this.typeParameters = typeParameters; @@ -40,6 +48,7 @@ public class InterfaceNode implements TypeNode { this.fields = fields; this.staticConstructor = staticConstructor; this.methods = methods; + this.innerTypes = innerTypes; } @Override @@ -71,6 +80,11 @@ public class InterfaceNode implements TypeNode { } @Override + public List<TypeNode> getInnerTypes() { + return innerTypes; + } + + @Override public int nodeType() { return NodeTypes.INTERFACE; } @@ -88,7 +102,22 @@ public class InterfaceNode implements TypeNode { transformer.transformTypes(superTypes), transformer.transformFields(fields), transformer.transformStatements(staticConstructor), - transformer.transformMethods(methods)); + transformer.transformMethods(methods), + eagerFlatMap(innerTypes, transformer::transformTypeDeclaration) + ); + } + + @Override + public TypeNode stripInnerTypes() { + return new InterfaceNode( + name, + typeParameters, + superTypes, + fields, + staticConstructor, + methods, + list() + ); } @Override @@ -100,6 +129,7 @@ public class InterfaceNode implements TypeNode { ", fields=" + fields + ", staticConstructor=" + staticConstructor + ", methods=" + methods + + ", innerTypes=" + innerTypes + ')'; } @@ -115,7 +145,8 @@ public class InterfaceNode implements TypeNode { if (!superTypes.equals(that.superTypes)) return false; if (!fields.equals(that.fields)) return false; if (!staticConstructor.equals(that.staticConstructor)) return false; - return methods.equals(that.methods); + if (!methods.equals(that.methods)) return false; + return innerTypes.equals(that.innerTypes); } @@ -127,6 +158,7 @@ public class InterfaceNode implements TypeNode { result = 31 * result + fields.hashCode(); result = 31 * result + staticConstructor.hashCode(); result = 31 * result + methods.hashCode(); + result = 31 * result + innerTypes.hashCode(); return result; } } diff --git a/src/main/java/org/zwobble/couscous/ast/TypeNode.java b/src/main/java/org/zwobble/couscous/ast/TypeNode.java index 9aa9cfb..01b033b 100644 --- a/src/main/java/org/zwobble/couscous/ast/TypeNode.java +++ b/src/main/java/org/zwobble/couscous/ast/TypeNode.java @@ -12,5 +12,7 @@ public interface TypeNode extends Node { List<FormalTypeParameterNode> getTypeParameters(); Set<Type> getSuperTypes(); List<MethodNode> getMethods(); + List<TypeNode> getInnerTypes(); TypeNode transformSubtree(NodeTransformer transformer); + TypeNode stripInnerTypes(); } diff --git a/src/main/java/org/zwobble/couscous/ast/sugar/TypeDeclarationBody.java b/src/main/java/org/zwobble/couscous/ast/sugar/TypeDeclarationBody.java index 381c521..99b7c06 100644 --- a/src/main/java/org/zwobble/couscous/ast/sugar/TypeDeclarationBody.java +++ b/src/main/java/org/zwobble/couscous/ast/sugar/TypeDeclarationBody.java @@ -19,6 +19,7 @@ public class TypeDeclarationBody { private final ImmutableList.Builder<StatementNode> instanceInitializers = ImmutableList.builder(); private ConstructorNode constructor = ConstructorNode.DEFAULT; private final ImmutableList.Builder<MethodNode> methods = ImmutableList.builder(); + private final ImmutableList.Builder<TypeNode> innerTypes = ImmutableList.builder(); private Builder() { } @@ -43,6 +44,10 @@ public class TypeDeclarationBody { this.methods.add(method); } + public void addInnerType(TypeNode typeDeclaration) { + this.innerTypes.add(typeDeclaration); + } + public TypeDeclarationBody build() { return new TypeDeclarationBody( fields.build(), @@ -50,7 +55,9 @@ public class TypeDeclarationBody { ConstructorNode.constructor( constructor.getArguments(), ExtraLists.concat(instanceInitializers.build(), constructor.getBody())), - methods.build()); + methods.build(), + innerTypes.build() + ); } } @@ -58,16 +65,21 @@ public class TypeDeclarationBody { private final List<StatementNode> staticConstructor; private final ConstructorNode constructor; private final List<MethodNode> methods; + private final List<TypeNode> innerTypes; public TypeDeclarationBody( List<FieldDeclarationNode> fields, - List<StatementNode> staticConstructor, ConstructorNode constructor, - List<MethodNode> methods) + List<StatementNode> staticConstructor, + ConstructorNode constructor, + List<MethodNode> methods, + List<TypeNode> innerTypes + ) { this.fields = fields; this.staticConstructor = staticConstructor; this.constructor = constructor; this.methods = methods; + this.innerTypes = innerTypes; } public List<FieldDeclarationNode> getFields() { @@ -85,4 +97,8 @@ public class TypeDeclarationBody { public List<MethodNode> getMethods() { return methods; } + + public List<TypeNode> getInnerTypes() { + return innerTypes; + } } diff --git a/src/main/java/org/zwobble/couscous/backends/csharp/CsharpCodeGenerator.java b/src/main/java/org/zwobble/couscous/backends/csharp/CsharpCodeGenerator.java index 47a7635..260d7ec 100644 --- a/src/main/java/org/zwobble/couscous/backends/csharp/CsharpCodeGenerator.java +++ b/src/main/java/org/zwobble/couscous/backends/csharp/CsharpCodeGenerator.java @@ -7,6 +7,7 @@ import org.zwobble.couscous.backends.csharp.primitives.CsharpPrimitiveMethods; import org.zwobble.couscous.backends.naming.Naming; import org.zwobble.couscous.transforms.DesugarForEachToFor; import org.zwobble.couscous.transforms.DesugarForToWhile; +import org.zwobble.couscous.transforms.HoistNestedTypes; import org.zwobble.couscous.transforms.SplitStaticsFromInterface; import org.zwobble.couscous.types.*; @@ -56,7 +57,7 @@ public class CsharpCodeGenerator { CsharpPrimitiveMethods.TRANSFORMER, nodeTransformer ), - types + HoistNestedTypes.hoist(types) ) ); } diff --git a/src/main/java/org/zwobble/couscous/backends/csharp/CsharpSerializer.java b/src/main/java/org/zwobble/couscous/backends/csharp/CsharpSerializer.java index 4ddeb7c..c2fa029 100644 --- a/src/main/java/org/zwobble/couscous/backends/csharp/CsharpSerializer.java +++ b/src/main/java/org/zwobble/couscous/backends/csharp/CsharpSerializer.java @@ -596,7 +596,7 @@ public class CsharpSerializer { } private void writeInNamespace(TypeNode typeNode, Action writeBody) { - writeInNamespace(typeNode.getName().getPackage().get(), writeBody); + writeInNamespace(typeNode.getName().getQualifier().get(), writeBody); } private void writeInNamespace(String namespace, Action writeBody) { diff --git a/src/main/java/org/zwobble/couscous/backends/python/PythonBackend.java b/src/main/java/org/zwobble/couscous/backends/python/PythonBackend.java index 5b6e819..6509fc4 100644 --- a/src/main/java/org/zwobble/couscous/backends/python/PythonBackend.java +++ b/src/main/java/org/zwobble/couscous/backends/python/PythonBackend.java @@ -8,6 +8,7 @@ import org.zwobble.couscous.ast.visitors.NodeTransformer; import org.zwobble.couscous.transforms.DesugarForEachToFor; import org.zwobble.couscous.transforms.DesugarForToWhile; import org.zwobble.couscous.transforms.DesugarSwitchToIfElse; +import org.zwobble.couscous.transforms.HoistNestedTypes; import java.io.File; import java.io.IOException; @@ -50,13 +51,15 @@ public class PythonBackend implements Backend { } private List<TypeNode> desugar(List<TypeNode> classes) { - return NodeTransformer.applyAll( - list( - DesugarSwitchToIfElse.transformer(), - DesugarForEachToFor.transformer(), - DesugarForToWhile.transformer() - ), - classes + return HoistNestedTypes.hoist( + NodeTransformer.applyAll( + list( + DesugarSwitchToIfElse.transformer(), + DesugarForEachToFor.transformer(), + DesugarForToWhile.transformer() + ), + classes + ) ); } diff --git a/src/main/java/org/zwobble/couscous/frontends/java/AnonymousType.java b/src/main/java/org/zwobble/couscous/frontends/java/AnonymousType.java index 845c6f9..5d5915d 100644 --- a/src/main/java/org/zwobble/couscous/frontends/java/AnonymousType.java +++ b/src/main/java/org/zwobble/couscous/frontends/java/AnonymousType.java @@ -2,6 +2,8 @@ package org.zwobble.couscous.frontends.java; import org.zwobble.couscous.types.Type; +import java.util.function.Function; + public class AnonymousType implements Type { public static Type anonymousType(String key) { return new AnonymousType(key); @@ -19,6 +21,11 @@ public class AnonymousType implements Type { } @Override + public Type transformSubTypes(Function<Type, Type> transform) { + throw new UnsupportedOperationException(); + } + + @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; diff --git a/src/main/java/org/zwobble/couscous/frontends/java/FreeVariables.java b/src/main/java/org/zwobble/couscous/frontends/java/FreeVariables.java index b904f0d..7679431 100644 --- a/src/main/java/org/zwobble/couscous/frontends/java/FreeVariables.java +++ b/src/main/java/org/zwobble/couscous/frontends/java/FreeVariables.java @@ -14,8 +14,6 @@ import java.util.stream.Stream; import static org.zwobble.couscous.ast.structure.NodeStructure.descendantNodesAndSelf; import static org.zwobble.couscous.util.Casts.tryCast; import static org.zwobble.couscous.util.ExtraIterables.iterable; -import static org.zwobble.couscous.util.ExtraIterables.lazyFlatMap; -import static org.zwobble.couscous.util.ExtraLists.list; import static org.zwobble.couscous.util.ExtraStreams.toStream; public class FreeVariables { @@ -50,32 +48,52 @@ public class FreeVariables { } } + public static class FindDeclaredTypes { + private static final Function<Node, Stream<TypeParameter>> VISITOR = + DynamicNodeMapper.instantiate(new FindDeclaredTypes(), "visit"); + + public Stream<TypeParameter> visit(Node node) { + return Stream.empty(); + } + + public Stream<TypeParameter> visit(MethodNode methodNode) { + return methodNode.getTypeParameters().stream().map(parameter -> parameter.getType()); + } + } + public static Set<TypeParameter> findFreeTypeParameters(Node root) { + // TODO: test removal of declared types + Set<TypeParameter> declaredTypes = descendantNodesAndSelf(root) + .flatMap(FindDeclaredTypes.VISITOR) + .collect(Collectors.toSet()); + // TODO: this assumes that *all* type parameters are free, which is often but not always true in inner types - Iterable<Type> types = iterable(() -> descendantNodesAndSelf(root) - .flatMap(FindReferencedTypes.VISITOR)); - - return ImmutableSet.copyOf(lazyFlatMap(types, type -> type.accept(new Type.Visitor<Iterable<TypeParameter>>() { - @Override - public Iterable<TypeParameter> visit(ScalarType type) { - return list(); - } - - @Override - public Iterable<TypeParameter> visit(TypeParameter parameter) { - return list(parameter); - } - - @Override - public Iterable<TypeParameter> visit(ParameterizedType type) { - return lazyFlatMap(type.getParameters(), parameter -> parameter.accept(this)); - } - - @Override - public Iterable<TypeParameter> visit(BoundTypeParameter type) { - return type.getValue().accept(this); - } - }))); + Stream<TypeParameter> types = descendantNodesAndSelf(root) + .flatMap(FindReferencedTypes.VISITOR) + .flatMap(type -> type.accept(new Type.Visitor<Stream<TypeParameter>>() { + @Override + public Stream<TypeParameter> visit(ScalarType type) { + return Stream.empty(); + } + + @Override + public Stream<TypeParameter> visit(TypeParameter parameter) { + return Stream.of(parameter); + } + + @Override + public Stream<TypeParameter> visit(ParameterizedType type) { + return type.getParameters().stream().flatMap(parameter -> parameter.accept(this)); + } + + @Override + public Stream<TypeParameter> visit(BoundTypeParameter type) { + return type.getValue().accept(this); + } + })) + .filter(type -> !declaredTypes.contains(type)); + + return ImmutableSet.copyOf(iterable(() -> types)); } public static List<ReferenceNode> findFreeVariables(List<? extends Node> body) { diff --git a/src/main/java/org/zwobble/couscous/frontends/java/GeneratedClosure.java b/src/main/java/org/zwobble/couscous/frontends/java/GeneratedClosure.java index 9bad1d2..c019cb8 100644 --- a/src/main/java/org/zwobble/couscous/frontends/java/GeneratedClosure.java +++ b/src/main/java/org/zwobble/couscous/frontends/java/GeneratedClosure.java @@ -40,6 +40,10 @@ public class GeneratedClosure { concat(capturedVariables, arguments)); } + public boolean hasCaptures() { + return !capturedTypes.isEmpty() || !capturedVariables.isEmpty(); + } + private ScalarType getType() { return classNode.getName(); } 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 00198a6..aa64543 100644 --- a/src/main/java/org/zwobble/couscous/frontends/java/JavaExpressionReader.java +++ b/src/main/java/org/zwobble/couscous/frontends/java/JavaExpressionReader.java @@ -290,6 +290,7 @@ public class JavaExpressionReader { return methodCall( thisReference(typeOf(declaringClass.getDeclaringClass())), // TODO: remove duplication with JavaReader in name generation + // TODO: handle this when hoisting (i.e. in HoistNestedTypes) "create_" + declaringClass.getName(), arguments, type); 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 5202127..a047e9e 100644 --- a/src/main/java/org/zwobble/couscous/frontends/java/JavaReader.java +++ b/src/main/java/org/zwobble/couscous/frontends/java/JavaReader.java @@ -1,8 +1,6 @@ package org.zwobble.couscous.frontends.java; import com.google.common.collect.ImmutableList; -import com.google.common.collect.Iterables; -import com.google.common.collect.Maps; import org.eclipse.jdt.core.compiler.IProblem; import org.eclipse.jdt.core.dom.*; import org.zwobble.couscous.ast.*; @@ -11,21 +9,19 @@ import org.zwobble.couscous.ast.sugar.AnonymousClass; import org.zwobble.couscous.ast.sugar.Lambda; import org.zwobble.couscous.ast.sugar.TypeDeclarationBody; import org.zwobble.couscous.ast.visitors.NodeTransformer; -import org.zwobble.couscous.types.*; +import org.zwobble.couscous.transforms.ClosureGenerator; +import org.zwobble.couscous.types.ScalarType; import org.zwobble.couscous.types.Type; import org.zwobble.couscous.util.ExtraLists; -import org.zwobble.couscous.util.InsertionOrderSet; import java.io.IOException; import java.nio.file.Path; import java.util.List; -import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.function.Function; import java.util.stream.Collectors; -import static com.google.common.collect.Iterables.concat; import static com.google.common.collect.Iterables.transform; import static java.util.Arrays.asList; import static java.util.Collections.emptyList; @@ -37,17 +33,12 @@ import static org.zwobble.couscous.ast.FieldDeclarationNode.field; import static org.zwobble.couscous.ast.FormalArgumentNode.formalArg; import static org.zwobble.couscous.ast.FormalTypeParameterNode.formalTypeParameter; import static org.zwobble.couscous.ast.InstanceReceiver.instanceReceiver; -import static org.zwobble.couscous.ast.ReturnNode.returns; import static org.zwobble.couscous.ast.StaticReceiver.staticReceiver; import static org.zwobble.couscous.ast.ThisReferenceNode.thisReference; -import static org.zwobble.couscous.ast.VariableReferenceNode.reference; -import static org.zwobble.couscous.frontends.java.FreeVariables.findFreeTypeParameters; -import static org.zwobble.couscous.frontends.java.FreeVariables.findFreeVariables; import static org.zwobble.couscous.frontends.java.JavaMethods.signature; import static org.zwobble.couscous.frontends.java.JavaTypes.*; import static org.zwobble.couscous.types.Types.erasure; import static org.zwobble.couscous.util.Casts.tryCast; -import static org.zwobble.couscous.util.ExtraIterables.lazyMap; import static org.zwobble.couscous.util.ExtraLists.*; import static org.zwobble.couscous.util.ExtraMaps.map; @@ -125,7 +116,8 @@ public class JavaReader { superTypes, body.getFields(), body.getStaticConstructor(), - body.getMethods() + body.getMethods(), + body.getInnerTypes() ); } else { return ClassNode.declareClass( @@ -135,7 +127,9 @@ public class JavaReader { body.getFields(), body.getStaticConstructor(), body.getConstructor(), - body.getMethods()); + body.getMethods(), + body.getInnerTypes() + ); } } @@ -197,46 +191,6 @@ public class JavaReader { list(method)); } - private List<StatementNode> replaceCaptureReferences( - ScalarType className, - List<StatementNode> body, - InsertionOrderSet<CapturedVariable> freeVariables) - { - Map<ExpressionNode, ExpressionNode> replacements = Maps.transformValues( - Maps.uniqueIndex(freeVariables, variable -> variable.freeVariable), - freeVariable -> captureAccess(className, freeVariable)); - NodeTransformer transformer = NodeTransformer.replaceExpressions(replacements); - return eagerFlatMap(body, transformer::transformStatement); - } - - private ConstructorNode buildConstructor( - Scope outerScope, - ScalarType type, - InsertionOrderSet<CapturedVariable> freeVariables, - ConstructorNode existing) - { - Scope scope = outerScope.enterConstructor(); - - ImmutableList.Builder<FormalArgumentNode> arguments = ImmutableList.builder(); - ImmutableList.Builder<StatementNode> body = ImmutableList.builder(); - - for (CapturedVariable variable : freeVariables) { - FormalArgumentNode argument = scope.formalArgument(variable.field.getName(), variable.field.getType()); - arguments.add(argument); - body.add(assignStatement(captureAccess(type, variable), reference(argument))); - } - - arguments.addAll(existing.getArguments()); - body.addAll(existing.getBody()); - - return new ConstructorNode(arguments.build(), body.build()); - } - - private FieldAccessNode captureAccess(ScalarType type, CapturedVariable freeVariable) { - FieldDeclarationNode field = freeVariable.field; - return fieldAccess(thisReference(type), field.getName(), field.getType()); - } - GeneratedClosure readAnonymousClass(Scope outerScope, AnonymousClassDeclaration declaration) { ScalarType className = generateAnonymousName(declaration); Scope scope = outerScope.enterClass(className); @@ -255,61 +209,6 @@ public class JavaReader { return closure; } - private static class CapturedVariable { - private final ReferenceNode freeVariable; - private final FieldDeclarationNode field; - - public CapturedVariable(ReferenceNode freeVariable, FieldDeclarationNode field) { - this.freeVariable = freeVariable; - this.field = field; - } - } - - private GeneratedClosure classWithCapture( - Scope scope, - ClassNode classNode - ) { - InsertionOrderSet<org.zwobble.couscous.types.TypeParameter> freeTypeParameters = InsertionOrderSet.copyOf( - findFreeTypeParameters(classNode)); - - InsertionOrderSet<ReferenceNode> freeVariables = InsertionOrderSet.copyOf(Iterables.filter( - findFreeVariables(ExtraLists.concat( - classNode.getFields(), - classNode.getMethods())), - // TODO: check this references work correctly in anonymous classes - variable -> !isThisReference(classNode.getName(), variable))); - InsertionOrderSet<CapturedVariable> capturedVariables = InsertionOrderSet.copyOf(transform( - freeVariables, - freeVariable -> new CapturedVariable(freeVariable, fieldForCapture(freeVariable)))); - Iterable<FieldDeclarationNode> captureFields = transform(capturedVariables, capture -> capture.field); - - List<FieldDeclarationNode> fields = ImmutableList.copyOf(concat( - classNode.getFields(), - captureFields)); - - if (!classNode.getStaticConstructor().isEmpty()) { - throw new RuntimeException("Class has unexpected static constructor"); - } - - ClassNode generatedClass = new ClassNode( - classNode.getName(), - // TODO: generate fresh type parameter and replace in body - ExtraLists.concat(lazyMap(freeTypeParameters, parameter -> formalTypeParameter(parameter)), classNode.getTypeParameters()), - classNode.getSuperTypes(), - fields, - list(), - buildConstructor(scope, classNode.getName(), capturedVariables, classNode.getConstructor()), - eagerMap(classNode.getMethods(), method -> - method.mapBody(body -> replaceCaptureReferences(classNode.getName(), body, capturedVariables)))); - return new GeneratedClosure(generatedClass, freeTypeParameters, freeVariables); - } - - private boolean isThisReference(ScalarType name, ReferenceNode reference) { - return tryCast(ThisReferenceNode.class, reference) - .map(node -> node.getType().equals(name)) - .orElse(false); - } - private GeneratedClosure classWithCapture( Scope scope, ScalarType className, @@ -322,7 +221,9 @@ public class JavaReader { anonymousClass.getFields(), list(), ConstructorNode.DEFAULT, - anonymousClass.getMethods()); + anonymousClass.getMethods(), + list() + ); if (anonymousClass.getType().isPresent()) { NodeTransformer nodeTransformer = NodeTransformer.replaceExpressions(map( thisReference(anonymousClass.getType().get()), @@ -330,23 +231,7 @@ public class JavaReader { )); classNode = nodeTransformer.transformClass(classNode); } - return classWithCapture(scope, classNode); - } - - private FieldDeclarationNode fieldForCapture(ReferenceNode freeVariable) { - return freeVariable.accept(new ReferenceNode.Visitor<FieldDeclarationNode>() { - @Override - public FieldDeclarationNode visit(VariableReferenceNode reference) { - return field(reference.getReferent().getName(), reference.getType()); - } - - @Override - public FieldDeclarationNode visit(ThisReferenceNode thisReference) { - Type type = thisReference.getType(); - String name = "this_" + erasure(type).getQualifiedName().replace(".", "__"); - return field(name, type); - } - }); + return ClosureGenerator.classWithCapture(scope, classNode); } private ScalarType generateAnonymousName(ASTNode node) { @@ -380,39 +265,11 @@ public class JavaReader { .ifPresent(field -> readField(body, scope, type, field, isInterface)); tryCast(TypeDeclaration.class, declaration) - .ifPresent(typeDeclaration -> classes.add(readNestedTypeDeclaration(body, typeDeclaration))); + .ifPresent(typeDeclaration -> body.addInnerType(readTypeDeclaration(typeDeclaration))); } return body.build(); } - private TypeNode readNestedTypeDeclaration(TypeDeclarationBody.Builder body, TypeDeclaration typeDeclaration) { - TypeNode typeNode = readTypeDeclaration(typeDeclaration); - // TODO: can we remove duplication of scope creation with readTypeDeclaration()? - Scope scope = topScope.enterClass(typeNode.getName()); - return tryCast(ClassNode.class, typeNode) - .filter(node -> !Modifier.isStatic(typeDeclaration.getModifiers())) - .<TypeNode>map(node -> { - GeneratedClosure closure = classWithCapture(scope, node); -// Type type = node.getTypeParameters().isEmpty() -// ? typeNode.getName() -// : parameterizedType(typeNode.getName(), eagerMap(node.getTypeParameters())); - Type type = typeNode.getName(); - // TODO: method type parameters - List<FormalArgumentNode> methodArguments = eagerMap( - node.getConstructor().getArguments(), - // TODO: use scope of outer class - argument -> scope.formalArgument(argument.getName(), argument.getType())); - MethodNode method = MethodNode.builder("create_" + typeDeclaration.getName().getIdentifier()) - .arguments(methodArguments) - .returns(type) - .statement(returns(closure.generateConstructor(lazyMap(methodArguments, argument -> reference(argument))))) - .build(); - body.addMethod(method); - return closure.getClassNode(); - }) - .orElse(typeNode); - } - private void readField(TypeDeclarationBody.Builder builder, Scope scope, ScalarType declaringType, FieldDeclaration field, boolean isInterface) { @SuppressWarnings("unchecked") List<VariableDeclarationFragment> fragments = field.fragments(); diff --git a/src/main/java/org/zwobble/couscous/frontends/java/JavaTypes.java b/src/main/java/org/zwobble/couscous/frontends/java/JavaTypes.java index aa20fc9..5d9eddd 100644 --- a/src/main/java/org/zwobble/couscous/frontends/java/JavaTypes.java +++ b/src/main/java/org/zwobble/couscous/frontends/java/JavaTypes.java @@ -47,10 +47,12 @@ public class JavaTypes { Identifier declaringScope = getDeclaringScope(typeBinding); return new TypeParameter(declaringScope, typeBinding.getName()); } - ScalarType rawType = outerClass == null - ? ScalarType.of(typeBinding.getErasure().getQualifiedName()) - // TODO: test for erasure of inner type name - : ScalarType.of(erasure(typeOf(outerClass)).getQualifiedName() + "__" + typeBinding.getErasure().getName()); + // TODO: tidy-up +// ScalarType rawType = outerClass == null +// ? ScalarType.of(typeBinding.getErasure().getQualifiedName()) +// // TODO: test for erasure of inner type name +// : ScalarType.of(erasure(typeOf(outerClass)).getQualifiedName() + "__" + typeBinding.getErasure().getName()); + ScalarType rawType = ScalarType.of(typeBinding.getErasure().getQualifiedName()); if (typeBinding.isParameterizedType()) { List<Type> typeParameters = eagerMap( asList(typeBinding.getTypeArguments()), diff --git a/src/main/java/org/zwobble/couscous/interpreter/JavaProject.java b/src/main/java/org/zwobble/couscous/interpreter/JavaProject.java index f813faf..a85d126 100644 --- a/src/main/java/org/zwobble/couscous/interpreter/JavaProject.java +++ b/src/main/java/org/zwobble/couscous/interpreter/JavaProject.java @@ -10,6 +10,7 @@ import org.zwobble.couscous.interpreter.values.BoxedIntegerInterpreterValue; import org.zwobble.couscous.interpreter.values.InternalCouscousInterpreterValue; import org.zwobble.couscous.transforms.DesugarForToWhile; import org.zwobble.couscous.transforms.DesugarSwitchToIfElse; +import org.zwobble.couscous.transforms.HoistNestedTypes; import java.util.List; @@ -30,7 +31,9 @@ public class JavaProject { NodeTransformer switchTransformer = DesugarSwitchToIfElse.transformer(); NodeTransformer forTransformer = DesugarForToWhile.transformer(); Iterable<InterpreterType> concreteTypes = Iterables.transform( - NodeTransformer.applyAll(list(switchTransformer, forTransformer), classNodes), + HoistNestedTypes.hoist( + NodeTransformer.applyAll(list(switchTransformer, forTransformer), classNodes) + ), UserDefinedInterpreterType::new ); return builder() diff --git a/src/main/java/org/zwobble/couscous/transforms/ClosureGenerator.java b/src/main/java/org/zwobble/couscous/transforms/ClosureGenerator.java new file mode 100644 index 0000000..68c0aa6 --- /dev/null +++ b/src/main/java/org/zwobble/couscous/transforms/ClosureGenerator.java @@ -0,0 +1,147 @@ +package org.zwobble.couscous.transforms; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Iterables; +import com.google.common.collect.Maps; +import org.zwobble.couscous.ast.*; +import org.zwobble.couscous.ast.visitors.NodeTransformer; +import org.zwobble.couscous.frontends.java.GeneratedClosure; +import org.zwobble.couscous.frontends.java.Scope; +import org.zwobble.couscous.types.ScalarType; +import org.zwobble.couscous.types.Type; +import org.zwobble.couscous.types.TypeParameter; +import org.zwobble.couscous.util.ExtraLists; +import org.zwobble.couscous.util.InsertionOrderSet; + +import java.util.List; +import java.util.Map; + +import static com.google.common.collect.Iterables.concat; +import static com.google.common.collect.Iterables.transform; +import static org.zwobble.couscous.ast.AssignmentNode.assignStatement; +import static org.zwobble.couscous.ast.FieldAccessNode.fieldAccess; +import static org.zwobble.couscous.ast.FieldDeclarationNode.field; +import static org.zwobble.couscous.ast.FormalTypeParameterNode.formalTypeParameter; +import static org.zwobble.couscous.ast.ThisReferenceNode.thisReference; +import static org.zwobble.couscous.ast.VariableReferenceNode.reference; +import static org.zwobble.couscous.frontends.java.FreeVariables.findFreeTypeParameters; +import static org.zwobble.couscous.frontends.java.FreeVariables.findFreeVariables; +import static org.zwobble.couscous.types.Types.erasure; +import static org.zwobble.couscous.util.Casts.tryCast; +import static org.zwobble.couscous.util.ExtraIterables.lazyMap; +import static org.zwobble.couscous.util.ExtraLists.*; + +public class ClosureGenerator { + public static GeneratedClosure classWithCapture( + Scope scope, + ClassNode classNode + ) { + InsertionOrderSet<TypeParameter> freeTypeParameters = InsertionOrderSet.copyOf( + findFreeTypeParameters(classNode)); + + InsertionOrderSet<ReferenceNode> freeVariables = InsertionOrderSet.copyOf(Iterables.filter( + findFreeVariables(ExtraLists.concat( + classNode.getFields(), + classNode.getMethods())), + // TODO: check this references work correctly in anonymous classes + variable -> !isThisReference(classNode.getName(), variable))); + InsertionOrderSet<CapturedVariable> capturedVariables = InsertionOrderSet.copyOf(transform( + freeVariables, + freeVariable -> new CapturedVariable(freeVariable, fieldForCapture(freeVariable)))); + Iterable<FieldDeclarationNode> captureFields = transform(capturedVariables, capture -> capture.field); + + List<FieldDeclarationNode> fields = ImmutableList.copyOf(concat( + classNode.getFields(), + captureFields)); + + if (!classNode.getStaticConstructor().isEmpty()) { + throw new RuntimeException("Class has unexpected static constructor"); + } + + ClassNode generatedClass = new ClassNode( + classNode.getName(), + // TODO: generate fresh type parameter and replace in body + ExtraLists.concat(lazyMap(freeTypeParameters, parameter -> formalTypeParameter(parameter)), classNode.getTypeParameters()), + classNode.getSuperTypes(), + fields, + list(), + buildConstructor(scope, classNode.getName(), capturedVariables, classNode.getConstructor()), + eagerMap(classNode.getMethods(), method -> + method.mapBody(body -> replaceCaptureReferences(classNode.getName(), body, capturedVariables))), + list() + ); + return new GeneratedClosure(generatedClass, freeTypeParameters, freeVariables); + } + + private static boolean isThisReference(ScalarType name, ReferenceNode reference) { + return tryCast(ThisReferenceNode.class, reference) + .map(node -> node.getType().equals(name)) + .orElse(false); + } + + private static FieldDeclarationNode fieldForCapture(ReferenceNode freeVariable) { + return freeVariable.accept(new ReferenceNode.Visitor<FieldDeclarationNode>() { + @Override + public FieldDeclarationNode visit(VariableReferenceNode reference) { + return field(reference.getReferent().getName(), reference.getType()); + } + + @Override + public FieldDeclarationNode visit(ThisReferenceNode thisReference) { + Type type = thisReference.getType(); + String name = "this_" + erasure(type).getQualifiedName().replace(".", "__"); + return field(name, type); + } + }); + } + + private static List<StatementNode> replaceCaptureReferences( + ScalarType className, + List<StatementNode> body, + InsertionOrderSet<CapturedVariable> freeVariables) + { + Map<ExpressionNode, ExpressionNode> replacements = Maps.transformValues( + Maps.uniqueIndex(freeVariables, variable -> variable.freeVariable), + freeVariable -> captureAccess(className, freeVariable)); + NodeTransformer transformer = NodeTransformer.replaceExpressions(replacements); + return eagerFlatMap(body, transformer::transformStatement); + } + + private static ConstructorNode buildConstructor( + Scope outerScope, + ScalarType type, + InsertionOrderSet<CapturedVariable> freeVariables, + ConstructorNode existing) + { + Scope scope = outerScope.enterConstructor(); + + ImmutableList.Builder<FormalArgumentNode> arguments = ImmutableList.builder(); + ImmutableList.Builder<StatementNode> body = ImmutableList.builder(); + + for (CapturedVariable variable : freeVariables) { + FormalArgumentNode argument = scope.formalArgument(variable.field.getName(), variable.field.getType()); + arguments.add(argument); + body.add(assignStatement(captureAccess(type, variable), reference(argument))); + } + + arguments.addAll(existing.getArguments()); + body.addAll(existing.getBody()); + + return new ConstructorNode(arguments.build(), body.build()); + } + + private static FieldAccessNode captureAccess(ScalarType type, CapturedVariable freeVariable) { + FieldDeclarationNode field = freeVariable.field; + return fieldAccess(thisReference(type), field.getName(), field.getType()); + } + + private static class CapturedVariable { + private final ReferenceNode freeVariable; + private final FieldDeclarationNode field; + + public CapturedVariable(ReferenceNode freeVariable, FieldDeclarationNode field) { + this.freeVariable = freeVariable; + this.field = field; + } + } +} diff --git a/src/main/java/org/zwobble/couscous/transforms/HoistNestedTypes.java b/src/main/java/org/zwobble/couscous/transforms/HoistNestedTypes.java new file mode 100644 index 0000000..e4ff1c8 --- /dev/null +++ b/src/main/java/org/zwobble/couscous/transforms/HoistNestedTypes.java @@ -0,0 +1,137 @@ +package org.zwobble.couscous.transforms; + +import org.zwobble.couscous.ast.ClassNode; +import org.zwobble.couscous.ast.FormalArgumentNode; +import org.zwobble.couscous.ast.MethodNode; +import org.zwobble.couscous.ast.TypeNode; +import org.zwobble.couscous.ast.visitors.NodeTransformer; +import org.zwobble.couscous.frontends.java.GeneratedClosure; +import org.zwobble.couscous.frontends.java.Scope; +import org.zwobble.couscous.types.ScalarType; +import org.zwobble.couscous.types.Type; + +import java.util.List; +import java.util.Optional; +import java.util.regex.Pattern; + +import static org.zwobble.couscous.ast.ReturnNode.returns; +import static org.zwobble.couscous.ast.VariableReferenceNode.reference; +import static org.zwobble.couscous.util.Casts.tryCast; +import static org.zwobble.couscous.util.ExtraIterables.iterable; +import static org.zwobble.couscous.util.ExtraIterables.lazyMap; +import static org.zwobble.couscous.util.ExtraLists.*; + +public class HoistNestedTypes { + public static List<TypeNode> hoist(List<TypeNode> declarations) { + return new HoistNestedTypes().hoistTypes(declarations); + } + + private final Scope topScope = Scope.create().temporaryPrefix("_couscous_hoist_nested_types"); + + private List<TypeNode> hoistTypes(List<TypeNode> declarations) { + Iterable<TypeNode> types = eagerFlatMap(declarations, declaration -> hoistInnerTypes(declaration)); + + NodeTransformer referenceTransformer = NodeTransformer.builder() + .transformType(this::transformType) + .build(); + + return eagerFlatMap(types, referenceTransformer::transformTypeDeclaration); + } + + private Type transformType(Type type) { + if (isInnerType(type)) { + return hoistedTypeName((ScalarType) type); + } else { + return type.transformSubTypes(this::transformType); + } + } + + private static final Pattern INNER_TYPE_QUALIFIER_REGEX = Pattern.compile(".*\\.[A-Z][^.]*$"); + + private boolean isInnerType(Type type) { + // TODO: implement separate type classes for top-level and inner types rather than using this heuristic + + return tryCast(ScalarType.class, type) + .flatMap(scalarType -> scalarType.getQualifier()) + .map(qualifier -> INNER_TYPE_QUALIFIER_REGEX.matcher(qualifier).matches()) + .orElse(false); + } + + private static Type hoistedTypeName(ScalarType type) { + return ScalarType.of(type.getQualifier().get() + "__" + type.getSimpleName()); + } + + private Iterable<TypeNode> hoistInnerTypes(TypeNode declaration) { + TypeNode type = declaration.stripInnerTypes(); + Iterable<HoistedType> hoistedTypes = eagerMap(declaration.getInnerTypes(), this::hoistType); + + return cons( + addMethods(type, eagerFlatMap(hoistedTypes, hoisted -> iterable(hoisted.method))), + lazyMap(hoistedTypes, hoisted -> hoisted.type) + ); + } + + private static TypeNode addMethods(TypeNode declaration, List<MethodNode> methods) { + if (methods.isEmpty()) { + return declaration; + } else if (declaration instanceof ClassNode) { + ClassNode classDeclaration = (ClassNode) declaration; + return new ClassNode( + classDeclaration.getName(), + classDeclaration.getTypeParameters(), + classDeclaration.getSuperTypes(), + classDeclaration.getFields(), + classDeclaration.getStaticConstructor(), + classDeclaration.getConstructor(), + concat(classDeclaration.getMethods(), methods), + classDeclaration.getInnerTypes() + ); + } else { + throw new UnsupportedOperationException("Cannot add methods to non-class node"); + } + } + + private HoistedType hoistType(TypeNode typeNode) { + // TODO: we need to hoist inner types of the inner types, specifically *after* the initial hoist (to make closures easier to generate) + // TODO: can we remove duplication of scope creation with readTypeDeclaration()? + Scope scope = topScope.enterClass(typeNode.getName()); + return tryCast(ClassNode.class, typeNode) + .flatMap(node -> { + // TODO: don't generate closure if not necessary (i.e. it's an explicit or implicit static inner class) + GeneratedClosure closure = ClosureGenerator.classWithCapture(scope, node); +// Type type = node.getTypeParameters().isEmpty() +// ? typeNode.getName() +// : parameterizedType(typeNode.getName(), eagerMap(node.getTypeParameters())); + if (closure.hasCaptures()) { + Type type = typeNode.getName(); + // TODO: method type parameters + List<FormalArgumentNode> methodArguments = eagerMap( + node.getConstructor().getArguments(), + // TODO: use scope of outer class + argument -> scope.formalArgument(argument.getName(), argument.getType())); + MethodNode method = MethodNode.builder("create_" + typeNode.getName().getSimpleName()) + .arguments(methodArguments) + .returns(type) + .statement(returns(closure.generateConstructor(lazyMap(methodArguments, argument -> reference(argument))))) + .build(); + return Optional.of(new HoistedType( + closure.getClassNode(), + Optional.of(method) + )); + } else { + return Optional.empty(); + } + }) + .orElse(new HoistedType(typeNode, Optional.empty())); + } + + private static class HoistedType { + private final TypeNode type; + private final Optional<MethodNode> method; + + private HoistedType(TypeNode type, Optional<MethodNode> method) { + this.type = type; + this.method = method; + } + } +} diff --git a/src/main/java/org/zwobble/couscous/transforms/SplitStaticsFromInterface.java b/src/main/java/org/zwobble/couscous/transforms/SplitStaticsFromInterface.java index b2c88ea..3af4042 100644 --- a/src/main/java/org/zwobble/couscous/transforms/SplitStaticsFromInterface.java +++ b/src/main/java/org/zwobble/couscous/transforms/SplitStaticsFromInterface.java @@ -27,7 +27,8 @@ public class SplitStaticsFromInterface { interfaceNode.getFields(), interfaceNode.getStaticConstructor(), ConstructorNode.DEFAULT, - eagerFilter(interfaceNode.getMethods(), method -> method.isStatic()) + eagerFilter(interfaceNode.getMethods(), method -> method.isStatic()), + list() )); result.add(InterfaceNode.declareInterface( interfaceNode.getName(), @@ -35,7 +36,8 @@ public class SplitStaticsFromInterface { interfaceNode.getSuperTypes(), list(), list(), - eagerFilter(interfaceNode.getMethods(), method -> !method.isStatic()) + eagerFilter(interfaceNode.getMethods(), method -> !method.isStatic()), + interfaceNode.getInnerTypes() )); } else { result.add(typeNode); diff --git a/src/main/java/org/zwobble/couscous/types/BoundTypeParameter.java b/src/main/java/org/zwobble/couscous/types/BoundTypeParameter.java index 8eb5f5a..4272ac7 100644 --- a/src/main/java/org/zwobble/couscous/types/BoundTypeParameter.java +++ b/src/main/java/org/zwobble/couscous/types/BoundTypeParameter.java @@ -1,5 +1,7 @@ package org.zwobble.couscous.types; +import java.util.function.Function; + public class BoundTypeParameter implements Type { public static BoundTypeParameter boundTypeParameter(TypeParameter parameter, Type value) { return new BoundTypeParameter(parameter, value); @@ -27,6 +29,14 @@ public class BoundTypeParameter implements Type { } @Override + public Type transformSubTypes(Function<Type, Type> transform) { + return new BoundTypeParameter( + (TypeParameter) transform.apply(parameter), + transform.apply(value) + ); + } + + @Override public String toString() { return "BoundTypeParameter(" + "parameter=" + parameter + diff --git a/src/main/java/org/zwobble/couscous/types/ParameterizedType.java b/src/main/java/org/zwobble/couscous/types/ParameterizedType.java index 7db73e0..6d4dca1 100644 --- a/src/main/java/org/zwobble/couscous/types/ParameterizedType.java +++ b/src/main/java/org/zwobble/couscous/types/ParameterizedType.java @@ -1,6 +1,9 @@ package org.zwobble.couscous.types; import java.util.List; +import java.util.function.Function; + +import static org.zwobble.couscous.util.ExtraLists.eagerMap; public class ParameterizedType implements Type { public static ParameterizedType parameterizedType(ScalarType rawType, List<Type> parameters) { @@ -29,6 +32,14 @@ public class ParameterizedType implements Type { } @Override + public Type transformSubTypes(Function<Type, Type> transform) { + return new ParameterizedType( + (ScalarType)transform.apply(rawType), + eagerMap(parameters, transform) + ); + } + + @Override public String toString() { return "ParameterizedType(" + "rawType=" + rawType + diff --git a/src/main/java/org/zwobble/couscous/types/ScalarType.java b/src/main/java/org/zwobble/couscous/types/ScalarType.java index 136b6cb..4f77342 100644 --- a/src/main/java/org/zwobble/couscous/types/ScalarType.java +++ b/src/main/java/org/zwobble/couscous/types/ScalarType.java @@ -3,6 +3,7 @@ package org.zwobble.couscous.types; import com.google.common.base.Strings; import java.util.Optional; +import java.util.function.Function; public class ScalarType implements Type { public static ScalarType of(String qualifiedName) { @@ -29,7 +30,7 @@ public class ScalarType implements Type { return qualifiedName.substring(qualifiedName.lastIndexOf(".") + 1); } - public Optional<String> getPackage() { + public Optional<String> getQualifier() { int lastDot = qualifiedName.lastIndexOf("."); if (lastDot == -1) { return Optional.empty(); @@ -73,4 +74,9 @@ public class ScalarType implements Type { public <T> T accept(Visitor<T> visitor) { return visitor.visit(this); } + + @Override + public Type transformSubTypes(Function<Type, Type> transform) { + return this; + } } diff --git a/src/main/java/org/zwobble/couscous/types/Type.java b/src/main/java/org/zwobble/couscous/types/Type.java index ad0f6fc..b5acd6d 100644 --- a/src/main/java/org/zwobble/couscous/types/Type.java +++ b/src/main/java/org/zwobble/couscous/types/Type.java @@ -1,5 +1,7 @@ package org.zwobble.couscous.types; +import java.util.function.Function; + public interface Type { <T> T accept(Visitor<T> visitor); @@ -9,4 +11,6 @@ public interface Type { T visit(ParameterizedType type); T visit(BoundTypeParameter type); } + + Type transformSubTypes(Function<Type, Type> transform); } diff --git a/src/main/java/org/zwobble/couscous/types/TypeParameter.java b/src/main/java/org/zwobble/couscous/types/TypeParameter.java index 39bf46b..fd4ada4 100644 --- a/src/main/java/org/zwobble/couscous/types/TypeParameter.java +++ b/src/main/java/org/zwobble/couscous/types/TypeParameter.java @@ -2,6 +2,8 @@ package org.zwobble.couscous.types; import org.zwobble.couscous.ast.identifiers.Identifier; +import java.util.function.Function; + public class TypeParameter implements Type { public static TypeParameter typeParameter(Identifier declaringScope, String name) { return new TypeParameter(declaringScope, name); @@ -29,6 +31,11 @@ public class TypeParameter implements Type { } @Override + public Type transformSubTypes(Function<Type, Type> transform) { + return this; + } + + @Override public String toString() { return "TypeParameter(" + "declaringScope=" + declaringScope + diff --git a/src/main/java/org/zwobble/couscous/util/ExtraLists.java b/src/main/java/org/zwobble/couscous/util/ExtraLists.java index 7849ea6..b84afcd 100644 --- a/src/main/java/org/zwobble/couscous/util/ExtraLists.java +++ b/src/main/java/org/zwobble/couscous/util/ExtraLists.java @@ -158,4 +158,12 @@ public class ExtraLists { public static <T> List<T> copyOf(Iterable<T> iterable) { return ImmutableList.copyOf(iterable); } + + public static <T> T getLast(T[] values) { + return values[values.length - 1]; + } + + public static <T> T getLast(List<T> values) { + return values.get(values.size() - 1); + } } diff --git a/src/main/java/org/zwobble/couscous/util/InsertionOrderSet.java b/src/main/java/org/zwobble/couscous/util/InsertionOrderSet.java index 4272add..8d168fc 100644 --- a/src/main/java/org/zwobble/couscous/util/InsertionOrderSet.java +++ b/src/main/java/org/zwobble/couscous/util/InsertionOrderSet.java @@ -20,8 +20,14 @@ public interface InsertionOrderSet<T> extends Iterable<T> { public Iterator<T> iterator() { return list.iterator(); } + + @Override + public boolean isEmpty() { + return list.isEmpty(); + } }; } List<T> asList(); + boolean isEmpty(); } diff --git a/src/test/java/org/zwobble/couscous/tests/frontends/java/FreeVariablesTests.java b/src/test/java/org/zwobble/couscous/tests/frontends/java/FreeVariablesTests.java index 8bcf38d..0af0020 100644 --- a/src/test/java/org/zwobble/couscous/tests/frontends/java/FreeVariablesTests.java +++ b/src/test/java/org/zwobble/couscous/tests/frontends/java/FreeVariablesTests.java @@ -1,22 +1,27 @@ package org.zwobble.couscous.tests.frontends.java; import org.junit.Test; -import org.zwobble.couscous.ast.ReferenceNode; -import org.zwobble.couscous.ast.VariableDeclaration; +import org.zwobble.couscous.ast.*; +import org.zwobble.couscous.frontends.java.FreeVariables; import org.zwobble.couscous.tests.TestIds; +import org.zwobble.couscous.types.Type; +import org.zwobble.couscous.types.TypeParameter; import org.zwobble.couscous.types.Types; import java.util.List; +import java.util.Set; import static java.util.Collections.emptyList; import static org.junit.Assert.assertEquals; import static org.zwobble.couscous.ast.FormalArgumentNode.formalArg; +import static org.zwobble.couscous.ast.FormalTypeParameterNode.formalTypeParameter; import static org.zwobble.couscous.ast.LiteralNode.literal; import static org.zwobble.couscous.ast.LocalVariableDeclarationNode.localVariableDeclaration; import static org.zwobble.couscous.ast.VariableDeclaration.var; import static org.zwobble.couscous.ast.VariableReferenceNode.reference; import static org.zwobble.couscous.frontends.java.FreeVariables.findFreeVariables; import static org.zwobble.couscous.util.ExtraLists.list; +import static org.zwobble.couscous.util.ExtraSets.set; public class FreeVariablesTests { @Test @@ -43,4 +48,27 @@ public class FreeVariablesTests { reference(declaration))); assertEquals(emptyList(), freeVariables); } + + @Test + public void typeParameterIsFreeIfThereIsNoAssociatedDeclaration() { + TypeParameter typeParameter = new TypeParameter(TestIds.ANY_ID, "T"); + MethodNode method = MethodNode.builder("f").argument(argOfType(typeParameter)).build(); + Set<TypeParameter> freeTypeParameters = FreeVariables.findFreeTypeParameters(method); + assertEquals(set(typeParameter), freeTypeParameters); + } + + @Test + public void typeParameterIsNotFreeIfItIsDeclared() { + TypeParameter typeParameter = new TypeParameter(TestIds.ANY_ID, "T"); + MethodNode method = MethodNode.builder("f") + .typeParameter(formalTypeParameter(typeParameter)) + .argument(argOfType(typeParameter)) + .build(); + Set<TypeParameter> freeTypeParameters = FreeVariables.findFreeTypeParameters(method); + assertEquals(set(), freeTypeParameters); + } + + private FormalArgumentNode argOfType(Type type) { + return FormalArgumentNode.formalArg(var(TestIds.variable("x"), "x", type)); + } } |
