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
This commit is contained in:
Leonid Startsev
2024-01-05 13:11:13 +01:00
committed by Space Team
parent c8f84a74b9
commit 7c8c65d291
4 changed files with 163 additions and 24 deletions
@@ -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<Pair<IrType, IrClassSymbol?>>,
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<Pair<IrType, IrClassSymbol?>>): 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 {
@@ -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<*>>): 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<IApiError> {
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<T> {
val param: List<T>
}
class PSer<T>(val tSer: KSerializer<T>) : KSerializer<Parametrized<T>> {
override val descriptor: SerialDescriptor
get() = buildClassSerialDescriptor("PSer<${tSer.descriptor.serialName}>")
override fun serialize(encoder: Encoder, value: Parametrized<T>) {
TODO("Not yet implemented")
}
override fun deserialize(decoder: Decoder): Parametrized<T> {
TODO("Not yet implemented")
}
}
fun testParametrized() {
val md = SerializersModule {
contextual(Parametrized::class) { PSer(it[0]) }
}
assertEquals("PSer<kotlin.String>", md.serializer<Parametrized<String>>().descriptor.serialName)
}
fun box(): String {
val module = serializersModuleOf(IApiError::class, MyApiErrorSerializer)
assertSame(MyApiErrorSerializer, module.serializer<IApiError>() as KSerializer<IApiError>)
assertEquals(
MyApiErrorSerializer.descriptor,
module.serializer<List<IApiError>>().descriptor.elementDescriptors.first()
)
testParametrized()
return "OK"
}
@@ -189,6 +189,12 @@ public class SerializationFirLightTreeBlackBoxTestGenerated extends AbstractSeri
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() {
@@ -186,6 +186,12 @@ public class SerializationIrBoxTestGenerated extends AbstractSerializationIrBoxT
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() {