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(
thisIrType: IrType,
thisIrType: IrSimpleType,
typeArgs: List<IrType>,
args: List<IrExpression>
args: List<IrExpression>,
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<IrExpression> =
// 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<IrType> = 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<IrExpression>
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
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)
}
@@ -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<IrType> {
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)
@@ -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");
}
@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
@@ -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");
}
}