Skip to content

Kotlin recipe DSL: emit precise arg-count matcher patterns instead of (..)#7737

Merged
jkschneider merged 1 commit into
mainfrom
kotlin-munich
May 20, 2026
Merged

Kotlin recipe DSL: emit precise arg-count matcher patterns instead of (..)#7737
jkschneider merged 1 commit into
mainfrom
kotlin-munich

Conversation

@jkschneider
Copy link
Copy Markdown
Member

Summary

Recipe xs.filter(p).any()xs.any(p) was silently dropping code when the source had xs.filter(p1).any { p2 } — the with-predicate any overload. The K2 plugin's computed matcher spec was kotlin.collections.CollectionsKt any(..), where (..) matches any arity, so both any() and any(predicate) matched. The substitution-source CSV references only the inner's arg slot, so the runtime helper happily produced xs.any(p1), throwing away the outer .any { p2 } body the author wrote.

Real-world surface: a ~15-line .any { bounds -> Rectangle(...).intersects(...) } in YiiGuxing/TranslationPlugin collapsed to a one-line .any { windowLocation == ... }. The .any { } body was the actual intersect check; it just vanished.

Tighten computeMatcherSpec to emit the precise JVM arg count instead of (..):

  • 0 args → ()
  • 1 arg → (*)
  • 2 args → (*,*)
  • etc.

Arg count is computed against the JVM-resolved signature, differing by call shape:

  • Member call (dispatch receiver, e.g. String.lowercase()): just the source-level value args.
  • Extension or top-level facade call (e.g. Iterable<T>.any(p)): the receiver is lifted to the first arg of the static facade method, so the JVM arg list is (extension) receiver + value args.

Overloaded names like any / none / count no longer cross-match across overloads.

Test plan

  • New recipe targeting no-arg overload does not match the with-predicate overload test in RecipePluginRewriteTest pins the user-reported case: the xs.filter(p).any() → xs.any(p) recipe leaves xs.filter { ... }.any { ... } untouched instead of clobbering it.
  • ./gradlew :rewrite-kotlin:test green (no regression in the existing single-overload recipes).

… (..)

Recipe `xs.filter(p).any()` -> `xs.any(p)` was silently dropping code when
the source had `xs.filter(p1).any { p2 }` — the with-predicate `any`
overload. The K2 plugin's computed matcher spec was `kotlin.collections.CollectionsKt any(..)`,
where `(..)` matches any arity, so both `any()` and `any(predicate)` matched.
The substitution-source CSV references only the inner's arg slot, so the
runtime helper happily produced `xs.any(p1)`, throwing away the outer
`.any { p2 }` body the author wrote. Real-world surface: a `~15-line`
.any { bounds -> Rectangle(...).intersects(...) } in YiiGuxing/TranslationPlugin
collapsed to a one-line `.any { windowLocation == ... }`.

Tighten `computeMatcherSpec` to emit the precise JVM arg count instead of
`(..)`:

  - 0 args -> `()`
  - 1 arg  -> `(*)`
  - 2 args -> `(*,*)`
  - etc.

Arg count is computed against the JVM-resolved signature, differing by
call shape:

  - Member call (dispatch receiver, e.g. `String.lowercase()`): just the
    source-level value args.
  - Extension or top-level facade call (e.g. `Iterable<T>.any(p)`): the
    receiver is lifted to the first arg of the static facade method, so
    the JVM arg list is (extension) receiver + value args.

Overloaded names like `any` / `none` / `count` no longer cross-match
across overloads.

Regression test pins the user-reported case: the
`xs.filter(p).any() -> xs.any(p)` recipe leaves
`xs.filter { ... }.any { ... }` untouched instead of clobbering it.

Surfaced by recipes-kotlin Performance recipes against real corpora.
@jkschneider jkschneider merged commit 1d24016 into main May 20, 2026
1 check passed
@jkschneider jkschneider deleted the kotlin-munich branch May 20, 2026 01:41
@github-project-automation github-project-automation Bot moved this from In Progress to Done in OpenRewrite May 20, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Archived in project

Development

Successfully merging this pull request may close these issues.

1 participant