[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 internal val IrClass.isSealedSerializableInterface: Boolean
get() = kind == ClassKind.INTERFACE && modality == Modality.SEALED && hasSerializableOrMetaAnnotation() get() = kind == ClassKind.INTERFACE && modality == Modality.SEALED && hasSerializableOrMetaAnnotation()
internal val IrClass.isSerializableInterfaceWithCustom: Boolean
get() = kind == ClassKind.INTERFACE && hasSerializableAnnotationWithArgs()
internal fun IrClass.isInternallySerializableEnum(): Boolean = internal fun IrClass.isInternallySerializableEnum(): Boolean =
kind == ClassKind.ENUM_CLASS && hasSerializableOrMetaAnnotationWithoutArgs() kind == ClassKind.ENUM_CLASS && hasSerializableOrMetaAnnotationWithoutArgs()
@@ -93,6 +96,11 @@ internal fun IrClass.hasSerializableOrMetaAnnotationWithoutArgs(): Boolean = che
fun IrClass.hasSerializableOrMetaAnnotation() = checkSerializableOrMetaAnnotationArgs(mustDoNotHaveArgs = false) 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 { private fun IrClass.checkSerializableOrMetaAnnotationArgs(mustDoNotHaveArgs: Boolean): Boolean {
val annot = getAnnotation(SerializationAnnotations.serializableAnnotationFqName) val annot = getAnnotation(SerializationAnnotations.serializableAnnotationFqName)
if (annot != null) { // @Serializable have higher priority if (annot != null) { // @Serializable have higher priority
@@ -118,7 +126,7 @@ internal fun IrClass.shouldHaveGeneratedSerializer(): Boolean =
|| isEnumWithLegacyGeneratedSerializer() || isEnumWithLegacyGeneratedSerializer()
internal val IrClass.shouldHaveGeneratedMethodsInCompanion: Boolean 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() 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.isSerializableEnum()) return true
if (serializableClass.isAbstractOrSealedSerializableClass) return true if (serializableClass.isAbstractOrSealedSerializableClass) return true
if (serializableClass.isSealedSerializableInterface) return true if (serializableClass.isSealedSerializableInterface) return true
if (serializableClass.isSerializableInterfaceWithCustom) return true
if (serializableClass.typeParameters.isEmpty()) return false if (serializableClass.typeParameters.isEmpty()) return false
return true return true
} }
@@ -113,7 +113,11 @@ val KotlinType?.toClassDescriptor: ClassDescriptor?
} }
val ClassDescriptor.shouldHaveGeneratedMethodsInCompanion: Boolean 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 val ClassDescriptor.isSerializableObject: Boolean
get() = kind == ClassKind.OBJECT && hasSerializableOrMetaAnnotation get() = kind == ClassKind.OBJECT && hasSerializableOrMetaAnnotation
@@ -124,6 +128,9 @@ val ClassDescriptor.isInternallySerializableObject: Boolean
val ClassDescriptor.isSealedSerializableInterface: Boolean val ClassDescriptor.isSealedSerializableInterface: Boolean
get() = kind == ClassKind.INTERFACE && modality == Modality.SEALED && hasSerializableOrMetaAnnotation get() = kind == ClassKind.INTERFACE && modality == Modality.SEALED && hasSerializableOrMetaAnnotation
val ClassDescriptor.isSerializableInterfaceWithCustom: Boolean
get() = kind == ClassKind.INTERFACE && hasSerializableAnnotationWithArgs
val ClassDescriptor.isAbstractOrSealedOrInterface: Boolean val ClassDescriptor.isAbstractOrSealedOrInterface: Boolean
get() = kind == ClassKind.INTERFACE || modality == Modality.SEALED || modality == Modality.ABSTRACT get() = kind == ClassKind.INTERFACE || modality == Modality.SEALED || modality == Modality.ABSTRACT
@@ -196,6 +203,15 @@ private val ClassDescriptor.hasSerializableAnnotationWithoutArgs: Boolean
return psi.valueArguments.isEmpty() 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? { private fun Annotated.findSerializableAnnotationDeclaration(): KtAnnotationEntry? {
val lazyDesc = annotations.findAnnotation(serializableAnnotationFqName) as? LazyAnnotationDescriptor val lazyDesc = annotations.findAnnotation(serializableAnnotationFqName) as? LazyAnnotationDescriptor
return lazyDesc?.annotationEntry return lazyDesc?.annotationEntry
@@ -277,6 +293,7 @@ fun ClassDescriptor.needSerializerFactory(): Boolean {
if (serializableClass.isSerializableEnum()) return true if (serializableClass.isSerializableEnum()) return true
if (serializableClass.isAbstractOrSealedSerializableClass()) return true if (serializableClass.isAbstractOrSealedSerializableClass()) return true
if (serializableClass.isSealedSerializableInterface) return true if (serializableClass.isSealedSerializableInterface) return true
if (serializableClass.isSerializableInterfaceWithCustom) return true
if (serializableClass.declaredTypeParameters.isEmpty()) return false if (serializableClass.declaredTypeParameters.isEmpty()) return false
return true return true
} }
@@ -98,6 +98,15 @@ fun FirClassSymbol<*>.hasSerializableAnnotationWithoutArgs(session: FirSession):
} }
} ?: false } ?: 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? = internal fun FirBasedSymbol<*>.getSerializableWith(session: FirSession): ConeKotlinType? =
serializableAnnotation(needArguments = true, session)?.getKClassArgument(AnnotationParameterNames.WITH) serializableAnnotation(needArguments = true, session)?.getKClassArgument(AnnotationParameterNames.WITH)
@@ -132,6 +141,10 @@ context(FirSession)
internal val FirClassSymbol<*>.isSealedSerializableInterface: Boolean internal val FirClassSymbol<*>.isSealedSerializableInterface: Boolean
get() = classKind.isInterface && rawStatus.modality == Modality.SEALED && hasSerializableOrMetaAnnotation get() = classKind.isInterface && rawStatus.modality == Modality.SEALED && hasSerializableOrMetaAnnotation
context(FirSession)
internal val FirClassSymbol<*>.isSerializableInterfaceWithCustom: Boolean
get() = classKind.isInterface && hasSerializableAnnotationWithArgs(this@FirSession)
context(FirSession) context(FirSession)
val FirClassSymbol<*>.hasSerializableOrMetaAnnotation: Boolean val FirClassSymbol<*>.hasSerializableOrMetaAnnotation: Boolean
get() = hasSerializableAnnotation || hasMetaSerializableAnnotation get() = hasSerializableAnnotation || hasMetaSerializableAnnotation
@@ -146,6 +159,7 @@ internal val FirClassSymbol<*>.shouldHaveGeneratedMethodsInCompanion: Boolean
|| isSerializableEnum || isSerializableEnum
|| (classKind == ClassKind.CLASS && hasSerializableOrMetaAnnotation) || (classKind == ClassKind.CLASS && hasSerializableOrMetaAnnotation)
|| isSealedSerializableInterface || isSealedSerializableInterface
|| isSerializableInterfaceWithCustom
context(FirSession) context(FirSession)
internal val FirClassSymbol<*>.companionNeedsSerializerFactory: Boolean internal val FirClassSymbol<*>.companionNeedsSerializerFactory: Boolean
@@ -155,6 +169,7 @@ internal val FirClassSymbol<*>.companionNeedsSerializerFactory: Boolean
if (isSerializableEnum) return true if (isSerializableEnum) return true
if (isAbstractOrSealedSerializableClass) return true if (isAbstractOrSealedSerializableClass) return true
if (isSealedSerializableInterface) return true if (isSealedSerializableInterface) return true
if (isSerializableInterfaceWithCustom) return true
if (typeParameterSymbols.isEmpty()) return false if (typeParameterSymbols.isEmpty()) return false
return true 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"); 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 @Test
@TestMetadata("serializerFactory.kt") @TestMetadata("serializerFactory.kt")
public void testSerializerFactory() throws Exception { public void testSerializerFactory() throws Exception {
@@ -141,6 +141,12 @@ public class SerializationFirLightTreeBlackBoxTestGenerated extends AbstractSeri
runTest("plugins/kotlinx-serialization/testData/boxIr/inlineClasses.kt"); 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 @Test
@TestMetadata("intrinsicAnnotations.kt") @TestMetadata("intrinsicAnnotations.kt")
public void testIntrinsicAnnotations() throws Exception { public void testIntrinsicAnnotations() throws Exception {
@@ -139,6 +139,12 @@ public class SerializationIrBoxTestGenerated extends AbstractSerializationIrBoxT
runTest("plugins/kotlinx-serialization/testData/boxIr/inlineClasses.kt"); 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 @Test
@TestMetadata("intrinsicAnnotations.kt") @TestMetadata("intrinsicAnnotations.kt")
public void testIntrinsicAnnotations() throws Exception { public void testIntrinsicAnnotations() throws Exception {
@@ -49,6 +49,12 @@ public class SerializationIrJsBoxTestGenerated extends AbstractSerializationIrJs
runTest("plugins/kotlinx-serialization/testData/boxIr/excludedFromFileExport.kt"); 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 @Test
@TestMetadata("serializerFactory.kt") @TestMetadata("serializerFactory.kt")
public void testSerializerFactory() throws Exception { public void testSerializerFactory() throws Exception {