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:
committed by
Space Team
parent
c8f84a74b9
commit
7c8c65d291
+68
-24
@@ -27,7 +27,10 @@ import org.jetbrains.kotlin.ir.symbols.IrTypeParameterSymbol
|
|||||||
import org.jetbrains.kotlin.ir.types.*
|
import org.jetbrains.kotlin.ir.types.*
|
||||||
import org.jetbrains.kotlin.ir.util.*
|
import org.jetbrains.kotlin.ir.util.*
|
||||||
import org.jetbrains.kotlin.load.kotlin.TypeMappingMode
|
import org.jetbrains.kotlin.load.kotlin.TypeMappingMode
|
||||||
|
import org.jetbrains.kotlin.name.CallableId
|
||||||
import org.jetbrains.kotlin.name.ClassId
|
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.kotlin.resolve.jvm.AsmTypes
|
||||||
import org.jetbrains.kotlinx.serialization.compiler.backend.jvm.*
|
import org.jetbrains.kotlinx.serialization.compiler.backend.jvm.*
|
||||||
import org.jetbrains.kotlinx.serialization.compiler.backend.jvm.annotationArrayType
|
import org.jetbrains.kotlinx.serialization.compiler.backend.jvm.annotationArrayType
|
||||||
@@ -113,6 +116,7 @@ class SerializationJvmIrIntrinsicSupport(
|
|||||||
const val serializersKtInternalName = "kotlinx/serialization/SerializersKt"
|
const val serializersKtInternalName = "kotlinx/serialization/SerializersKt"
|
||||||
const val callMethodName = "serializer"
|
const val callMethodName = "serializer"
|
||||||
const val noCompiledSerializerMethodName = "noCompiledSerializer"
|
const val noCompiledSerializerMethodName = "noCompiledSerializer"
|
||||||
|
const val moduleOverPolymorphicName = "moduleThenPolymorphic"
|
||||||
|
|
||||||
const val magicMarkerStringPrefix = "kotlinx.serialization.serializer."
|
const val magicMarkerStringPrefix = "kotlinx.serialization.serializer."
|
||||||
|
|
||||||
@@ -164,6 +168,11 @@ class SerializationJvmIrIntrinsicSupport(
|
|||||||
private val hasNewContextSerializerSignature: Boolean
|
private val hasNewContextSerializerSignature: Boolean
|
||||||
get() = currentVersion != null && currentVersion!! >= ApiVersion.parse("1.2.0")!!
|
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? =
|
private fun findTypeSerializerOrContext(argType: IrType): IrClassSymbol? =
|
||||||
emptyGenerator.findTypeSerializerOrContextUnchecked(this, argType, useTypeAnnotations = false)
|
emptyGenerator.findTypeSerializerOrContextUnchecked(this, argType, useTypeAnnotations = false)
|
||||||
|
|
||||||
@@ -320,6 +329,63 @@ class SerializationJvmIrIntrinsicSupport(
|
|||||||
if (type.isMarkedNullable()) adapter.wrapStackValueIntoNullableSerializer()
|
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(
|
private fun stackValueSerializerInstance(
|
||||||
kType: IrType, maybeSerializer: IrClassSymbol?,
|
kType: IrType, maybeSerializer: IrClassSymbol?,
|
||||||
iv: InstructionAdapter,
|
iv: InstructionAdapter,
|
||||||
@@ -376,31 +442,9 @@ class SerializationJvmIrIntrinsicSupport(
|
|||||||
signature?.append(kSerializerType.descriptor)
|
signature?.append(kSerializerType.descriptor)
|
||||||
}
|
}
|
||||||
|
|
||||||
val serializer = maybeSerializer ?: iv.run {// insert noCompilerSerializer(module, kClass, arguments)
|
val serializer = maybeSerializer ?: return iv.insertNoCompiledSerializerCall(kType, argSerializers, intrinsicType)
|
||||||
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}")
|
if (iv.moduleOverPolymorphic(serializer, kType, intrinsicType, argSerializers)) return true
|
||||||
// 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
|
|
||||||
}
|
|
||||||
|
|
||||||
// new serializer if needed
|
// new serializer if needed
|
||||||
iv.apply {
|
iv.apply {
|
||||||
|
|||||||
+83
@@ -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"
|
||||||
|
}
|
||||||
+6
@@ -189,6 +189,12 @@ public class SerializationFirLightTreeBlackBoxTestGenerated extends AbstractSeri
|
|||||||
runTest("plugins/kotlinx-serialization/testData/boxIr/intrinsicsNullable.kt");
|
runTest("plugins/kotlinx-serialization/testData/boxIr/intrinsicsNullable.kt");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@TestMetadata("intrinsicsPolymorphicPriority.kt")
|
||||||
|
public void testIntrinsicsPolymorphicPriority() {
|
||||||
|
runTest("plugins/kotlinx-serialization/testData/boxIr/intrinsicsPolymorphicPriority.kt");
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@TestMetadata("intrinsicsStarProjections.kt")
|
@TestMetadata("intrinsicsStarProjections.kt")
|
||||||
public void testIntrinsicsStarProjections() {
|
public void testIntrinsicsStarProjections() {
|
||||||
|
|||||||
+6
@@ -186,6 +186,12 @@ public class SerializationIrBoxTestGenerated extends AbstractSerializationIrBoxT
|
|||||||
runTest("plugins/kotlinx-serialization/testData/boxIr/intrinsicsNullable.kt");
|
runTest("plugins/kotlinx-serialization/testData/boxIr/intrinsicsNullable.kt");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@TestMetadata("intrinsicsPolymorphicPriority.kt")
|
||||||
|
public void testIntrinsicsPolymorphicPriority() {
|
||||||
|
runTest("plugins/kotlinx-serialization/testData/boxIr/intrinsicsPolymorphicPriority.kt");
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@TestMetadata("intrinsicsStarProjections.kt")
|
@TestMetadata("intrinsicsStarProjections.kt")
|
||||||
public void testIntrinsicsStarProjections() {
|
public void testIntrinsicsStarProjections() {
|
||||||
|
|||||||
Reference in New Issue
Block a user