kotlinx.serialization: Support InheritableSerialInfo

This commit is contained in:
Leonid Startsev
2021-07-21 10:06:12 +00:00
committed by Space
parent 8eea749231
commit 4bc521249b
12 changed files with 131 additions and 11 deletions

View File

@@ -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.

View File

@@ -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>,

View File

@@ -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)
)

View File

@@ -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() {
{

View File

@@ -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) {

View File

@@ -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
)
}
}

View File

@@ -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 }

View File

@@ -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

View File

@@ -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>) {

View File

@@ -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

View File

@@ -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");

View File

@@ -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()