diff --git a/plugins/kotlinx-serialization/kotlinx-serialization.backend/src/org/jetbrains/kotlinx/serialization/compiler/backend/ir/BaseIrGenerator.kt b/plugins/kotlinx-serialization/kotlinx-serialization.backend/src/org/jetbrains/kotlinx/serialization/compiler/backend/ir/BaseIrGenerator.kt index d4c5b47bb67..a7900dc9c4f 100644 --- a/plugins/kotlinx-serialization/kotlinx-serialization.backend/src/org/jetbrains/kotlinx/serialization/compiler/backend/ir/BaseIrGenerator.kt +++ b/plugins/kotlinx-serialization/kotlinx-serialization.backend/src/org/jetbrains/kotlinx/serialization/compiler/backend/ir/BaseIrGenerator.kt @@ -266,9 +266,10 @@ abstract class BaseIrGenerator(private val currentClass: IrClass, final override } fun IrBuilderWithScope.callSerializerFromCompanion( - thisIrType: IrType, + thisIrType: IrSimpleType, typeArgs: List, - args: List + args: List, + expectedSerializer: ClassId? ): IrExpression? { val baseClass = thisIrType.getClass() ?: return null val companionClass = baseClass.companionObject() ?: return null @@ -276,22 +277,25 @@ abstract class BaseIrGenerator(private val currentClass: IrClass, final override it is IrFunction && it.name == SerialEntityNames.SERIALIZER_PROVIDER_NAME && it.valueParameters.size == baseClass.typeParameters.size } ?: return null + // workaround for sealed and abstract classes - the `Companion.serializer()` function expects non-null serializers, but does not use them, so serializers of any type can be passed + val replaceArgsWithUnitSerializer = expectedSerializer == polymorphicSerializerId || expectedSerializer == sealedSerializerId + val adjustedArgs: List = - // if typeArgs.size == args.size then the serializer is custom - we need to use the actual serializers from the arguments - if ((typeArgs.size != args.size) && (baseClass.modality == Modality.SEALED || baseClass.modality == Modality.ABSTRACT)) { + if (replaceArgsWithUnitSerializer) { val serializer = findStandardKotlinTypeSerializer(compilerContext, context.irBuiltIns.unitType)!! - // workaround for sealed and abstract classes - the `serializer` function expects non-null serializers, but does not use them, so serializers of any type can be passed List(baseClass.typeParameters.size) { irGetObject(serializer) } } else { args } + val adjustedTypeArgs: List = if (replaceArgsWithUnitSerializer) thisIrType.argumentTypesOrUpperBounds() else typeArgs + with(serializerProviderFunction as IrFunction) { // Note that [typeArgs] may be unused if we short-cut to e.g. SealedClassSerializer return irInvoke( irGetObject(companionClass), symbol, - typeArgs.takeIf { it.size == typeParameters.size }.orEmpty(), + adjustedTypeArgs.takeIf { it.size == typeParameters.size }.orEmpty(), adjustedArgs.takeIf { it.size == valueParameters.size }.orEmpty() ) } @@ -384,18 +388,19 @@ abstract class BaseIrGenerator(private val currentClass: IrClass, final override var serializerClass = serializerClassOriginal var args: List var typeArgs: List - val thisIrType = (kType as? IrSimpleType) ?: error("Don't know how to work with type ${kType::class}") + @Suppress("NAME_SHADOWING") val kType = (kType as? IrSimpleType) ?: error("Don't know how to work with type ${kType::class}") + val typeArgumentsAsTypes = kType.argumentTypesOrUpperBounds() var needToCopyAnnotations = false when (serializerClassOriginal.owner.classId) { polymorphicSerializerId -> { needToCopyAnnotations = true args = listOf(classReference(kType.classOrUpperBound()!!)) - typeArgs = listOf(thisIrType) + typeArgs = listOf(kType) } contextSerializerId -> { args = listOf(classReference(kType.classOrUpperBound()!!)) - typeArgs = listOf(thisIrType) + typeArgs = listOf(kType) val hasNewCtxSerCtor = compilerContext.referenceConstructors(contextSerializerId).any { it.owner.valueParameters.size == 3 } @@ -408,15 +413,15 @@ abstract class BaseIrGenerator(private val currentClass: IrClass, final override add( createArrayOfExpression( wrapIrTypeIntoKSerializerIrType( - thisIrType, + kType, variance = Variance.OUT_VARIANCE ), - thisIrType.arguments.map { + typeArgumentsAsTypes.map { val argSer = findTypeSerializerOrContext( compilerContext, - it.typeOrNull!! //todo: handle star projections here? + it ) - instantiate(argSer, it.typeOrNull!!)!! + instantiate(argSer, it)!! }) ) } @@ -425,7 +430,7 @@ abstract class BaseIrGenerator(private val currentClass: IrClass, final override objectSerializerId -> { needToCopyAnnotations = true args = listOf(irString(kType.serialName()), irGetObject(kType.classOrUpperBound()!!)) - typeArgs = listOf(thisIrType) + typeArgs = listOf(kType) } sealedSerializerId -> { needToCopyAnnotations = true @@ -438,7 +443,7 @@ abstract class BaseIrGenerator(private val currentClass: IrClass, final override ) val projectedOutCurrentKClass = compilerContext.irBuiltIns.kClassClass.typeWithArguments( - listOf(makeTypeProjection(thisIrType, Variance.OUT_VARIANCE)) + listOf(makeTypeProjection(kType, Variance.OUT_VARIANCE)) ) add( createArrayOfExpression( @@ -448,7 +453,7 @@ abstract class BaseIrGenerator(private val currentClass: IrClass, final override ) add( createArrayOfExpression( - wrapIrTypeIntoKSerializerIrType(thisIrType, variance = Variance.OUT_VARIANCE), + wrapIrTypeIntoKSerializerIrType(kType, variance = Variance.OUT_VARIANCE), subSerializers.mapIndexed { i, serializer -> val type = subclasses[i] val expr = serializerInstance( @@ -468,20 +473,20 @@ abstract class BaseIrGenerator(private val currentClass: IrClass, final override ) ) } - typeArgs = listOf(thisIrType) + typeArgs = listOf(kType) } enumSerializerId -> { serializerClass = pluginContext.referenceClass(enumSerializerId) val enumDescriptor = kType.classOrNull!! - typeArgs = listOf(thisIrType) + typeArgs = listOf(kType) // instantiate serializer only inside enum Companion if (this@BaseIrGenerator !is SerializableCompanionIrGenerator) { // otherwise call Companion.serializer() - callSerializerFromCompanion(thisIrType, typeArgs, emptyList())?.let { return it } + callSerializerFromCompanion(kType, typeArgs, emptyList(), enumSerializerId)?.let { return it } } val enumArgs = mutableListOf( - irString(thisIrType.serialName()), + irString(kType.serialName()), irCall(enumDescriptor.owner.findEnumValuesMethod()), ) @@ -523,19 +528,19 @@ abstract class BaseIrGenerator(private val currentClass: IrClass, final override } } else -> { - args = kType.arguments.map { + args = typeArgumentsAsTypes.map { val argSer = findTypeSerializerOrContext( pluginContext, - it.typeOrNull!! // todo: stars? + it ) - instantiate(argSer, it.typeOrNull!!) ?: return null + instantiate(argSer, it) ?: return null } - typeArgs = kType.arguments.map { it.typeOrNull!! } + typeArgs = typeArgumentsAsTypes } } if (serializerClassOriginal.owner.classId == referenceArraySerializerId) { - args = listOf(wrapperClassReference(kType.arguments.single().typeOrNull!!)) + args + args = listOf(wrapperClassReference(typeArgumentsAsTypes.single())) + args typeArgs = listOf(typeArgs[0].makeNotNull()) + typeArgs } @@ -543,7 +548,7 @@ abstract class BaseIrGenerator(private val currentClass: IrClass, final override if (!kType.isInterface() && serializerClassOriginal == kType.classOrUpperBound()?.owner.classSerializer(pluginContext) && this@BaseIrGenerator !is SerializableCompanionIrGenerator) { // This is default type serializer, we can shortcut through Companion.serializer() // BUT not during generation of this method itself - callSerializerFromCompanion(thisIrType, typeArgs, args)?.let { return it } + callSerializerFromCompanion(kType, typeArgs, args, serializerClassOriginal.owner.classId)?.let { return it } } @@ -571,7 +576,7 @@ abstract class BaseIrGenerator(private val currentClass: IrClass, final override assert(ctor.isBound) val ctorDecl = ctor.owner if (needToCopyAnnotations) { - val classAnnotations = copyAnnotationsFrom(thisIrType.getClass()?.let { collectSerialInfoAnnotations(it) }.orEmpty()) + val classAnnotations = copyAnnotationsFrom(kType.getClass()?.let { collectSerialInfoAnnotations(it) }.orEmpty()) args = args + createArrayOfExpression(compilerContext.irBuiltIns.annotationType, classAnnotations) } diff --git a/plugins/kotlinx-serialization/kotlinx-serialization.backend/src/org/jetbrains/kotlinx/serialization/compiler/backend/ir/IrPredicates.kt b/plugins/kotlinx-serialization/kotlinx-serialization.backend/src/org/jetbrains/kotlinx/serialization/compiler/backend/ir/IrPredicates.kt index e2367a8c888..7958337bd88 100644 --- a/plugins/kotlinx-serialization/kotlinx-serialization.backend/src/org/jetbrains/kotlinx/serialization/compiler/backend/ir/IrPredicates.kt +++ b/plugins/kotlinx-serialization/kotlinx-serialization.backend/src/org/jetbrains/kotlinx/serialization/compiler/backend/ir/IrPredicates.kt @@ -236,5 +236,16 @@ fun IrType.classOrUpperBound(): IrClassSymbol? = when(val cls = classifierOrNull else -> null } +/** + * Replaces star projections with representativeUpperBound of respective type parameter + * to mimic behaviour of old FE (see StarProjectionImpl.getType()) + */ +fun IrSimpleType.argumentTypesOrUpperBounds(): List { + val params = this.classOrUpperBound()!!.owner.typeParameters + return arguments.mapIndexed { index, argument -> + argument.typeOrNull ?: params[index].representativeUpperBound + } +} + internal inline fun IrClass.shouldHaveSpecificSyntheticMethods(functionPresenceChecker: () -> IrSimpleFunction?) = !isValue && (isAbstractOrSealedSerializableClass || functionPresenceChecker() != null) \ No newline at end of file diff --git a/plugins/kotlinx-serialization/testData/boxIr/multipleGenericsPolymorphic.kt b/plugins/kotlinx-serialization/testData/boxIr/multipleGenericsPolymorphic.kt new file mode 100644 index 00000000000..db64cc98310 --- /dev/null +++ b/plugins/kotlinx-serialization/testData/boxIr/multipleGenericsPolymorphic.kt @@ -0,0 +1,33 @@ +// TARGET_BACKEND: JVM_IR + +// WITH_STDLIB + +import kotlinx.serialization.* +import kotlinx.serialization.json.* +import kotlinx.serialization.descriptors.* + +interface SomeInterface + +@Serializable +sealed class SealedMultiple + +@Serializable +abstract class AbstractMultiple + +@Serializable +sealed interface SealedInterfaceMultiple + +interface InterfaceMultiple + +@Serializable +class ToSerialize( + val a: SealedMultiple, + val b: AbstractMultiple<*, *>, + val c: SealedInterfaceMultiple<*, *>, + val d: InterfaceMultiple<*, *> +) + +fun box(): String { + val s = ToSerialize.serializer().descriptor.elementDescriptors.joinToString { it.kind.toString() } + return if (s == "SEALED, OPEN, SEALED, OPEN") "OK" else s +} diff --git a/plugins/kotlinx-serialization/testData/boxIr/starProjections.kt b/plugins/kotlinx-serialization/testData/boxIr/starProjections.kt new file mode 100644 index 00000000000..f3621326104 --- /dev/null +++ b/plugins/kotlinx-serialization/testData/boxIr/starProjections.kt @@ -0,0 +1,20 @@ +// TARGET_BACKEND: JVM_IR + +// WITH_STDLIB + +import kotlinx.serialization.* +import kotlinx.serialization.json.* +import kotlinx.serialization.descriptors.* + +interface E + +@Serializable +class Box(val boxed: T) + +@Serializable +class Wrapper(val boxed: Box<*>) + +fun box(): String { + val s = Wrapper.serializer().descriptor.elementDescriptors.joinToString() + return if (s == "Box(boxed: kotlinx.serialization.Polymorphic)") "OK" else s +} diff --git a/plugins/kotlinx-serialization/tests-gen/org/jetbrains/kotlinx/serialization/runners/SerializationFirBlackBoxTestGenerated.java b/plugins/kotlinx-serialization/tests-gen/org/jetbrains/kotlinx/serialization/runners/SerializationFirBlackBoxTestGenerated.java index 456ad14c8e8..e529d3d1abc 100644 --- a/plugins/kotlinx-serialization/tests-gen/org/jetbrains/kotlinx/serialization/runners/SerializationFirBlackBoxTestGenerated.java +++ b/plugins/kotlinx-serialization/tests-gen/org/jetbrains/kotlinx/serialization/runners/SerializationFirBlackBoxTestGenerated.java @@ -93,11 +93,23 @@ public class SerializationFirBlackBoxTestGenerated extends AbstractSerialization runTest("plugins/kotlinx-serialization/testData/boxIr/multimoduleInheritance.kt"); } + @Test + @TestMetadata("multipleGenericsPolymorphic.kt") + public void testMultipleGenericsPolymorphic() throws Exception { + runTest("plugins/kotlinx-serialization/testData/boxIr/multipleGenericsPolymorphic.kt"); + } + @Test @TestMetadata("sealedInterfaces.kt") public void testSealedInterfaces() throws Exception { runTest("plugins/kotlinx-serialization/testData/boxIr/sealedInterfaces.kt"); } + + @Test + @TestMetadata("starProjections.kt") + public void testStarProjections() throws Exception { + runTest("plugins/kotlinx-serialization/testData/boxIr/starProjections.kt"); + } } @Nested 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 fd346155a19..4133f186860 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 @@ -91,9 +91,21 @@ public class SerializationIrBoxTestGenerated extends AbstractSerializationIrBoxT runTest("plugins/kotlinx-serialization/testData/boxIr/multimoduleInheritance.kt"); } + @Test + @TestMetadata("multipleGenericsPolymorphic.kt") + public void testMultipleGenericsPolymorphic() throws Exception { + runTest("plugins/kotlinx-serialization/testData/boxIr/multipleGenericsPolymorphic.kt"); + } + @Test @TestMetadata("sealedInterfaces.kt") public void testSealedInterfaces() throws Exception { runTest("plugins/kotlinx-serialization/testData/boxIr/sealedInterfaces.kt"); } + + @Test + @TestMetadata("starProjections.kt") + public void testStarProjections() throws Exception { + runTest("plugins/kotlinx-serialization/testData/boxIr/starProjections.kt"); + } }