Skip to content

Commit

Permalink
Call a specified function in intrinsic if polymorphic serializer is p…
Browse files Browse the repository at this point in the history
…rovided for interface.

This prioritizes module contents over default polymorphic serializer.

See Kotlin/kotlinx.serialization#2060 and Kotlin/kotlinx.serialization#2565
  • Loading branch information
sandwwraith committed Mar 18, 2024
1 parent e72bac3 commit a2c51df
Show file tree
Hide file tree
Showing 4 changed files with 163 additions and 24 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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."

Expand Down Expand Up @@ -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)

Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
@@ -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"
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit a2c51df

Please sign in to comment.