From 1520f0159f92e8a2c86cf40ed1267e462cce250b Mon Sep 17 00:00:00 2001 From: Leonid Startsev Date: Fri, 5 Jan 2024 13:11:13 +0100 Subject: [PATCH] Call a specified function in intrinsic if polymorphic serializer is provided for interface. This prioritizes module contents over default polymorphic serializer. See https://github.com/Kotlin/kotlinx.serialization/issues/2060 and https://github.com/Kotlin/kotlinx.serialization/pull/2565 --- .../ir/SerializationJvmIrIntrinsicSupport.kt | 92 ++++++++++++++----- .../boxIr/intrinsicsPolymorphicPriority.kt | 83 +++++++++++++++++ ...tionFirLightTreeBlackBoxTestGenerated.java | 6 ++ .../SerializationIrBoxTestGenerated.java | 6 ++ 4 files changed, 163 insertions(+), 24 deletions(-) create mode 100644 plugins/kotlinx-serialization/testData/boxIr/intrinsicsPolymorphicPriority.kt diff --git a/plugins/kotlinx-serialization/kotlinx-serialization.backend/src/org/jetbrains/kotlinx/serialization/compiler/backend/ir/SerializationJvmIrIntrinsicSupport.kt b/plugins/kotlinx-serialization/kotlinx-serialization.backend/src/org/jetbrains/kotlinx/serialization/compiler/backend/ir/SerializationJvmIrIntrinsicSupport.kt index b1dbdeccad04d4..8e9fba8718ba25 100644 --- a/plugins/kotlinx-serialization/kotlinx-serialization.backend/src/org/jetbrains/kotlinx/serialization/compiler/backend/ir/SerializationJvmIrIntrinsicSupport.kt +++ b/plugins/kotlinx-serialization/kotlinx-serialization.backend/src/org/jetbrains/kotlinx/serialization/compiler/backend/ir/SerializationJvmIrIntrinsicSupport.kt @@ -27,7 +27,10 @@ import org.jetbrains.kotlin.ir.symbols.IrTypeParameterSymbol import org.jetbrains.kotlin.ir.types.* import org.jetbrains.kotlin.ir.util.* import org.jetbrains.kotlin.load.kotlin.TypeMappingMode +import org.jetbrains.kotlin.name.CallableId import org.jetbrains.kotlin.name.ClassId +import org.jetbrains.kotlin.name.FqName +import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.resolve.jvm.AsmTypes import org.jetbrains.kotlinx.serialization.compiler.backend.jvm.* import org.jetbrains.kotlinx.serialization.compiler.backend.jvm.annotationArrayType @@ -113,6 +116,7 @@ class SerializationJvmIrIntrinsicSupport( const val serializersKtInternalName = "kotlinx/serialization/SerializersKt" const val callMethodName = "serializer" const val noCompiledSerializerMethodName = "noCompiledSerializer" + const val moduleOverPolymorphicName = "moduleThenPolymorphic" const val magicMarkerStringPrefix = "kotlinx.serialization.serializer." @@ -164,6 +168,11 @@ class SerializationJvmIrIntrinsicSupport( private val hasNewContextSerializerSignature: Boolean get() = currentVersion != null && currentVersion!! >= ApiVersion.parse("1.2.0")!! + private val useModuleOverContextualForInterfaces: Boolean by lazy { + irPluginContext.referenceFunctions(CallableId(FqName("kotlinx.serialization"), Name.identifier(moduleOverPolymorphicName))) + .isNotEmpty() + } + private fun findTypeSerializerOrContext(argType: IrType): IrClassSymbol? = emptyGenerator.findTypeSerializerOrContextUnchecked(this, argType, useTypeAnnotations = false) @@ -320,6 +329,63 @@ class SerializationJvmIrIntrinsicSupport( if (type.isMarkedNullable()) adapter.wrapStackValueIntoNullableSerializer() } + private fun InstructionAdapter.insertNoCompiledSerializerCall( + kType: IrType, + argSerializers: List>, + intrinsicType: IntrinsicType, + ): Boolean { + require(intrinsicType is IntrinsicType.WithModule) // SIMPLE is covered in previous if + // SerializersModule + load(intrinsicType.storedIndex, serializersModuleType) + // KClass + aconst(typeMapper.mapTypeCommon(kType, TypeMappingMode.GENERIC_ARGUMENT)) + AsmUtil.wrapJavaClassIntoKClass(this) + + val descriptor = StringBuilder("(${serializersModuleType.descriptor}${AsmTypes.K_CLASS_TYPE.descriptor}") + // Generic args (if present) + if (argSerializers.isNotEmpty()) { + fillArray(kSerializerType, argSerializers) { _, (type, _) -> + generateSerializerForType(type, this, intrinsicType) + } + descriptor.append(kSerializerArrayType.descriptor) + } + descriptor.append(")${kSerializerType.descriptor}") + invokestatic( + serializersKtInternalName, + noCompiledSerializerMethodName, + descriptor.toString(), + false + ) + return false + } + + private fun InstructionAdapter.moduleOverPolymorphic(serializer: IrClassSymbol, kType: IrType, intrinsicType: IntrinsicType, argSerializers: List>): Boolean { + if (serializer.owner.classId == polymorphicSerializerId && kType.isInterface() && intrinsicType is IntrinsicType.WithModule && useModuleOverContextualForInterfaces) { + load(intrinsicType.storedIndex, serializersModuleType) + // KClass + aconst(typeMapper.mapTypeCommon(kType, TypeMappingMode.GENERIC_ARGUMENT)) + AsmUtil.wrapJavaClassIntoKClass(this) + + val descriptor = StringBuilder("(${serializersModuleType.descriptor}${AsmTypes.K_CLASS_TYPE.descriptor}") + // Generic args (if present) + if (argSerializers.isNotEmpty()) { + fillArray(kSerializerType, argSerializers) { _, (type, _) -> + generateSerializerForType(type, this, intrinsicType) + } + descriptor.append(kSerializerArrayType.descriptor) + } + descriptor.append(")${kSerializerType.descriptor}") + invokestatic( + serializersKtInternalName, + moduleOverPolymorphicName, + descriptor.toString(), + false + ) + return true + } + return false + } + private fun stackValueSerializerInstance( kType: IrType, maybeSerializer: IrClassSymbol?, iv: InstructionAdapter, @@ -376,31 +442,9 @@ class SerializationJvmIrIntrinsicSupport( signature?.append(kSerializerType.descriptor) } - val serializer = maybeSerializer ?: iv.run {// insert noCompilerSerializer(module, kClass, arguments) - require(intrinsicType is IntrinsicType.WithModule) // SIMPLE is covered in previous if - // SerializersModule - load(intrinsicType.storedIndex, serializersModuleType) - // KClass - aconst(typeMapper.mapTypeCommon(kType, TypeMappingMode.GENERIC_ARGUMENT)) - AsmUtil.wrapJavaClassIntoKClass(this) + val serializer = maybeSerializer ?: return iv.insertNoCompiledSerializerCall(kType, argSerializers, intrinsicType) - val descriptor = StringBuilder("(${serializersModuleType.descriptor}${AsmTypes.K_CLASS_TYPE.descriptor}") - // Generic args (if present) - if (argSerializers.isNotEmpty()) { - fillArray(kSerializerType, argSerializers) { _, (type, _) -> - generateSerializerForType(type, this, intrinsicType) - } - descriptor.append(kSerializerArrayType.descriptor) - } - descriptor.append(")${kSerializerType.descriptor}") - invokestatic( - serializersKtInternalName, - noCompiledSerializerMethodName, - descriptor.toString(), - false - ) - return false - } + if (iv.moduleOverPolymorphic(serializer, kType, intrinsicType, argSerializers)) return true // new serializer if needed iv.apply { diff --git a/plugins/kotlinx-serialization/testData/boxIr/intrinsicsPolymorphicPriority.kt b/plugins/kotlinx-serialization/testData/boxIr/intrinsicsPolymorphicPriority.kt new file mode 100644 index 00000000000000..609b6b172489f4 --- /dev/null +++ b/plugins/kotlinx-serialization/testData/boxIr/intrinsicsPolymorphicPriority.kt @@ -0,0 +1,83 @@ +// TARGET_BACKEND: JVM_IR + +// WITH_STDLIB + +// FILE: stub.kt + +@file:JvmName("SerializersKt") + +package kotlinx.serialization + +import kotlin.reflect.KClass +import kotlinx.serialization.modules.* + +// Copy of runtime function from kotlinx-serialization 1.7.0 +fun moduleThenPolymorphic(module: SerializersModule, kClass: KClass<*>): KSerializer<*> { + return module.getContextual(kClass) ?: PolymorphicSerializer(kClass) +} + +fun moduleThenPolymorphic(module: SerializersModule, kClass: KClass<*>, argSerializers: Array>): KSerializer<*> { + return module.getContextual(kClass, argSerializers.asList()) ?: PolymorphicSerializer(kClass) +} + +// FILE: test.kt + +package a + +import kotlinx.serialization.* +import kotlinx.serialization.descriptors.* +import kotlinx.serialization.encoding.* +import kotlinx.serialization.modules.* +import kotlin.reflect.KClass +import kotlin.test.* + +interface IApiError { + val code: Int +} + +object MyApiErrorSerializer : KSerializer { + override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("IApiError", PrimitiveKind.INT) + + override fun serialize(encoder: Encoder, value: IApiError) { + TODO() + } + + override fun deserialize(decoder: Decoder): IApiError { + TODO() + } +} + +interface Parametrized { + val param: List +} + +class PSer(val tSer: KSerializer) : KSerializer> { + override val descriptor: SerialDescriptor + get() = buildClassSerialDescriptor("PSer<${tSer.descriptor.serialName}>") + + override fun serialize(encoder: Encoder, value: Parametrized) { + TODO("Not yet implemented") + } + + override fun deserialize(decoder: Decoder): Parametrized { + TODO("Not yet implemented") + } +} + +fun testParametrized() { + val md = SerializersModule { + contextual(Parametrized::class) { PSer(it[0]) } + } + assertEquals("PSer", md.serializer>().descriptor.serialName) +} + +fun box(): String { + val module = serializersModuleOf(IApiError::class, MyApiErrorSerializer) + assertSame(MyApiErrorSerializer, module.serializer() as KSerializer) + assertEquals( + MyApiErrorSerializer.descriptor, + module.serializer>().descriptor.elementDescriptors.first() + ) + testParametrized() + return "OK" +} diff --git a/plugins/kotlinx-serialization/tests-gen/org/jetbrains/kotlinx/serialization/runners/SerializationFirLightTreeBlackBoxTestGenerated.java b/plugins/kotlinx-serialization/tests-gen/org/jetbrains/kotlinx/serialization/runners/SerializationFirLightTreeBlackBoxTestGenerated.java index 93c131232c85dd..4a9ea581383bc7 100644 --- a/plugins/kotlinx-serialization/tests-gen/org/jetbrains/kotlinx/serialization/runners/SerializationFirLightTreeBlackBoxTestGenerated.java +++ b/plugins/kotlinx-serialization/tests-gen/org/jetbrains/kotlinx/serialization/runners/SerializationFirLightTreeBlackBoxTestGenerated.java @@ -189,6 +189,12 @@ public void testIntrinsicsNullable() { runTest("plugins/kotlinx-serialization/testData/boxIr/intrinsicsNullable.kt"); } + @Test + @TestMetadata("intrinsicsPolymorphicPriority.kt") + public void testIntrinsicsPolymorphicPriority() { + runTest("plugins/kotlinx-serialization/testData/boxIr/intrinsicsPolymorphicPriority.kt"); + } + @Test @TestMetadata("intrinsicsStarProjections.kt") public void testIntrinsicsStarProjections() { diff --git a/plugins/kotlinx-serialization/tests-gen/org/jetbrains/kotlinx/serialization/runners/SerializationIrBoxTestGenerated.java b/plugins/kotlinx-serialization/tests-gen/org/jetbrains/kotlinx/serialization/runners/SerializationIrBoxTestGenerated.java index 611ae5d76b3a34..9f085833d8c831 100644 --- a/plugins/kotlinx-serialization/tests-gen/org/jetbrains/kotlinx/serialization/runners/SerializationIrBoxTestGenerated.java +++ b/plugins/kotlinx-serialization/tests-gen/org/jetbrains/kotlinx/serialization/runners/SerializationIrBoxTestGenerated.java @@ -186,6 +186,12 @@ public void testIntrinsicsNullable() { runTest("plugins/kotlinx-serialization/testData/boxIr/intrinsicsNullable.kt"); } + @Test + @TestMetadata("intrinsicsPolymorphicPriority.kt") + public void testIntrinsicsPolymorphicPriority() { + runTest("plugins/kotlinx-serialization/testData/boxIr/intrinsicsPolymorphicPriority.kt"); + } + @Test @TestMetadata("intrinsicsStarProjections.kt") public void testIntrinsicsStarProjections() {