kotlinx.serialization: Support @MetaSerializable (#4583)

This commit is contained in:
Rustam
2022-03-25 17:07:42 +01:00
committed by GitHub
parent 25c5d99a0c
commit e09f30f442
15 changed files with 404 additions and 76 deletions
@@ -14,6 +14,7 @@ dependencies {
compileOnly(project(":compiler:frontend"))
compileOnly(project(":compiler:backend"))
compileOnly(project(":compiler:ir.backend.common"))
compileOnly(project(":compiler:ir.tree.impl"))
compileOnly(project(":js:js.frontend"))
compileOnly(project(":js:js.translator"))
compileOnly(project(":kotlin-util-klib-metadata"))
@@ -44,7 +44,7 @@ fun AbstractSerialGenerator.findAddOnSerializer(propertyType: KotlinType, module
}
fun KotlinType.isGeneratedSerializableObject() =
toClassDescriptor?.run { kind == ClassKind.OBJECT && hasSerializableAnnotationWithoutArgs } == true
toClassDescriptor?.run { kind == ClassKind.OBJECT && hasSerializableOrMetaAnnotationWithoutArgs } == true
@Suppress("FunctionName", "LocalVariableName")
fun AbstractSerialGenerator.getSerialTypeInfo(property: SerializableProperty): SerialTypeInfo {
@@ -497,7 +497,7 @@ interface IrBuilderExtension {
propertyParent: IrClass,
fieldName: Name = propertyDescriptor.name,
): IrProperty {
val irProperty = propertyParent.searchForDeclaration<IrProperty>(propertyDescriptor) ?: run {
val irProperty = propertyParent.searchForDeclaration(propertyDescriptor) ?: run {
with(propertyDescriptor) {
propertyParent.factory.createProperty(
propertyParent.startOffset, propertyParent.endOffset, SERIALIZABLE_PLUGIN_ORIGIN, IrPropertySymbolImpl(propertyDescriptor),
@@ -520,6 +520,10 @@ interface IrBuilderExtension {
return irProperty
}
fun IrType.kClassToJClassIfNeeded(): IrType = this
fun kClassExprToJClassIfNeeded(startOffset: Int, endOffset: Int, irExpression: IrExpression): IrExpression = irExpression
private fun IrClass.generatePropertyBackingFieldIfNeeded(
propertyDescriptor: PropertyDescriptor,
originProperty: IrProperty,
@@ -568,7 +572,10 @@ interface IrBuilderExtension {
}
irAccessor.body = when (isGetter) {
true -> generateDefaultGetterBody(descriptor as PropertyGetterDescriptor, irAccessor)
true -> {
irAccessor.returnType = irAccessor.returnType.kClassToJClassIfNeeded()
generateDefaultGetterBody(descriptor as PropertyGetterDescriptor, irAccessor)
}
false -> generateDefaultSetterBody(descriptor as PropertySetterDescriptor, irAccessor)
}
@@ -588,6 +595,7 @@ interface IrBuilderExtension {
val receiver = generateReceiverExpressionForFieldAccess(irAccessor.dispatchReceiverParameter!!.symbol, property)
val propertyIrType = property.type.toIrType()
irBody.statements.add(
IrReturnImpl(
startOffset, endOffset, compilerContext.irBuiltIns.nothingType,
@@ -595,9 +603,11 @@ interface IrBuilderExtension {
IrGetFieldImpl(
startOffset, endOffset,
irProperty.backingField?.symbol ?: error("Property expected to have backing field"),
property.type.toIrType(),
propertyIrType,
receiver
)
).let {
if (propertyIrType.isKClass()) kClassExprToJClassIfNeeded(startOffset, endOffset, it) else it
}
)
)
return irBody
@@ -1104,7 +1114,7 @@ interface IrBuilderExtension {
}
fun collectSerialInfoAnnotations(irClass: IrClass): List<IrConstructorCall> {
if (!(irClass.isInterface || irClass.descriptor.hasSerializableAnnotation)) return emptyList()
if (!(irClass.isInterface || irClass.descriptor.hasSerializableOrMetaAnnotation)) 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
@@ -5,17 +5,29 @@
package org.jetbrains.kotlinx.serialization.compiler.backend.ir
import org.jetbrains.kotlin.backend.common.ir.createImplicitParameterDeclarationWithWrappedDescriptor
import org.jetbrains.kotlin.descriptors.ClassKind
import org.jetbrains.kotlin.descriptors.DescriptorVisibilities
import org.jetbrains.kotlin.descriptors.Modality
import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET
import org.jetbrains.kotlin.ir.builders.declarations.addConstructor
import org.jetbrains.kotlin.ir.builders.declarations.addValueParameter
import org.jetbrains.kotlin.ir.builders.declarations.*
import org.jetbrains.kotlin.ir.declarations.IrClass
import org.jetbrains.kotlin.ir.declarations.IrModuleFragment
import org.jetbrains.kotlin.ir.declarations.IrPackageFragment
import org.jetbrains.kotlin.ir.declarations.IrProperty
import org.jetbrains.kotlin.ir.declarations.impl.IrExternalPackageFragmentImpl
import org.jetbrains.kotlin.ir.declarations.impl.IrFactoryImpl
import org.jetbrains.kotlin.ir.expressions.IrConstructorCall
import org.jetbrains.kotlin.ir.expressions.IrExpression
import org.jetbrains.kotlin.ir.expressions.IrStatementOrigin
import org.jetbrains.kotlin.ir.expressions.impl.*
import org.jetbrains.kotlin.ir.symbols.IrClassSymbol
import org.jetbrains.kotlin.ir.symbols.IrPropertySymbol
import org.jetbrains.kotlin.ir.types.*
import org.jetbrains.kotlin.ir.util.constructors
import org.jetbrains.kotlin.ir.util.defaultType
import org.jetbrains.kotlin.ir.util.kotlinFqName
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.resolve.DescriptorUtils
import org.jetbrains.kotlinx.serialization.compiler.extensions.SerializationPluginContext
@@ -24,15 +36,18 @@ import org.jetbrains.kotlinx.serialization.compiler.resolve.SerialEntityNames
// This doesn't support annotation arguments of type KClass and Array<KClass> because the codegen doesn't compute JVM signatures for
// such cases correctly (because inheriting from annotation classes is prohibited in Kotlin).
// Currently it results in an "accidental override" error where a method with return type KClass conflicts with the one with Class.
// TODO: support annotation properties of types KClass<...> and Array<KClass<...>>.
class SerialInfoImplJvmIrGenerator(
private val context: SerializationPluginContext,
private val moduleFragment: IrModuleFragment,
) : IrBuilderExtension {
override val compilerContext: SerializationPluginContext
get() = context
private val jvmNameClass get() = context.referenceClass(DescriptorUtils.JVM_NAME)!!.owner
private val javaLangClass = createClass(createPackage("java.lang"), "Class", ClassKind.CLASS)
private val javaLangType = javaLangClass.starProjectedType
private val implGenerated = mutableSetOf<IrClass>()
private val annotationToImpl = mutableMapOf<IrClass, IrClass>()
@@ -78,7 +93,7 @@ class SerialInfoImplJvmIrGenerator(
field.visibility = DescriptorVisibilities.PRIVATE
field.origin = SERIALIZABLE_PLUGIN_ORIGIN
val parameter = ctor.addValueParameter(property.name.asString(), getter.returnType)
val parameter = ctor.addValueParameter(property.name.asString(), field.type)
ctorBody.statements += IrSetFieldImpl(
startOffset, endOffset, field.symbol,
IrGetValueImpl(startOffset, endOffset, irClass.thisReceiver!!.symbol),
@@ -95,4 +110,70 @@ class SerialInfoImplJvmIrGenerator(
).apply {
putValueArgument(0, IrConstImpl.string(UNDEFINED_OFFSET, UNDEFINED_OFFSET, context.irBuiltIns.stringType, name))
}
override fun IrType.kClassToJClassIfNeeded(): IrType = when {
this.isKClass() -> javaLangType
this.isKClassArray() -> compilerContext.irBuiltIns.arrayClass.typeWith(javaLangType)
else -> this
}
override fun kClassExprToJClassIfNeeded(startOffset: Int, endOffset: Int, irExpression: IrExpression): IrExpression {
val getterSymbol = kClassJava.owner.getter!!.symbol
return IrCallImpl(
startOffset, endOffset,
javaLangClass.starProjectedType,
getterSymbol,
typeArgumentsCount = getterSymbol.owner.typeParameters.size,
valueArgumentsCount = 0,
origin = IrStatementOrigin.GET_PROPERTY
).apply {
this.extensionReceiver = irExpression
}
}
private val jvmName: IrClassSymbol = createClass(createPackage("kotlin.jvm"), "JvmName", ClassKind.ANNOTATION_CLASS) { klass ->
klass.addConstructor().apply {
addValueParameter("name", context.irBuiltIns.stringType)
}
}
private val kClassJava: IrPropertySymbol =
IrFactoryImpl.buildProperty {
name = Name.identifier("java")
}.apply {
parent = createClass(createPackage("kotlin.jvm"), "JvmClassMappingKt", ClassKind.CLASS).owner
addGetter().apply {
annotations = listOf(
IrConstructorCallImpl.fromSymbolOwner(jvmName.typeWith(), jvmName.constructors.single()).apply {
putValueArgument(0, IrConstImpl.string(UNDEFINED_OFFSET, UNDEFINED_OFFSET, context.irBuiltIns.stringType, "getJavaClass"))
}
)
addExtensionReceiver(context.irBuiltIns.kClassClass.starProjectedType)
returnType = javaLangClass.starProjectedType
}
}.symbol
private fun IrType.isKClassArray() =
this is IrSimpleType && isArray() && arguments.single().typeOrNull?.isKClass() == true
private fun createPackage(packageName: String): IrPackageFragment =
IrExternalPackageFragmentImpl.createEmptyExternalPackageFragment(
moduleFragment.descriptor,
FqName(packageName)
)
private fun createClass(
irPackage: IrPackageFragment,
shortName: String,
classKind: ClassKind,
block: (IrClass) -> Unit = {}
): IrClassSymbol = IrFactoryImpl.buildClass {
name = Name.identifier(shortName)
kind = classKind
modality = Modality.FINAL
}.apply {
parent = irPackage
createImplicitParameterDeclarationWithWrappedDescriptor()
block(this)
}.symbol
}
@@ -52,17 +52,21 @@ class SerializableIrGenerator(
private val addElementFun = serialDescImplClass.referenceFunctionSymbol(CallingConventions.addElement)
private fun IrClass.hasSerializableAnnotationWithoutArgs(): Boolean {
val annot = getAnnotation(SerializationAnnotations.serializableAnnotationFqName) ?: return false
for (i in 0 until annot.valueArgumentsCount) {
if (annot.getValueArgument(i) != null) return false
private fun IrClass.hasSerializableOrMetaAnnotationWithoutArgs(): Boolean {
val annot = getAnnotation(SerializationAnnotations.serializableAnnotationFqName)
if (annot != null) {
for (i in 0 until annot.valueArgumentsCount) {
if (annot.getValueArgument(i) != null) return false
}
return true
}
return true
val metaAnnotation = annotations
.flatMap { it.symbol.owner.constructedClass.annotations }
.find { it.isAnnotation(SerializationAnnotations.metaSerializableAnnotationFqName) }
return metaAnnotation != null
}
private val IrClass.isInternalSerializable: Boolean get() = kind == ClassKind.CLASS && hasSerializableAnnotationWithoutArgs()
private val IrClass.isInternalSerializable: Boolean get() = kind == ClassKind.CLASS && hasSerializableOrMetaAnnotationWithoutArgs()
override fun generateInternalConstructor(constructorDescriptor: ClassConstructorDescriptor) =
irClass.contributeConstructor(constructorDescriptor) { ctor ->
@@ -26,7 +26,6 @@ open class SerializerCodegenImpl(
metadataPlugin: SerializationDescriptorSerializerPlugin?
) : SerializerCodegen(codegen.descriptor, codegen.bindingContext, metadataPlugin) {
private val serialDescField = "\$\$serialDesc"
protected val serializerAsmType = codegen.typeMapper.mapClass(codegen.descriptor)
@@ -32,7 +32,6 @@ import org.jetbrains.kotlinx.serialization.compiler.backend.common.bodyPropertie
import org.jetbrains.kotlinx.serialization.compiler.backend.common.findTypeSerializerOrContextUnchecked
import org.jetbrains.kotlinx.serialization.compiler.backend.common.primaryConstructorPropertiesDescriptorsMap
import org.jetbrains.kotlinx.serialization.compiler.resolve.*
import org.jetbrains.kotlinx.serialization.compiler.resolve.SerializationAnnotations.serializableAnnotationFqName
internal val SERIALIZABLE_PROPERTIES: WritableSlice<ClassDescriptor, SerializableProperties> = Slices.createSimpleSlice()
@@ -75,7 +74,13 @@ open class SerializationPluginDeclarationChecker : DeclarationChecker {
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))
trace.report(
SerializationErrors.INCONSISTENT_INHERITABLE_SERIALINFO.on(
entry,
existing.type,
clazz.defaultType
)
)
}
}
}
@@ -89,7 +94,7 @@ open class SerializationPluginDeclarationChecker : DeclarationChecker {
// For non-JAR distributions (klib, js) this method is not invoked, since getVersionsForCurrentModule
// unable to read from them
if (!versions.implementationVersionMatchSupported()) {
descriptor.onSerializableAnnotation {
descriptor.onSerializableOrMetaAnnotation {
trace.report(
SerializationErrors.PROVIDED_RUNTIME_TOO_LOW.on(
it,
@@ -104,7 +109,7 @@ open class SerializationPluginDeclarationChecker : DeclarationChecker {
private fun checkMinKotlin(versions: VersionReader.RuntimeVersions, descriptor: ClassDescriptor, trace: BindingTrace) {
if (versions.currentCompilerMatchRequired()) return
descriptor.onSerializableAnnotation {
descriptor.onSerializableOrMetaAnnotation {
trace.report(
SerializationErrors.REQUIRED_KOTLIN_TOO_HIGH.on(
it,
@@ -141,26 +146,25 @@ open class SerializationPluginDeclarationChecker : DeclarationChecker {
return false
}
if (!descriptor.annotations.hasAnnotation(serializableAnnotationFqName)) return false
if (!descriptor.hasSerializableOrMetaAnnotation) return false
if (!serializationPluginEnabledOn(descriptor)) {
trace.reportOnSerializableAnnotation(descriptor, SerializationErrors.PLUGIN_IS_NOT_ENABLED)
trace.reportOnSerializableOrMetaAnnotation(descriptor, SerializationErrors.PLUGIN_IS_NOT_ENABLED)
return false
}
if (descriptor.isAnonymousObjectOrContained) {
trace.reportOnSerializableAnnotation(descriptor, SerializationErrors.ANONYMOUS_OBJECTS_NOT_SUPPORTED)
trace.reportOnSerializableOrMetaAnnotation(descriptor, SerializationErrors.ANONYMOUS_OBJECTS_NOT_SUPPORTED)
return false
}
if (descriptor.isInner) {
trace.reportOnSerializableAnnotation(descriptor, SerializationErrors.INNER_CLASSES_NOT_SUPPORTED)
trace.reportOnSerializableOrMetaAnnotation(descriptor, SerializationErrors.INNER_CLASSES_NOT_SUPPORTED)
return false
}
if (descriptor.isInlineClass() && !canSupportInlineClasses(descriptor.module, trace)) {
descriptor.onSerializableAnnotation {
descriptor.onSerializableOrMetaAnnotation {
trace.report(
SerializationErrors.INLINE_CLASSES_NOT_SUPPORTED.on(
it,
@@ -171,14 +175,14 @@ open class SerializationPluginDeclarationChecker : DeclarationChecker {
}
return false
}
if (!descriptor.hasSerializableAnnotationWithoutArgs) {
if (!descriptor.hasSerializableOrMetaAnnotationWithoutArgs) {
// defined custom serializer
checkClassWithCustomSerializer(descriptor, declaration, trace)
return false
}
if (descriptor.serializableAnnotationIsUseless) {
trace.reportOnSerializableAnnotation(descriptor, SerializationErrors.SERIALIZABLE_ANNOTATION_IGNORED)
trace.reportOnSerializableOrMetaAnnotation(descriptor, SerializationErrors.SERIALIZABLE_ANNOTATION_IGNORED)
return false
}
@@ -186,7 +190,7 @@ open class SerializationPluginDeclarationChecker : DeclarationChecker {
if (!descriptor.isSerializableEnum()) { // enums are inherited from java.lang.Enum and can't be inherited from other classes
val superClass = descriptor.getSuperClassOrAny()
if (!superClass.isInternalSerializable && superClass.constructors.singleOrNull { it.valueParameters.size == 0 } == null) {
trace.reportOnSerializableAnnotation(descriptor, SerializationErrors.NON_SERIALIZABLE_PARENT_MUST_HAVE_NOARG_CTOR)
trace.reportOnSerializableOrMetaAnnotation(descriptor, SerializationErrors.NON_SERIALIZABLE_PARENT_MUST_HAVE_NOARG_CTOR)
return false
}
}
@@ -194,7 +198,7 @@ open class SerializationPluginDeclarationChecker : DeclarationChecker {
}
private fun checkClassWithCustomSerializer(descriptor: ClassDescriptor, declaration: KtDeclaration, trace: BindingTrace) {
val annotationPsi = descriptor.findSerializableAnnotationDeclaration()
val annotationPsi = descriptor.findSerializableOrMetaAnnotationDeclaration()
checkCustomSerializerMatch(descriptor.module, descriptor.defaultType, descriptor, annotationPsi, trace, declaration)
checkCustomSerializerIsNotLocal(descriptor.module, descriptor, trace, declaration)
}
@@ -213,7 +217,7 @@ open class SerializationPluginDeclarationChecker : DeclarationChecker {
private fun ClassDescriptor.isSerializableEnumWithMissingSerializer(): Boolean {
if (kind != ClassKind.ENUM_CLASS) return false
if (annotations.hasAnnotation(serializableAnnotationFqName)) return false
if (hasSerializableOrMetaAnnotation) return false
if (annotations.hasAnySerialAnnotation) return true
return enumEntries().any { (it.annotations.hasAnySerialAnnotation) }
}
@@ -226,12 +230,12 @@ open class SerializationPluginDeclarationChecker : DeclarationChecker {
}
private fun buildSerializableProperties(descriptor: ClassDescriptor, trace: BindingTrace): SerializableProperties? {
if (!descriptor.annotations.hasAnnotation(serializableAnnotationFqName)) return null
if (!descriptor.hasSerializableOrMetaAnnotation) return null
if (!descriptor.isInternalSerializable) return null
if (descriptor.hasCompanionObjectAsSerializer) return null // customized by user
val props = SerializableProperties(descriptor, trace.bindingContext)
if (!props.isExternallySerializable) trace.reportOnSerializableAnnotation(
if (!props.isExternallySerializable) trace.reportOnSerializableOrMetaAnnotation(
descriptor,
SerializationErrors.PRIMARY_CONSTRUCTOR_PARAMETER_IS_NOT_A_PROPERTY
)
@@ -240,7 +244,7 @@ open class SerializationPluginDeclarationChecker : DeclarationChecker {
val namesSet = mutableSetOf<String>()
props.serializableProperties.forEach {
if (!namesSet.add(it.name)) {
descriptor.onSerializableAnnotation { a ->
descriptor.onSerializableOrMetaAnnotation { a ->
trace.report(SerializationErrors.DUPLICATE_SERIAL_NAME.on(a, it.name))
}
}
@@ -331,11 +335,13 @@ open class SerializationPluginDeclarationChecker : DeclarationChecker {
if (type.genericIndex != null) return // type arguments always have serializer stored in class' field
val element = ktType?.typeElement
if (type.isUnsupportedInlineType() && !canSupportInlineClasses(module, trace)) {
trace.report(SerializationErrors.INLINE_CLASSES_NOT_SUPPORTED.on(
element ?: fallbackElement,
VersionReader.minVersionForInlineClasses.toString(),
VersionReader.getVersionsForCurrentModuleFromTrace(module, trace)?.implementationVersion.toString()
))
trace.report(
SerializationErrors.INLINE_CLASSES_NOT_SUPPORTED.on(
element ?: fallbackElement,
VersionReader.minVersionForInlineClasses.toString(),
VersionReader.getVersionsForCurrentModuleFromTrace(module, trace)?.implementationVersion.toString()
)
)
}
val serializer = findTypeSerializerOrContextUnchecked(module, type)
if (serializer != null) {
@@ -380,7 +386,7 @@ open class SerializationPluginDeclarationChecker : DeclarationChecker {
val serializerDescriptor = serializerType.toClassDescriptor ?: return
if (DescriptorUtils.isLocal(serializerDescriptor)) {
val element = declaration.findSerializableAnnotationDeclaration() ?: declarationElement
val element = declaration.findSerializableOrMetaAnnotationDeclaration() ?: declarationElement
trace.report(
SerializationErrors.LOCAL_SERIALIZER_USAGE.on(
@@ -408,16 +414,19 @@ open class SerializationPluginDeclarationChecker : DeclarationChecker {
)
}
private inline fun ClassDescriptor.onSerializableAnnotation(report: (KtAnnotationEntry) -> Unit) {
findSerializableAnnotationDeclaration()?.let(report)
private inline fun ClassDescriptor.onSerializableOrMetaAnnotation(report: (KtAnnotationEntry) -> Unit) {
findSerializableOrMetaAnnotationDeclaration()?.let(report)
}
private fun BindingTrace.reportOnSerializableAnnotation(descriptor: ClassDescriptor, error: DiagnosticFactory0<in KtAnnotationEntry>) {
descriptor.onSerializableAnnotation { e ->
private fun BindingTrace.reportOnSerializableOrMetaAnnotation(
descriptor: ClassDescriptor,
error: DiagnosticFactory0<in KtAnnotationEntry>
) {
descriptor.onSerializableOrMetaAnnotation { e ->
report(error.on(e))
}
}
}
internal val ClassDescriptor.serializableAnnotationIsUseless: Boolean
get() = hasSerializableAnnotationWithoutArgs && !isInternalSerializable && !hasCompanionObjectAsSerializer && !isSerializableEnum() && !isSealedSerializableInterface
get() = hasSerializableOrMetaAnnotationWithoutArgs && !isInternalSerializable && !hasCompanionObjectAsSerializer && !isSerializableEnum() && !isSealedSerializableInterface
@@ -48,13 +48,11 @@ class SerializationComponentRegistrar : ComponentRegistrar {
StorageComponentContainerContributor.registerExtension(project, SerializationPluginComponentContainerContributor())
}
private fun registerProtoExtensions() {
SerializationPluginMetadataExtensions.registerAllExtensions(JvmProtoBufUtil.EXTENSION_REGISTRY)
SerializationPluginMetadataExtensions.registerAllExtensions(JsSerializerProtocol.extensionRegistry)
SerializationPluginMetadataExtensions.registerAllExtensions(KlibMetadataSerializerProtocol.extensionRegistry)
}
}
}
@@ -49,10 +49,11 @@ class SerializationPluginContext(baseContext: IrPluginContext, val metadataPlugi
private class SerializerClassLowering(
baseContext: IrPluginContext,
metadataPlugin: SerializationDescriptorSerializerPlugin?
metadataPlugin: SerializationDescriptorSerializerPlugin?,
moduleFragment: IrModuleFragment
) : IrElementTransformerVoid(), ClassLoweringPass {
val context: SerializationPluginContext = SerializationPluginContext(baseContext, metadataPlugin)
private val serialInfoJvmGenerator = SerialInfoImplJvmIrGenerator(context).also { context.serialInfoImplJvmIrGenerator = it }
private val serialInfoJvmGenerator = SerialInfoImplJvmIrGenerator(context, moduleFragment).also { context.serialInfoImplJvmIrGenerator = it }
override fun lower(irClass: IrClass) {
SerializableIrGenerator.generate(irClass, context, context.bindingContext)
@@ -72,7 +73,7 @@ open class SerializationLoweringExtension @JvmOverloads constructor(
moduleFragment: IrModuleFragment,
pluginContext: IrPluginContext
) {
val serializerClassLowering = SerializerClassLowering(pluginContext, metadataPlugin)
val serializerClassLowering = SerializerClassLowering(pluginContext, metadataPlugin, moduleFragment)
for (file in moduleFragment.files)
serializerClassLowering.runOnFileInOrder(file)
}
@@ -8,6 +8,7 @@ package org.jetbrains.kotlinx.serialization.compiler.resolve
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
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.name.ClassId
import org.jetbrains.kotlin.name.FqName
@@ -23,7 +24,9 @@ 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.metaSerializableAnnotationFqName
import org.jetbrains.kotlinx.serialization.compiler.resolve.SerializationAnnotations.serialInfoFqName
import org.jetbrains.kotlinx.serialization.compiler.resolve.SerializationAnnotations.serializableAnnotationFqName
internal fun isAllowedToHaveAutoGeneratedSerializerMethods(
classDescriptor: ClassDescriptor,
@@ -47,7 +50,6 @@ internal fun isGeneratedKSerializer(type: KotlinType?): Boolean =
internal fun ClassDescriptor.getGeneratedSerializerDescriptor(): ClassDescriptor =
module.getClassFromInternalSerializationPackage(SerialEntityNames.GENERATED_SERIALIZER_CLASS.identifier)
internal fun ClassDescriptor.createSerializerTypeFor(argument: SimpleType, baseSerializerInterface: FqName): SimpleType {
val projectionType = Variance.INVARIANT
val types = listOf(TypeProjectionImpl(projectionType, argument))
@@ -68,13 +70,15 @@ internal val DeclarationDescriptor.serializableWith: KotlinType?
get() = annotations.serializableWith(module)
internal fun Annotations.serializableWith(module: ModuleDescriptor): KotlinType? =
this.findAnnotationKotlinTypeValue(SerializationAnnotations.serializableAnnotationFqName, module,"with")
this.findAnnotationKotlinTypeValue(serializableAnnotationFqName, module, "with")
internal val DeclarationDescriptor.serializerForClass: KotlinType?
get() = annotations.findAnnotationKotlinTypeValue(SerializationAnnotations.serializerAnnotationFqName, module, "forClass")
internal val ClassDescriptor.isSerialInfoAnnotation: Boolean
get() = annotations.hasAnnotation(serialInfoFqName) || annotations.hasAnnotation(inheritableSerialInfoFqName)
get() = annotations.hasAnnotation(serialInfoFqName)
|| annotations.hasAnnotation(inheritableSerialInfoFqName)
|| annotations.hasAnnotation(metaSerializableAnnotationFqName)
internal val ClassDescriptor.isInheritableSerialInfoAnnotation: Boolean
get() = annotations.hasAnnotation(inheritableSerialInfoFqName)
@@ -93,7 +97,7 @@ internal val Annotations.serialTransient: Boolean
val KotlinType?.toClassDescriptor: ClassDescriptor?
@JvmName("toClassDescriptor")
get() = this?.constructor?.declarationDescriptor?.let { descriptor ->
when(descriptor) {
when (descriptor) {
is ClassDescriptor -> descriptor
is TypeParameterDescriptor -> descriptor.representativeUpperBound.toClassDescriptor
else -> null
@@ -101,26 +105,27 @@ val KotlinType?.toClassDescriptor: ClassDescriptor?
}
internal val ClassDescriptor.shouldHaveGeneratedMethodsInCompanion: Boolean
get() = this.isSerializableObject || this.isSerializableEnum() || this.kind == ClassKind.CLASS && hasSerializableAnnotation || this.isSealedSerializableInterface
get() = this.isSerializableObject || this.isSerializableEnum() || (this.kind == ClassKind.CLASS && hasSerializableOrMetaAnnotation) || this.isSealedSerializableInterface
internal val ClassDescriptor.isSerializableObject: Boolean
get() = kind == ClassKind.OBJECT && hasSerializableAnnotation
get() = kind == ClassKind.OBJECT && hasSerializableOrMetaAnnotation
internal val ClassDescriptor.isInternallySerializableObject: Boolean
get() = kind == ClassKind.OBJECT && hasSerializableAnnotationWithoutArgs
get() = kind == ClassKind.OBJECT && hasSerializableOrMetaAnnotationWithoutArgs
internal val ClassDescriptor.isSealedSerializableInterface: Boolean
get() = kind == ClassKind.INTERFACE && modality == Modality.SEALED && hasSerializableAnnotation
get() = kind == ClassKind.INTERFACE && modality == Modality.SEALED && hasSerializableOrMetaAnnotation
internal val ClassDescriptor.isInternalSerializable: Boolean //todo normal checking
get() {
if (kind != ClassKind.CLASS) return false
return hasSerializableAnnotationWithoutArgs
return hasSerializableOrMetaAnnotationWithoutArgs
}
internal fun ClassDescriptor.isSerializableEnum(): Boolean = kind == ClassKind.ENUM_CLASS && hasSerializableAnnotation
internal fun ClassDescriptor.isSerializableEnum(): Boolean = kind == ClassKind.ENUM_CLASS && hasSerializableOrMetaAnnotation
internal fun ClassDescriptor.isInternallySerializableEnum(): Boolean = kind == ClassKind.ENUM_CLASS && hasSerializableAnnotationWithoutArgs
internal fun ClassDescriptor.isInternallySerializableEnum(): Boolean =
kind == ClassKind.ENUM_CLASS && hasSerializableOrMetaAnnotationWithoutArgs
internal val ClassDescriptor.shouldHaveGeneratedSerializer: Boolean
get() = (isInternalSerializable && (modality == Modality.FINAL || modality == Modality.OPEN)) || isInternallySerializableEnum()
@@ -136,25 +141,47 @@ internal fun ClassDescriptor.enumEntries(): List<ClassDescriptor> {
internal val Annotations.hasAnySerialAnnotation: Boolean
get() = serialNameValue != null || any { it.annotationClass?.isSerialInfoAnnotation == true }
internal val ClassDescriptor.hasSerializableAnnotation
get() = annotations.hasAnnotation(SerializationAnnotations.serializableAnnotationFqName)
internal val ClassDescriptor.hasSerializableOrMetaAnnotation
get() = hasSerializableAnnotation || hasMetaSerializableAnnotation
internal val ClassDescriptor.hasSerializableAnnotationWithoutArgs: Boolean
private val ClassDescriptor.hasSerializableAnnotation
get() = annotations.hasSerializableAnnotation
private val Annotations.hasSerializableAnnotation
get() = hasAnnotation(serializableAnnotationFqName)
internal val ClassDescriptor.hasMetaSerializableAnnotation: Boolean
get() = annotations.any { it.isMetaSerializableAnnotation }
internal val AnnotationDescriptor.isMetaSerializableAnnotation: Boolean
get() = annotationClass?.annotations?.hasAnnotation(metaSerializableAnnotationFqName) ?: false
internal val ClassDescriptor.hasSerializableOrMetaAnnotationWithoutArgs: Boolean
get() = hasSerializableAnnotationWithoutArgs
|| (!annotations.hasSerializableAnnotation && hasMetaSerializableAnnotation)
private val ClassDescriptor.hasSerializableAnnotationWithoutArgs: 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.
// 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.isEmpty()
}
internal fun Annotated.findSerializableAnnotationDeclaration(): KtAnnotationEntry? {
val lazyDesc = annotations.findAnnotation(SerializationAnnotations.serializableAnnotationFqName) as? LazyAnnotationDescriptor
private fun Annotated.findSerializableAnnotationDeclaration(): KtAnnotationEntry? {
val lazyDesc = annotations.findAnnotation(serializableAnnotationFqName) as? LazyAnnotationDescriptor
return lazyDesc?.annotationEntry
}
internal fun Annotated.findSerializableOrMetaAnnotationDeclaration(): KtAnnotationEntry? {
val lazyDesc = (annotations.findAnnotation(serializableAnnotationFqName)
?: annotations.firstOrNull { it.isMetaSerializableAnnotation }) as? LazyAnnotationDescriptor
return lazyDesc?.annotationEntry
}
// For abstract classes marked with @Serializable,
// methods are generated anyway although they shouldn't have
// methods are generated anyway, although they shouldn't have
// generated $serializer and use Polymorphic one.
internal fun ClassDescriptor.isAbstractOrSealedSerializableClass(): Boolean =
isInternalSerializable && (modality == Modality.ABSTRACT || modality == Modality.SEALED)
@@ -235,18 +262,18 @@ internal fun getSerializableClassDescriptorBySerializer(serializerDescriptor: Cl
}
internal fun ClassDescriptor.checkSerializableClassPropertyResult(prop: PropertyDescriptor): Boolean =
prop.returnType!!.isSubtypeOf(getClassFromSerializationPackage(SerialEntityNames.SERIAL_DESCRIPTOR_CLASS).toSimpleType(false)) // todo: cache lookup
prop.returnType!!.isSubtypeOf(getClassFromSerializationPackage(SerialEntityNames.SERIAL_DESCRIPTOR_CLASS).toSimpleType(false)) // todo: cache lookup
// todo: serialization: do an actual check better that just number of parameters
internal fun ClassDescriptor.checkSaveMethodParameters(parameters: List<ValueParameterDescriptor>): Boolean =
parameters.size == 2
parameters.size == 2
internal fun ClassDescriptor.checkSaveMethodResult(type: KotlinType): Boolean =
KotlinBuiltIns.isUnit(type)
KotlinBuiltIns.isUnit(type)
// todo: serialization: do an actual check better that just number of parameters
internal fun ClassDescriptor.checkLoadMethodParameters(parameters: List<ValueParameterDescriptor>): Boolean =
parameters.size == 1
parameters.size == 1
internal fun ClassDescriptor.checkLoadMethodResult(type: KotlinType): Boolean =
getSerializableClassDescriptorBySerializer(this)?.defaultType == type
@@ -31,6 +31,7 @@ object SerializationAnnotations {
// 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 metaSerializableAnnotationFqName = FqName("kotlinx.serialization.MetaSerializable")
internal val encodeDefaultFqName = FqName("kotlinx.serialization.EncodeDefault")
internal val contextualFqName = FqName("kotlinx.serialization.ContextualSerialization") // this one is deprecated
@@ -51,6 +52,7 @@ object SerialEntityNames {
const val CACHED_SERIALIZER_PROPERTY = "\$cachedSerializer"
// classes
val KCLASS_NAME_FQ = FqName("kotlin.reflect.KClass")
val KSERIALIZER_NAME = Name.identifier(KSERIALIZER_CLASS)
val SERIAL_CTOR_MARKER_NAME = Name.identifier("SerializationConstructorMarker")
val KSERIALIZER_NAME_FQ = SerializationPackages.packageFqName.child(KSERIALIZER_NAME)
@@ -29,7 +29,6 @@ class SerializableProperties(private val serializableClass: ClassDescriptor, val
val isExternallySerializable: Boolean
private val primaryConstructorProperties: Map<PropertyDescriptor, Boolean>
init {
val descriptorsSequence = serializableClass.unsubstitutedMemberScope.getContributedDescriptors(DescriptorKindFilter.VARIABLES)
.asSequence()
@@ -31,6 +31,12 @@ public class SerializationIrBoxTestGenerated extends AbstractSerializationIrBoxT
runTest("plugins/kotlin-serialization/kotlin-serialization-compiler/testData/boxIr/classSerializerAsObject.kt");
}
@Test
@TestMetadata("metaSerializable.kt")
public void testMetaSerializable() throws Exception {
runTest("plugins/kotlin-serialization/kotlin-serialization-compiler/testData/boxIr/metaSerializable.kt");
}
@Test
@TestMetadata("multimoduleInheritance.kt")
public void testMultimoduleInheritance() throws Exception {
@@ -59,6 +59,11 @@ public class SerializationPluginDiagnosticTestGenerated extends AbstractSerializ
runTest("plugins/kotlin-serialization/kotlin-serialization-compiler/testData/diagnostics/LocalAndAnonymous.kt");
}
@TestMetadata("MetaSerializable.kt")
public void testMetaSerializable() throws Exception {
runTest("plugins/kotlin-serialization/kotlin-serialization-compiler/testData/diagnostics/MetaSerializable.kt");
}
@TestMetadata("NoSuitableCtorInParent.kt")
public void testNoSuitableCtorInParent() throws Exception {
runTest("plugins/kotlin-serialization/kotlin-serialization-compiler/testData/diagnostics/NoSuitableCtorInParent.kt");
@@ -0,0 +1,186 @@
// IGNORE_BACKEND_FIR: JVM_IR
// TARGET_BACKEND: JVM_IR
// WITH_STDLIB
import kotlinx.serialization.*
import kotlinx.serialization.*
import kotlinx.serialization.encoding.*
import kotlinx.serialization.descriptors.*
import kotlinx.serialization.json.*
import kotlin.reflect.KClass
import kotlin.test.*
// TODO: for this test to work, runtime dependency should be updated to (yet unreleased) serialization with @MetaSerializable annotation
//@MetaSerializable
@Target(AnnotationTarget.CLASS, AnnotationTarget.PROPERTY)
annotation class MySerializable
//@MetaSerializable
@Target(AnnotationTarget.CLASS, AnnotationTarget.PROPERTY)
annotation class MySerializableWithInfo(
val value: Int,
val kclass: KClass<*>
)
@MySerializable
class Project1(val name: String, val language: String)
@MySerializableWithInfo(123, String::class)
class Project2(val name: String, val language: String)
@Serializable
class Wrapper(
// @MySerializableWithInfo(234, Int::class) val project: Project2
)
@Serializable
@MySerializableWithInfo(123, String::class)
class Project3(val name: String, val language: String)
@Serializable(with = MySerializer::class)
//@MySerializableWithInfo(123, String::class)
class Project4(val name: String, val language: String)
@MySerializableWithInfo(123, String::class)
sealed class TestSealed {
@MySerializableWithInfo(123, String::class)
class A(val value1: String) : TestSealed()
@MySerializableWithInfo(123, String::class)
class B(val value2: String) : TestSealed()
}
@MySerializable
abstract class TestAbstract {
@MySerializableWithInfo(123, String::class)
class A(val value1: String) : TestSealed()
@MySerializableWithInfo(123, String::class)
class B(val value2: String) : TestSealed()
}
@MySerializableWithInfo(123, String::class)
enum class TestEnum { Value1, Value2 }
@MySerializableWithInfo(123, String::class)
object TestObject
object MySerializer : KSerializer<Project4> {
override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("Project4", PrimitiveKind.STRING)
override fun serialize(encoder: Encoder, value: Project4) = encoder.encodeString("${value.name}:${value.language}")
override fun deserialize(decoder: Decoder): Project4 {
val params = decoder.decodeString().split(':')
return Project4(params[0], params[1])
}
}
fun testMetaSerializable() {
// val string = Json.encodeToString(Project1.serializer(), Project1("name", "lang"))
// assertEquals("""{"name":"name","language":"lang"}""", string)
//
// val reconstructed = Json.decodeFromString(Project1.serializer(), string)
// assertEquals("name", reconstructed.name)
// assertEquals("lang", reconstructed.language)
}
fun testMetaSerializableWithInfo() {
// val string = Json.encodeToString(Project2.serializer(), Project2("name", "lang"))
// assertEquals("""{"name":"name","language":"lang"}""", string)
//
// val reconstructed = Json.decodeFromString(Project2.serializer(), string)
// assertEquals("name", reconstructed.name)
// assertEquals("lang", reconstructed.language)
//
// val info = Project2.serializer().descriptor.annotations.filterIsInstance<MySerializableWithInfo>().first()
// assertEquals(123, info.value)
// assertEquals(String::class, info.kclass)
}
fun testMetaSerializableOnProperty() {
// val info = Wrapper.serializer().descriptor.getElementAnnotations(0).filterIsInstance<MySerializableWithInfo>().first()
// assertEquals(234, info.value)
// assertEquals(Int::class, info.kclass)
}
fun testSerializableAndMetaAnnotation() {
// val string = Json.encodeToString(Project3.serializer(), Project3("name", "lang"))
// assertEquals("""{"name":"name","language":"lang"}""", string)
//
// val reconstructed = Json.decodeFromString(Project3.serializer(), string)
// assertEquals("name", reconstructed.name)
// assertEquals("lang", reconstructed.language)
//
// val info = Project3.serializer().descriptor.annotations.filterIsInstance<MySerializableWithInfo>().first()
// assertEquals(123, info.value)
// assertEquals(String::class, info.kclass)
}
fun testCustomSerializerAndMetaAnnotation() {
// val string = Json.encodeToString(Project4.serializer(), Project4("name", "lang"))
// assertEquals("""name:lang""", string)
//
// val reconstructed = Json.decodeFromString(Project4.serializer(), string)
// assertEquals("name", reconstructed.name)
// assertEquals("lang", reconstructed.language)
}
fun testSealed() {
// val serializerA = TestSealed.A.serializer()
// val serializerB = TestSealed.B.serializer()
// assertNotNull(serializerA)
// assertNotNull(serializerB)
//
// val infoA = serializerA.descriptor.annotations.filterIsInstance<MySerializableWithInfo>().first()
// val infoB = serializerB.descriptor.annotations.filterIsInstance<MySerializableWithInfo>().first()
// assertEquals(123, infoA.value)
// assertEquals(String::class, infoA.kclass)
// assertEquals(123, infoB.value)
// assertEquals(String::class, infoB.kclass)
}
fun testAbstract() {
// val serializerA = TestAbstract.A.serializer()
// val serializerB = TestAbstract.B.serializer()
// assertNotNull(serializerA)
// assertNotNull(serializerB)
//
// val infoA = serializerA.descriptor.annotations.filterIsInstance<MySerializableWithInfo>().first()
// val infoB = serializerB.descriptor.annotations.filterIsInstance<MySerializableWithInfo>().first()
// assertEquals(123, infoA.value)
// assertEquals(String::class, infoA.kclass)
// assertEquals(123, infoB.value)
// assertEquals(String::class, infoB.kclass)
}
fun testEnum() {
// val serializer = TestEnum.serializer()
// assertNotNull(serializer)
//
// val info = serializer.descriptor.annotations.filterIsInstance<MySerializableWithInfo>().first()
// assertEquals(123, info.value)
// assertEquals(String::class, info.kclass)
}
fun testObject() {
// val serializer = TestObject.serializer()
// assertNotNull(serializer)
//
// val info = serializer.descriptor.annotations.filterIsInstance<MySerializableWithInfo>().first()
// assertEquals(123, info.value)
// assertEquals(String::class, info.kclass)
}
fun box(): String {
testMetaSerializable()
testMetaSerializableWithInfo()
testMetaSerializableOnProperty()
testSealed()
testAbstract()
testEnum()
testObject()
return "OK"
}