Skip to content

Commit

Permalink
Refactor constructor delegation
Browse files Browse the repository at this point in the history
Add new `K.ConstructorDelegationCall` type to be used for constructor delegation when specified in class header.
  • Loading branch information
knutwannheden committed Sep 8, 2023
1 parent cf9c2dc commit ce32fcf
Show file tree
Hide file tree
Showing 7 changed files with 138 additions and 106 deletions.
5 changes: 5 additions & 0 deletions src/main/java/org/openrewrite/kotlin/KotlinIsoVisitor.java
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,11 @@ public K.Binary visitBinary(K.Binary binary, P p) {
return (K.Binary) super.visitBinary(binary, p);
}

@Override
public K.ConstructorDelegationCall visitConstructorDelegationCall(K.ConstructorDelegationCall destructuringDeclaration, P p) {
return (K.ConstructorDelegationCall) super.visitConstructorDelegationCall(destructuringDeclaration, p);
}

@Override
public K.DestructuringDeclaration visitDestructuringDeclaration(K.DestructuringDeclaration destructuringDeclaration, P p) {
return (K.DestructuringDeclaration) super.visitDestructuringDeclaration(destructuringDeclaration, p);
Expand Down
9 changes: 9 additions & 0 deletions src/main/java/org/openrewrite/kotlin/KotlinVisitor.java
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,15 @@ public J visitBinary(K.Binary binary, P p) {
return b;
}

public J visitConstructorDelegationCall(K.ConstructorDelegationCall constructorDelegationCall, P p) {
K.ConstructorDelegationCall d = constructorDelegationCall;
d = d.withPrefix(visitSpace(d.getPrefix(), KSpace.Location.CONSTRUCTOR_DELEGATION_CALL_PREFIX, p));
d = d.withMarkers(visitMarkers(d.getMarkers(), p));
d = d.withTypeTree(visitAndCast(d.getTypeTree(), p));
d = d.getPadding().withArguments(visitContainer(d.getPadding().getArguments(), JContainer.Location.METHOD_INVOCATION_ARGUMENTS, p));
return d;
}

public J visitDestructuringDeclaration(K.DestructuringDeclaration destructuringDeclaration, P p) {
K.DestructuringDeclaration d = destructuringDeclaration;
d = d.withPrefix(visitSpace(d.getPrefix(), KSpace.Location.DESTRUCTURING_DECLARATION_PREFIX, p));
Expand Down
39 changes: 10 additions & 29 deletions src/main/java/org/openrewrite/kotlin/internal/KotlinPrinter.java
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,6 @@
import java.util.Optional;
import java.util.function.UnaryOperator;

import static org.openrewrite.Tree.randomId;

public class KotlinPrinter<P> extends KotlinVisitor<PrintOutputCapture<P>> {
private final KotlinJavaPrinter<P> delegate;

Expand Down Expand Up @@ -129,6 +127,15 @@ public J visitBinary(K.Binary binary, PrintOutputCapture<P> p) {
return binary;
}

@Override
public J visitConstructorDelegationCall(K.ConstructorDelegationCall constructorDelegationCall, PrintOutputCapture<P> p) {
beforeSyntax(constructorDelegationCall, KSpace.Location.CONSTRUCTOR_DELEGATION_CALL_PREFIX, p);
visit(constructorDelegationCall.getTypeTree(), p);
delegate.visitArgumentsContainer(constructorDelegationCall.getPadding().getArguments(), Space.Location.METHOD_INVOCATION_ARGUMENTS, p);
afterSyntax(constructorDelegationCall, p);
return constructorDelegationCall;
}

@Override
public J visitDestructuringDeclaration(K.DestructuringDeclaration destructuringDeclaration, PrintOutputCapture<P> p) {
beforeSyntax(destructuringDeclaration, KSpace.Location.DESTRUCTURING_DECLARATION_PREFIX, p);
Expand Down Expand Up @@ -560,18 +567,7 @@ public J visitClassDeclaration(J.ClassDeclaration classDecl, PrintOutputCapture<
for (int i = 0; i < nodes.size(); i++) {
JRightPadded<? extends J> node = nodes.get(i);
J element = node.getElement();
visit(element.getMarkers().findFirst(ConstructorDelegation.class)
.flatMap(m -> getConstructorDelegationCall(classDecl)
.map(c -> {
if (element instanceof J.Identifier) {
return c.withName((J.Identifier) element).withPrefix(Space.EMPTY);
} else if (element instanceof J.ParameterizedType) {
return new J.NewClass(randomId(), Space.EMPTY, Markers.EMPTY, null, Space.EMPTY, ((J.ParameterizedType) element), c.getPadding().getArguments(), null, null);
}
return element;
}))
.orElse(element),
p);
visit(element, p);
visitSpace(node.getAfter(), JContainer.Location.IMPLEMENTS.getElementLocation().getAfterLocation(), p);
visitMarkers(node.getMarkers(), p);
if (i < nodes.size() - 1) {
Expand All @@ -588,21 +584,6 @@ public J visitClassDeclaration(J.ClassDeclaration classDecl, PrintOutputCapture<
return classDecl;
}

private Optional<J.MethodInvocation> getConstructorDelegationCall(J.ClassDeclaration classDecl) {
for (Statement statement : classDecl.getBody().getStatements()) {
if (statement instanceof J.MethodDeclaration && statement.getMarkers().findFirst(PrimaryConstructor.class).isPresent()) {
J.MethodDeclaration constructor = (J.MethodDeclaration) statement;
if (constructor.isConstructor() && constructor.getBody() != null && !constructor.getBody().getStatements().isEmpty()) {
Statement delegationCall = constructor.getBody().getStatements().get(0);
if (delegationCall instanceof J.MethodInvocation && delegationCall.getMarkers().findFirst(ConstructorDelegation.class).isPresent()) {
return Optional.of((J.MethodInvocation) delegationCall);
}
}
}
}
return Optional.empty();
}

@Override
public J visitContinue(J.Continue continueStatement, PrintOutputCapture<P> p) {
beforeSyntax(continueStatement, Space.Location.CONTINUE_PREFIX, p);
Expand Down
94 changes: 94 additions & 0 deletions src/main/java/org/openrewrite/kotlin/tree/K.java
Original file line number Diff line number Diff line change
Expand Up @@ -494,6 +494,100 @@ public K.Binary withOperator(JLeftPadded<K.Binary.Type> operator) {
}
}

@SuppressWarnings("unused")
@FieldDefaults(makeFinal = true, level = AccessLevel.PRIVATE)
@EqualsAndHashCode(callSuper = false, onlyExplicitlyIncluded = true)
@AllArgsConstructor(access = AccessLevel.PRIVATE)
final class ConstructorDelegationCall implements K, TypeTree {

@Nullable
@NonFinal
transient WeakReference<ConstructorDelegationCall.Padding> padding;

@Getter
@With
@EqualsAndHashCode.Include
UUID id;

@Getter
@With
Space prefix;

@Getter
@With
Markers markers;

@Getter
@With
TypeTree typeTree;

JContainer<Expression> arguments;

public List<Expression> getArguments() {
return arguments.getElements();
}

public ConstructorDelegationCall withArguments(List<Expression> arguments) {
return getPadding().withArguments(JContainer.withElements(this.arguments, arguments));
}

public ConstructorDelegationCall(UUID id, Space prefix, Markers markers, TypeTree typeTree, JContainer<Expression> arguments) {
this.id = id;
this.prefix = prefix;
this.markers = markers;
this.typeTree = typeTree;
this.arguments = arguments;
}

@Override
public ConstructorDelegationCall withType(@Nullable JavaType type) {
return withTypeTree(typeTree.withType(type));
}

@Override
public <P> J acceptKotlin(KotlinVisitor<P> v, P p) {
return v.visitConstructorDelegationCall(this, p);
}

public ConstructorDelegationCall.Padding getPadding() {
ConstructorDelegationCall.Padding p;
if (this.padding == null) {
p = new ConstructorDelegationCall.Padding(this);
this.padding = new WeakReference<>(p);
} else {
p = this.padding.get();
if (p == null || p.t != this) {
p = new ConstructorDelegationCall.Padding(this);
this.padding = new WeakReference<>(p);
}
}
return p;
}

@Override
public @Nullable JavaType getType() {
return typeTree.getType();
}

@RequiredArgsConstructor
public static class Padding {
private final ConstructorDelegationCall t;

public JContainer<Expression> getArguments() {
return t.arguments;
}

public ConstructorDelegationCall withArguments(JContainer<Expression> arguments) {
return t.arguments == arguments ? t : new ConstructorDelegationCall(t.id, t.prefix, t.markers, t.typeTree, arguments);
}
}

@Override
public String toString() {
return withPrefix(Space.EMPTY).printTrimmed(new KotlinPrinter<>());
}
}

@SuppressWarnings("unused")
@FieldDefaults(makeFinal = true, level = AccessLevel.PRIVATE)
@EqualsAndHashCode(callSuper = false, onlyExplicitlyIncluded = true)
Expand Down
1 change: 1 addition & 0 deletions src/main/java/org/openrewrite/kotlin/tree/KSpace.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ public enum Location {
BINARY_OPERATOR,
BINARY_SUFFIX,
CHECK_NOT_NULL_PREFIX,
CONSTRUCTOR_DELEGATION_CALL_PREFIX,
CONSTRUCTOR_DELEGATION_PREFIX,
DESTRUCTURING_DECLARATION_PREFIX,
DESTRUCT_ELEMENTS,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4030,71 +4030,19 @@ class KotlinParserVisitor(
val symbol = typeRef.coneType.toRegularClassSymbol(firSession)
// Filter out generated types.
if (typeRef.source != null && typeRef.source!!.kind !is KtFakeSourceElementKind) {
var element = visitElement(typeRef, data) as TypeTree
val element: TypeTree
if (firPrimaryConstructor != null && symbol != null && ClassKind.CLASS == symbol.fir.classKind) {
// Wrap the element in a J.NewClass to preserve the whitespace and container of `( )`
val args = mapFunctionalCallArguments(firPrimaryConstructor.delegatedConstructor!!)
val delegationCall = J.MethodInvocation(
randomId(),
element.prefix,
Markers.EMPTY.addIfAbsent(ConstructorDelegation(randomId(), before)).addIfAbsent(Implicit(randomId())),
null,
null,
J.Identifier(
val delegationCall = K.ConstructorDelegationCall(
randomId(),
prefix,
whitespace(),
Markers.EMPTY,
emptyList(),
if (firPrimaryConstructor.delegatedConstructor!!.isThis) "this" else "super",
typeMapping.type(typeRef),
null
),
args,
typeMapping.type(firPrimaryConstructor.delegatedConstructor!!.calleeReference.resolved!!.resolvedSymbol) as? JavaType.Method
visitElement(typeRef, data) as TypeTree,
mapFunctionalCallArguments(firPrimaryConstructor.delegatedConstructor!!)
)
if (primaryConstructor == null) {
primaryConstructor = J.MethodDeclaration(
randomId(),
Space.EMPTY,
Markers.build(
listOf(
PrimaryConstructor(randomId()),
Implicit(randomId())
)
),
emptyList(), // TODO annotations
emptyList(), // TODO modifiers
null,
null,
J.MethodDeclaration.IdentifierWithAnnotations(
name.withMarkers(name.markers.addIfAbsent(Implicit(randomId()))),
emptyList()
),
JContainer.empty(),
null,
J.Block(
randomId(),
Space.EMPTY,
Markers.EMPTY.addIfAbsent(OmitBraces(randomId())),
JRightPadded(false, Space.EMPTY, Markers.EMPTY),
listOf(JRightPadded.build(delegationCall)),
Space.EMPTY
),
null,
null // TODO type
)
} else {
primaryConstructor = primaryConstructor.withBody(J.Block(
randomId(),
Space.EMPTY,
Markers.EMPTY.addIfAbsent(OmitBraces(randomId())),
JRightPadded(false, Space.EMPTY, Markers.EMPTY),
listOf(JRightPadded.build(delegationCall)),
Space.EMPTY
))
}
markers = markers.addIfAbsent(PrimaryConstructor(randomId()))
element = element.withMarkers(element.markers.addIfAbsent(ConstructorDelegation(randomId(), Space.EMPTY)))
element = delegationCall
} else {
element = visitElement(typeRef, data) as TypeTree
}
superTypes.add(
JRightPadded.build(element)
Expand Down
28 changes: 11 additions & 17 deletions src/test/java/org/openrewrite/kotlin/tree/FieldAccessTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,6 @@
import org.openrewrite.java.tree.J;
import org.openrewrite.java.tree.JavaType;
import org.openrewrite.kotlin.KotlinIsoVisitor;
import org.openrewrite.kotlin.marker.ConstructorDelegation;
import org.openrewrite.kotlin.marker.Implicit;
import org.openrewrite.test.RewriteTest;

import static org.assertj.core.api.Assertions.assertThat;
Expand Down Expand Up @@ -91,27 +89,23 @@ class Test(val id2 : Int) : @Suppress Super(1 + 3)
""",
spec -> spec.afterRecipe(cu -> {
J.ClassDeclaration test = (J.ClassDeclaration) cu.getStatements().get(1);
assertThat(test.getImplements()).satisfiesExactly(
superType -> {
K.ConstructorDelegationCall call = (K.ConstructorDelegationCall) superType;
assertThat(((JavaType.FullyQualified) call.getType()).getFullyQualifiedName()).isEqualTo("Super");
assertThat(((J.Identifier) call.getTypeTree()).getSimpleName()).isEqualTo("Super");
assertThat(call.getArguments()).satisfiesExactly(
id -> assertThat(id).isInstanceOf(J.Binary.class)
);
}
);
assertThat(test.getBody().getStatements()).satisfiesExactly(
stmt -> {
J.MethodDeclaration constr = (J.MethodDeclaration) stmt;
assertThat(constr.getParameters()).satisfiesExactly(
id2 -> assertThat(id2).isInstanceOf(J.VariableDeclarations.class)
);
assertThat(constr.getBody()).isNotNull();
assertThat(constr.getBody().getStatements()).satisfiesExactly(
stmt1 -> {
J.MethodInvocation call = (J.MethodInvocation) stmt1;
assertThat(call.getMarkers().getMarkers()).satisfiesExactlyInAnyOrder(
implicit -> assertThat(implicit).isInstanceOf(Implicit.class),
delegation -> assertThat(delegation).isInstanceOf(ConstructorDelegation.class)
);
assertThat(call.getArguments()).satisfiesExactly(
expr -> {
assertThat(expr).isInstanceOf(J.Binary.class);
}
);
}
);
assertThat(constr.getBody()).isNull();
}
);
})
Expand Down

0 comments on commit ce32fcf

Please sign in to comment.