mirror of
https://github.com/jlengrand/kotlin.git
synced 2026-03-10 08:31:29 +00:00
kotlinx.serialization: Support InheritableSerialInfo
This commit is contained in:
@@ -12,9 +12,11 @@ import org.jetbrains.kotlin.name.FqName
|
||||
|
||||
internal fun IrClass.isNonGeneratedAnnotation(): Boolean =
|
||||
this.kind == ClassKind.ANNOTATION_CLASS &&
|
||||
!this.annotations.hasAnnotation(serialInfoAnnotationFqName)
|
||||
!this.annotations.hasAnnotation(serialInfoAnnotationFqName) &&
|
||||
!this.annotations.hasAnnotation(inheritableSerialInfoFqName)
|
||||
|
||||
private val serialInfoAnnotationFqName = FqName("kotlinx.serialization.SerialInfo")
|
||||
private val inheritableSerialInfoFqName = FqName("kotlinx.serialization.InheritableSerialInfo")
|
||||
|
||||
/**
|
||||
* We don't need to generate RTTI in some cases, e.g. Objective-C external classes.
|
||||
|
||||
@@ -18,6 +18,7 @@ import org.jetbrains.kotlin.ir.descriptors.IrBuiltInsOverDescriptors
|
||||
import org.jetbrains.kotlin.ir.descriptors.IrPropertyDelegateDescriptorImpl
|
||||
import org.jetbrains.kotlin.ir.expressions.*
|
||||
import org.jetbrains.kotlin.ir.expressions.impl.*
|
||||
import org.jetbrains.kotlin.ir.interpreter.toIrConst
|
||||
import org.jetbrains.kotlin.ir.symbols.*
|
||||
import org.jetbrains.kotlin.ir.symbols.impl.*
|
||||
import org.jetbrains.kotlin.ir.types.*
|
||||
@@ -27,6 +28,7 @@ import org.jetbrains.kotlin.ir.util.*
|
||||
import org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid
|
||||
import org.jetbrains.kotlin.js.resolve.diagnostics.findPsi
|
||||
import org.jetbrains.kotlin.name.ClassId
|
||||
import org.jetbrains.kotlin.name.FqName
|
||||
import org.jetbrains.kotlin.name.Name
|
||||
import org.jetbrains.kotlin.platform.jvm.isJvm
|
||||
import org.jetbrains.kotlin.resolve.BindingContext
|
||||
@@ -1096,7 +1098,7 @@ interface IrBuilderExtension {
|
||||
assert(ctor.isBound)
|
||||
val ctorDecl = ctor.owner
|
||||
if (needToCopyAnnotations) {
|
||||
val classAnnotations = copyAnnotationsFrom(thisIrType.getClass()?.annotations.orEmpty())
|
||||
val classAnnotations = copyAnnotationsFrom(thisIrType.getClass()?.let { collectSerialInfoAnnotations(it) }.orEmpty())
|
||||
args = args + createArrayOfExpression(compilerContext.irBuiltIns.annotationType, classAnnotations)
|
||||
}
|
||||
|
||||
@@ -1105,6 +1107,26 @@ interface IrBuilderExtension {
|
||||
return irInvoke(null, ctor, typeArguments = typeArgs, valueArguments = args, returnTypeHint = substitutedReturnType)
|
||||
}
|
||||
|
||||
fun collectSerialInfoAnnotations(irClass: IrClass): List<IrConstructorCall> {
|
||||
if (!(irClass.isInterface || irClass.descriptor.hasSerializableAnnotation)) return emptyList()
|
||||
val annotationByFq: MutableMap<FqName, IrConstructorCall> = irClass.annotations.associateBy { it.symbol.owner.parentAsClass.descriptor.fqNameSafe }.toMutableMap()
|
||||
for (clazz in irClass.getAllSuperclasses()) {
|
||||
val annotations = clazz.annotations
|
||||
.mapNotNull {
|
||||
val descriptor = it.symbol.owner.parentAsClass.descriptor
|
||||
if (descriptor.isInheritableSerialInfoAnnotation) descriptor.fqNameSafe to it else null
|
||||
}
|
||||
annotations.forEach { (fqname, call) ->
|
||||
if (fqname !in annotationByFq) {
|
||||
annotationByFq[fqname] = call
|
||||
} else {
|
||||
// SerializationPluginDeclarationChecker already reported inconsistency
|
||||
}
|
||||
}
|
||||
}
|
||||
return annotationByFq.values.toList()
|
||||
}
|
||||
|
||||
fun IrBuilderWithScope.callSerializerFromCompanion(
|
||||
thisIrType: IrType,
|
||||
typeArgs: List<IrType>,
|
||||
|
||||
@@ -21,7 +21,6 @@ import org.jetbrains.kotlin.ir.symbols.IrFunctionSymbol
|
||||
import org.jetbrains.kotlin.ir.symbols.impl.IrAnonymousInitializerSymbolImpl
|
||||
import org.jetbrains.kotlin.ir.types.*
|
||||
import org.jetbrains.kotlin.ir.util.*
|
||||
import org.jetbrains.kotlin.platform.jvm.isJvm
|
||||
import org.jetbrains.kotlin.resolve.BindingContext
|
||||
import org.jetbrains.kotlin.resolve.DescriptorUtils
|
||||
import org.jetbrains.kotlin.resolve.calls.components.hasDefaultValue
|
||||
@@ -96,7 +95,7 @@ open class SerializerIrGenerator(
|
||||
addElementsContentToDescriptor(serialDescImplClass, localDesc, addFuncS)
|
||||
// add class annotations
|
||||
copySerialInfoAnnotationsToDescriptor(
|
||||
serializableIrClass.annotations,
|
||||
collectSerialInfoAnnotations(serializableIrClass),
|
||||
localDesc,
|
||||
serialDescImplClass.referenceFunctionSymbol(CallingConventions.addClassAnnotation)
|
||||
)
|
||||
|
||||
@@ -40,6 +40,8 @@ public interface SerializationErrors {
|
||||
DiagnosticFactory3<KtAnnotationEntry, String, String, String> REQUIRED_KOTLIN_TOO_HIGH = DiagnosticFactory3.create(ERROR);
|
||||
DiagnosticFactory3<KtAnnotationEntry, String, String, String> PROVIDED_RUNTIME_TOO_LOW = DiagnosticFactory3.create(ERROR);
|
||||
|
||||
DiagnosticFactory2<PsiElement, KotlinType, KotlinType> INCONSISTENT_INHERITABLE_SERIALINFO = DiagnosticFactory2.create(ERROR);
|
||||
|
||||
@SuppressWarnings("UnusedDeclaration")
|
||||
Object _initializer = new Object() {
|
||||
{
|
||||
|
||||
@@ -10,8 +10,11 @@ import org.jetbrains.kotlin.builtins.KotlinBuiltIns
|
||||
import org.jetbrains.kotlin.config.KotlinCompilerVersion
|
||||
import org.jetbrains.kotlin.descriptors.*
|
||||
import org.jetbrains.kotlin.descriptors.annotations.Annotated
|
||||
import org.jetbrains.kotlin.descriptors.annotations.AnnotationDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.annotations.Annotations
|
||||
import org.jetbrains.kotlin.diagnostics.DiagnosticFactory0
|
||||
import org.jetbrains.kotlin.js.resolve.diagnostics.findPsi
|
||||
import org.jetbrains.kotlin.name.FqName
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
import org.jetbrains.kotlin.resolve.*
|
||||
import org.jetbrains.kotlin.resolve.checkers.DeclarationChecker
|
||||
@@ -51,6 +54,33 @@ open class SerializationPluginDeclarationChecker : DeclarationChecker {
|
||||
checkCorrectTransientAnnotationIsUsed(descriptor, props.serializableProperties, context.trace)
|
||||
checkTransients(declaration, context.trace)
|
||||
analyzePropertiesSerializers(context.trace, descriptor, props.serializableProperties)
|
||||
checkInheritedAnnotations(descriptor, declaration, context.trace)
|
||||
}
|
||||
|
||||
private fun checkInheritedAnnotations(descriptor: ClassDescriptor, declaration: KtDeclaration, trace: BindingTrace) {
|
||||
val annotationsFilter: (Annotations) -> List<Pair<FqName, AnnotationDescriptor>> = { an ->
|
||||
an.map { it.annotationClass!!.fqNameSafe to it }
|
||||
.filter { it.second.annotationClass?.isInheritableSerialInfoAnnotation == true }
|
||||
}
|
||||
val annotationByFq: MutableMap<FqName, AnnotationDescriptor> = mutableMapOf()
|
||||
val reported: MutableSet<FqName> = mutableSetOf()
|
||||
// my annotations
|
||||
annotationByFq.putAll(annotationsFilter(descriptor.annotations))
|
||||
// inherited
|
||||
for (clazz in descriptor.getAllSuperClassifiers()) {
|
||||
val annotations = annotationsFilter(clazz.annotations)
|
||||
annotations.forEach { (fqname, call) ->
|
||||
if (fqname in annotationByFq) {
|
||||
val existing = annotationByFq.getValue(fqname)
|
||||
if (existing.allValueArguments != call.allValueArguments) {
|
||||
if (reported.add(fqname)) {
|
||||
val entry = (existing as? LazyAnnotationDescriptor)?.annotationEntry ?: declaration
|
||||
trace.report(SerializationErrors.INCONSISTENT_INHERITABLE_SERIALINFO.on(entry, existing.type, clazz.defaultType))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun checkMinRuntime(versions: VersionReader.RuntimeVersions, descriptor: ClassDescriptor, trace: BindingTrace) {
|
||||
|
||||
@@ -116,5 +116,12 @@ object SerializationPluginErrorsRendering : DefaultErrorMessages.Extension {
|
||||
Renderers.STRING,
|
||||
Renderers.STRING
|
||||
)
|
||||
|
||||
MAP.put(
|
||||
SerializationErrors.INCONSISTENT_INHERITABLE_SERIALINFO,
|
||||
"Argument values for inheritable serial info annotation ''{0}'' must be the same as the values in parent type ''{1}''",
|
||||
Renderers.RENDER_TYPE,
|
||||
Renderers.RENDER_TYPE
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,12 +21,11 @@ import org.jetbrains.kotlin.resolve.lazy.declarations.ClassMemberDeclarationProv
|
||||
import org.jetbrains.kotlin.types.KotlinType
|
||||
import org.jetbrains.kotlinx.serialization.compiler.backend.common.SerializerCodegen
|
||||
import org.jetbrains.kotlinx.serialization.compiler.resolve.*
|
||||
import org.jetbrains.kotlinx.serialization.compiler.resolve.SerializationAnnotations.serialInfoFqName
|
||||
import java.util.*
|
||||
|
||||
open class SerializationResolveExtension @JvmOverloads constructor(val metadataPlugin: SerializationDescriptorSerializerPlugin? = null) : SyntheticResolveExtension {
|
||||
override fun getSyntheticNestedClassNames(thisDescriptor: ClassDescriptor): List<Name> = when {
|
||||
thisDescriptor.annotations.hasAnnotation(serialInfoFqName) && thisDescriptor.platform?.isJvm() == true -> listOf(SerialEntityNames.IMPL_NAME)
|
||||
thisDescriptor.isSerialInfoAnnotation && thisDescriptor.platform?.isJvm() == true -> listOf(SerialEntityNames.IMPL_NAME)
|
||||
(thisDescriptor.shouldHaveGeneratedSerializer) && !thisDescriptor.hasCompanionObjectAsSerializer ->
|
||||
listOf(SerialEntityNames.SERIALIZER_CLASS_NAME)
|
||||
else -> listOf()
|
||||
@@ -65,7 +64,7 @@ open class SerializationResolveExtension @JvmOverloads constructor(val metadataP
|
||||
declarationProvider: ClassMemberDeclarationProvider,
|
||||
result: MutableSet<ClassDescriptor>
|
||||
) {
|
||||
if (thisDescriptor.annotations.hasAnnotation(serialInfoFqName) && name == SerialEntityNames.IMPL_NAME)
|
||||
if (thisDescriptor.isSerialInfoAnnotation && name == SerialEntityNames.IMPL_NAME)
|
||||
result.add(KSerializerDescriptorResolver.addSerialInfoImplClass(thisDescriptor, declarationProvider, ctx))
|
||||
else if (thisDescriptor.shouldHaveGeneratedSerializer && name == SerialEntityNames.SERIALIZER_CLASS_NAME &&
|
||||
result.none { it.name == SerialEntityNames.SERIALIZER_CLASS_NAME }
|
||||
|
||||
@@ -22,6 +22,7 @@ import org.jetbrains.kotlin.resolve.scopes.getDescriptorsFiltered
|
||||
import org.jetbrains.kotlin.types.*
|
||||
import org.jetbrains.kotlin.types.typeUtil.isSubtypeOf
|
||||
import org.jetbrains.kotlin.types.typeUtil.representativeUpperBound
|
||||
import org.jetbrains.kotlinx.serialization.compiler.resolve.SerializationAnnotations.inheritableSerialInfoFqName
|
||||
import org.jetbrains.kotlinx.serialization.compiler.resolve.SerializationAnnotations.serialInfoFqName
|
||||
|
||||
internal fun isAllowedToHaveAutoGeneratedSerializerMethods(
|
||||
@@ -73,7 +74,10 @@ internal val DeclarationDescriptor.serializerForClass: KotlinType?
|
||||
get() = annotations.findAnnotationKotlinTypeValue(SerializationAnnotations.serializerAnnotationFqName, module, "forClass")
|
||||
|
||||
internal val ClassDescriptor.isSerialInfoAnnotation: Boolean
|
||||
get() = annotations.hasAnnotation(serialInfoFqName)
|
||||
get() = annotations.hasAnnotation(serialInfoFqName) || annotations.hasAnnotation(inheritableSerialInfoFqName)
|
||||
|
||||
internal val ClassDescriptor.isInheritableSerialInfoAnnotation: Boolean
|
||||
get() = annotations.hasAnnotation(inheritableSerialInfoFqName)
|
||||
|
||||
internal val Annotations.serialNameValue: String?
|
||||
get() = findAnnotationConstantValue(SerializationAnnotations.serialNameAnnotationFqName, "value")
|
||||
@@ -129,7 +133,7 @@ internal fun ClassDescriptor.enumEntries(): List<ClassDescriptor> {
|
||||
internal val Annotations.hasAnySerialAnnotation: Boolean
|
||||
get() = serialNameValue != null || any { it.annotationClass?.isSerialInfoAnnotation == true }
|
||||
|
||||
private val ClassDescriptor.hasSerializableAnnotation
|
||||
internal val ClassDescriptor.hasSerializableAnnotation
|
||||
get() = annotations.hasAnnotation(SerializationAnnotations.serializableAnnotationFqName)
|
||||
|
||||
internal val ClassDescriptor.hasSerializableAnnotationWithoutArgs: Boolean
|
||||
|
||||
@@ -33,7 +33,6 @@ import org.jetbrains.kotlinx.serialization.compiler.extensions.SerializationDesc
|
||||
import org.jetbrains.kotlinx.serialization.compiler.resolve.SerialEntityNames.IMPL_NAME
|
||||
import org.jetbrains.kotlinx.serialization.compiler.resolve.SerialEntityNames.SERIALIZER_CLASS_NAME
|
||||
import org.jetbrains.kotlinx.serialization.compiler.resolve.SerialEntityNames.typeArgPrefix
|
||||
import org.jetbrains.kotlinx.serialization.compiler.resolve.SerializationAnnotations.serialInfoFqName
|
||||
import java.util.*
|
||||
|
||||
object KSerializerDescriptorResolver {
|
||||
@@ -48,7 +47,7 @@ object KSerializerDescriptorResolver {
|
||||
fun isSerialInfoImpl(thisDescriptor: ClassDescriptor): Boolean {
|
||||
return thisDescriptor.name == IMPL_NAME
|
||||
&& thisDescriptor.containingDeclaration is LazyClassDescriptor
|
||||
&& thisDescriptor.containingDeclaration.annotations.hasAnnotation(serialInfoFqName)
|
||||
&& (thisDescriptor.containingDeclaration as ClassDescriptor).isSerialInfoAnnotation
|
||||
}
|
||||
|
||||
fun addSerialInfoSuperType(thisDescriptor: ClassDescriptor, supertypes: MutableList<KotlinType>) {
|
||||
|
||||
@@ -28,7 +28,9 @@ object SerializationAnnotations {
|
||||
internal val serialNameAnnotationFqName = FqName("kotlinx.serialization.SerialName")
|
||||
internal val requiredAnnotationFqName = FqName("kotlinx.serialization.Required")
|
||||
val serialTransientFqName = FqName("kotlinx.serialization.Transient")
|
||||
// Also implicitly used in kotlin-native.compiler.backend.native/CodeGenerationInfo.kt
|
||||
internal val serialInfoFqName = FqName("kotlinx.serialization.SerialInfo")
|
||||
internal val inheritableSerialInfoFqName = FqName("kotlinx.serialization.InheritableSerialInfo")
|
||||
internal val encodeDefaultFqName = FqName("kotlinx.serialization.EncodeDefault")
|
||||
|
||||
internal val contextualFqName = FqName("kotlinx.serialization.ContextualSerialization") // this one is deprecated
|
||||
|
||||
@@ -44,6 +44,11 @@ public class SerializationPluginDiagnosticTestGenerated extends AbstractSerializ
|
||||
runTest("plugins/kotlin-serialization/kotlin-serialization-compiler/testData/diagnostics/IncorrectTransient2.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("InheritableInfo.kt")
|
||||
public void testInheritableInfo() throws Exception {
|
||||
runTest("plugins/kotlin-serialization/kotlin-serialization-compiler/testData/diagnostics/InheritableInfo.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("LazyRecursionBug.kt")
|
||||
public void testLazyRecursionBug() throws Exception {
|
||||
runTest("plugins/kotlin-serialization/kotlin-serialization-compiler/testData/diagnostics/LazyRecursionBug.kt");
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
// !DIAGNOSTICS: -UNUSED_PARAMETER,-UNUSED_VARIABLE
|
||||
// WITH_RUNTIME
|
||||
// SKIP_TXT
|
||||
// !USE_EXPERIMENTAL: kotlinx.serialization.ExperimentalSerializationApi
|
||||
// FILE: test.kt
|
||||
|
||||
import kotlinx.serialization.*
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
// TODO: for this test to work, runtime dependency should be updated to (yet unreleased) serialization 1.3.0
|
||||
//@InheritableSerialInfo
|
||||
annotation class I(val value: String)
|
||||
|
||||
enum class E { A, B }
|
||||
|
||||
//@InheritableSerialInfo
|
||||
annotation class I2(val e: E, val k: KClass<*>)
|
||||
|
||||
@Serializable
|
||||
@I("a")
|
||||
sealed class Result {
|
||||
// @I("b")
|
||||
@Serializable class OK(val s: String): Result()
|
||||
}
|
||||
|
||||
@Serializable
|
||||
@I("a")
|
||||
@I2(E.A, E::class)
|
||||
open class A
|
||||
|
||||
@Serializable
|
||||
@I("a")
|
||||
@I2(E.A, E::class)
|
||||
open class Correct: A()
|
||||
|
||||
@Serializable
|
||||
@I("a")
|
||||
//@I2(E.B, E::class)
|
||||
open class B: A()
|
||||
|
||||
@Serializable
|
||||
@I("a")
|
||||
//@I2(E.A, I::class)
|
||||
open class B2: A()
|
||||
|
||||
@Serializable
|
||||
//@I("b")
|
||||
//@I2(E.A, E::class)
|
||||
open class C: B()
|
||||
Reference in New Issue
Block a user