Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -704,7 +704,7 @@ class KotlinTypeMapping(
parentType = parentType.type
}
val resolvedDeclaringType = TypeUtils.asFullyQualified(parentType)
var returnType = type(function.returnTypeRef)
var returnType = remapKotlinBuiltin(type(function.returnTypeRef))
// Java parser uses the raw Class for a constructor's returnType, not the
// class's parameterized form. Kotlin's FIR renders a constructor's
// `returnTypeRef` as the class instantiated with its own type parameters
Expand All @@ -721,11 +721,11 @@ class KotlinTypeMapping(
else -> null
}
if (function.receiverParameter != null) {
parameterTypes!!.add(type(function.receiverParameter!!.typeRef))
parameterTypes!!.add(remapKotlinBuiltin(type(function.receiverParameter!!.typeRef))!!)
}
if (function.valueParameters.isNotEmpty()) {
for (p in function.valueParameters) {
val t = type(p.returnTypeRef, function)
val t = remapKotlinBuiltin(type(p.returnTypeRef, function))
if (t != null) {
parameterTypes!!.add(t)
}
Expand Down Expand Up @@ -973,10 +973,10 @@ class KotlinTypeMapping(
if (declaringType == null) {
declaringType = TypeUtils.asFullyQualified(type(firFile))
}
val returnType = type(function.resolvedType)
val returnType = remapKotlinBuiltin(type(function.resolvedType))

if (function.toResolvedCallableSymbol()?.receiverParameterSymbol != null) {
paramTypes!!.add(type(function.toResolvedCallableSymbol()?.receiverParameterSymbol!!.fir.typeRef))
paramTypes!!.add(remapKotlinBuiltin(type(function.toResolvedCallableSymbol()?.receiverParameterSymbol!!.fir.typeRef))!!)
}
// Build a mapping from parameter name to its corresponding argument expression
val paramToArg: Map<String, FirExpression>? =
Expand All @@ -993,12 +993,12 @@ class KotlinTypeMapping(
if (t is GenericTypeVariable) {
val arg = paramToArg?.get(p.name.asString())
if (arg != null) {
paramTypes.add(type(arg.resolvedType, function)!!)
paramTypes.add(remapKotlinBuiltin(type(arg.resolvedType, function))!!)
} else {
paramTypes.add(t)
}
} else {
paramTypes.add(t)
paramTypes.add(remapKotlinBuiltin(t)!!)
}
}
method.unsafeSet(
Expand Down Expand Up @@ -1114,6 +1114,18 @@ class KotlinTypeMapping(
return type.fqName.asString()
}

/**
* Convenience overload: apply [remapKotlinBuiltin] to any [JavaType], returning the
* input unchanged when it is not a [FullyQualified]. Callers use this on method
* parameter, receiver, return, and field types so Kotlin builtins (kotlin.String /
* kotlin.Throwable / ...) surface as the JVM FQN the Java parser would produce.
*/
private fun remapKotlinBuiltin(t: JavaType?): JavaType? {
if (t == null) return null
val fq = TypeUtils.asFullyQualified(t)
return if (fq != null) remapKotlinBuiltin(fq) else t
}

private fun remapKotlinBuiltin(fq: FullyQualified): FullyQualified {
// For a Parameterized (e.g. kotlin.Enum<Foo>) remap the underlying raw type but
// keep the parameterization so we don't lose type argument information.
Expand Down Expand Up @@ -1279,7 +1291,7 @@ class KotlinTypeMapping(
declaringType = declaringType.type
}

val typeRef = type(variable.returnTypeRef)
val typeRef = remapKotlinBuiltin(type(variable.returnTypeRef))
vt.unsafeSet(declaringType!!, typeRef, annotations)
return vt
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -535,7 +535,7 @@ fun method() {
@Override
public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, AtomicBoolean atomicBoolean) {
if ("println".equals(method.getSimpleName())) {
assertThat(method.getMethodType().toString()).isEqualTo("kotlin.io.ConsoleKt{name=println,return=void,parameters=[kotlin.Any]}");
assertThat(method.getMethodType().toString()).isEqualTo("kotlin.io.ConsoleKt{name=println,return=void,parameters=[java.lang.Object]}");
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is where I'm a bit doubtful; do we really want to map all Kotlin types to their Java equivalents, even for packages like kotlin.io ?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suppose not all Kotlin types have a direct Java equivalent. E.g. Nothing?
So I guess the answer would be no? For consistency.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Scoped to Java-origin classes (FirJavaClass) in 97295cd. The motivating Jackson case still works; Kotlin-declared signatures like kotlin.io.ConsoleKt.println(kotlin.Any) are now preserved. JDK classes go through Kotlin's classfile loader as a different FIR type, so they're out of scope here — that'd need a Kotlin-aware MethodMatcher.

found.set(true);
}
return super.visitMethodInvocation(method, atomicBoolean);
Expand All @@ -547,6 +547,39 @@ public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, Atomi
);
}

@Issue("https://github.com/openrewrite/rewrite/issues/7427")
@Test
void constructorParameterTypesRemapKotlinBuiltins() {
// A Kotlin constructor taking `String` / `Throwable` must surface parameter types
// as the Java-parser's `java.lang.String` / `java.lang.Throwable`, not the Kotlin
// builtins, so `MethodMatcher` patterns written against the Java FQN match.
rewriteRun(
kotlin(
"""
fun example(cause: Throwable): IllegalArgumentException {
return IllegalArgumentException("message", cause)
}
""",
spec -> spec.afterRecipe(cu -> {
var matcher = new MethodMatcher("java.lang.IllegalArgumentException <constructor>(String, Throwable)");
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The problem we saw was specifically with MethodMatcher not being KotlinTypeUtils aware, and hence seeing Kotlin types here instead of their Java equivalents.

var found = new AtomicBoolean(false);
new KotlinIsoVisitor<AtomicBoolean>() {
@Override
public J.NewClass visitNewClass(J.NewClass newClass, AtomicBoolean atomicBoolean) {
var paramTypes = newClass.getConstructorType().getParameterTypes();
assertThat(paramTypes.get(0).toString()).isEqualTo("java.lang.String");
assertThat(paramTypes.get(1).toString()).isEqualTo("java.lang.Throwable");
assertThat(matcher.matches(newClass)).isTrue();
atomicBoolean.set(true);
return super.visitNewClass(newClass, atomicBoolean);
}
}.visit(cu, found);
assertThat(found.get()).isTrue();
})
)
);
}

@Test
void whenExpression() {
rewriteRun(
Expand Down Expand Up @@ -658,13 +691,13 @@ public J.Binary visitBinary(J.Binary binary, AtomicBoolean b) {

@CsvSource(value = {
// Method type on overload with no named arguments.
"foo(\"\", 1, true)~org.example.openRewriteFile0Kt{name=foo,return=void,parameters=[kotlin.String,int,boolean]}",
"foo(\"\", 1, true)~org.example.openRewriteFile0Kt{name=foo,return=void,parameters=[java.lang.String,int,boolean]}",
// Method type on overload with named arguments.
"foo(b = 1)~org.example.openRewriteFile0Kt{name=foo,return=void,parameters=[int,boolean]}",
// Method type when named arguments are declared out of order.
"foo(trailingLambda = {}, noDefault = true, c = true, b = 1)~org.example.openRewriteFile0Kt{name=foo,return=void,parameters=[kotlin.String,int,boolean,boolean,kotlin.Function0<void>]}",
"foo(trailingLambda = {}, noDefault = true, c = true, b = 1)~org.example.openRewriteFile0Kt{name=foo,return=void,parameters=[java.lang.String,int,boolean,boolean,kotlin.Function0<void>]}",
// Method type with trailing lambda
"foo(b = 1, noDefault = true) {}~org.example.openRewriteFile0Kt{name=foo,return=void,parameters=[kotlin.String,int,boolean,boolean,kotlin.Function0<void>]}"
"foo(b = 1, noDefault = true) {}~org.example.openRewriteFile0Kt{name=foo,return=void,parameters=[java.lang.String,int,boolean,boolean,kotlin.Function0<void>]}"
}, delimiter = '~')
@ParameterizedTest
void methodInvocationWithDefaults(String invocation, String methodType) {
Expand Down