Kotlin recipe DSL: trailing-lambda parens + Java-static chain inner + bare-param after body#7709
Merged
Conversation
…ng lambda is present
`preserveTrailingLambdaShape` was unconditionally adding `OmitParentheses`
to the args container whenever the last arg was a `TrailingLambdaArgument`-
marked lambda. For multi-arg calls (`obj.method(arg, lambda)`),
`OmitParentheses` tells the Kotlin printer to drop ALL the call's parens
— so the call rendered as `obj.methodarg { lambda }` with no `(` after
the method name and no `)` before the lambda.
Gate `OmitParentheses` on `padded.size() == 1` (single-lambda-only form).
For multi-arg trailing-lambda calls, the `TrailingLambdaArgument` marker
on the lambda is the only signal needed: `KotlinPrinter.visitArgumentsContainer`
already emits `(` ... `)` around the non-lambda args and the lambda
outside.
Unblocks `xs.map(f).toMutableList()` -> `xs.mapTo(mutableListOf(), f)`
and the rest of the `mapTo`/`filterTo`/`flatMapTo` family.
…fter bodies
Two fixes in `RecipeIrGenerationExtension` that landed together because
the second only surfaces once the first is in place — verified by a new
end-to-end test `chain with Java-static inner segment — Optional_of_x_get
to x`.
1. Chain validator rejected Java-static inner segments. K2 wraps Java-
platform-typed call results (`Optional<T>!` -> `Optional<T>`) in an
`IrTypeOperatorCall(IMPLICIT_CAST)`. The chain detector's
`rawReceiver is IrCall` check was false because `rawReceiver` was the
type-op wrapper, not the wrapped `IrCall`. Added
`unwrapImplicitCasts(expr)` that peels `IMPLICIT_CAST` / `IMPLICIT_NOTNULL`
wrappers and call it at every `IrCall`-shape-check site in
`validateBeforeLambda` (root receiver, inner receiver, inner args,
outer args, root args).
2. `buildAfterTemplate` missed bare-param-reference after bodies.
With (1) in place, `Optional.of("hi").get()` matched but the rewrite
emitted the literal `x` instead of `"hi"`. The substitution-spot
visitor was driven by `expr.acceptChildrenVoid(visitor)`, which only
walks expr's CHILDREN. For an after body that's a single
`{ x -> x }`, expr IS the `IrGetValue` — it has no children, so the
visitor never registered it as a substitution spot and the template
rendered the source slice unchanged. Switched to
`expr.accept(visitor, null)` so the visitor sees expr itself first.
Unblocks `Optional.of(x).get() -> x` style chain rewrites where the
inner segment is a Java static call (the same shape covers
`Stream.of(x).findFirst()`, `List.of(x).getFirst()`, etc.).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
preserveTrailingLambdaShapewas dropping parens around non-lambda args. Multi-arg calls with a trailing lambda (obj.method(arg) { lambda }) were rendering asobj.methodarg { lambda }. Caused by unconditionally addingOmitParenthesesto the args container whenever the last arg carriedTrailingLambdaArgument. Fix: gateOmitParenthesesonpadded.size() == 1. For multi-arg trailing-lambda calls, the marker on the lambda is sufficient —KotlinPrinter.visitArgumentsContaineralready handles the rendering.Chain validator rejected Java-static inner segments. Patterns like
Optional.of(x).get()were silently falling through to the imperative path (thepluginRequiredruntime stub). Root cause: K2 wraps Java-platform-typed call results (Optional<T>!→Optional<T>) inIrTypeOperatorCall(IMPLICIT_CAST), so the chain detector'srawReceiver is IrCallcheck was false. Fix: addedunwrapImplicitCasts(expr)and called it at everyIrCall-shape-check site invalidateBeforeLambda.buildAfterTemplatemissed bare-param after bodies. With (2) in place,Optional.of("hi").get()matched but rewrote to the literalxinstead of"hi". The substitution-spot visitor usedexpr.acceptChildrenVoid(visitor), which only walksexpr's CHILDREN. For an after body like{ x -> x },exprIS theIrGetValue— it has no children, so the visitor never registered it and the template rendered the source slice unchanged. Fix:expr.accept(visitor, null)instead.New end-to-end test
RecipePluginRewriteTest."chain with Java-static inner segment — Optional_of_x_get to x"exercises all three fixes.Test plan
:rewrite-kotlin:test— all 1205+ tests green locally.moderneinc/recipes-kotlinPerformance and Interop recipe families against the rebuilt8.82.0-SNAPSHOT. Unblocksxs.map(f).toMutableList() → xs.mapTo(mutableListOf(), f)(and thefilterTo/filterNotTo/flatMapTosiblings) andOptional.of(x).get() → x.