rewrite-kotlin: variadic-by-default matching in the recipe DSL#7895
Merged
Conversation
Teach the `rewrite { } to { }` recipe DSL to handle varargs methods as
varargs by default. When the before pattern targets a method whose declared
last parameter is varargs, the args the author places in that slot become a
representative group rather than a literal count, so the recipe matches call
sites of any arity and carries them all through:
rewrite { a: Any, b: Any, c: Any -> java.util.Arrays.asList(a, b, c) } to
{ a, b, c -> java.util.List.of(a, b, c) }
matches asList(...) of any arity (2, 4, even 0). The group can be repositioned
in the after template (the slf4j case: move a trailing Throwable from before
the varargs run to after them), and an explicit `.strictArity()` modifier opts
out into exact-arity matching. The spread form `{ args -> asList(*args) }` is
also accepted.
Mechanics:
- The matcher for a varargs callee is always `prefix…, ..` (a MethodMatcher
only matches the declared single array param, so `(*,*,*)` never would),
with typed prefix params to avoid sibling-overload matches.
- The after template carries a sentinel + `V<k>` substitution source; the
runtime expands it to one `#{any()}` per matched arg and splices
getArguments()[k..end] (the canonical zero-arg `[J.Empty]` collapses to 0).
- `to` now returns a `RewriteRule` so `.strictArity()` chains off it; the FIR
checker accepts it as a valid rewrite terminal.
Also fix the mapped-type fallback customizer to PRESERVE varargs: re-exposed
Java varargs methods like `String.formatted(Object...)` now surface as a real
Kotlin `vararg args: Any?` (out-projected `Array<out Any?>` + isVararg=true)
instead of a plain `Array<Any?>`. The prior invariant-array form tripped
`IllegalStateException: Vararg expression has incorrect type` in fir2ir; the
out projection resolves it. So `formatted` is a first-class variadic callee and
`format(fmt, a, b) -> fmt.formatted(a, b)` works as natural by-example.
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.
What
Teaches the
rewrite { } to { }recipe DSL to treat varargs methods as variadic by default. When the before pattern targets a method whose declared last parameter is varargs, the args the author places in that slot become a representative group rather than a literal count — so the recipe matches call sites of any arity and carries them all through. The author writes the natural shape; no*argsceremony required.The variadic group can be repositioned in the after template — the motivating slf4j case moves a trailing
Throwablefrom before the varargs run to after them (log(msg, t, params…)→error(msg, params…, t)). An explicit.strictArity()modifier opts out into exact-arity matching, and the explicit spread form{ args: Array<Any> -> asList(*args) }is also supported.How
prefix…, ..— aMethodMatchermatches the declared signature, where varargs is a single array param, so a fixed(*,*,*)spec can never match it. Fixed prefix params are typed (Kotlin builtins → Java FQN) to avoid sibling-overload matches likeString.format(Locale, String, Object...).V<k>substitution source. At apply time the runtime expands the sentinel to one#{any()}per matched arg and splicesgetArguments()[k..end](the canonical zero-arg[J.Empty]collapses to count 0). Placeholders bind positionally, so in-place expansion keeps template and substitution array aligned. This handles pass-through, reorder, and.strictArity()(a runtime exact-count guard) uniformly.tonow returns aRewriteRuleso(rewrite { } to { }).strictArity()chains off it; the FIR checker acceptsstrictArityas a valid rewrite terminal.Bonus: the mapped-type fallback customizer now preserves varargs
String.formatted(Object...)(surfaced by rewrite-kotlin: re-expose Java instance methods Kotlin's mapped-type customizer hides #7893) now appear as a real Kotlinvararg args: Any?instead of a plainArray<Any?>. rewrite-kotlin: re-expose Java instance methods Kotlin's mapped-type customizer hides #7893 deliberately punted on this because surfacingisVararg = truetrippedIllegalStateException: Vararg expression has incorrect typein fir2ir — the cause was declaring the param as an invariantArray<Any?>; a Kotlin vararg's type is the out-projectedArray<out Any?>. With that fix authors callx.formatted("x", "y")naturally, andformattedbecomes a first-class variadic callee that the DSL handles for free.Testing
New cases in
RecipePluginRewriteTest(compile-and-run via the K2 plugin +rewriteRun): by-example pass-through (Java + Kotlin sources), explicit spread,String.format→formatted, the slf4j reorder, and.strictArity().RecipeMappedTypeFallbackTestupdated to assert the naturalformatted("x", "y")call. Full:rewrite-kotlin:testis green.