Skip to content

Commit 3453c89

Browse files
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/EqualsMethodUsage.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import org.openrewrite.kotlin.KotlinVisitor;
2828
import org.openrewrite.kotlin.marker.IsNullSafe;
2929
import org.openrewrite.kotlin.tree.K;
30+
import org.openrewrite.kotlin.tree.KotlinTypeUtils;
3031

3132
import java.time.Duration;
3233
import java.util.Set;
@@ -92,7 +93,7 @@ public J visitMethodInvocation(J.MethodInvocation method,
9293
if ("equals".equals(method.getSimpleName()) &&
9394
method.getMethodType() != null &&
9495
method.getArguments().size() == 1 &&
95-
TypeUtils.isOfClassType(method.getMethodType().getReturnType(), "kotlin.Boolean") &&
96+
KotlinTypeUtils.isOfClassType(method.getMethodType().getReturnType(), "kotlin.Boolean") &&
9697
method.getSelect() != null &&
9798
!method.getMarkers().findFirst(IsNullSafe.class).isPresent()
9899
) {

rewrite-kotlin/src/main/java/org/openrewrite/kotlin/cleanup/ReplaceCharToIntWithCode.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,9 @@ public TreeVisitor<?, ExecutionContext> getVisitor() {
4343
@Override
4444
public J visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) {
4545
if (CHAR_TO_INT_METHOD_MATCHER.matches(method) && method.getSelect() != null) {
46-
return KotlinTemplate.builder("#{any(kotlin.Char)}.code")
46+
// Receiver may be JVM primitive `char` (Kotlin's non-nullable Char
47+
// collapses to a primitive in the type model) or boxed Character.
48+
return KotlinTemplate.builder("#{any()}.code")
4749
.build()
4850
.apply(getCursor(), method.getCoordinates().replace(), method.getSelect())
4951
.withPrefix(method.getPrefix());
Lines changed: 269 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,269 @@
1+
/*
2+
* Copyright 2026 the original author or authors.
3+
* <p>
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
* <p>
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
* <p>
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.openrewrite.kotlin.tree;
17+
18+
import org.jspecify.annotations.Nullable;
19+
import org.openrewrite.java.tree.JavaType;
20+
import org.openrewrite.java.tree.TypeUtils;
21+
22+
import java.util.HashMap;
23+
import java.util.Map;
24+
25+
/**
26+
* Kotlin-aware counterpart to {@link TypeUtils}.
27+
* <p>
28+
* {@link org.openrewrite.kotlin.KotlinTypeMapping} produces {@link JavaType} instances
29+
* that use JVM fully-qualified names (so {@code kotlin.Any} becomes {@code java.lang.Object},
30+
* {@code kotlin.String} becomes {@code java.lang.String}, and so on). That means Java-authored
31+
* recipes matching on {@code java.lang.Object} / {@code java.lang.String} work uniformly
32+
* over Kotlin sources.
33+
* <p>
34+
* Recipes written from the Kotlin author's perspective — e.g. ones that refer to
35+
* {@code kotlin.collections.List} or {@code kotlin.Int} — should use the methods in this
36+
* class so the Kotlin FQN is accepted as an alias for the JVM FQN that the type model
37+
* actually carries.
38+
*/
39+
public final class KotlinTypeUtils {
40+
41+
/**
42+
* Kotlin → JVM FQN aliases. The keys are the Kotlin-world FQNs a recipe author may
43+
* reasonably type; the values are the JVM FQNs the type model actually uses.
44+
*/
45+
private static final Map<String, String> KOTLIN_TO_JVM_FQN = buildKotlinToJvmFqnMap();
46+
47+
/**
48+
* Inverse mapping: JVM → canonical Kotlin FQN. Only populated for JVM types that
49+
* have a natural Kotlin alias.
50+
*/
51+
private static final Map<String, String> JVM_TO_KOTLIN_FQN = buildJvmToKotlinFqnMap();
52+
53+
private KotlinTypeUtils() {
54+
}
55+
56+
private static Map<String, String> buildKotlinToJvmFqnMap() {
57+
Map<String, String> m = new HashMap<>();
58+
// Core built-in classes.
59+
m.put("kotlin.Any", "java.lang.Object");
60+
m.put("kotlin.Annotation", "java.lang.annotation.Annotation");
61+
m.put("kotlin.CharSequence", "java.lang.CharSequence");
62+
m.put("kotlin.Comparable", "java.lang.Comparable");
63+
m.put("kotlin.Enum", "java.lang.Enum");
64+
m.put("kotlin.Number", "java.lang.Number");
65+
m.put("kotlin.String", "java.lang.String");
66+
m.put("kotlin.Throwable", "java.lang.Throwable");
67+
68+
// Meta-annotations.
69+
m.put("kotlin.annotation.MustBeDocumented", "java.lang.annotation.Documented");
70+
m.put("kotlin.annotation.Repeatable", "java.lang.annotation.Repeatable");
71+
m.put("kotlin.annotation.Retention", "java.lang.annotation.Retention");
72+
m.put("kotlin.annotation.Target", "java.lang.annotation.Target");
73+
74+
// Kotlin primitives compile to JVM primitives (non-nullable) or boxed classes
75+
// (nullable). The parser produces JVM primitives for non-nullable positions, so
76+
// a recipe asking for "kotlin.Int" should also match `int` / `java.lang.Integer`.
77+
m.put("kotlin.Int", "java.lang.Integer");
78+
m.put("kotlin.Long", "java.lang.Long");
79+
m.put("kotlin.Short", "java.lang.Short");
80+
m.put("kotlin.Byte", "java.lang.Byte");
81+
m.put("kotlin.Float", "java.lang.Float");
82+
m.put("kotlin.Double", "java.lang.Double");
83+
m.put("kotlin.Boolean", "java.lang.Boolean");
84+
m.put("kotlin.Char", "java.lang.Character");
85+
m.put("kotlin.Unit", "java.lang.Void");
86+
87+
// Kotlin collection interfaces compile to their java.util counterparts at the JVM level.
88+
m.put("kotlin.collections.Collection", "java.util.Collection");
89+
m.put("kotlin.collections.Iterable", "java.lang.Iterable");
90+
m.put("kotlin.collections.Iterator", "java.util.Iterator");
91+
m.put("kotlin.collections.List", "java.util.List");
92+
m.put("kotlin.collections.ListIterator", "java.util.ListIterator");
93+
m.put("kotlin.collections.Map", "java.util.Map");
94+
m.put("kotlin.collections.Map.Entry", "java.util.Map$Entry");
95+
m.put("kotlin.collections.MutableCollection", "java.util.Collection");
96+
m.put("kotlin.collections.MutableIterable", "java.lang.Iterable");
97+
m.put("kotlin.collections.MutableIterator", "java.util.Iterator");
98+
m.put("kotlin.collections.MutableList", "java.util.List");
99+
m.put("kotlin.collections.MutableListIterator", "java.util.ListIterator");
100+
m.put("kotlin.collections.MutableMap", "java.util.Map");
101+
m.put("kotlin.collections.MutableMap.Entry", "java.util.Map$Entry");
102+
m.put("kotlin.collections.MutableSet", "java.util.Set");
103+
m.put("kotlin.collections.Set", "java.util.Set");
104+
return m;
105+
}
106+
107+
private static Map<String, String> buildJvmToKotlinFqnMap() {
108+
Map<String, String> m = new HashMap<>();
109+
m.put("java.lang.Object", "kotlin.Any");
110+
m.put("java.lang.annotation.Annotation", "kotlin.Annotation");
111+
m.put("java.lang.CharSequence", "kotlin.CharSequence");
112+
m.put("java.lang.Comparable", "kotlin.Comparable");
113+
m.put("java.lang.Enum", "kotlin.Enum");
114+
m.put("java.lang.Number", "kotlin.Number");
115+
m.put("java.lang.String", "kotlin.String");
116+
m.put("java.lang.Throwable", "kotlin.Throwable");
117+
return m;
118+
}
119+
120+
/**
121+
* If {@code fqn} is a recognised Kotlin built-in name (e.g. {@code kotlin.Any}),
122+
* returns the JVM FQN that the type model uses for it (e.g. {@code java.lang.Object}).
123+
* Otherwise returns {@code fqn} unchanged.
124+
*/
125+
public static String toJvmFqn(String fqn) {
126+
String jvm = KOTLIN_TO_JVM_FQN.get(fqn);
127+
return jvm != null ? jvm : fqn;
128+
}
129+
130+
/**
131+
* If {@code fqn} has a canonical Kotlin alias (e.g. {@code java.lang.Object}),
132+
* returns that alias (e.g. {@code kotlin.Any}). Otherwise returns {@code fqn}
133+
* unchanged.
134+
*/
135+
public static String toKotlinFqn(String fqn) {
136+
String kotlin = JVM_TO_KOTLIN_FQN.get(fqn);
137+
return kotlin != null ? kotlin : fqn;
138+
}
139+
140+
/**
141+
* {@link TypeUtils#isOfClassType(JavaType, String)} that also recognises Kotlin
142+
* built-in FQNs. Callers may pass either the Kotlin name or the JVM name; both
143+
* forms match types that carry the JVM representation. For Kotlin primitive FQNs
144+
* (e.g. {@code kotlin.Int}) the match also accepts the JVM primitive (e.g. {@code int}),
145+
* since the parser unboxes non-nullable primitive uses.
146+
*/
147+
public static boolean isOfClassType(@Nullable JavaType type, String fqn) {
148+
if (TypeUtils.isOfClassType(type, fqn)) {
149+
return true;
150+
}
151+
// Kotlin primitive aliases also accept the JVM primitive form.
152+
if (type instanceof JavaType.Primitive) {
153+
JavaType.Primitive expected = kotlinFqnToPrimitive(fqn);
154+
if (expected != null && type == expected) {
155+
return true;
156+
}
157+
}
158+
String jvm = KOTLIN_TO_JVM_FQN.get(fqn);
159+
return jvm != null && TypeUtils.isOfClassType(type, jvm);
160+
}
161+
162+
private static JavaType.@Nullable Primitive kotlinFqnToPrimitive(String fqn) {
163+
switch (fqn) {
164+
case "kotlin.Int": return JavaType.Primitive.Int;
165+
case "kotlin.Long": return JavaType.Primitive.Long;
166+
case "kotlin.Short": return JavaType.Primitive.Short;
167+
case "kotlin.Byte": return JavaType.Primitive.Byte;
168+
case "kotlin.Float": return JavaType.Primitive.Float;
169+
case "kotlin.Double": return JavaType.Primitive.Double;
170+
case "kotlin.Boolean": return JavaType.Primitive.Boolean;
171+
case "kotlin.Char": return JavaType.Primitive.Char;
172+
case "kotlin.Unit": return JavaType.Primitive.Void;
173+
default: return null;
174+
}
175+
}
176+
177+
/**
178+
* {@link TypeUtils#isAssignableTo(String, JavaType)} that also recognises Kotlin
179+
* built-in FQNs. Callers may pass either form for {@code to}; both are treated as
180+
* equivalent when checking assignability against the JVM-oriented type model.
181+
*/
182+
public static boolean isAssignableTo(String to, @Nullable JavaType from) {
183+
if (TypeUtils.isAssignableTo(to, from)) {
184+
return true;
185+
}
186+
String jvm = KOTLIN_TO_JVM_FQN.get(to);
187+
return jvm != null && TypeUtils.isAssignableTo(jvm, from);
188+
}
189+
190+
/**
191+
* True when {@code type} corresponds to Kotlin's {@code Int} — either as the
192+
* JVM primitive {@code int} or as the boxed {@code java.lang.Integer}.
193+
*/
194+
public static boolean isKotlinInt(@Nullable JavaType type) {
195+
return type == JavaType.Primitive.Int || TypeUtils.isOfClassType(type, "java.lang.Integer");
196+
}
197+
198+
/**
199+
* True when {@code type} corresponds to Kotlin's {@code Long} — either as the
200+
* JVM primitive {@code long} or as the boxed {@code java.lang.Long}.
201+
*/
202+
public static boolean isKotlinLong(@Nullable JavaType type) {
203+
return type == JavaType.Primitive.Long || TypeUtils.isOfClassType(type, "java.lang.Long");
204+
}
205+
206+
/**
207+
* True when {@code type} corresponds to Kotlin's {@code Short} — either as the
208+
* JVM primitive {@code short} or as the boxed {@code java.lang.Short}.
209+
*/
210+
public static boolean isKotlinShort(@Nullable JavaType type) {
211+
return type == JavaType.Primitive.Short || TypeUtils.isOfClassType(type, "java.lang.Short");
212+
}
213+
214+
/**
215+
* True when {@code type} corresponds to Kotlin's {@code Byte} — either as the
216+
* JVM primitive {@code byte} or as the boxed {@code java.lang.Byte}.
217+
*/
218+
public static boolean isKotlinByte(@Nullable JavaType type) {
219+
return type == JavaType.Primitive.Byte || TypeUtils.isOfClassType(type, "java.lang.Byte");
220+
}
221+
222+
/**
223+
* True when {@code type} corresponds to Kotlin's {@code Float} — either as the
224+
* JVM primitive {@code float} or as the boxed {@code java.lang.Float}.
225+
*/
226+
public static boolean isKotlinFloat(@Nullable JavaType type) {
227+
return type == JavaType.Primitive.Float || TypeUtils.isOfClassType(type, "java.lang.Float");
228+
}
229+
230+
/**
231+
* True when {@code type} corresponds to Kotlin's {@code Double} — either as the
232+
* JVM primitive {@code double} or as the boxed {@code java.lang.Double}.
233+
*/
234+
public static boolean isKotlinDouble(@Nullable JavaType type) {
235+
return type == JavaType.Primitive.Double || TypeUtils.isOfClassType(type, "java.lang.Double");
236+
}
237+
238+
/**
239+
* True when {@code type} corresponds to Kotlin's {@code Boolean} — either as the
240+
* JVM primitive {@code boolean} or as the boxed {@code java.lang.Boolean}.
241+
*/
242+
public static boolean isKotlinBoolean(@Nullable JavaType type) {
243+
return type == JavaType.Primitive.Boolean || TypeUtils.isOfClassType(type, "java.lang.Boolean");
244+
}
245+
246+
/**
247+
* True when {@code type} corresponds to Kotlin's {@code Char} — either as the
248+
* JVM primitive {@code char} or as the boxed {@code java.lang.Character}.
249+
*/
250+
public static boolean isKotlinChar(@Nullable JavaType type) {
251+
return type == JavaType.Primitive.Char || TypeUtils.isOfClassType(type, "java.lang.Character");
252+
}
253+
254+
/**
255+
* True when {@code type} is Kotlin's {@code Unit} or the JVM's {@code void}.
256+
* The Kotlin type model keeps {@code kotlin.Unit} as a class in non-return positions;
257+
* in return positions Kotlin's compiler normalises to JVM {@code void}.
258+
*/
259+
public static boolean isKotlinUnit(@Nullable JavaType type) {
260+
return type == JavaType.Primitive.Void || TypeUtils.isOfClassType(type, "kotlin.Unit");
261+
}
262+
263+
/**
264+
* True when {@code type} is {@code kotlin.Any} / {@code java.lang.Object}.
265+
*/
266+
public static boolean isAny(@Nullable JavaType type) {
267+
return TypeUtils.isObject(type);
268+
}
269+
}

0 commit comments

Comments
 (0)