[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
|
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
|
||||||
}
|
}
|
||||||
|
|||||||
+18
-1
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
+15
@@ -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()
|
||||||
|
}
|
||||||
+6
@@ -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 {
|
||||||
|
|||||||
+6
@@ -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 {
|
||||||
|
|||||||
+6
@@ -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 {
|
||||||
|
|||||||
+6
@@ -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 {
|
||||||
|
|||||||
Reference in New Issue
Block a user