Skip to content

Leave Kotlin and Groovy sources to ListFirstAndLast - #1222

Merged
jkschneider merged 1 commit into
mainfrom
list-first-last-jvm-languages
Aug 24, 2026
Merged

Leave Kotlin and Groovy sources to ListFirstAndLast#1222
jkschneider merged 1 commit into
mainfrom
list-first-last-jvm-languages

Conversation

@jkschneider

Copy link
Copy Markdown
Member

ListFirstAndLast rewrites list.get(0) to list.getFirst(). Kotlin and Groovy sources reach it through the shared J model, and in both the rewrite produces something that does not work.

Kotlin

xs[0] is modelled as a J.MethodInvocation named <get> carrying an org.openrewrite.kotlin.marker.IndexedAccess marker, with methodType java.util.List.get(int). MethodMatcher dispatches on the method type alone and cannot see the syntactic form, so the recipe fires, renames it, and empties the argument list. The marker then faithfully prints:

assertThat(capturedMdcValues[])
Syntax error: Expecting an index element.

Groovy

collection.get(0) loses its parentheses when the argument list empties:

collection.getFirst

That parses, and Groovy compiles it as a property read, so it fails at runtime with MissingPropertyException rather than at build time. Groovy's subscript form collection[0] is unaffected — it resolves to getAt, not get.

Fix

Preconditions.not(new KotlinFileChecker<>()) and GroovyFileChecker, which is what the seven MigrateCollections* recipes in this same package already do. No new mechanism, and rewrite-kotlin and rewrite-groovy are already dependencies.

Guarding rather than teaching the recipe to emit valid Kotlin is the right call on the merits, not just for convenience: getFirst() is not declared on kotlin.collections.List, so there is no correct output for this recipe to produce there. Kotlin's own idiom is first(), which would be a separate Kotlin-native recipe.

The sibling SequencedCollection recipes were checked and left alone — IteratorNext and StreamFindFirst gate on java.util.SequencedCollection appearing in the receiver's type hierarchy, which Kotlin's mapped collection types do not carry, so a guard there would be untestable dead code.

Tests

Three cases added to the existing NoChange nested class covering Kotlin index access, Kotlin get(0), and Groovy get(0). Each fails without the guard, producing collection[], collection.getFirst() and collection.getFirst respectively. Full migrate.util and migrate.lang packages: 669 tests, 0 failures.

Found by compiling the output of UpgradeToJava25 across a set of open source repositories.

Kotlin models `xs[0]` as a `get` invocation carrying an index-access marker, so
the matcher fires and emptying the argument list prints `xs[]`. Groovy fails more
quietly: `collection.get(0)` loses its parentheses and becomes a property read
that only fails at runtime. Neither language can use `getFirst()` anyway — it is
not declared on their mapped list types — so the file checkers already used by
the neighbouring collection recipes are the right guard.

The Lombok annotation processor test no longer needs to be marked as expected to
fail now that the reactor handling it depends on has been corrected.
@github-project-automation github-project-automation Bot moved this to In Progress in OpenRewrite Aug 24, 2026
@jkschneider
jkschneider merged commit ff321ae into main Aug 24, 2026
1 check failed
@jkschneider
jkschneider deleted the list-first-last-jvm-languages branch August 24, 2026 12:53
@github-project-automation github-project-automation Bot moved this from In Progress to Done in OpenRewrite Aug 24, 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