[Serialization] Workaround registering static writeSelf method in metadata

`IrGeneratedDeclarationsRegistrar` assumes that all generated functions
  are correct from a Kotlin point of view. But `writeSelf` method on JVM
  is a static method outside any object/companion object

So to properly calculate containing class for this method we should
  generate a dispatch receiver parameter, register the method in metadata,
  and then remove the parameter (to make function static)
This commit is contained in:
Dmitriy Novozhilov
2023-11-28 17:55:34 +02:00
parent 51dcd7c076
commit 139e1223ea
4 changed files with 46 additions and 6 deletions
@@ -15,8 +15,10 @@ import org.jetbrains.kotlin.ir.declarations.IrClass
import org.jetbrains.kotlin.ir.declarations.isSingleFieldValueClass
import org.jetbrains.kotlin.ir.types.*
import org.jetbrains.kotlin.ir.util.companionObject
import org.jetbrains.kotlin.ir.util.copyTo
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.platform.jvm.isJvm
import org.jetbrains.kotlin.resolve.annotations.JVM_STATIC_ANNOTATION_CLASS_ID
import org.jetbrains.kotlinx.serialization.compiler.extensions.SerializationPluginContext
import org.jetbrains.kotlinx.serialization.compiler.resolve.SerialEntityNames
import org.jetbrains.kotlinx.serialization.compiler.resolve.bitMaskSlotCount
@@ -58,17 +60,17 @@ class IrPreGenerator(
modality = Modality.FINAL
origin = SERIALIZATION_PLUGIN_ORIGIN
}
method.apply {
dispatchReceiverParameter = null // function is static
excludeFromJsExport()
}
method.excludeFromJsExport()
val typeParams = irClass.typeParameters.map {
val typeParamsMap = irClass.typeParameters.associateWith {
method.addTypeParameter(
it.name.asString(), compilerContext.irBuiltIns.anyNType
)
}
val typeParamsAsArguments = typeParams.map { it.defaultType }
// Despite this function should be static in bytecode, registerFunctionAsMetadataVisible needs a correct dispatch receiver
// to handle function metadata correctly. This dispatchReceiverParameter will become `null` later.
method.dispatchReceiverParameter = irClass.thisReceiver?.copyTo(method, remapTypeMap = typeParamsMap)
val typeParamsAsArguments = typeParamsMap.values.map { it.defaultType }
// object
method.addValueParameter(
@@ -96,7 +98,14 @@ class IrPreGenerator(
SERIALIZATION_PLUGIN_ORIGIN
)
}
compilerContext.referenceClass(JVM_STATIC_ANNOTATION_CLASS_ID)
?.let { method.annotations += method.createAnnotationCallWithoutArgs(it) }
compilerContext.metadataDeclarationRegistrar.registerFunctionAsMetadataVisible(method)
// Make function static in bytecode (see JvmStaticAnnotationLowering.makeStatic).
method.dispatchReceiverParameter = null
}
private fun preGenerateDeserializationConstructorIfNeeded() {
@@ -0,0 +1,19 @@
// TARGET_BACKEND: JVM_IR
// WITH_REFLECT
import kotlinx.serialization.Serializable
import kotlin.reflect.jvm.javaConstructor
import kotlin.reflect.jvm.kotlinFunction
@Serializable
data class Tuple2<out A1, out A2>(
val _1: A1,
val _2: A2,
)
fun box(): String {
val cls = Tuple2::class
val ctor = cls.constructors.single { it.parameters.size == 4 }
val kf = ctor.javaConstructor?.kotlinFunction
return if (kf.toString() == "fun `<init>`(kotlin.Int, A1?, A2?, kotlinx.serialization.internal.SerializationConstructorMarker?): Tuple2<A1, A2>") "OK" else "Fail: $kf"
}
@@ -22,6 +22,12 @@ public class SerializationFirLightTreeBlackBoxTestGenerated extends AbstractSeri
@TestMetadata("plugins/kotlinx-serialization/testData/boxIr")
@TestDataPath("$PROJECT_ROOT")
public class BoxIr {
@Test
@TestMetadata("allConstructorsAccessible.kt")
public void testAllConstructorsAccessible() throws Exception {
runTest("plugins/kotlinx-serialization/testData/boxIr/allConstructorsAccessible.kt");
}
@Test
public void testAllFilesPresentInBoxIr() throws Exception {
KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("plugins/kotlinx-serialization/testData/boxIr"), Pattern.compile("^(.+)\\.kt$"), null, TargetBackend.JVM_IR, true);
@@ -20,6 +20,12 @@ import java.util.regex.Pattern;
@TestMetadata("plugins/kotlinx-serialization/testData/boxIr")
@TestDataPath("$PROJECT_ROOT")
public class SerializationIrBoxTestGenerated extends AbstractSerializationIrBoxTest {
@Test
@TestMetadata("allConstructorsAccessible.kt")
public void testAllConstructorsAccessible() throws Exception {
runTest("plugins/kotlinx-serialization/testData/boxIr/allConstructorsAccessible.kt");
}
@Test
public void testAllFilesPresentInBoxIr() throws Exception {
KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("plugins/kotlinx-serialization/testData/boxIr"), Pattern.compile("^(.+)\\.kt$"), null, TargetBackend.JVM_IR, true);