Correctly handle star projections according to logic of the old FE.

#KT-54297 Fixed

Properly substitute surrogate UnitSerializer in the Companion.serializer()
function generated on classes that use polymorphic or sealed serializer
by default. (Fixes https://github.com/Kotlin/kotlinx.serialization/issues/1692)
This commit is contained in:
Leonid Startsev
2022-10-03 19:06:48 +02:00
committed by Space Team
parent 2a626b27d3
commit 596949a501
6 changed files with 120 additions and 27 deletions
@@ -266,9 +266,10 @@ abstract class BaseIrGenerator(private val currentClass: IrClass, final override
} }
fun IrBuilderWithScope.callSerializerFromCompanion( fun IrBuilderWithScope.callSerializerFromCompanion(
thisIrType: IrType, thisIrType: IrSimpleType,
typeArgs: List<IrType>, typeArgs: List<IrType>,
args: List<IrExpression> args: List<IrExpression>,
expectedSerializer: ClassId?
): IrExpression? { ): IrExpression? {
val baseClass = thisIrType.getClass() ?: return null val baseClass = thisIrType.getClass() ?: return null
val companionClass = baseClass.companionObject() ?: 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 it is IrFunction && it.name == SerialEntityNames.SERIALIZER_PROVIDER_NAME && it.valueParameters.size == baseClass.typeParameters.size
} ?: return null } ?: 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<IrExpression> = val adjustedArgs: List<IrExpression> =
// if typeArgs.size == args.size then the serializer is custom - we need to use the actual serializers from the arguments if (replaceArgsWithUnitSerializer) {
if ((typeArgs.size != args.size) && (baseClass.modality == Modality.SEALED || baseClass.modality == Modality.ABSTRACT)) {
val serializer = findStandardKotlinTypeSerializer(compilerContext, context.irBuiltIns.unitType)!! 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) } List(baseClass.typeParameters.size) { irGetObject(serializer) }
} else { } else {
args args
} }
val adjustedTypeArgs: List<IrType> = if (replaceArgsWithUnitSerializer) thisIrType.argumentTypesOrUpperBounds() else typeArgs
with(serializerProviderFunction as IrFunction) { with(serializerProviderFunction as IrFunction) {
// Note that [typeArgs] may be unused if we short-cut to e.g. SealedClassSerializer // Note that [typeArgs] may be unused if we short-cut to e.g. SealedClassSerializer
return irInvoke( return irInvoke(
irGetObject(companionClass), irGetObject(companionClass),
symbol, symbol,
typeArgs.takeIf { it.size == typeParameters.size }.orEmpty(), adjustedTypeArgs.takeIf { it.size == typeParameters.size }.orEmpty(),
adjustedArgs.takeIf { it.size == valueParameters.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 serializerClass = serializerClassOriginal
var args: List<IrExpression> var args: List<IrExpression>
var typeArgs: List<IrType> var typeArgs: List<IrType>
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 var needToCopyAnnotations = false
when (serializerClassOriginal.owner.classId) { when (serializerClassOriginal.owner.classId) {
polymorphicSerializerId -> { polymorphicSerializerId -> {
needToCopyAnnotations = true needToCopyAnnotations = true
args = listOf(classReference(kType.classOrUpperBound()!!)) args = listOf(classReference(kType.classOrUpperBound()!!))
typeArgs = listOf(thisIrType) typeArgs = listOf(kType)
} }
contextSerializerId -> { contextSerializerId -> {
args = listOf(classReference(kType.classOrUpperBound()!!)) args = listOf(classReference(kType.classOrUpperBound()!!))
typeArgs = listOf(thisIrType) typeArgs = listOf(kType)
val hasNewCtxSerCtor = compilerContext.referenceConstructors(contextSerializerId).any { it.owner.valueParameters.size == 3 } 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( add(
createArrayOfExpression( createArrayOfExpression(
wrapIrTypeIntoKSerializerIrType( wrapIrTypeIntoKSerializerIrType(
thisIrType, kType,
variance = Variance.OUT_VARIANCE variance = Variance.OUT_VARIANCE
), ),
thisIrType.arguments.map { typeArgumentsAsTypes.map {
val argSer = findTypeSerializerOrContext( val argSer = findTypeSerializerOrContext(
compilerContext, 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 -> { objectSerializerId -> {
needToCopyAnnotations = true needToCopyAnnotations = true
args = listOf(irString(kType.serialName()), irGetObject(kType.classOrUpperBound()!!)) args = listOf(irString(kType.serialName()), irGetObject(kType.classOrUpperBound()!!))
typeArgs = listOf(thisIrType) typeArgs = listOf(kType)
} }
sealedSerializerId -> { sealedSerializerId -> {
needToCopyAnnotations = true needToCopyAnnotations = true
@@ -438,7 +443,7 @@ abstract class BaseIrGenerator(private val currentClass: IrClass, final override
) )
val projectedOutCurrentKClass = val projectedOutCurrentKClass =
compilerContext.irBuiltIns.kClassClass.typeWithArguments( compilerContext.irBuiltIns.kClassClass.typeWithArguments(
listOf(makeTypeProjection(thisIrType, Variance.OUT_VARIANCE)) listOf(makeTypeProjection(kType, Variance.OUT_VARIANCE))
) )
add( add(
createArrayOfExpression( createArrayOfExpression(
@@ -448,7 +453,7 @@ abstract class BaseIrGenerator(private val currentClass: IrClass, final override
) )
add( add(
createArrayOfExpression( createArrayOfExpression(
wrapIrTypeIntoKSerializerIrType(thisIrType, variance = Variance.OUT_VARIANCE), wrapIrTypeIntoKSerializerIrType(kType, variance = Variance.OUT_VARIANCE),
subSerializers.mapIndexed { i, serializer -> subSerializers.mapIndexed { i, serializer ->
val type = subclasses[i] val type = subclasses[i]
val expr = serializerInstance( val expr = serializerInstance(
@@ -468,20 +473,20 @@ abstract class BaseIrGenerator(private val currentClass: IrClass, final override
) )
) )
} }
typeArgs = listOf(thisIrType) typeArgs = listOf(kType)
} }
enumSerializerId -> { enumSerializerId -> {
serializerClass = pluginContext.referenceClass(enumSerializerId) serializerClass = pluginContext.referenceClass(enumSerializerId)
val enumDescriptor = kType.classOrNull!! val enumDescriptor = kType.classOrNull!!
typeArgs = listOf(thisIrType) typeArgs = listOf(kType)
// instantiate serializer only inside enum Companion // instantiate serializer only inside enum Companion
if (this@BaseIrGenerator !is SerializableCompanionIrGenerator) { if (this@BaseIrGenerator !is SerializableCompanionIrGenerator) {
// otherwise call Companion.serializer() // otherwise call Companion.serializer()
callSerializerFromCompanion(thisIrType, typeArgs, emptyList())?.let { return it } callSerializerFromCompanion(kType, typeArgs, emptyList(), enumSerializerId)?.let { return it }
} }
val enumArgs = mutableListOf( val enumArgs = mutableListOf(
irString(thisIrType.serialName()), irString(kType.serialName()),
irCall(enumDescriptor.owner.findEnumValuesMethod()), irCall(enumDescriptor.owner.findEnumValuesMethod()),
) )
@@ -523,19 +528,19 @@ abstract class BaseIrGenerator(private val currentClass: IrClass, final override
} }
} }
else -> { else -> {
args = kType.arguments.map { args = typeArgumentsAsTypes.map {
val argSer = findTypeSerializerOrContext( val argSer = findTypeSerializerOrContext(
pluginContext, 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) { 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 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) { if (!kType.isInterface() && serializerClassOriginal == kType.classOrUpperBound()?.owner.classSerializer(pluginContext) && this@BaseIrGenerator !is SerializableCompanionIrGenerator) {
// This is default type serializer, we can shortcut through Companion.serializer() // This is default type serializer, we can shortcut through Companion.serializer()
// BUT not during generation of this method itself // 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) assert(ctor.isBound)
val ctorDecl = ctor.owner val ctorDecl = ctor.owner
if (needToCopyAnnotations) { 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) args = args + createArrayOfExpression(compilerContext.irBuiltIns.annotationType, classAnnotations)
} }
@@ -236,5 +236,16 @@ fun IrType.classOrUpperBound(): IrClassSymbol? = when(val cls = classifierOrNull
else -> null else -> null
} }
/**
* Replaces star projections with representativeUpperBound of respective type parameter
* to mimic behaviour of old FE (see StarProjectionImpl.getType())
*/
fun IrSimpleType.argumentTypesOrUpperBounds(): List<IrType> {
val params = this.classOrUpperBound()!!.owner.typeParameters
return arguments.mapIndexed { index, argument ->
argument.typeOrNull ?: params[index].representativeUpperBound
}
}
internal inline fun IrClass.shouldHaveSpecificSyntheticMethods(functionPresenceChecker: () -> IrSimpleFunction?) = internal inline fun IrClass.shouldHaveSpecificSyntheticMethods(functionPresenceChecker: () -> IrSimpleFunction?) =
!isValue && (isAbstractOrSealedSerializableClass || functionPresenceChecker() != null) !isValue && (isAbstractOrSealedSerializableClass || functionPresenceChecker() != null)
@@ -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<T1 : SomeInterface, T2 : SomeInterface>
@Serializable
abstract class AbstractMultiple<T1 : SomeInterface, T2 : SomeInterface>
@Serializable
sealed interface SealedInterfaceMultiple<T1 : SomeInterface, T2 : SomeInterface>
interface InterfaceMultiple<T1 : SomeInterface, T2 : SomeInterface>
@Serializable
class ToSerialize(
val a: SealedMultiple<SomeInterface, SomeInterface>,
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
}
@@ -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<T: E>(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<E>)") "OK" else s
}
@@ -93,11 +93,23 @@ public class SerializationFirBlackBoxTestGenerated extends AbstractSerialization
runTest("plugins/kotlinx-serialization/testData/boxIr/multimoduleInheritance.kt"); 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 @Test
@TestMetadata("sealedInterfaces.kt") @TestMetadata("sealedInterfaces.kt")
public void testSealedInterfaces() throws Exception { public void testSealedInterfaces() throws Exception {
runTest("plugins/kotlinx-serialization/testData/boxIr/sealedInterfaces.kt"); 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 @Nested
@@ -91,9 +91,21 @@ public class SerializationIrBoxTestGenerated extends AbstractSerializationIrBoxT
runTest("plugins/kotlinx-serialization/testData/boxIr/multimoduleInheritance.kt"); 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 @Test
@TestMetadata("sealedInterfaces.kt") @TestMetadata("sealedInterfaces.kt")
public void testSealedInterfaces() throws Exception { public void testSealedInterfaces() throws Exception {
runTest("plugins/kotlinx-serialization/testData/boxIr/sealedInterfaces.kt"); 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");
}
} }