[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:
committed by
Space Team
parent
38b55c659b
commit
a45fe2d8a8
+10
-1
@@ -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
|
||||
}
|
||||
|
||||
+18
-1
@@ -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
|
||||
}
|
||||
|
||||
+15
@@ -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()
|
||||
}
|
||||
+6
@@ -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 {
|
||||
|
||||
+6
@@ -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 {
|
||||
|
||||
+6
@@ -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 {
|
||||
|
||||
+6
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user