[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 <Sergey.Shanshin@jetbrains.com>
This commit is contained in:
Sergey.Shanshin
2023-07-28 15:53:09 +00:00
committed by Space Team
parent 175c00e5c7
commit 6417f6456f
7 changed files with 170 additions and 0 deletions
@@ -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) {
@@ -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 {
@@ -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<ClassDescriptor, SerializableProperties> = 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
@@ -82,6 +82,9 @@ fun ClassDescriptor.getKSerializer(): ClassDescriptor =
)
)!!
fun ClassDescriptor.findNamedCompanionAnnotation(): ClassDescriptor? =
module.findClassAcrossModuleDependencies(SerializationAnnotations.namedCompanionClassId)
fun ModuleDescriptor.getJsExportIgnore(): ClassDescriptor? =
findClassAcrossModuleDependencies(SerializationJsDependenciesClassIds.jsExportIgnore)
@@ -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<InterfaceWithCustom>("interface")
@Serializable(InterfaceWithCustomSerializer::class)
interface InterfaceWithCustom {
}
object NamedInterfaceWithCustomSerializer: ToDoSerializer<NamedInterfaceWithCustom>("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<T: Any>(descriptorName: String): KSerializer<T> {
override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor(descriptorName, PrimitiveKind.STRING)
override fun deserialize(decoder: Decoder): T = TODO()
override fun serialize(encoder: Encoder, value: T) = TODO()
}
@@ -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 {
@@ -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 {