Skip to content

Commit

Permalink
Support anonymous functions
Browse files Browse the repository at this point in the history
Anonymous functions are mapped to a lambda with an additional `AnonymousFunction` marker, so that it can be serialized as expected again.

Fixes: #287
  • Loading branch information
knutwannheden committed Sep 7, 2023
1 parent 7e23c35 commit bb118d4
Show file tree
Hide file tree
Showing 5 changed files with 147 additions and 61 deletions.
53 changes: 28 additions & 25 deletions src/main/java/org/openrewrite/kotlin/internal/KotlinPrinter.java
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@

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

public KotlinPrinter() {
delegate = delegate();
}
Expand Down Expand Up @@ -153,7 +154,7 @@ public J visitDestructuringDeclaration(K.DestructuringDeclaration destructuringD
}

if (!destructuringDeclaration.getInitializer().getVariables().isEmpty() &&
destructuringDeclaration.getInitializer().getVariables().get(0).getPadding().getInitializer() != null) {
destructuringDeclaration.getInitializer().getVariables().get(0).getPadding().getInitializer() != null) {
visitSpace(Objects.requireNonNull(destructuringDeclaration.getInitializer().getVariables().get(0).getPadding()
.getInitializer()).getBefore(), Space.Location.LANGUAGE_EXTENSION, p);
p.append("=");
Expand Down Expand Up @@ -272,14 +273,14 @@ public J visitProperty(K.Property property, PrintOutputCapture<P> p) {
Extension extension = vd.getMarkers().findFirst(Extension.class).orElse(null);
if (extension != null) {
if (property.getSetter() != null &&
!property.getSetter().getParameters().isEmpty() &&
property.getSetter().getParameters().get(0) instanceof J.VariableDeclarations) {
!property.getSetter().getParameters().isEmpty() &&
property.getSetter().getParameters().get(0) instanceof J.VariableDeclarations) {
visit(((J.VariableDeclarations) property.getSetter().getParameters().get(0)).getTypeExpression(), p);
delegate.visitSpace(property.getSetter().getPadding().getParameters().getPadding().getElements().get(0).getAfter(), Space.Location.LANGUAGE_EXTENSION, p);
p.append(".");
} else if (property.getGetter() != null &&
!property.getGetter().getParameters().isEmpty() &&
property.getGetter().getParameters().get(0) instanceof J.VariableDeclarations) {
!property.getGetter().getParameters().isEmpty() &&
property.getGetter().getParameters().get(0) instanceof J.VariableDeclarations) {
visit(((J.VariableDeclarations) property.getGetter().getParameters().get(0)).getTypeExpression(), p);
delegate.visitSpace(property.getGetter().getPadding().getParameters().getPadding().getElements().get(0).getAfter(), Space.Location.LANGUAGE_EXTENSION, p);
p.append(".");
Expand Down Expand Up @@ -461,13 +462,13 @@ public J visitBlock(J.Block block, PrintOutputCapture<P> p) {
p.append("=");
}

boolean omitParens = block.getMarkers().findFirst(OmitBraces.class).isPresent();
if (!omitParens) {
boolean omitBraces = block.getMarkers().findFirst(OmitBraces.class).isPresent();
if (!omitBraces) {
p.append("{");
}
visitStatements(block.getPadding().getStatements(), JRightPadded.Location.BLOCK_STATEMENT, p);
visitSpace(block.getEnd(), Space.Location.BLOCK_END, p);
if (!omitParens) {
if (!omitBraces) {
p.append("}");
}
afterSyntax(block, p);
Expand Down Expand Up @@ -693,26 +694,28 @@ public J visitLabel(J.Label label, PrintOutputCapture<P> p) {
@Override
public J visitLambda(J.Lambda lambda, PrintOutputCapture<P> p) {
beforeSyntax(lambda, Space.Location.LAMBDA_PREFIX, p);
boolean omitBraces = lambda.getMarkers().findFirst(OmitBraces.class).isPresent();
if (!omitBraces) {
p.append('{');
}

visitLambdaParameters(lambda.getParameters(), p);
if (!lambda.getParameters().getParameters().isEmpty()) {
visitSpace(lambda.getArrow(), Space.Location.LAMBDA_ARROW_PREFIX, p);
p.append("->");
}
if (lambda.getBody() instanceof J.Block) {
J.Block block = (J.Block) lambda.getBody();
visitStatements(block.getPadding().getStatements(), JRightPadded.Location.BLOCK_STATEMENT, p);
visitSpace(block.getEnd(), Space.Location.BLOCK_END, p);
if (lambda.getMarkers().findFirst(AnonymousFunction.class).isPresent()) {
p.append("fun");
visitLambdaParameters(lambda.getParameters(), p);
visitBlock((J.Block) lambda.getBody(), p);
} else {
boolean omitBraces = lambda.getMarkers().findFirst(OmitBraces.class).isPresent();
if (!omitBraces) {
p.append('{');
}

visitLambdaParameters(lambda.getParameters(), p);
if (!lambda.getParameters().getParameters().isEmpty()) {
visitSpace(lambda.getArrow(), Space.Location.LAMBDA_ARROW_PREFIX, p);
p.append("->");
}
visit(lambda.getBody(), p);
if (!omitBraces) {
p.append('}');
}
}
if (!omitBraces) {
p.append('}');
}

afterSyntax(lambda, p);
return lambda;
}
Expand Down Expand Up @@ -874,7 +877,7 @@ private void visitArgumentsContainer(JContainer<Expression> argContainer, Space.

if (i > 0 && omitParensOnMethod && (
!args.get(0).getElement().getMarkers().findFirst(OmitParentheses.class).isPresent() &&
!args.get(0).getElement().getMarkers().findFirst(OmitParentheses.class).isPresent())) {
!args.get(0).getElement().getMarkers().findFirst(OmitParentheses.class).isPresent())) {
p.append(')');
} else if (i > 0) {
p.append(',');
Expand Down
32 changes: 32 additions & 0 deletions src/main/java/org/openrewrite/kotlin/marker/AnonymousFunction.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*
* Copyright 2023 the original author or authors.
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* https://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.openrewrite.kotlin.marker;

import lombok.Value;
import lombok.With;
import org.openrewrite.marker.Marker;

import java.util.UUID;

@Value
@With
public class AnonymousFunction implements Marker {
UUID id;

public AnonymousFunction(UUID id) {
this.id = id;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -452,9 +452,6 @@ class KotlinParserVisitor(
if (body is J.Block && !omitBraces) {
body = body.withEnd(sourceBefore("}"))
}
if (body != null && anonymousFunction.valueParameters.isEmpty()) {
body = body.withMarkers(body.markers.removeByType(OmitBraces::class.java))
}
if (body == null) {
body = J.Block(
randomId(),
Expand All @@ -481,10 +478,54 @@ class KotlinParserVisitor(
anonymousFunctionExpression: FirAnonymousFunctionExpression,
data: ExecutionContext
): J {
if (!anonymousFunctionExpression.anonymousFunction.isLambda) {
throw UnsupportedOperationException("Unsupported anonymous function expression.")
val anonymousFunction = anonymousFunctionExpression.anonymousFunction
if (anonymousFunction.isLambda) {
return visitAnonymousFunction(anonymousFunction, data)
} else {
val prefix = sourceBefore("fun")
val before = sourceBefore("(")
val params: List<JRightPadded<J>> = if (anonymousFunction.valueParameters.isNotEmpty())
convertAll(
anonymousFunction.valueParameters, ",", ")", data
)
else
listOf(
padRight(
J.Empty(randomId(), sourceBefore(")"), Markers.EMPTY), Space.EMPTY
)
)

val body = mapFunctionBody(anonymousFunction, data)!!
return J.Lambda(
randomId(),
prefix,
Markers.EMPTY.addIfAbsent(AnonymousFunction(randomId())),
J.Lambda.Parameters(randomId(), before, Markers.EMPTY, true, params),
Space.EMPTY,
body,
null
)
}
}

private fun mapFunctionBody(function: FirFunction, data: ExecutionContext): J.Block? {
val saveCursor = cursor
val before = whitespace()
return if (function.body is FirSingleExpressionBlock) {
if (skip("=")) {
convertToBlock(function.body as FirSingleExpressionBlock, data).withPrefix(before)
} else {
throw IllegalStateException("Unexpected single block expression, cursor is likely at the wrong position.")
}
} else if (function.body is FirBlock) {
cursor(saveCursor)
visitElement(function.body!!, data) as J.Block?
} else if (function.body == null) {
cursor(saveCursor)
null
} else {
throw IllegalStateException("Unexpected function body.")
}
return visitAnonymousFunction(anonymousFunctionExpression.anonymousFunction, data)
}

override fun visitAnonymousInitializer(anonymousInitializer: FirAnonymousInitializer, data: ExecutionContext): J {
Expand Down Expand Up @@ -2291,17 +2332,7 @@ class KotlinParserVisitor(
} else {
cursor(saveCursor)
}
var body: J.Block? = null
saveCursor = cursor
val blockPrefix = whitespace()
if (propertyAccessor.body is FirSingleExpressionBlock) {
if (skip("=")) {
body = convertToBlock(propertyAccessor.body as FirSingleExpressionBlock, data).withPrefix(blockPrefix)
}
} else {
cursor(saveCursor)
body = visitElement(propertyAccessor.body!!, data) as J.Block?
}
val body = mapFunctionBody(propertyAccessor, data)
return J.MethodDeclaration(
randomId(),
prefix,
Expand Down Expand Up @@ -2663,24 +2694,7 @@ class KotlinParserVisitor(
} else {
cursor(saveCursor)
}
val body: J.Block?
saveCursor = cursor
before = whitespace()
if (simpleFunction.body is FirSingleExpressionBlock) {
if (skip("=")) {
body = convertToBlock(simpleFunction.body as FirSingleExpressionBlock, data).withPrefix(before)
} else {
throw IllegalStateException("Unexpected single block expression, cursor is likely at the wrong position.")
}
} else if (simpleFunction.body is FirBlock) {
cursor(saveCursor)
body = visitElement(simpleFunction.body!!, data) as J.Block?
} else if (simpleFunction.body == null) {
cursor(saveCursor)
body = null
} else {
throw IllegalStateException("Unexpected function body.")
}
val body = mapFunctionBody(simpleFunction, data)

return J.MethodDeclaration(
randomId(),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
* Copyright 2023 the original author or authors.
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* https://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.openrewrite.kotlin.tree;

import org.junit.jupiter.api.Test;
import org.openrewrite.Issue;
import org.openrewrite.test.RewriteTest;

import static org.openrewrite.kotlin.Assertions.kotlin;

class AnonymousFunctionTest implements RewriteTest {

@Test
@Issue("https://github.com/openrewrite/rewrite-kotlin/issues/287")
void notInitialized() {
rewriteRun(
kotlin(
"""
val positive = fun(i: Int) = i > 0
"""
)
);
}
}
2 changes: 1 addition & 1 deletion src/test/java/org/openrewrite/kotlin/tree/ArrayTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ void conditionalArraySize() {
rewriteRun(
kotlin(
"""
val arr = IntArray ( if (true) else 1 )
val arr = IntArray ( if (true) 0 else 1 )
"""
)
);
Expand Down

0 comments on commit bb118d4

Please sign in to comment.