From 6417f6456fc5aca8dbff520a5bc076d1e091305c Mon Sep 17 00:00:00 2001 From: "Sergey.Shanshin" Date: Fri, 28 Jul 2023 15:53:09 +0000 Subject: [PATCH] [KxSerialization] Implemented addition of annotations to the named companion For a more efficient lookup of a companion with the functions of obtaining a serializer, a special annotation is added to the named companions. Merge-request: KT-MR-11003 Merged-by: Sergey Shanshin --- .../ir/SerializableCompanionIrGenerator.kt | 19 +++ .../compiler/resolve/NamingConventions.kt | 3 + ...SerializationDescriptorSerializerPlugin.kt | 17 +++ .../compiler/resolve/SearchUtils.kt | 3 + .../testData/boxIr/namedCompanions.kt | 116 ++++++++++++++++++ ...tionFirLightTreeBlackBoxTestGenerated.java | 6 + .../SerializationIrBoxTestGenerated.java | 6 + 7 files changed, 170 insertions(+) create mode 100644 plugins/kotlinx-serialization/testData/boxIr/namedCompanions.kt diff --git a/plugins/kotlinx-serialization/kotlinx-serialization.backend/src/org/jetbrains/kotlinx/serialization/compiler/backend/ir/SerializableCompanionIrGenerator.kt b/plugins/kotlinx-serialization/kotlinx-serialization.backend/src/org/jetbrains/kotlinx/serialization/compiler/backend/ir/SerializableCompanionIrGenerator.kt index 9fe817dd4c5..ca76f5426f0 100644 --- a/plugins/kotlinx-serialization/kotlinx-serialization.backend/src/org/jetbrains/kotlinx/serialization/compiler/backend/ir/SerializableCompanionIrGenerator.kt +++ b/plugins/kotlinx-serialization/kotlinx-serialization.backend/src/org/jetbrains/kotlinx/serialization/compiler/backend/ir/SerializableCompanionIrGenerator.kt @@ -23,8 +23,10 @@ import org.jetbrains.kotlin.ir.types.typeWith import org.jetbrains.kotlin.ir.util.* import org.jetbrains.kotlin.name.ClassId import org.jetbrains.kotlin.name.Name +import org.jetbrains.kotlin.name.SpecialNames import org.jetbrains.kotlinx.serialization.compiler.extensions.SerializationPluginContext import org.jetbrains.kotlinx.serialization.compiler.resolve.SerialEntityNames +import org.jetbrains.kotlinx.serialization.compiler.resolve.SerializationAnnotations import org.jetbrains.kotlinx.serialization.compiler.resolve.SerializationPackages class SerializableCompanionIrGenerator( @@ -75,6 +77,23 @@ class SerializableCompanionIrGenerator( } else { generateSerializerGetter(serializerGetterFunction) } + + patchNamedCompanionWithMarkerAnnotation() + } + + private fun patchNamedCompanionWithMarkerAnnotation() { + if (serializableIrClass.kind == ClassKind.OBJECT || irClass.name == SpecialNames.DEFAULT_NAME_FOR_COMPANION_OBJECT) { + return + } + + val annotationClass = compilerContext.referenceClass(SerializationAnnotations.namedCompanionClassId) ?: return + + val annotationCtor = annotationClass.constructors.single { it.owner.isPrimary } + val annotationType = annotationClass.defaultType + + val annotationCall = IrConstructorCallImpl.fromSymbolOwner(irClass.startOffset, irClass.endOffset, annotationType, annotationCtor) + + compilerContext.annotationsRegistrar.addMetadataVisibleAnnotationsToElement(irClass, annotationCall) } private fun IrBuilderWithScope.patchSerializableClassWithMarkerAnnotation(serializer: IrClass) { 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 9de94409c1b..d93e35cbb9c 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 @@ -43,6 +43,8 @@ object SerializationAnnotations { val polymorphicFqName = FqName("kotlinx.serialization.Polymorphic") val additionalSerializersFqName = FqName("kotlinx.serialization.UseSerializers") + val namedCompanionFqName = FqName("kotlinx.serialization.internal.NamedCompanion") + val serializableAnnotationClassId = ClassId.topLevel(serializableAnnotationFqName) val serializerAnnotationClassId = ClassId.topLevel(serializerAnnotationFqName) val serialNameAnnotationClassId = ClassId.topLevel(serialNameAnnotationFqName) @@ -58,6 +60,7 @@ object SerializationAnnotations { val contextualOnPropertyClassId = ClassId.topLevel(contextualOnPropertyFqName) val polymorphicClassId = ClassId.topLevel(polymorphicFqName) val additionalSerializersClassId = ClassId.topLevel(additionalSerializersFqName) + val namedCompanionClassId = ClassId.topLevel(namedCompanionFqName) } object SerialEntityNames { diff --git a/plugins/kotlinx-serialization/kotlinx-serialization.k1/src/org/jetbrains/kotlinx/serialization/compiler/extensions/SerializationDescriptorSerializerPlugin.kt b/plugins/kotlinx-serialization/kotlinx-serialization.k1/src/org/jetbrains/kotlinx/serialization/compiler/extensions/SerializationDescriptorSerializerPlugin.kt index 7094d1ab144..b24e7ba505e 100644 --- a/plugins/kotlinx-serialization/kotlinx-serialization.k1/src/org/jetbrains/kotlinx/serialization/compiler/extensions/SerializationDescriptorSerializerPlugin.kt +++ b/plugins/kotlinx-serialization/kotlinx-serialization.k1/src/org/jetbrains/kotlinx/serialization/compiler/extensions/SerializationDescriptorSerializerPlugin.kt @@ -9,15 +9,21 @@ import org.jetbrains.kotlin.descriptors.ClassDescriptor import org.jetbrains.kotlin.descriptors.Modality import org.jetbrains.kotlin.metadata.ProtoBuf import org.jetbrains.kotlin.metadata.SerializationPluginMetadataExtensions +import org.jetbrains.kotlin.metadata.deserialization.Flags import org.jetbrains.kotlin.metadata.serialization.MutableVersionRequirementTable import org.jetbrains.kotlin.name.Name +import org.jetbrains.kotlin.name.SpecialNames import org.jetbrains.kotlin.serialization.DescriptorSerializer import org.jetbrains.kotlin.serialization.DescriptorSerializerPlugin import org.jetbrains.kotlin.serialization.SerializerExtension import org.jetbrains.kotlinx.serialization.compiler.resolve.SerializableProperties +import org.jetbrains.kotlinx.serialization.compiler.resolve.findNamedCompanionAnnotation import org.jetbrains.kotlinx.serialization.compiler.resolve.isInternalSerializable +import org.jetbrains.kotlinx.serialization.compiler.resolve.shouldHaveGeneratedMethodsInCompanion class SerializationDescriptorSerializerPlugin : DescriptorSerializerPlugin { + private val hasAnnotationFlag = Flags.HAS_ANNOTATIONS.toFlags(true) + private val descriptorMetadataMap: MutableMap = hashMapOf() private val ClassDescriptor.needSaveProgramOrder: Boolean @@ -37,6 +43,17 @@ class SerializationDescriptorSerializerPlugin : DescriptorSerializerPlugin { ) { fun Name.toIndex() = extension.stringTable.getStringIndex(asString()) + if (descriptor.isCompanionObject + && descriptor.name != SpecialNames.DEFAULT_NAME_FOR_COMPANION_OBJECT + && (descriptor.containingDeclaration as? ClassDescriptor)?.shouldHaveGeneratedMethodsInCompanion == true + ) { + // named companion objects are always marked by special annotation in backend + // in order for this annotation to be visible in runtime through reflection, it is necessary to put down the hasAnnotation flag + if (proto.flags and hasAnnotationFlag == 0 && descriptor.findNamedCompanionAnnotation() != null) { + proto.flags = proto.flags or hasAnnotationFlag + } + } + if (!descriptor.needSaveProgramOrder) return val propertiesCorrectOrder = (descriptorMetadataMap[descriptor] ?: return).serializableProperties diff --git a/plugins/kotlinx-serialization/kotlinx-serialization.k1/src/org/jetbrains/kotlinx/serialization/compiler/resolve/SearchUtils.kt b/plugins/kotlinx-serialization/kotlinx-serialization.k1/src/org/jetbrains/kotlinx/serialization/compiler/resolve/SearchUtils.kt index 9d3ae7d7cf2..ab4ffd54040 100644 --- a/plugins/kotlinx-serialization/kotlinx-serialization.k1/src/org/jetbrains/kotlinx/serialization/compiler/resolve/SearchUtils.kt +++ b/plugins/kotlinx-serialization/kotlinx-serialization.k1/src/org/jetbrains/kotlinx/serialization/compiler/resolve/SearchUtils.kt @@ -82,6 +82,9 @@ fun ClassDescriptor.getKSerializer(): ClassDescriptor = ) )!! +fun ClassDescriptor.findNamedCompanionAnnotation(): ClassDescriptor? = + module.findClassAcrossModuleDependencies(SerializationAnnotations.namedCompanionClassId) + fun ModuleDescriptor.getJsExportIgnore(): ClassDescriptor? = findClassAcrossModuleDependencies(SerializationJsDependenciesClassIds.jsExportIgnore) diff --git a/plugins/kotlinx-serialization/testData/boxIr/namedCompanions.kt b/plugins/kotlinx-serialization/testData/boxIr/namedCompanions.kt new file mode 100644 index 00000000000..2e56518f859 --- /dev/null +++ b/plugins/kotlinx-serialization/testData/boxIr/namedCompanions.kt @@ -0,0 +1,116 @@ +// WITH_STDLIB +// WITH_REFLECT +// TARGET_BACKEND: JVM_IR + +package kotlinx.serialization.internal + +import kotlin.annotation.* +import kotlinx.serialization.* +import kotlinx.serialization.descriptors.* +import kotlinx.serialization.encoding.* +import kotlin.reflect.KClass + +/* + Until the annotation is added to the serialization runtime, + we have to create an annotation with that name in the project itself + */ +@Target(AnnotationTarget.CLASS) +@Retention(AnnotationRetention.RUNTIME) +annotation class NamedCompanion + + +@Serializable +class Plain(val i: Int) + +@Serializable +object Object + + +@Serializable +class WithStd(val i: Int) { + companion object { + var j: Int = 42 + } +} + +@Serializable +class WithNamed(val i: Int) { + companion object MyCompanion { + var j: Int = 42 + } +} + + +object InterfaceWithCustomSerializer: ToDoSerializer("interface") +@Serializable(InterfaceWithCustomSerializer::class) +interface InterfaceWithCustom { +} + + +object NamedInterfaceWithCustomSerializer: ToDoSerializer("interface.name") +@Serializable(NamedInterfaceWithCustomSerializer::class) +interface NamedInterfaceWithCustom { + companion object MyCompanion { + var j: Int = 42 + } +} + +@Serializable +sealed interface SealedInterface { +} + +@Serializable +sealed interface NamedSealedInterface { + companion object MyCompanion { + var j: Int = 42 + } +} + +fun box(): String { + if (Plain::class.annotations.any { it.annotationClass.simpleName == "NamedCompanion" }) { + return "Annotation on Plain class" + } + if (Plain.Companion::class.annotations.any { it.annotationClass.simpleName == "NamedCompanion" }) { + return "Annotation on Plain.Companion class" + } + + if (Object::class.annotations.any { it.annotationClass.simpleName == "NamedCompanion" }) { + return "Annotation on Object" + } + + if (WithStd::class.annotations.any { it.annotationClass.simpleName == "NamedCompanion" }) { + return "Annotation on WithStd class" + } + if (WithStd.Companion::class.annotations.any { it.annotationClass.simpleName == "NamedCompanion" }) { + return "Annotation on WithStd.Companion class" + } + + if (WithNamed::class.annotations.any { it.annotationClass.simpleName == "NamedCompanion" }) { + return "Annotation on WithNamed class" + } + if (WithNamed.MyCompanion::class.annotations.none { it.annotationClass.simpleName == "NamedCompanion" }) { + return "Missed annotation on WithNamed.MyCompanion class ${WithNamed.MyCompanion::class.annotations}" + } + + if (InterfaceWithCustom::class.annotations.any { it.annotationClass.simpleName == "NamedCompanion" }) { + return "Annotation on InterfaceWithCustom class" + } + if (NamedInterfaceWithCustom.MyCompanion::class.annotations.none { it.annotationClass.simpleName == "NamedCompanion" }) { + return "Missed annotation on NamedInterfaceWithCustom.MyCompanion class ${WithNamed.MyCompanion::class.annotations}" + } + + if (SealedInterface::class.annotations.any { it.annotationClass.simpleName == "NamedCompanion" }) { + return "Annotation on SealedInterface class" + } + if (NamedSealedInterface.MyCompanion::class.annotations.none { it.annotationClass.simpleName == "NamedCompanion" }) { + return "Missed annotation on NamedSealedInterface.MyCompanion class ${WithNamed.MyCompanion::class.annotations}" + } + + return "OK" +} + +abstract class ToDoSerializer(descriptorName: String): KSerializer { + override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor(descriptorName, PrimitiveKind.STRING) + override fun deserialize(decoder: Decoder): T = TODO() + override fun serialize(encoder: Encoder, value: T) = TODO() +} diff --git a/plugins/kotlinx-serialization/tests-gen/org/jetbrains/kotlinx/serialization/runners/SerializationFirLightTreeBlackBoxTestGenerated.java b/plugins/kotlinx-serialization/tests-gen/org/jetbrains/kotlinx/serialization/runners/SerializationFirLightTreeBlackBoxTestGenerated.java index f88f914eac2..f50cad2efa8 100644 --- a/plugins/kotlinx-serialization/tests-gen/org/jetbrains/kotlinx/serialization/runners/SerializationFirLightTreeBlackBoxTestGenerated.java +++ b/plugins/kotlinx-serialization/tests-gen/org/jetbrains/kotlinx/serialization/runners/SerializationFirLightTreeBlackBoxTestGenerated.java @@ -201,6 +201,12 @@ public class SerializationFirLightTreeBlackBoxTestGenerated extends AbstractSeri runTest("plugins/kotlinx-serialization/testData/boxIr/multipleGenericsPolymorphic.kt"); } + @Test + @TestMetadata("namedCompanions.kt") + public void testNamedCompanions() throws Exception { + runTest("plugins/kotlinx-serialization/testData/boxIr/namedCompanions.kt"); + } + @Test @TestMetadata("repeatableSerialInfo.kt") public void testRepeatableSerialInfo() throws Exception { 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 3d43947e9ad..509b3d6af41 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 @@ -199,6 +199,12 @@ public class SerializationIrBoxTestGenerated extends AbstractSerializationIrBoxT runTest("plugins/kotlinx-serialization/testData/boxIr/multipleGenericsPolymorphic.kt"); } + @Test + @TestMetadata("namedCompanions.kt") + public void testNamedCompanions() throws Exception { + runTest("plugins/kotlinx-serialization/testData/boxIr/namedCompanions.kt"); + } + @Test @TestMetadata("repeatableSerialInfo.kt") public void testRepeatableSerialInfo() throws Exception {