diff options
| author | Michael Williamson <mike@zwobble.org> | 2016-08-29 17:13:03 +0100 |
|---|---|---|
| committer | Michael Williamson <mike@zwobble.org> | 2016-08-29 17:13:03 +0100 |
| commit | a82ecb60d6ee7ee46e5800088f1769f86872077b (patch) | |
| tree | 8b3c5442a1bd047606de63477839755e88dab2f5 | |
| parent | 1babfdf8daa45ffef3d624db6c717f846d295440 (diff) | |
Initial support for static methods/fields on interfaces
7 files changed, 172 insertions, 34 deletions
diff --git a/src/main/java/org/zwobble/couscous/ast/ClassNodeBuilder.java b/src/main/java/org/zwobble/couscous/ast/ClassNodeBuilder.java index a567254..4735a2e 100644 --- a/src/main/java/org/zwobble/couscous/ast/ClassNodeBuilder.java +++ b/src/main/java/org/zwobble/couscous/ast/ClassNodeBuilder.java @@ -127,7 +127,10 @@ public class ClassNodeBuilder { name, typeParameters.build(), superTypes.build(), - methods.build()); + fields.build(), + staticConstructor, + methods.build() + ); } public class MethodBuilder<T> { diff --git a/src/main/java/org/zwobble/couscous/ast/InterfaceNode.java b/src/main/java/org/zwobble/couscous/ast/InterfaceNode.java index de151e4..c0444a4 100644 --- a/src/main/java/org/zwobble/couscous/ast/InterfaceNode.java +++ b/src/main/java/org/zwobble/couscous/ast/InterfaceNode.java @@ -12,26 +12,34 @@ public class InterfaceNode implements TypeNode { ScalarType name, List<FormalTypeParameterNode> typeParameters, Set<Type> superTypes, + List<FieldDeclarationNode> fields, + List<StatementNode> staticConstructor, List<MethodNode> methods) { - return new InterfaceNode(name, typeParameters, superTypes, methods); + return new InterfaceNode(name, typeParameters, superTypes, fields, staticConstructor, methods); } private final ScalarType name; private final List<FormalTypeParameterNode> typeParameters; private final Set<Type> superTypes; + private final List<FieldDeclarationNode> fields; + private final List<StatementNode> staticConstructor; private final List<MethodNode> methods; private InterfaceNode( ScalarType name, List<FormalTypeParameterNode> typeParameters, Set<Type> superTypes, - List<MethodNode> methodNodes) + List<FieldDeclarationNode> fields, + List<StatementNode> staticConstructor, + List<MethodNode> methods) { this.name = name; this.typeParameters = typeParameters; this.superTypes = superTypes; - methods = methodNodes; + this.fields = fields; + this.staticConstructor = staticConstructor; + this.methods = methods; } @Override @@ -49,19 +57,17 @@ public class InterfaceNode implements TypeNode { return superTypes; } - @Override - public List<MethodNode> getMethods() { - return methods; + public List<FieldDeclarationNode> getFields() { + return fields; + } + + public List<StatementNode> getStaticConstructor() { + return staticConstructor; } @Override - public String toString() { - return "InterfaceNode(" + - "name=" + name + - ", typeParameters=" + typeParameters + - ", superTypes=" + superTypes + - ", methods=" + methods + - ')'; + public List<MethodNode> getMethods() { + return methods; } @Override @@ -80,10 +86,24 @@ public class InterfaceNode implements TypeNode { transformer.transform(name), transformer.transformFormalTypeParameters(typeParameters), transformer.transformTypes(superTypes), + transformer.transformFields(fields), + transformer.transformStatements(staticConstructor), transformer.transformMethods(methods)); } @Override + public String toString() { + return "InterfaceNode(" + + "name=" + name + + ", typeParameters=" + typeParameters + + ", superTypes=" + superTypes + + ", fields=" + fields + + ", staticConstructor=" + staticConstructor + + ", methods=" + methods + + ')'; + } + + @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; @@ -93,6 +113,8 @@ public class InterfaceNode implements TypeNode { if (!name.equals(that.name)) return false; if (!typeParameters.equals(that.typeParameters)) return false; 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); } @@ -102,6 +124,8 @@ public class InterfaceNode implements TypeNode { int result = name.hashCode(); result = 31 * result + typeParameters.hashCode(); result = 31 * result + superTypes.hashCode(); + result = 31 * result + fields.hashCode(); + result = 31 * result + staticConstructor.hashCode(); result = 31 * result + methods.hashCode(); return result; } diff --git a/src/main/java/org/zwobble/couscous/ast/visitors/NodeTransformer.java b/src/main/java/org/zwobble/couscous/ast/visitors/NodeTransformer.java index b2255d2..bcc920e 100644 --- a/src/main/java/org/zwobble/couscous/ast/visitors/NodeTransformer.java +++ b/src/main/java/org/zwobble/couscous/ast/visitors/NodeTransformer.java @@ -45,6 +45,7 @@ public class NodeTransformer { private Function<Type, Type> transformType = type -> type; private Function<MethodSignature, String> transformMethodName = MethodSignature::getName; private Function<String, String> transformFieldName = name -> name; + private Function<Receiver, Receiver> transformReceiver = receiver -> receiver; public Builder transformExpression( Function<ExpressionNode, Optional<ExpressionNode>> transformExpression) @@ -77,13 +78,19 @@ public class NodeTransformer { return this; } + public Builder transformReceiver(Function<Receiver, Receiver> receiver) { + this.transformReceiver = receiver; + return this; + } + public NodeTransformer build() { return new NodeTransformer( transformExpression, transformStatement, transformType, transformMethodName, - transformFieldName + transformFieldName, + transformReceiver ); } } @@ -93,19 +100,22 @@ public class NodeTransformer { private final Function<Type, Type> transformType; private final Function<MethodSignature, String> transformMethodName; private final Function<String, String> transformFieldName; + private final Function<Receiver, Receiver> transformReceiver; private NodeTransformer( Function<ExpressionNode, Optional<ExpressionNode>> transformExpression, Function<StatementNode, Optional<List<StatementNode>>> transformStatement, Function<Type, Type> transformType, Function<MethodSignature, String> transformMethodName, - Function<String, String> transformFieldName) + Function<String, String> transformFieldName, + Function<Receiver, Receiver> transformReceiver) { this.transformExpression = transformExpression; this.transformStatement = transformStatement; this.transformType = transformType; this.transformMethodName = transformMethodName; this.transformFieldName = transformFieldName; + this.transformReceiver = transformReceiver; } public MethodSignature transform(MethodSignature signature) { @@ -142,7 +152,7 @@ public class NodeTransformer { } public Receiver transformReceiver(Receiver receiver) { - return receiver.transformSubtree(this); + return transformReceiver.apply(receiver.transformSubtree(this)); } public VariableDeclaration transform(VariableDeclaration declaration) { 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 e576102..47a7635 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.SplitStaticsFromInterface; import org.zwobble.couscous.types.*; import java.util.List; @@ -47,14 +48,16 @@ public class CsharpCodeGenerator { } private List<TypeNode> generateCode(List<TypeNode> types) { - return NodeTransformer.applyAll( - list( - DesugarForEachToFor.transformer(), - DesugarForToWhile.transformer(), - CsharpPrimitiveMethods.TRANSFORMER, - nodeTransformer - ), - types + return SplitStaticsFromInterface.transform( + NodeTransformer.applyAll( + list( + DesugarForEachToFor.transformer(), + DesugarForToWhile.transformer(), + CsharpPrimitiveMethods.TRANSFORMER, + nodeTransformer + ), + types + ) ); } 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 1c4ac81..5202127 100644 --- a/src/main/java/org/zwobble/couscous/frontends/java/JavaReader.java +++ b/src/main/java/org/zwobble/couscous/frontends/java/JavaReader.java @@ -115,11 +115,18 @@ public class JavaReader { ScalarType name = erasure(typeOf(type.resolveBinding())); Scope scope = topScope.enterClass(name); List<FormalTypeParameterNode> typeParameters = readTypeParameters(scope.getIdentifier(), type); - TypeDeclarationBody body = readTypeDeclarationBody(scope, name, type.bodyDeclarations()); + TypeDeclarationBody body = readTypeDeclarationBody(scope, name, type.bodyDeclarations(), type.isInterface()); Set<Type> superTypes = superTypes(type); if (type.isInterface()) { // TODO: throw exception if other parts of body are declared - return InterfaceNode.declareInterface(name, typeParameters, superTypes, body.getMethods()); + return InterfaceNode.declareInterface( + name, + typeParameters, + superTypes, + body.getFields(), + body.getStaticConstructor(), + body.getMethods() + ); } else { return ClassNode.declareClass( name, @@ -233,7 +240,7 @@ public class JavaReader { GeneratedClosure readAnonymousClass(Scope outerScope, AnonymousClassDeclaration declaration) { ScalarType className = generateAnonymousName(declaration); Scope scope = outerScope.enterClass(className); - TypeDeclarationBody bodyDeclarations = readTypeDeclarationBody(scope, className, declaration.bodyDeclarations()); + TypeDeclarationBody bodyDeclarations = readTypeDeclarationBody(scope, className, declaration.bodyDeclarations(), false); AnonymousClass anonymousClass = new AnonymousClass( Optional.of((AnonymousType) typeOf(declaration.resolveBinding())), superTypes(declaration), @@ -357,7 +364,7 @@ public class JavaReader { return ((AbstractTypeDeclaration)node).resolveBinding(); } - private TypeDeclarationBody readTypeDeclarationBody(Scope scope, ScalarType type, List<Object> bodyDeclarations) { + private TypeDeclarationBody readTypeDeclarationBody(Scope scope, ScalarType type, List<Object> bodyDeclarations, boolean isInterface) { TypeDeclarationBody.Builder body = TypeDeclarationBody.builder(); for (Object declaration : bodyDeclarations) { @@ -370,7 +377,7 @@ public class JavaReader { readStatementBody(scope, initializer.getBody()))); tryCast(FieldDeclaration.class, declaration) - .ifPresent(field -> readField(body, scope, type, field)); + .ifPresent(field -> readField(body, scope, type, field, isInterface)); tryCast(TypeDeclaration.class, declaration) .ifPresent(typeDeclaration -> classes.add(readNestedTypeDeclaration(body, typeDeclaration))); @@ -406,22 +413,22 @@ public class JavaReader { .orElse(typeNode); } - private void readField(TypeDeclarationBody.Builder builder, Scope scope, ScalarType declaringType, FieldDeclaration field) { + private void readField(TypeDeclarationBody.Builder builder, Scope scope, ScalarType declaringType, FieldDeclaration field, boolean isInterface) { @SuppressWarnings("unchecked") List<VariableDeclarationFragment> fragments = field.fragments(); + boolean isStatic = isInterface || Modifier.isStatic(field.getModifiers()); Type type = typeOf(field.getType()); fragments.forEach(fragment -> { - builder.field(field(Modifier.isStatic(field.getModifiers()), fragment.getName().getIdentifier(), type)); + builder.field(field(isStatic, fragment.getName().getIdentifier(), type)); if (fragment.getInitializer() != null) { ExpressionNode value = readExpression(scope, type, fragment.getInitializer()); String name = fragment.getName().getIdentifier(); - boolean isStatic = Modifier.isStatic(field.getModifiers()); Receiver receiver = isStatic ? staticReceiver(declaringType) : instanceReceiver(thisReference(declaringType)); StatementNode assignment = assignStatement(fieldAccess(receiver, name, type), value); - builder.addInitializer(isStatic,assignment); + builder.addInitializer(isStatic, assignment); } }); diff --git a/src/main/java/org/zwobble/couscous/transforms/SplitStaticsFromInterface.java b/src/main/java/org/zwobble/couscous/transforms/SplitStaticsFromInterface.java new file mode 100644 index 0000000..b2c88ea --- /dev/null +++ b/src/main/java/org/zwobble/couscous/transforms/SplitStaticsFromInterface.java @@ -0,0 +1,68 @@ +package org.zwobble.couscous.transforms; + +import com.google.common.collect.Iterables; +import org.zwobble.couscous.ast.*; +import org.zwobble.couscous.ast.visitors.NodeTransformer; +import org.zwobble.couscous.types.ScalarType; + +import java.util.*; + +import static org.zwobble.couscous.util.ExtraLists.eagerFilter; +import static org.zwobble.couscous.util.ExtraLists.list; +import static org.zwobble.couscous.util.ExtraSets.set; + +public class SplitStaticsFromInterface { + public static List<TypeNode> transform(List<TypeNode> typeNodes) { + List<TypeNode> result = new ArrayList<>(); + Set<ScalarType> splitInterfaces = new HashSet<>(); + + for (TypeNode typeNode : typeNodes) { + if (isSplittable(typeNode)) { + splitInterfaces.add(typeNode.getName()); + InterfaceNode interfaceNode = (InterfaceNode) typeNode; + result.add(ClassNode.declareClass( + staticTypeFor(interfaceNode.getName()), + list(), + set(), + interfaceNode.getFields(), + interfaceNode.getStaticConstructor(), + ConstructorNode.DEFAULT, + eagerFilter(interfaceNode.getMethods(), method -> method.isStatic()) + )); + result.add(InterfaceNode.declareInterface( + interfaceNode.getName(), + interfaceNode.getTypeParameters(), + interfaceNode.getSuperTypes(), + list(), + list(), + eagerFilter(interfaceNode.getMethods(), method -> !method.isStatic()) + )); + } else { + result.add(typeNode); + } + } + + NodeTransformer transformer = NodeTransformer.builder() + .transformReceiver((Receiver receiver) -> { + if (receiver instanceof StaticReceiver && splitInterfaces.contains(((StaticReceiver) receiver).getType())) { + return StaticReceiver.staticReceiver(staticTypeFor(((StaticReceiver) receiver).getType())); + } else { + return receiver; + } + }) + .build(); + return NodeTransformer.apply(transformer, result); + } + + private static ScalarType staticTypeFor(ScalarType name) { + return ScalarType.of(name.getQualifiedName() + "_static"); + } + + private static boolean isSplittable(TypeNode typeNode) { + if (!(typeNode instanceof InterfaceNode)) { + return false; + } + InterfaceNode interfaceNode = (InterfaceNode) typeNode; + return !interfaceNode.getFields().isEmpty() || Iterables.any(interfaceNode.getMethods(), MethodNode::isStatic); + } +} diff --git a/src/test/java/org/zwobble/couscous/tests/frontends/java/JavaReaderTests.java b/src/test/java/org/zwobble/couscous/tests/frontends/java/JavaReaderTests.java index 73745b2..7b208f5 100644 --- a/src/test/java/org/zwobble/couscous/tests/frontends/java/JavaReaderTests.java +++ b/src/test/java/org/zwobble/couscous/tests/frontends/java/JavaReaderTests.java @@ -18,6 +18,7 @@ import static org.zwobble.couscous.ast.AssignmentNode.assignStatement; import static org.zwobble.couscous.ast.ConstructorCallNode.constructorCall; import static org.zwobble.couscous.ast.ExpressionStatementNode.expressionStatement; import static org.zwobble.couscous.ast.FieldAccessNode.fieldAccess; +import static org.zwobble.couscous.ast.FieldDeclarationNode.staticField; import static org.zwobble.couscous.ast.FormalTypeParameterNode.formalTypeParameter; import static org.zwobble.couscous.ast.IfStatementNode.ifStatement; import static org.zwobble.couscous.ast.LiteralNode.UNIT; @@ -479,6 +480,28 @@ public class JavaReaderTests { } @Test + public void canReadInterfaceWithField() { + String source = "package com.example;" + + "public interface Example {" + + "final int VALUE = 1;" + + "}"; + List<TypeNode> classes = readSource("com/example/Example.java", source); + assertThat(classes, hasSize(1)); + InterfaceNode classNode = (InterfaceNode) classes.get(0); + + assertEquals(list(staticField("VALUE", Types.INT)), classNode.getFields()); + assertEquals( + list( + assignStatement( + FieldAccessNode.fieldAccess(ScalarType.of("com.example.Example"), "VALUE", Types.INT), + literal(1) + ) + ), + classNode.getStaticConstructor() + ); + } + + @Test public void canReadEmptyEnum() { String source = "package com.example;" + "public enum Example {}"; |
