Skip to content

Commit ac50a15

Browse files
committed
[JVM] Fix VerifyError with value class default argument in inline function
When an inline function has a value class parameter with a default value and the underlying type is nullable, the $default stub's parameter stays boxed while the implementation's parameter is unboxed. IrInlineDefaultCodegen copies bytecode verbatim, causing VerifyError due to the type mismatch. The fix checks for boxed inline class types in the stub's parameters and clears the DEFAULT_STUB_CALL_TO_IMPLEMENTATION origin, forcing IrInlineCodegen which handles type coercion correctly. ^KT-78051 fixed
1 parent de3f3cf commit ac50a15

File tree

2 files changed

+92
-1
lines changed

2 files changed

+92
-1
lines changed

compiler/ir/backend.jvm/lower/src/org/jetbrains/kotlin/backend/jvm/lower/JvmDefaultArgumentStubGenerator.kt

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,16 @@ import org.jetbrains.kotlin.backend.jvm.JvmBackendContext
1212
import org.jetbrains.kotlin.backend.jvm.JvmLoweredDeclarationOrigin
1313
import org.jetbrains.kotlin.backend.jvm.JvmLoweredStatementOrigin
1414
import org.jetbrains.kotlin.backend.jvm.ir.getJvmVisibilityOfDefaultArgumentStub
15+
import org.jetbrains.kotlin.backend.jvm.ir.isBoxedInlineClassType
1516
import org.jetbrains.kotlin.ir.builders.*
1617
import org.jetbrains.kotlin.ir.declarations.*
18+
import org.jetbrains.kotlin.ir.expressions.IrCall
19+
import org.jetbrains.kotlin.ir.expressions.IrExpression
1720
import org.jetbrains.kotlin.ir.util.hasAnnotation
1821
import org.jetbrains.kotlin.ir.util.isFinalClass
1922
import org.jetbrains.kotlin.ir.util.isTopLevelDeclaration
23+
import org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid
24+
import org.jetbrains.kotlin.ir.visitors.transformChildrenVoid
2025
import org.jetbrains.kotlin.name.JvmStandardClassIds
2126

2227
@PhasePrerequisites(
@@ -32,7 +37,26 @@ internal class JvmDefaultArgumentStubGenerator(context: JvmBackendContext) : Def
3237
) {
3338
override fun transformFlat(declaration: IrDeclaration): List<IrDeclaration>? {
3439
if (declaration is IrFunction && declaration.hasAnnotation(JvmStandardClassIds.JVM_EXPOSE_BOXED_ANNOTATION_FQ_NAME)) return null
35-
return super.transformFlat(declaration)
40+
val lowered = super.transformFlat(declaration) ?: return null
41+
if (lowered.size != 2) return lowered
42+
val stub = lowered[1] as? IrFunction ?: return lowered
43+
// KT-78051: For value class parameters with nullable underlying types,
44+
// the $default stub parameter stays boxed while the implementation
45+
// parameter is unboxed. IrInlineDefaultCodegen copies bytecode verbatim,
46+
// causing VerifyError due to the type mismatch.
47+
if (stub.parameters.none { it.type.isBoxedInlineClassType() }) return lowered
48+
49+
stub.body?.transformChildrenVoid(object : IrElementTransformerVoid() {
50+
override fun visitCall(expression: IrCall): IrExpression {
51+
if (expression.origin == JvmLoweredStatementOrigin.DEFAULT_STUB_CALL_TO_IMPLEMENTATION) {
52+
// Clearing the origin forces IrInlineCodegen which handles type coercion.
53+
expression.origin = null
54+
}
55+
return super.visitCall(expression)
56+
}
57+
})
58+
59+
return lowered
3660
}
3761

3862
override fun defaultArgumentStubVisibility(function: IrFunction) = function.getJvmVisibilityOfDefaultArgumentStub()
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
// WITH_STDLIB
2+
// TARGET_BACKEND: JVM_IR
3+
4+
@JvmInline
5+
value class ICNullableString(val s: String?)
6+
7+
@JvmInline
8+
value class ICNullableInt(val x: Int?)
9+
10+
@JvmInline
11+
value class ICString(val s: String)
12+
13+
@JvmInline
14+
value class ICInt(val x: Int)
15+
16+
fun fooNullableString(ic: ICNullableString) {
17+
if (ic.s != "OK") throw AssertionError("Fail NullableString: ${ic.s}")
18+
}
19+
20+
fun fooNullableInt(ic: ICNullableInt) {
21+
if (ic.x != 42) throw AssertionError("Fail NullableInt: ${ic.x}")
22+
}
23+
24+
fun fooString(ic: ICString) {
25+
if (ic.s != "OK") throw AssertionError("Fail String: ${ic.s}")
26+
}
27+
28+
fun fooInt(ic: ICInt) {
29+
if (ic.x != 42) throw AssertionError("Fail Int: ${ic.x}")
30+
}
31+
32+
inline fun withDefaultICNullableString(ic: ICNullableString = ICNullableString("OK")) {
33+
fooNullableString(ic)
34+
}
35+
36+
inline fun withDefaultICNullableInt(ic: ICNullableInt = ICNullableInt(42)) {
37+
fooNullableInt(ic)
38+
}
39+
40+
inline fun withDefaultICString(ic: ICString = ICString("OK")) {
41+
fooString(ic)
42+
}
43+
44+
inline fun withDefaultICInt(ic: ICInt = ICInt(42)) {
45+
fooInt(ic)
46+
}
47+
48+
// From the original bug report
49+
inline fun withDefaultICAndCrossinline(
50+
ic: ICNullableString = ICNullableString("OK"),
51+
crossinline check: (ICNullableString) -> Unit
52+
) {
53+
check(ic)
54+
}
55+
56+
fun box(): String {
57+
withDefaultICNullableString()
58+
withDefaultICNullableInt()
59+
withDefaultICString()
60+
withDefaultICInt()
61+
62+
withDefaultICAndCrossinline { ic ->
63+
if (ic.s != "OK") throw AssertionError("Fail crossinline: ${ic.s}")
64+
}
65+
66+
return "OK"
67+
}

0 commit comments

Comments
 (0)