[KxSerialization] Implemented generation of companions for interfaces

For interfaces with custom serializer (@Serializer(`SerializerType::class)`) now the `serializer()` function is generated in the companion so that user can get a serializer by type

Merge-request: KT-MR-11040
Merged-by: Sergey Shanshin <Sergey.Shanshin@jetbrains.com>
This commit is contained in:
Sergey.Shanshin
2023-07-17 19:44:01 +00:00
committed by Space Team
parent 38b55c659b
commit a45fe2d8a8
8 changed files with 157 additions and 2 deletions
@@ -79,6 +79,9 @@ internal fun IrClass.findEnumLegacySerializer(): IrClass? {
internal val IrClass.isSealedSerializableInterface: Boolean
get() = kind == ClassKind.INTERFACE && modality == Modality.SEALED && hasSerializableOrMetaAnnotation()
internal val IrClass.isSerializableInterfaceWithCustom: Boolean
get() = kind == ClassKind.INTERFACE && hasSerializableAnnotationWithArgs()
internal fun IrClass.isInternallySerializableEnum(): Boolean =
kind == ClassKind.ENUM_CLASS && hasSerializableOrMetaAnnotationWithoutArgs()
@@ -93,6 +96,11 @@ internal fun IrClass.hasSerializableOrMetaAnnotationWithoutArgs(): Boolean = che
fun IrClass.hasSerializableOrMetaAnnotation() = checkSerializableOrMetaAnnotationArgs(mustDoNotHaveArgs = false)
private fun IrClass.hasSerializableAnnotationWithArgs(): Boolean {
val annot = getAnnotation(SerializationAnnotations.serializableAnnotationFqName)
return annot?.getValueArgument(0) != null
}
private fun IrClass.checkSerializableOrMetaAnnotationArgs(mustDoNotHaveArgs: Boolean): Boolean {
val annot = getAnnotation(SerializationAnnotations.serializableAnnotationFqName)
if (annot != null) { // @Serializable have higher priority
@@ -118,7 +126,7 @@ internal fun IrClass.shouldHaveGeneratedSerializer(): Boolean =
|| isEnumWithLegacyGeneratedSerializer()
internal val IrClass.shouldHaveGeneratedMethodsInCompanion: Boolean
get() = this.isSerializableObject || this.isSerializableEnum() || (this.kind == ClassKind.CLASS && hasSerializableOrMetaAnnotation()) || this.isSealedSerializableInterface
get() = this.isSerializableObject || this.isSerializableEnum() || (this.kind == ClassKind.CLASS && hasSerializableOrMetaAnnotation()) || this.isSealedSerializableInterface || this.isSerializableInterfaceWithCustom
internal fun IrClass.isSerializableEnum(): Boolean = kind == ClassKind.ENUM_CLASS && hasSerializableOrMetaAnnotation()
@@ -193,6 +201,7 @@ internal fun IrClass.needSerializerFactory(compilerContext: SerializationPluginC
if (serializableClass.isSerializableEnum()) return true
if (serializableClass.isAbstractOrSealedSerializableClass) return true
if (serializableClass.isSealedSerializableInterface) return true
if (serializableClass.isSerializableInterfaceWithCustom) return true
if (serializableClass.typeParameters.isEmpty()) return false
return true
}
@@ -113,7 +113,11 @@ val KotlinType?.toClassDescriptor: ClassDescriptor?
}
val ClassDescriptor.shouldHaveGeneratedMethodsInCompanion: Boolean
get() = this.isSerializableObject || this.isSerializableEnum() || (this.kind == ClassKind.CLASS && hasSerializableOrMetaAnnotation) || this.isSealedSerializableInterface
get() = this.isSerializableObject
|| this.isSerializableEnum()
|| (this.kind == ClassKind.CLASS && hasSerializableOrMetaAnnotation)
|| this.isSealedSerializableInterface
|| this.isSerializableInterfaceWithCustom
val ClassDescriptor.isSerializableObject: Boolean
get() = kind == ClassKind.OBJECT && hasSerializableOrMetaAnnotation
@@ -124,6 +128,9 @@ val ClassDescriptor.isInternallySerializableObject: Boolean
val ClassDescriptor.isSealedSerializableInterface: Boolean
get() = kind == ClassKind.INTERFACE && modality == Modality.SEALED && hasSerializableOrMetaAnnotation
val ClassDescriptor.isSerializableInterfaceWithCustom: Boolean
get() = kind == ClassKind.INTERFACE && hasSerializableAnnotationWithArgs
val ClassDescriptor.isAbstractOrSealedOrInterface: Boolean
get() = kind == ClassKind.INTERFACE || modality == Modality.SEALED || modality == Modality.ABSTRACT
@@ -196,6 +203,15 @@ private val ClassDescriptor.hasSerializableAnnotationWithoutArgs: Boolean
return psi.valueArguments.isEmpty()
}
private val ClassDescriptor.hasSerializableAnnotationWithArgs: Boolean
get() {
if (!hasSerializableAnnotation) return false
// If provided descriptor is lazy, carefully look at psi in order not to trigger full resolve which may be recursive.
// Otherwise, this descriptor is deserialized from another module, and it is OK to check value right away.
val psi = findSerializableAnnotationDeclaration() ?: return (serializableWith != null)
return psi.valueArguments.isNotEmpty()
}
private fun Annotated.findSerializableAnnotationDeclaration(): KtAnnotationEntry? {
val lazyDesc = annotations.findAnnotation(serializableAnnotationFqName) as? LazyAnnotationDescriptor
return lazyDesc?.annotationEntry
@@ -277,6 +293,7 @@ fun ClassDescriptor.needSerializerFactory(): Boolean {
if (serializableClass.isSerializableEnum()) return true
if (serializableClass.isAbstractOrSealedSerializableClass()) return true
if (serializableClass.isSealedSerializableInterface) return true
if (serializableClass.isSerializableInterfaceWithCustom) return true
if (serializableClass.declaredTypeParameters.isEmpty()) return false
return true
}
@@ -98,6 +98,15 @@ fun FirClassSymbol<*>.hasSerializableAnnotationWithoutArgs(session: FirSession):
}
} ?: false
fun FirClassSymbol<*>.hasSerializableAnnotationWithArgs(session: FirSession): Boolean {
val annotation = serializableAnnotation(needArguments = false, session) ?: return false
return if (annotation is FirAnnotationCall) {
annotation.arguments.isNotEmpty()
} else {
annotation.argumentMapping.mapping.isNotEmpty()
}
}
internal fun FirBasedSymbol<*>.getSerializableWith(session: FirSession): ConeKotlinType? =
serializableAnnotation(needArguments = true, session)?.getKClassArgument(AnnotationParameterNames.WITH)
@@ -132,6 +141,10 @@ context(FirSession)
internal val FirClassSymbol<*>.isSealedSerializableInterface: Boolean
get() = classKind.isInterface && rawStatus.modality == Modality.SEALED && hasSerializableOrMetaAnnotation
context(FirSession)
internal val FirClassSymbol<*>.isSerializableInterfaceWithCustom: Boolean
get() = classKind.isInterface && hasSerializableAnnotationWithArgs(this@FirSession)
context(FirSession)
val FirClassSymbol<*>.hasSerializableOrMetaAnnotation: Boolean
get() = hasSerializableAnnotation || hasMetaSerializableAnnotation
@@ -146,6 +159,7 @@ internal val FirClassSymbol<*>.shouldHaveGeneratedMethodsInCompanion: Boolean
|| isSerializableEnum
|| (classKind == ClassKind.CLASS && hasSerializableOrMetaAnnotation)
|| isSealedSerializableInterface
|| isSerializableInterfaceWithCustom
context(FirSession)
internal val FirClassSymbol<*>.companionNeedsSerializerFactory: Boolean
@@ -155,6 +169,7 @@ internal val FirClassSymbol<*>.companionNeedsSerializerFactory: Boolean
if (isSerializableEnum) return true
if (isAbstractOrSealedSerializableClass) return true
if (isSealedSerializableInterface) return true
if (isSerializableInterfaceWithCustom) return true
if (typeParameterSymbols.isEmpty()) return false
return true
}
@@ -0,0 +1,90 @@
// WITH_STDLIB
package a
import kotlinx.serialization.*
import kotlinx.serialization.descriptors.*
import kotlinx.serialization.encoding.*
import kotlin.reflect.*
object InterfaceCustomSerializer: ToDoSerializer<InterfaceWithCustom>("interface")
@Serializable(InterfaceCustomSerializer::class)
interface InterfaceWithCustom
object NamedInterfaceSerializer: ToDoSerializer<NamedInterfaceWithCustom>("interface.named")
@Serializable(NamedInterfaceSerializer::class)
interface NamedInterfaceWithCustom {
companion object Named {
}
}
@Serializable
sealed interface SealedInterface
@Serializable
sealed interface NamedSealedInterface {
companion object Named {
}
}
object SealedInterfaceCustomSerializer: ToDoSerializer<SealedInterfaceWithCustom>("sealed interface")
@Serializable(SealedInterfaceCustomSerializer::class)
sealed interface SealedInterfaceWithCustom
object NamedSealedInterfaceCustomSerializer: ToDoSerializer<NamedSealedInterfaceWithCustom>("sealed interface.named")
@Serializable(NamedSealedInterfaceCustomSerializer::class)
sealed interface NamedSealedInterfaceWithCustom {
companion object Named {
}
}
@Serializable
class Holder(
val a: InterfaceWithCustom,
val b: NamedInterfaceWithCustom,
val c: SealedInterface,
val d: NamedSealedInterface,
val e: SealedInterfaceWithCustom,
val f: NamedSealedInterfaceWithCustom,
)
fun box(): String {
check(InterfaceWithCustom.serializer(), "interface", 0)?.let { return it }
check(NamedInterfaceWithCustom.Named.serializer(), "interface.named", 1, true)?.let { return it }
check(SealedInterface.serializer(), "a.SealedInterface", 2)?.let { return it }
check(NamedSealedInterface.serializer(), "a.NamedSealedInterface", 3, true)?.let { return it }
check(SealedInterfaceWithCustom.serializer(), "sealed interface", 4)?.let { return it }
check(NamedSealedInterfaceWithCustom.serializer(), "sealed interface.named", 5, true)?.let { return it }
return "OK"
}
inline fun <reified T> check(serializer: KSerializer<T>, serializerName: String, index: Int, namedCompanion: Boolean = false): String? {
if (serializer.descriptor.serialName != serializerName) return "${T::class.simpleName}.serializer().descriptor.serialName=" + serializer.descriptor.serialName
if (serializer<T>().descriptor.serialName != serializerName) return "serializer<${T::class.simpleName}>().descriptor.serialName=" + serializer<T>().descriptor.serialName
if (!namedCompanion && serializer(typeOf<T>()).descriptor.serialName != serializerName) return "serializer(typeOf<${T::class.simpleName}>()).descriptor.serialName=" + serializer(typeOf<T>()).descriptor.serialName
if (Holder.serializer().descriptor.getElementDescriptor(index).serialName != serializerName) return "holderDescriptor.getElementDescriptor($index).serialName=" + Holder.serializer().descriptor.getElementDescriptor(index).serialName
return null
}
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()
}
@@ -49,6 +49,12 @@ public class SerializationFirJsBoxTestGenerated extends AbstractSerializationFir
runTest("plugins/kotlinx-serialization/testData/boxIr/excludedFromFileExport.kt");
}
@Test
@TestMetadata("interfaces.kt")
public void testInterfaces() throws Exception {
runTest("plugins/kotlinx-serialization/testData/boxIr/interfaces.kt");
}
@Test
@TestMetadata("serializerFactory.kt")
public void testSerializerFactory() throws Exception {
@@ -141,6 +141,12 @@ public class SerializationFirLightTreeBlackBoxTestGenerated extends AbstractSeri
runTest("plugins/kotlinx-serialization/testData/boxIr/inlineClasses.kt");
}
@Test
@TestMetadata("interfaces.kt")
public void testInterfaces() throws Exception {
runTest("plugins/kotlinx-serialization/testData/boxIr/interfaces.kt");
}
@Test
@TestMetadata("intrinsicAnnotations.kt")
public void testIntrinsicAnnotations() throws Exception {
@@ -139,6 +139,12 @@ public class SerializationIrBoxTestGenerated extends AbstractSerializationIrBoxT
runTest("plugins/kotlinx-serialization/testData/boxIr/inlineClasses.kt");
}
@Test
@TestMetadata("interfaces.kt")
public void testInterfaces() throws Exception {
runTest("plugins/kotlinx-serialization/testData/boxIr/interfaces.kt");
}
@Test
@TestMetadata("intrinsicAnnotations.kt")
public void testIntrinsicAnnotations() throws Exception {
@@ -49,6 +49,12 @@ public class SerializationIrJsBoxTestGenerated extends AbstractSerializationIrJs
runTest("plugins/kotlinx-serialization/testData/boxIr/excludedFromFileExport.kt");
}
@Test
@TestMetadata("interfaces.kt")
public void testInterfaces() throws Exception {
runTest("plugins/kotlinx-serialization/testData/boxIr/interfaces.kt");
}
@Test
@TestMetadata("serializerFactory.kt")
public void testSerializerFactory() throws Exception {