From 1c4614e93b08273614441f1309dee3ea69d0b5c5 Mon Sep 17 00:00:00 2001 From: Leonid Startsev Date: Wed, 7 Dec 2022 13:57:19 +0100 Subject: [PATCH] Handle situation where KSerializer is absent from immediate supertypes of the class used in @UseSerializers: Use methods to receive full list of supertypes. K1 supertypes() call returned all supertypes, while IrClass.supertypes and FirClassSymbol.resolvedSuperTypes return only immediate ones. This lead to a difference in behavior between K1 and K2, and regression after plugin backend was rewritten from descriptors to IR. #KT-55340 Fixed --- .../compiler/backend/ir/BaseIrGenerator.kt | 4 +- .../compiler/resolve/NamingConventions.kt | 2 + .../compiler/fir/SerializationFirUtils.kt | 24 +++++++++- .../FirSerializationPluginClassChecker.kt | 17 ++++--- .../testData/boxIr/useSerializersChain.kt | 48 +++++++++++++++++++ ...SerializationFirBlackBoxTestGenerated.java | 6 +++ .../SerializationIrBoxTestGenerated.java | 6 +++ 7 files changed, 98 insertions(+), 9 deletions(-) create mode 100644 plugins/kotlinx-serialization/testData/boxIr/useSerializersChain.kt 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 3b6d817d66d..7db77b46ef8 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 @@ -82,10 +82,10 @@ abstract class BaseIrGenerator(private val currentClass: IrClass, final override } val additionalSerializersInScopeOfCurrentFile: Map, IrClassSymbol> by lazy { - getClassListFromFileAnnotation(SerializationAnnotations.additionalSerializersFqName,) + getClassListFromFileAnnotation(SerializationAnnotations.additionalSerializersFqName) .associateBy( { serializerSymbol -> - val kotlinType = (serializerSymbol.owner.superTypes.find(IrType::isKSerializer) as? IrSimpleType)?.arguments?.firstOrNull()?.typeOrNull + val kotlinType = getAllSubstitutedSupertypes(serializerSymbol.owner).find(IrType::isKSerializer)?.arguments?.firstOrNull()?.typeOrNull val classSymbol = kotlinType?.classOrNull ?: error("Argument for ${SerializationAnnotations.additionalSerializersFqName} does not implement KSerializer or does not provide serializer for concrete type") classSymbol to kotlinType.isNullable() diff --git a/plugins/kotlinx-serialization/kotlinx-serialization.common/src/org/jetbrains/kotlinx/serialization/compiler/resolve/NamingConventions.kt b/plugins/kotlinx-serialization/kotlinx-serialization.common/src/org/jetbrains/kotlinx/serialization/compiler/resolve/NamingConventions.kt index c1e20943aa9..72bddac7c7c 100644 --- a/plugins/kotlinx-serialization/kotlinx-serialization.common/src/org/jetbrains/kotlinx/serialization/compiler/resolve/NamingConventions.kt +++ b/plugins/kotlinx-serialization/kotlinx-serialization.common/src/org/jetbrains/kotlinx/serialization/compiler/resolve/NamingConventions.kt @@ -208,6 +208,8 @@ object SerializersClassIds { val sealedSerializerId = ClassId(SerializationPackages.packageFqName, Name.identifier(SpecialBuiltins.sealedSerializer)) val contextSerializerId = ClassId(SerializationPackages.packageFqName, Name.identifier(SpecialBuiltins.contextSerializer)) val generatedSerializerId = ClassId(SerializationPackages.internalPackageFqName, SerialEntityNames.GENERATED_SERIALIZER_CLASS) + + val setOfSpecialSerializers = setOf(contextSerializerId, polymorphicSerializerId) } fun findStandardKotlinTypeSerializerName(typeName: String?): String? { diff --git a/plugins/kotlinx-serialization/kotlinx-serialization.k2/src/org/jetbrains/kotlinx/serialization/compiler/fir/SerializationFirUtils.kt b/plugins/kotlinx-serialization/kotlinx-serialization.k2/src/org/jetbrains/kotlinx/serialization/compiler/fir/SerializationFirUtils.kt index 2a515c136b9..69110cfd3c0 100644 --- a/plugins/kotlinx-serialization/kotlinx-serialization.k2/src/org/jetbrains/kotlinx/serialization/compiler/fir/SerializationFirUtils.kt +++ b/plugins/kotlinx-serialization/kotlinx-serialization.k2/src/org/jetbrains/kotlinx/serialization/compiler/fir/SerializationFirUtils.kt @@ -13,10 +13,14 @@ import org.jetbrains.kotlin.fir.expressions.* import org.jetbrains.kotlin.fir.expressions.FirAnnotation import org.jetbrains.kotlin.fir.expressions.FirAnnotationCall import org.jetbrains.kotlin.fir.expressions.arguments +import org.jetbrains.kotlin.fir.resolve.createSubstitutionForSupertype import org.jetbrains.kotlin.fir.extensions.predicateBasedProvider import org.jetbrains.kotlin.fir.resolve.fullyExpandedType +import org.jetbrains.kotlin.fir.resolve.substitution.ChainedSubstitutor +import org.jetbrains.kotlin.fir.resolve.substitution.ConeSubstitutor import org.jetbrains.kotlin.fir.symbols.FirBasedSymbol import org.jetbrains.kotlin.fir.symbols.impl.FirClassSymbol +import org.jetbrains.kotlin.fir.symbols.impl.FirRegularClassSymbol import org.jetbrains.kotlin.fir.types.* import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlinx.serialization.compiler.fir.services.dependencySerializationInfoProvider @@ -180,13 +184,31 @@ val ConeKotlinType.isKSerializer: Boolean fun ConeKotlinType.serializerForType(session: FirSession): ConeKotlinType? { return this.fullyExpandedType(session) .toRegularClassSymbol(session) - ?.resolvedSuperTypes + ?.getAllSubstitutedSupertypes(session) ?.find { it.isKSerializer } ?.typeArguments ?.firstOrNull() ?.type } +fun FirRegularClassSymbol.getAllSubstitutedSupertypes(session: FirSession): Set { + val result = mutableSetOf() + + fun process(symbol: FirRegularClassSymbol, substitutor: ConeSubstitutor) { + for (superType in symbol.resolvedSuperTypes) { + if (result.add(substitutor.substituteOrSelf(superType))) { + val superClassSymbol = superType.fullyExpandedType(session).toRegularClassSymbol(session) ?: continue + val superSubstitutor = + (superType as? ConeLookupTagBasedType)?.let { createSubstitutionForSupertype(it, session) } ?: ConeSubstitutor.Empty + process(superClassSymbol, ChainedSubstitutor(superSubstitutor, substitutor)) + } + } + } + + process(this, ConeSubstitutor.Empty) + return result +} + val ConeKotlinType.isTypeParameter: Boolean get() = this is ConeTypeParameterType diff --git a/plugins/kotlinx-serialization/kotlinx-serialization.k2/src/org/jetbrains/kotlinx/serialization/compiler/fir/checkers/FirSerializationPluginClassChecker.kt b/plugins/kotlinx-serialization/kotlinx-serialization.k2/src/org/jetbrains/kotlinx/serialization/compiler/fir/checkers/FirSerializationPluginClassChecker.kt index 17aae6a5f9a..34d304e3150 100644 --- a/plugins/kotlinx-serialization/kotlinx-serialization.k2/src/org/jetbrains/kotlinx/serialization/compiler/fir/checkers/FirSerializationPluginClassChecker.kt +++ b/plugins/kotlinx-serialization/kotlinx-serialization.k2/src/org/jetbrains/kotlinx/serialization/compiler/fir/checkers/FirSerializationPluginClassChecker.kt @@ -34,6 +34,7 @@ import org.jetbrains.kotlinx.serialization.compiler.fir.services.findTypeSeriali import org.jetbrains.kotlinx.serialization.compiler.fir.services.serializablePropertiesProvider import org.jetbrains.kotlinx.serialization.compiler.fir.services.versionReader import org.jetbrains.kotlinx.serialization.compiler.resolve.SerializationAnnotations +import org.jetbrains.kotlinx.serialization.compiler.resolve.SerializersClassIds object FirSerializationPluginClassChecker : FirClassChecker() { private val JAVA_SERIALIZABLE_ID = ClassId.topLevel(FqName("java.io.Serializable")) @@ -421,27 +422,31 @@ object FirSerializationPluginClassChecker : FirClassChecker() { ) { for (property in properties) { // TODO: reporting diagnostics on properties from superclasses looks a bad idea - val serializerType = property.serializableWith - val serializerSymbol = serializerType?.toRegularClassSymbol(session) + val customSerializerType = property.serializableWith + val serializerSymbol = customSerializerType?.toRegularClassSymbol(session) val propertySymbol = property.propertySymbol val typeRef = propertySymbol.resolvedReturnTypeRef val propertyType = typeRef.coneType val source = typeRef.source ?: propertySymbol.source - if (serializerType != null && serializerSymbol != null) { + if (customSerializerType != null && serializerSymbol != null) { + // Do not account for @Polymorphic and @Contextual, as they are serializers for T: Any + // and would not be compatible on direct comparison + if (customSerializerType.classId in SerializersClassIds.setOfSpecialSerializers) return + checkCustomSerializerMatch( classSymbol, source = typeRef.source ?: propertySymbol.source, propertyType, - serializerType, + customSerializerType, reporter ) checkCustomSerializerIsNotLocal( source = propertySymbol.serializableAnnotation(needArguments = false)?.source, classSymbol, - serializerType, + customSerializerType, reporter ) - checkSerializerNullability(propertyType, serializerType, source, reporter) + checkSerializerNullability(propertyType, customSerializerType, source, reporter) } else { checkType(propertyType, source, reporter) checkGenericArrayType(propertyType, source, reporter) diff --git a/plugins/kotlinx-serialization/testData/boxIr/useSerializersChain.kt b/plugins/kotlinx-serialization/testData/boxIr/useSerializersChain.kt new file mode 100644 index 00000000000..da6d36c38c0 --- /dev/null +++ b/plugins/kotlinx-serialization/testData/boxIr/useSerializersChain.kt @@ -0,0 +1,48 @@ +// TARGET_BACKEND: JVM_IR + +// WITH_STDLIB + +// FILE: a.kt + +package a + +import kotlinx.serialization.* +import kotlinx.serialization.descriptors.* +import kotlinx.serialization.encoding.* + +class IList + +abstract class DataSerializer: KSerializer { + abstract fun getK(): K +} + +class MySerializer(val elementSer: KSerializer): DataSerializer, Int>() { + + override fun getK(): Int = 42 + + override val descriptor: SerialDescriptor + get() = PrimitiveSerialDescriptor("MySer<${elementSer.descriptor.serialName}>", PrimitiveKind.STRING) + + override fun serialize(encoder: Encoder, value: IList) = TODO("serialize") + + override fun deserialize(decoder: Decoder): IList = TODO("deserialize") +} + +// FILE: test.kt + +@file:UseSerializers(MySerializer::class) + +package a + +import kotlinx.serialization.* + +@Serializable +class Holder( + val i: Int, + val c: IList +) + +fun box(): String { + val d = Holder.serializer().descriptor.toString() + return if (d == "a.Holder(i: kotlin.Int, c: MySer)") "OK" else d +} \ No newline at end of file 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 1e53da74f9d..bd63653ab9a 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 @@ -134,6 +134,12 @@ public class SerializationFirBlackBoxTestGenerated extends AbstractSerialization public void testTypealiasesTest() throws Exception { runTest("plugins/kotlinx-serialization/testData/boxIr/typealiasesTest.kt"); } + + @Test + @TestMetadata("useSerializersChain.kt") + public void testUseSerializersChain() throws Exception { + runTest("plugins/kotlinx-serialization/testData/boxIr/useSerializersChain.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 ccbd11d761f..36966b9de8f 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 @@ -132,4 +132,10 @@ public class SerializationIrBoxTestGenerated extends AbstractSerializationIrBoxT public void testTypealiasesTest() throws Exception { runTest("plugins/kotlinx-serialization/testData/boxIr/typealiasesTest.kt"); } + + @Test + @TestMetadata("useSerializersChain.kt") + public void testUseSerializersChain() throws Exception { + runTest("plugins/kotlinx-serialization/testData/boxIr/useSerializersChain.kt"); + } }