Commit 3453c89
authored
Align Kotlin type model with Java parser output (#7364)
* Align Kotlin parser JVM type representation with Java parser
The Kotlin parser produces JavaType instances that diverge from the Java
parser's output for the same JDK classpath types, preventing cross-parser
deduplication in mixed Java/Kotlin monorepos (google/dagger scale: 1113
Bazel targets hitting OOM at 6GB+ heap from linear variant growth).
Fixes in KotlinTypeMapping.kt / KotlinIrTypeMapping.kt:
**Class flags for interfaces and annotation types.** Both the FirClass
path and the BinaryJavaClass path now set ACC_INTERFACE and ACC_ABSTRACT
(and clear ACC_FINAL) for INTERFACE and ANNOTATION_CLASS, and set
ACC_ENUM for ENUM_CLASS. Previously `java.io.Serializable` had flags
1025 (Public+Abstract), `@IntrinsicCandidate` / `@ValueBased` had 17
(Public+Final); now all match the Java parser's 1537.
**Default methods on interfaces.** Non-abstract, non-static instance
methods on interfaces now carry the Default flag (bit 43). Applied in
the FirFunction path, the JavaMethod path, the IR path, and
methodInvocationType. Interface instance methods also carry Abstract
(matching Java's parser) regardless of whether they have a default body.
**Annotation classes no longer synthesize a constructor.** Kotlin's FIR
includes a FirConstructor for annotation classes; the Java parser omits
constructors for annotations, so skip them here for cross-parser dedup.
**Remap Kotlin builtins for Java-origin types.** When a class whose
origin is FirDeclarationOrigin.Java has its supertype or interfaces
resolved to kotlin.Any, kotlin.Annotation, etc., remap to the Java FQN
(java.lang.Object, java.lang.annotation.Annotation, etc.). Kotlin-source
classes keep their explicit Kotlin references. Meta-annotations
(kotlin.annotation.Retention/Target/MustBeDocumented) are always remapped
in listAnnotations so Java classes' meta-annotations align.
**Strip kotlin.Any bounds on Java-origin type parameters.** For
`java.util.Optional<T>`, Kotlin's FIR resolves T's bound to kotlin.Any;
the Java parser represents unbounded type parameters with no bounds.
Strip kotlin.Any from bounds only when the containing declaration is
Java-origin so `<T : Any>` in Kotlin source is preserved.
* Remap Kotlin builtins to JVM FQNs universally
Previously the remap was scoped to Java-origin classes only, leaving
Kotlin source classes with supertype `kotlin.Any` / `kotlin.Enum<...>`
and type parameter bounds like `T extends kotlin.Any`. That meant
Java-authored recipes matching on `java.lang.Object` / `java.lang.String`
failed on Kotlin sources — `FindSql` in rewrite-sql wouldn't light up on
Kotlin code even though the runtime types are identical.
Apply the builtin remap universally so the parser produces the same JVM
FQNs the Java parser would for the same bytecode:
- Supertype and interfaces resolve through `remapKotlinBuiltin`
regardless of origin.
- Explicit `<T : Any>` in Kotlin source remaps to `T extends
java.lang.Object`. Implicit `kotlin.Any` bounds on Java-origin type
parameters (e.g. `java.util.Optional<T>`) are still stripped so they
match Java's unbounded `<T>`.
- `remapKotlinBuiltin` now preserves `Parameterized` wrappers —
`kotlin.Enum<Foo>` becomes `java.lang.Enum<Foo>` rather than losing
its type arguments.
Tests updated to reflect the JVM-native output. Kotlin-specific
semantics (e.g. reasoning about `kotlin.Any?` nullability, or whether a
declaration came from Kotlin source) would belong in a Kotlin-specific
`TypeUtils` layered over this representation, not in the type mapping
itself.
* Add KotlinTypeUtils for Kotlin-aware type matching
`KotlinTypeMapping` now produces JVM-native FQNs on the type model, so
Java-authored recipes matching on java.lang.Object / java.lang.String
already work over Kotlin sources. But recipes written from the Kotlin
author's perspective may reasonably say "match kotlin.Int" or
"match kotlin.collections.List" — those wouldn't find anything because
the type model carries the JVM name.
`KotlinTypeUtils` layers over `TypeUtils` with Kotlin-aware variants:
- `toJvmFqn` / `toKotlinFqn` — explicit FQN remap across the two worlds.
- `isOfClassType(type, fqn)` — accepts either the Kotlin or JVM name
and matches against the JVM representation the type model carries.
- `isAssignableTo(fqn, type)` — same aliasing for supertype checks.
- `isKotlinInt` / `isKotlinLong` / `isKotlinBoolean` / ... — match the
Kotlin primitive types regardless of whether they appear as JVM
primitives or their boxed equivalents.
- `isKotlinUnit`, `isAny` — convenience for the top-type and unit-return
semantics.
The alias table covers `kotlin.Any`, `kotlin.Annotation`,
`kotlin.CharSequence`, `kotlin.Comparable`, `kotlin.Enum`,
`kotlin.Number`, `kotlin.String`, `kotlin.Throwable`, the
`kotlin.annotation.*` meta-annotations, and the `kotlin.collections.*`
interfaces.
* Resolve F-bounded generics, fix nested class FQN, align method flags
Four related fixes that let Kotlin-produced JavaType instances dedup
against Java-parser output for common JDK types:
**F-bounded generic resolution.** `BaseStream.sequential()` returns `S`
where `<S extends BaseStream<T, S>>`. The Kotlin parser was resolving
`S` — a `JavaClassifierType` whose classifier is a `JavaTypeParameter` —
through `TypeUtils.asFullyQualified`, which returns null for a
GenericTypeVariable. The fallback then produced `JavaType.Unknown`,
which cascaded through every type whose method signatures referenced
`S`. A JavaClassifierType whose classifier is a type parameter can't
itself be parameterized, so return the GTV directly.
**Java-primitive signature collision.** `KotlinTypeSignatureBuilder`
was using `JavaType.Primitive.Int.className` (`"java.lang.Integer"`) as
the signature for JVM `int`. That collided with the `java.lang.Integer`
Class entry written by `javaClassType`, so once Integer was resolved
(as it is during Number processing) every subsequent primitive-int
lookup got the boxed Class back. Switch to `.keyword` (`"int"`) so the
cache keys are distinct.
**ACC_VARARGS → Flag.Varargs.** The JVM reuses bit 7 (`0x0080`) for
`ACC_TRANSIENT` on fields and `ACC_VARARGS` on methods. OpenRewrite's
`Flag` keeps them separate: Transient at bit 7, Varargs at bit 34. When
we read `method.access.toLong()` directly from the class file, the
varargs methods (`Set.of(E[])`, `MethodHandles.argumentsWithCombiner`,
annotation-array parameters on constructors) were being mis-flagged
as Transient. Rewrite the bit in the method / constructor paths.
**Nested-class FQN dollar-separation.** `BinaryJavaClass.fqName.asString()`
returns `java.util.Map.Entry` — dotted form, indistinguishable from a
package-qualified top-level class named `Entry`. The Java parser emits
the JVM-style `java.util.Map$Entry`. Walk `outerClass` to produce the
dollar-separated FQN so nested types dedup between the two parsers.
* Map non-nullable Kotlin primitives to JVM primitives
Previously the parser kept `kotlin.Int` / `kotlin.Boolean` / `kotlin.Unit`
etc. as `JavaType.Class` instances with FQNs in the `kotlin.*` namespace.
Java-authored recipes that reason about JVM primitives — `MethodMatcher`s
with `int` parameters, `TypeUtils.isString`, etc. — silently failed on
Kotlin sources because the primitive was hidden behind a class wrapper.
In `KotlinTypeMapping.type()`, intercept `ConeClassLikeType` /
`FirResolvedQualifier` references whose FQN is a Kotlin built-in
primitive and whose nullability is known to be non-nullable (so they
collapse to a JVM primitive on the JVM rather than to a boxed class).
Return the matching `JavaType.Primitive` directly.
Class-definition contexts (where we're processing the kotlin.Int class
itself to populate its methods) still go through `classType()`. Method
declaring-type lookups bypass the primitive remap via a new
`asDeclaringType()` helper — methods are members of the kotlin.Int
class, not of the JVM `int` primitive.
`KotlinTypeUtils.isOfClassType("kotlin.Int", primitive)` now also
returns true so Kotlin-perspective recipes still match.
Existing recipes that depended on the wrapped form updated:
- `EqualsMethodUsage` checks `kotlin.Boolean` returns through
`KotlinTypeUtils.isOfClassType` so Primitive.Boolean matches.
- `ReplaceCharToIntWithCode`'s template constraint loosened from
`any(kotlin.Char)` to `any()` since the receiver is now a primitive.
Test expectations updated: `kotlin.Int` → `int`, `kotlin.Boolean` →
`boolean`, `kotlin.Unit` → `void` in the type-string assertions, and
`isInstanceOf(JavaType.Class.class)` checks for primitive types
replaced with `isEqualTo(JavaType.Primitive.X)` equality assertions.
* Synthesize enum values/valueOf methods and nested-type flags
Three related additions that chip further through the cross-parser dedup
cascade for JDK types:
**Enum values()/valueOf() synthesis.** The Java compiler generates
`static T[] values()` and `static T valueOf(String)` on every enum, and
the Java parser surfaces them. Kotlin's BinaryJavaClass only exposes
source-declared methods, so enum types dedup-mismatch with Java over a
missing `values()` and extra/missing `valueOf` overload. Synthesize both
when processing a BinaryJavaClass whose `isEnum` is true — `values()`
with flags ACC_PUBLIC | ACC_STATIC and return type `T[]`, and
`valueOf(String)` with ACC_PUBLIC | ACC_STATIC returning T.
**Static flag on nested interfaces and annotation types.** Nested
interfaces and annotation types are always implicitly static on the JVM,
but the ACC_STATIC bit lives in the InnerClasses attribute — not in
BinaryJavaClass.access. Apply it explicitly so
`java.lang.invoke.MethodHandle$PolymorphicSignature` and its peers get
flags 1544 rather than 1536, matching the Java parser. Applied in both
the FirClass path (`classId.isNestedClass`) and the BinaryJavaClass path
(FQN contains `$` after toJvmFqn normalization).
**SignaturePolymorphic flag.** Methods annotated with
`@java.lang.invoke.MethodHandle.PolymorphicSignature` — e.g.
`MethodHandle.invoke`, `VarHandle.compareAndExchange` — carry bit 46
(Flag.SignaturePolymorphic) in the Java parser. Detect the annotation
and set the bit.
* Don't mark private interface methods as Abstract/Default
Java 9 introduced private methods on interfaces for refactoring the
default-method helper pattern. These are concrete methods with private
visibility — not abstract, not default. Two code paths (FirFunction and
JavaMethod) were unconditionally marking all instance methods on an
interface as Abstract, and as Default when non-abstract, producing
wrong flags for methods like `java.lang.foreign.SegmentAllocator`'s
private helpers.
Check the Private bit (bit 1) before applying the Abstract/Default
adjustment so private interface methods retain their narrower flag set.
* Strip Object bounds and align constructor/nested-class representation
Resolves the remaining cross-parser dedup divergences between the Kotlin
and Java parsers on JDK types like Predicate, Optional, and Properties:
- Strip `java.lang.Object` bounds from wildcards (both JavaWildcardType
and ConeKotlinTypeProjection paths) and from Java-origin type
parameters (ConeTypeParameterType path). Kotlin's FIR surfaces the
bytecode's explicit Object bound as `Generic{? super Object}` or
`Generic{T extends Object}`; the Java parser elides these as
`Generic{? super }` / `Generic{T}`. Only strip when the original
bound wasn't explicitly `kotlin.Any` so Kotlin-source `<T : Any>`
still surfaces with its author-intended bound.
- Also strip Object bounds in `KotlinIrTypeMapping.generic`.
- Align `javaClassSignature(BinaryJavaClass)` and
`javaParameterizedSignature(JavaClassifierType)` to produce the
JVM `Outer$Inner` form for nested classes, matching the Java parser
and the `toJvmFqn` used for class-cache keys.
- Return the raw `Class` (not the class's own `Parameterized`) for a
raw `JavaClassifierType` reference. Without this, a raw `Reference`
field inside a generic `Reference<T>` surfaces as `Reference<T>`
rather than `Reference`.
- Strip `ACC_FINAL` from constructor flags in both
`methodDeclarationType(FirFunction)` and `javaConstructorType`.
Kotlin's FIR synthesizes `FINAL` modality for every constructor on a
final class; the JVM bytecode carries no ACC_FINAL on constructors
and the Java parser reads flags from bytecode directly.
- Use the raw `Class` (not its `Parameterized` form) as the
declaringType/returnType of a constructor in both paths.
- Filter `java.lang.String.serialPersistentFields` to match the Java
parser's filter for this serialization-specific field.1 parent a8ac3d3 commit 3453c89
11 files changed
Lines changed: 1035 additions & 142 deletions
File tree
- rewrite-kotlin/src
- main
- java/org/openrewrite/kotlin
- cleanup
- tree
- kotlin/org/openrewrite/kotlin
- test/java/org/openrewrite/kotlin
- tree
Lines changed: 2 additions & 1 deletion
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
27 | 27 | | |
28 | 28 | | |
29 | 29 | | |
| 30 | + | |
30 | 31 | | |
31 | 32 | | |
32 | 33 | | |
| |||
92 | 93 | | |
93 | 94 | | |
94 | 95 | | |
95 | | - | |
| 96 | + | |
96 | 97 | | |
97 | 98 | | |
98 | 99 | | |
| |||
Lines changed: 3 additions & 1 deletion
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
43 | 43 | | |
44 | 44 | | |
45 | 45 | | |
46 | | - | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
47 | 49 | | |
48 | 50 | | |
49 | 51 | | |
| |||
Lines changed: 269 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
| 152 | + | |
| 153 | + | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
| 157 | + | |
| 158 | + | |
| 159 | + | |
| 160 | + | |
| 161 | + | |
| 162 | + | |
| 163 | + | |
| 164 | + | |
| 165 | + | |
| 166 | + | |
| 167 | + | |
| 168 | + | |
| 169 | + | |
| 170 | + | |
| 171 | + | |
| 172 | + | |
| 173 | + | |
| 174 | + | |
| 175 | + | |
| 176 | + | |
| 177 | + | |
| 178 | + | |
| 179 | + | |
| 180 | + | |
| 181 | + | |
| 182 | + | |
| 183 | + | |
| 184 | + | |
| 185 | + | |
| 186 | + | |
| 187 | + | |
| 188 | + | |
| 189 | + | |
| 190 | + | |
| 191 | + | |
| 192 | + | |
| 193 | + | |
| 194 | + | |
| 195 | + | |
| 196 | + | |
| 197 | + | |
| 198 | + | |
| 199 | + | |
| 200 | + | |
| 201 | + | |
| 202 | + | |
| 203 | + | |
| 204 | + | |
| 205 | + | |
| 206 | + | |
| 207 | + | |
| 208 | + | |
| 209 | + | |
| 210 | + | |
| 211 | + | |
| 212 | + | |
| 213 | + | |
| 214 | + | |
| 215 | + | |
| 216 | + | |
| 217 | + | |
| 218 | + | |
| 219 | + | |
| 220 | + | |
| 221 | + | |
| 222 | + | |
| 223 | + | |
| 224 | + | |
| 225 | + | |
| 226 | + | |
| 227 | + | |
| 228 | + | |
| 229 | + | |
| 230 | + | |
| 231 | + | |
| 232 | + | |
| 233 | + | |
| 234 | + | |
| 235 | + | |
| 236 | + | |
| 237 | + | |
| 238 | + | |
| 239 | + | |
| 240 | + | |
| 241 | + | |
| 242 | + | |
| 243 | + | |
| 244 | + | |
| 245 | + | |
| 246 | + | |
| 247 | + | |
| 248 | + | |
| 249 | + | |
| 250 | + | |
| 251 | + | |
| 252 | + | |
| 253 | + | |
| 254 | + | |
| 255 | + | |
| 256 | + | |
| 257 | + | |
| 258 | + | |
| 259 | + | |
| 260 | + | |
| 261 | + | |
| 262 | + | |
| 263 | + | |
| 264 | + | |
| 265 | + | |
| 266 | + | |
| 267 | + | |
| 268 | + | |
| 269 | + | |
0 commit comments