From f192c2a541869ec974b7511ec4d94260d23fa46b Mon Sep 17 00:00:00 2001 From: "Sergey.Shanshin" Date: Tue, 14 Jun 2022 10:21:48 +0000 Subject: [PATCH] Use EnumSerializer for explicitly serializable enum instead of auto-generated Resolves Kotlin/kotlinx.serialization#683 Kotlin/kotlinx.serialization#1372 --- .../common/SerializableCompanionCodegen.kt | 5 +- .../compiler/backend/common/TypeUtil.kt | 2 +- .../compiler/backend/ir/GeneratorHelpers.kt | 60 ++++++- .../backend/ir/SerializerIrGenerator.kt | 2 +- .../compiler/backend/js/JsCodegenUtil.kt | 70 ++++++-- .../backend/js/SerializerJsTranslator.kt | 2 +- .../compiler/backend/jvm/JVMCodegenUtil.kt | 157 +++++++++++++++--- .../backend/jvm/SerializableCodegenImpl.kt | 10 +- .../jvm/SerializableCompanionCodegenImpl.kt | 10 +- .../backend/jvm/SerializerCodegenImpl.kt | 37 +---- .../SerializationPluginDeclarationChecker.kt | 13 +- .../compiler/resolve/KSerializationUtil.kt | 21 ++- .../compiler/resolve/NamingConventions.kt | 6 + .../testData/diagnostics/SerializableEnums.kt | 2 +- 14 files changed, 313 insertions(+), 84 deletions(-) diff --git a/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/common/SerializableCompanionCodegen.kt b/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/common/SerializableCompanionCodegen.kt index 7ca9f081d49..728abb1e1bf 100644 --- a/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/common/SerializableCompanionCodegen.kt +++ b/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/common/SerializableCompanionCodegen.kt @@ -33,7 +33,10 @@ abstract class SerializableCompanionCodegen( "probably clash with user-defined function has occurred" ) - if (serializableDescriptor.isSerializableObject || serializableDescriptor.isAbstractOrSealedSerializableClass()) { + if (serializableDescriptor.isSerializableObject + || serializableDescriptor.isAbstractOrSealedSerializableClass() + || serializableDescriptor.isSerializableEnum() + ) { generateLazySerializerGetter(serializerGetterDescriptor) } else { generateSerializerGetter(serializerGetterDescriptor) diff --git a/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/common/TypeUtil.kt b/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/common/TypeUtil.kt index b112954c189..d59cbfd7b85 100644 --- a/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/common/TypeUtil.kt +++ b/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/common/TypeUtil.kt @@ -225,7 +225,7 @@ fun findStandardKotlinTypeSerializer(module: ModuleDescriptor, kType: KotlinType fun findEnumTypeSerializer(module: ModuleDescriptor, kType: KotlinType): ClassDescriptor? { val classDescriptor = kType.toClassDescriptor ?: return null - return if (classDescriptor.kind == ClassKind.ENUM_CLASS && !classDescriptor.isInternallySerializableEnum()) + return if (classDescriptor.kind == ClassKind.ENUM_CLASS && !classDescriptor.isEnumWithLegacyGeneratedSerializer()) module.findClassAcrossModuleDependencies(enumSerializerId) else null } diff --git a/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/ir/GeneratorHelpers.kt b/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/ir/GeneratorHelpers.kt index efaf90a468f..09d48f0505b 100644 --- a/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/ir/GeneratorHelpers.kt +++ b/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/ir/GeneratorHelpers.kt @@ -52,6 +52,12 @@ interface IrBuilderExtension { private val throwMissedFieldExceptionArrayFunc get() = compilerContext.referenceFunctions(SerialEntityNames.ARRAY_MASK_FIELD_MISSING_FUNC_FQ).singleOrNull() + private val enumSerializerFactoryFunc + get() = compilerContext.referenceFunctions(SerialEntityNames.ENUM_SERIALIZER_FACTORY_FUNC_FQ).singleOrNull() + + private val markedEnumSerializerFactoryFunc + get() = compilerContext.referenceFunctions(SerialEntityNames.MARKED_ENUM_SERIALIZER_FACTORY_FUNC_FQ).singleOrNull() + private inline fun IrClass.searchForDeclaration(descriptor: DeclarationDescriptor): T? { return declarations.singleOrNull { it.descriptor == descriptor } as? T } @@ -1041,13 +1047,55 @@ interface IrBuilderExtension { } enumSerializerId -> { serializerClass = module.getClassFromInternalSerializationPackage(SpecialBuiltins.enumSerializer) - args = kType.toClassDescriptor!!.let { enumDesc -> - listOf( - irString(enumDesc.serialName()), - irCall(findEnumValuesMethod(enumDesc)) - ) - } + val enumDescriptor = kType.toClassDescriptor!! typeArgs = listOf(thisIrType) + // instantiate serializer only inside enum Companion + if (enclosingGenerator !is SerializableCompanionIrGenerator) { + // otherwise call Companion.serializer() + callSerializerFromCompanion(thisIrType, typeArgs, emptyList())?.let { return it } + } + + val enumArgs = mutableListOf( + irString(enumDescriptor.serialName()), + irCall(findEnumValuesMethod(enumDescriptor)), + ) + + val enumSerializerFactoryFunc = enumSerializerFactoryFunc + val markedEnumSerializerFactoryFunc = markedEnumSerializerFactoryFunc + if (enumSerializerFactoryFunc != null && markedEnumSerializerFactoryFunc != null) { + // runtime contains enum serializer factory functions + val factoryFunc: IrSimpleFunctionSymbol = if (enumDescriptor.isEnumWithSerialInfoAnnotation()) { + // need to store SerialInfo annotation in descriptor + val enumEntries = enumDescriptor.enumEntries() + val entriesNames = enumEntries.map { it.annotations.serialNameValue?.let { n -> irString(n) } ?: irNull() } + val entriesAnnotations = enumEntries.map { + val annotationConstructors = it.annotations.mapNotNull { a -> + compilerContext.typeTranslator.constantValueGenerator.generateAnnotationConstructorCall(a) + } + val annotationsConstructors = copyAnnotationsFrom(annotationConstructors) + if (annotationsConstructors.isEmpty()) { + irNull() + } else { + createArrayOfExpression(compilerContext.irBuiltIns.annotationType, annotationsConstructors) + } + } + val annotationArrayType = + compilerContext.irBuiltIns.arrayClass.typeWith(compilerContext.irBuiltIns.annotationType.makeNullable()) + + enumArgs += createArrayOfExpression(compilerContext.irBuiltIns.stringType.makeNullable(), entriesNames) + enumArgs += createArrayOfExpression(annotationArrayType, entriesAnnotations) + + markedEnumSerializerFactoryFunc + } else { + enumSerializerFactoryFunc + } + + val factoryReturnType = factoryFunc.owner.returnType.substitute(factoryFunc.owner.typeParameters, typeArgs) + return irInvoke(null, factoryFunc, typeArgs, enumArgs, factoryReturnType) + } else { + // support legacy serializer instantiation by constructor for old runtimes + args = enumArgs + } } else -> { args = kType.arguments.map { diff --git a/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/ir/SerializerIrGenerator.kt b/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/ir/SerializerIrGenerator.kt index 1cd374f8370..ec9e0ca4567 100644 --- a/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/ir/SerializerIrGenerator.kt +++ b/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/ir/SerializerIrGenerator.kt @@ -566,7 +566,7 @@ open class SerializerIrGenerator( ) { val serializableDesc = getSerializableClassDescriptorBySerializer(irClass.symbol.descriptor) ?: return val generator = when { - serializableDesc.isInternallySerializableEnum() -> SerializerForEnumsGenerator(irClass, context, bindingContext, serialInfoJvmGenerator) + serializableDesc.isEnumWithLegacyGeneratedSerializer() -> SerializerForEnumsGenerator(irClass, context, bindingContext, serialInfoJvmGenerator) serializableDesc.isInlineClass() -> SerializerForInlineClassGenerator(irClass, context, bindingContext, serialInfoJvmGenerator) else -> SerializerIrGenerator(irClass, context, bindingContext, metadataPlugin, serialInfoJvmGenerator) } diff --git a/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/js/JsCodegenUtil.kt b/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/js/JsCodegenUtil.kt index 4f15584aaf4..e65bd7d2543 100644 --- a/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/js/JsCodegenUtil.kt +++ b/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/js/JsCodegenUtil.kt @@ -12,6 +12,7 @@ import org.jetbrains.kotlin.js.resolve.diagnostics.findPsi import org.jetbrains.kotlin.js.translate.context.TranslationContext import org.jetbrains.kotlin.js.translate.expression.ExpressionVisitor import org.jetbrains.kotlin.js.translate.expression.translateAndAliasParameters +import org.jetbrains.kotlin.js.translate.general.Translation import org.jetbrains.kotlin.js.translate.reference.ReferenceTranslator import org.jetbrains.kotlin.js.translate.utils.JsAstUtils import org.jetbrains.kotlin.psi.KtExpression @@ -165,18 +166,65 @@ internal fun AbstractSerialGenerator.serializerInstance( serializerClass.classId == contextSerializerId || serializerClass.classId == polymorphicSerializerId -> listOf( ExpressionVisitor.getObjectKClass(context, kType.toClassDescriptor!!) ) - serializerClass.classId == enumSerializerId -> listOf( - JsStringLiteral(kType.serialName()), - // EnumClass.values() invocation - JsInvocation( - context.getInnerNameForDescriptor( - DescriptorUtils.getFunctionByName( - kType.toClassDescriptor!!.staticScope, - StandardNames.ENUM_VALUES - ) - ).makeRef() + serializerClass.classId == enumSerializerId -> { + val enumDescriptor = kType.toClassDescriptor!! + + val enumArgs = mutableListOf( + JsStringLiteral(enumDescriptor.serialName()), + // EnumClass.values() invocation + JsInvocation( + context.getInnerNameForDescriptor( + DescriptorUtils.getFunctionByName( + enumDescriptor.staticScope, + StandardNames.ENUM_VALUES + ) + ).makeRef() + ) ) - ) + + val packageScope = context.currentModule.getPackage(SerializationPackages.internalPackageFqName).memberScope + val enumSerializerFactoryFunc = DescriptorUtils.getFunctionByNameOrNull( + packageScope, + SerialEntityNames.ENUM_SERIALIZER_FACTORY_FUNC_NAME + ) + val markedEnumSerializerFactoryFunc = DescriptorUtils.getFunctionByNameOrNull( + packageScope, + SerialEntityNames.MARKED_ENUM_SERIALIZER_FACTORY_FUNC_NAME + ) + if (enumSerializerFactoryFunc != null && markedEnumSerializerFactoryFunc != null) { + // runtime contains enum serializer factory functions + val factoryFunc = if (enumDescriptor.isEnumWithSerialInfoAnnotation()) { + val enumEntries = enumDescriptor.enumEntries() + val entriesNames = + enumEntries.map { it.annotations.serialNameValue?.let { n -> JsStringLiteral(n) } ?: JsNullLiteral() } + + val entriesAnnotations = enumEntries.map { + val annotationsConstructors = it.annotationsWithArguments().map { (annotationClass, args, _) -> + val argExprs = args.map { arg -> + Translation.translateAsExpression(arg.getArgumentExpression()!!, context) + } + val classRef = context.translateQualifiedReference(annotationClass) + JsNew(classRef, argExprs) + } + + if (annotationsConstructors.isEmpty()) { + JsNullLiteral() + } else { + JsArrayLiteral(annotationsConstructors) + } + } + enumArgs += JsArrayLiteral(entriesNames) + enumArgs += JsArrayLiteral(entriesAnnotations) + markedEnumSerializerFactoryFunc + } else { + enumSerializerFactoryFunc + } + return JsInvocation(context.getInnerReference(factoryFunc), enumArgs) + } else { + // support legacy serializer instantiation by constructor for old runtimes + enumArgs + } + } serializerClass.classId == objectSerializerId -> listOf( JsStringLiteral(kType.serialName()), context.serializerObjectGetter(kType.toClassDescriptor!!) diff --git a/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/js/SerializerJsTranslator.kt b/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/js/SerializerJsTranslator.kt index 726a2d7a233..7fa5826fb56 100644 --- a/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/js/SerializerJsTranslator.kt +++ b/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/js/SerializerJsTranslator.kt @@ -373,7 +373,7 @@ open class SerializerJsTranslator( metadataPlugin: SerializationDescriptorSerializerPlugin? ) { val serializableDesc = getSerializableClassDescriptorBySerializer(descriptor) ?: return - if (serializableDesc.isInternallySerializableEnum()) { + if (serializableDesc.isEnumWithLegacyGeneratedSerializer()) { SerializerForEnumsTranslator(descriptor, translator, context).generate() } else { SerializerJsTranslator(descriptor, translator, context, metadataPlugin).generate() diff --git a/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/jvm/JVMCodegenUtil.kt b/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/jvm/JVMCodegenUtil.kt index cde5bf45734..801c82df8df 100644 --- a/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/jvm/JVMCodegenUtil.kt +++ b/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/jvm/JVMCodegenUtil.kt @@ -19,6 +19,7 @@ import org.jetbrains.kotlin.load.kotlin.TypeMappingMode import org.jetbrains.kotlin.load.kotlin.internalName import org.jetbrains.kotlin.name.ClassId import org.jetbrains.kotlin.name.Name +import org.jetbrains.kotlin.psi.ValueArgument import org.jetbrains.kotlin.resolve.DescriptorFactory import org.jetbrains.kotlin.resolve.descriptorUtil.builtIns import org.jetbrains.kotlin.resolve.descriptorUtil.classId @@ -38,7 +39,10 @@ import org.jetbrains.kotlinx.serialization.compiler.backend.common.* import org.jetbrains.kotlinx.serialization.compiler.resolve.* import org.jetbrains.kotlinx.serialization.compiler.resolve.SerialEntityNames.DECODER_CLASS import org.jetbrains.kotlinx.serialization.compiler.resolve.SerialEntityNames.ENCODER_CLASS +import org.jetbrains.kotlinx.serialization.compiler.resolve.SerialEntityNames.ENUMS_FILE +import org.jetbrains.kotlinx.serialization.compiler.resolve.SerialEntityNames.ENUM_SERIALIZER_FACTORY_FUNC_NAME import org.jetbrains.kotlinx.serialization.compiler.resolve.SerialEntityNames.KSERIALIZER_CLASS +import org.jetbrains.kotlinx.serialization.compiler.resolve.SerialEntityNames.MARKED_ENUM_SERIALIZER_FACTORY_FUNC_NAME import org.jetbrains.kotlinx.serialization.compiler.resolve.SerialEntityNames.MISSING_FIELD_EXC import org.jetbrains.kotlinx.serialization.compiler.resolve.SerialEntityNames.PLUGIN_EXCEPTIONS_FILE import org.jetbrains.kotlinx.serialization.compiler.resolve.SerialEntityNames.SERIAL_CTOR_MARKER_NAME @@ -68,7 +72,9 @@ internal val kOutputType = Type.getObjectType("kotlinx/serialization/encoding/$S internal val encoderType = Type.getObjectType("kotlinx/serialization/encoding/$ENCODER_CLASS") internal val decoderType = Type.getObjectType("kotlinx/serialization/encoding/$DECODER_CLASS") internal val kInputType = Type.getObjectType("kotlinx/serialization/encoding/$STRUCTURE_DECODER_CLASS") + internal val pluginUtilsType = Type.getObjectType("kotlinx/serialization/internal/${PLUGIN_EXCEPTIONS_FILE}Kt") +internal val enumFactoriesType = Type.getObjectType("kotlinx/serialization/internal/${ENUMS_FILE}Kt") internal val jvmLambdaType = Type.getObjectType("kotlin/jvm/internal/Lambda") internal val kotlinLazyType = Type.getObjectType("kotlin/Lazy") @@ -84,6 +90,12 @@ internal val serializationExceptionName = "kotlinx/serialization/$SERIAL_EXC" internal val serializationExceptionMissingFieldName = "kotlinx/serialization/$MISSING_FIELD_EXC" internal val serializationExceptionUnknownIndexName = "kotlinx/serialization/$UNKNOWN_FIELD_EXC" +private val annotationType = Type.getObjectType("java/lang/annotation/Annotation") +private val annotationArrayType = Type.getObjectType("[${annotationType.descriptor}") +private val doubleAnnotationArrayType = Type.getObjectType("[${annotationArrayType.descriptor}") +private val stringType = AsmTypes.JAVA_STRING_TYPE +private val stringArrayType = Type.getObjectType("[${stringType.descriptor}") + internal val descriptorGetterName = JvmAbi.getterName(SERIAL_DESC_FIELD) internal val getLazyValueName = JvmAbi.getterName("value") @@ -114,8 +126,8 @@ fun InstructionAdapter.genKOutputMethodCall( ) { val propertyType = classCodegen.typeMapper.mapType(property.type) val sti = generator.getSerialTypeInfo(property, propertyType) - val useSerializer = if (fromClassStartVar == null) stackValueSerializerInstanceFromSerializer(classCodegen, sti, generator) - else stackValueSerializerInstanceFromClass(classCodegen, sti, fromClassStartVar, generator) + val useSerializer = if (fromClassStartVar == null) stackValueSerializerInstanceFromSerializer(expressionCodegen, classCodegen, sti, generator) + else stackValueSerializerInstanceFromClass(expressionCodegen, classCodegen, sti, fromClassStartVar, generator) val actualType = ImplementationBodyCodegen.genPropertyOnStack( this, expressionCodegen.context, @@ -179,14 +191,16 @@ internal val contextSerializerId = ClassId(packageFqName, Name.identifier(Specia internal fun InstructionAdapter.stackValueSerializerInstanceFromClass( - codegen: ClassBodyCodegen, + expressionCodegen: ExpressionCodegen, + classCodegen: ClassBodyCodegen, sti: JVMSerialTypeInfo, varIndexStart: Int, serializerCodegen: AbstractSerialGenerator ): Boolean { val serializer = sti.serializer return serializerCodegen.stackValueSerializerInstance( - codegen, + expressionCodegen, + classCodegen, sti.property.module, sti.property.type, serializer, @@ -198,6 +212,7 @@ internal fun InstructionAdapter.stackValueSerializerInstanceFromClass( } internal fun InstructionAdapter.stackValueSerializerInstanceFromSerializerWithoutSti( + expressionCodegen: ExpressionCodegen, codegen: ClassBodyCodegen, property: SerializableProperty, serializerCodegen: AbstractSerialGenerator @@ -210,6 +225,7 @@ internal fun InstructionAdapter.stackValueSerializerInstanceFromSerializerWithou property.descriptor.findPsi() ) else null return serializerCodegen.stackValueSerializerInstance( + expressionCodegen, codegen, property.module, property.type, @@ -222,9 +238,14 @@ internal fun InstructionAdapter.stackValueSerializerInstanceFromSerializerWithou }.also { if (it && property.type.isMarkedNullable) wrapStackValueIntoNullableSerializer() } } -internal fun InstructionAdapter.stackValueSerializerInstanceFromSerializer(codegen: ClassBodyCodegen, sti: JVMSerialTypeInfo, serializerCodegen: AbstractSerialGenerator): Boolean { +internal fun InstructionAdapter.stackValueSerializerInstanceFromSerializer( + expressionCodegen: ExpressionCodegen, + codegen: ClassBodyCodegen, + sti: JVMSerialTypeInfo, + serializerCodegen: AbstractSerialGenerator +): Boolean { return serializerCodegen.stackValueSerializerInstance( - codegen, sti.property.module, sti.property.type, + expressionCodegen, codegen, sti.property.module, sti.property.type, sti.serializer, this, sti.property.genericIndex ) { idx, _ -> load(0, kSerializerType) @@ -234,7 +255,7 @@ internal fun InstructionAdapter.stackValueSerializerInstanceFromSerializer(codeg // returns false is cannot not use serializer // use iv == null to check only (do not emit serializer onto stack) -internal fun AbstractSerialGenerator.stackValueSerializerInstance(codegen: ClassBodyCodegen, module: ModuleDescriptor, kType: KotlinType, maybeSerializer: ClassDescriptor?, +internal fun AbstractSerialGenerator.stackValueSerializerInstance(expressionCodegen: ExpressionCodegen, classCodegen: ClassBodyCodegen, module: ModuleDescriptor, kType: KotlinType, maybeSerializer: ClassDescriptor?, iv: InstructionAdapter?, genericIndex: Int? = null, genericSerializerFieldGetter: (InstructionAdapter.(Int, KotlinType) -> Unit)? = null @@ -248,7 +269,7 @@ internal fun AbstractSerialGenerator.stackValueSerializerInstance(codegen: Class if (serializer.kind == ClassKind.OBJECT) { // singleton serializer -- just get it if (iv != null) - StackValue.singleton(serializer, codegen.typeMapper).put(kSerializerType, iv) + StackValue.singleton(serializer, classCodegen.typeMapper).put(kSerializerType, iv) return true } // serializer is not singleton object and shall be instantiated @@ -256,12 +277,13 @@ internal fun AbstractSerialGenerator.stackValueSerializerInstance(codegen: Class // bail out from stackValueSerializerInstance if any type argument is not serializable val argType = projection.type val argSerializer = if (argType.isTypeParameter()) null else { - findTypeSerializerOrContext(module, argType, sourceElement = codegen.descriptor.findPsi()) + findTypeSerializerOrContext(module, argType, sourceElement = classCodegen.descriptor.findPsi()) ?: return false } // check if it can be properly serialized with its args recursively if (!stackValueSerializerInstance( - codegen, + expressionCodegen, + classCodegen, module, argType, argSerializer, @@ -275,7 +297,65 @@ internal fun AbstractSerialGenerator.stackValueSerializerInstance(codegen: Class } // new serializer if needed iv?.apply { - val serializerType = codegen.typeMapper.mapClass(serializer) + val serializerType = classCodegen.typeMapper.mapClass(serializer) + val classDescriptor = kType.toClassDescriptor!! + if (serializer.classId == enumSerializerId && !classDescriptor.useGeneratedEnumSerializer) { + // runtime contains enum serializer factory functions + val javaEnumArray = Type.getType("[Ljava/lang/Enum;") + val enumJavaType = classCodegen.typeMapper.mapType(kType, null, TypeMappingMode.GENERIC_ARGUMENT) + val serialName = classDescriptor.serialName() + + if (classDescriptor.isEnumWithSerialInfoAnnotation()) { + aconst(serialName) + invokestatic(enumJavaType.internalName, "values", "()[${enumJavaType.descriptor}", false) + checkcast(javaEnumArray) + + val entries = classDescriptor.enumEntries() + fillArray(stringType, entries) { _, entry -> + entry.annotations.serialNameValue.let { + if (it == null) { + aconst(null) + } else { + aconst(it) + } + } + } + checkcast(stringArrayType) + + fillArray(annotationArrayType, entries) { _, entry -> + val annotations = entry.annotationsWithArguments() + if (annotations.isEmpty()) { + aconst(null) + } else { + fillArray(annotationType, annotations) { _, annotation -> + val (annotationClass, args, consParams) = annotation + expressionCodegen.generateSyntheticAnnotationOnStack(annotationClass, args, consParams) + } + } + } + checkcast(doubleAnnotationArrayType) + + invokestatic( + enumFactoriesType.internalName, + MARKED_ENUM_SERIALIZER_FACTORY_FUNC_NAME.asString(), + "(${stringType.descriptor}${javaEnumArray.descriptor}${stringArrayType.descriptor}${doubleAnnotationArrayType.descriptor})${kSerializerType.descriptor}", + false + ) + } else { + aconst(serialName) + invokestatic(enumJavaType.internalName, "values", "()[${enumJavaType.descriptor}", false) + checkcast(javaEnumArray) + + invokestatic( + enumFactoriesType.internalName, + ENUM_SERIALIZER_FACTORY_FUNC_NAME.asString(), + "(${stringType.descriptor}${javaEnumArray.descriptor})${kSerializerType.descriptor}", + false + ) + } + return true + } + // todo: support static factory methods for serializers for shorter bytecode anew(serializerType) dup() @@ -286,7 +366,8 @@ internal fun AbstractSerialGenerator.stackValueSerializerInstance(codegen: Class val (argType, argSerializer) = typeArgument assert( stackValueSerializerInstance( - codegen, + expressionCodegen, + classCodegen, module, argType, argSerializer, @@ -303,9 +384,10 @@ internal fun AbstractSerialGenerator.stackValueSerializerInstance(codegen: Class val serialName = kType.serialName() when (serializer.classId) { enumSerializerId -> { + // support legacy serializer instantiation by constructor for old runtimes aconst(serialName) signature.append("Ljava/lang/String;") - val enumJavaType = codegen.typeMapper.mapType(kType, null, TypeMappingMode.GENERIC_ARGUMENT) + val enumJavaType = classCodegen.typeMapper.mapType(kType, null, TypeMappingMode.GENERIC_ARGUMENT) val javaEnumArray = Type.getType("[Ljava/lang/Enum;") invokestatic(enumJavaType.internalName, "values","()[${enumJavaType.descriptor}", false) checkcast(javaEnumArray) @@ -314,7 +396,7 @@ internal fun AbstractSerialGenerator.stackValueSerializerInstance(codegen: Class contextSerializerId, polymorphicSerializerId -> { // a special way to instantiate enum -- need a enum KClass reference // GENERIC_ARGUMENT forces boxing in order to obtain KClass - aconst(codegen.typeMapper.mapType(kType, null, TypeMappingMode.GENERIC_ARGUMENT)) + aconst(classCodegen.typeMapper.mapType(kType, null, TypeMappingMode.GENERIC_ARGUMENT)) AsmUtil.wrapJavaClassIntoKClass(this) signature.append(AsmTypes.K_CLASS_TYPE.descriptor) if (serializer.classId == contextSerializerId && serializer.constructors.any { it.valueParameters.size == 3 }) { @@ -334,7 +416,7 @@ internal fun AbstractSerialGenerator.stackValueSerializerInstance(codegen: Class } referenceArraySerializerId -> { // a special way to instantiate reference array serializer -- need an element KClass reference - aconst(codegen.typeMapper.mapType(kType.arguments[0].type, null, TypeMappingMode.GENERIC_ARGUMENT)) + aconst(classCodegen.typeMapper.mapType(kType.arguments[0].type, null, TypeMappingMode.GENERIC_ARGUMENT)) AsmUtil.wrapJavaClassIntoKClass(this) signature.append(AsmTypes.K_CLASS_TYPE.descriptor) // Reference array serializer still needs serializer for its argument type @@ -343,13 +425,13 @@ internal fun AbstractSerialGenerator.stackValueSerializerInstance(codegen: Class sealedSerializerId -> { aconst(serialName) signature.append("Ljava/lang/String;") - aconst(codegen.typeMapper.mapType(kType, null, TypeMappingMode.GENERIC_ARGUMENT)) + aconst(classCodegen.typeMapper.mapType(kType, null, TypeMappingMode.GENERIC_ARGUMENT)) AsmUtil.wrapJavaClassIntoKClass(this) signature.append(AsmTypes.K_CLASS_TYPE.descriptor) val (subClasses, subSerializers) = allSealedSerializableSubclassesFor(kType.toClassDescriptor!!, module) // KClasses vararg fillArray(AsmTypes.K_CLASS_TYPE, subClasses) { _, type -> - aconst(codegen.typeMapper.mapType(type, null, TypeMappingMode.GENERIC_ARGUMENT)) + aconst(classCodegen.typeMapper.mapType(type, null, TypeMappingMode.GENERIC_ARGUMENT)) AsmUtil.wrapJavaClassIntoKClass(this) } signature.append(AsmTypes.K_CLASS_ARRAY_TYPE.descriptor) @@ -358,7 +440,8 @@ internal fun AbstractSerialGenerator.stackValueSerializerInstance(codegen: Class val (argType, argSerializer) = subClasses[i] to serializer assert( stackValueSerializerInstance( - codegen, + expressionCodegen, + classCodegen, module, argType, argSerializer, @@ -368,7 +451,8 @@ internal fun AbstractSerialGenerator.stackValueSerializerInstance(codegen: Class // if we encountered generic type parameter in one of subclasses of sealed class, use polymorphism from upper bound assert( stackValueSerializerInstance( - codegen, + expressionCodegen, + classCodegen, module, (genericType.constructor.declarationDescriptor as TypeParameterDescriptor).representativeUpperBound, module.getClassFromSerializationPackage(SpecialBuiltins.polymorphicSerializer), @@ -384,7 +468,7 @@ internal fun AbstractSerialGenerator.stackValueSerializerInstance(codegen: Class objectSerializerId -> { aconst(serialName) signature.append("Ljava/lang/String;") - StackValue.singleton(kType.toClassDescriptor!!, codegen.typeMapper).put(Type.getType("Ljava/lang/Object;"), iv) + StackValue.singleton(kType.toClassDescriptor!!, classCodegen.typeMapper).put(Type.getType("Ljava/lang/Object;"), iv) signature.append("Ljava/lang/Object;") } // all serializers get arguments with serializers of their generic types @@ -397,6 +481,29 @@ internal fun AbstractSerialGenerator.stackValueSerializerInstance(codegen: Class return true } +internal fun ExpressionCodegen.generateSyntheticAnnotationOnStack( + annotationClass: ClassDescriptor, + args: List, + ctorParams: List +) { + val implType = typeMapper.mapType(annotationClass).internalName + "\$" + SerialEntityNames.IMPL_NAME.identifier + with(v) { + // new Annotation$Impl(...) + anew(Type.getObjectType(implType)) + dup() + val sb = StringBuilder("(") + for (i in ctorParams.indices) { + val decl = args[i] + val desc = ctorParams[i] + val valAsmType = typeMapper.mapType(desc.type) + this@generateSyntheticAnnotationOnStack.gen(decl.getArgumentExpression(), valAsmType) + sb.append(valAsmType.descriptor) + } + sb.append(")V") + invokespecial(implType, "", sb.toString(), false) + } +} + fun InstructionAdapter.wrapStackValueIntoNullableSerializer() = invokestatic( "kotlinx/serialization/builtins/BuiltinSerializersKt", "getNullable", @@ -406,10 +513,10 @@ fun InstructionAdapter.wrapStackValueIntoNullableSerializer() = fun InstructionAdapter.fillArray(type: Type, args: List, onEach: (Int, T) -> Unit) { iconst(args.size) newarray(type) - args.forEachIndexed { i, serializer -> + args.forEachIndexed { i, arg -> dup() iconst(i) - onEach(i, serializer) + onEach(i, arg) astore(type) } } @@ -510,7 +617,7 @@ internal fun createSingletonLambda( lambdaName: String, outerClassCodegen: ImplementationBodyCodegen, resultSimpleType: SimpleType, - block: InstructionAdapter.(ImplementationBodyCodegen) -> Unit + block: InstructionAdapter.(ImplementationBodyCodegen, ExpressionCodegen) -> Unit ): Type { val lambdaType = Type.getObjectType("${outerClassCodegen.className}\$$lambdaName") @@ -631,8 +738,8 @@ internal fun createSingletonLambda( DescriptorVisibilities.PUBLIC ) - lambdaCodegen.generateMethod(invokeFunction) { _, _ -> - block(lambdaCodegen) + lambdaCodegen.generateMethod(invokeFunction) { _, expressionCodegen -> + block(lambdaCodegen, expressionCodegen) } val bridgeInvokeFunction = SimpleFunctionDescriptorImpl.create( diff --git a/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/jvm/SerializableCodegenImpl.kt b/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/jvm/SerializableCodegenImpl.kt index cde26effd94..cf8e31e6f00 100644 --- a/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/jvm/SerializableCodegenImpl.kt +++ b/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/jvm/SerializableCodegenImpl.kt @@ -103,7 +103,15 @@ class SerializableCodegenImpl( superTypeArguments.forEach { val genericIdx = serializableDescriptor.defaultType.arguments.indexOf(it).let { if (it == -1) null else it } val serial = findTypeSerializerOrContext(serializableDescriptor.module, it.type) - stackValueSerializerInstance(classCodegen, serializableDescriptor.module, it.type, serial, this, genericIdx) { i, _ -> + stackValueSerializerInstance( + exprCodegen, + classCodegen, + serializableDescriptor.module, + it.type, + serial, + this, + genericIdx + ) { i, _ -> load(offsetI + i, kSerializerType) } } diff --git a/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/jvm/SerializableCompanionCodegenImpl.kt b/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/jvm/SerializableCompanionCodegenImpl.kt index 02990ee8e76..648c3d8de6f 100644 --- a/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/jvm/SerializableCompanionCodegenImpl.kt +++ b/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/jvm/SerializableCompanionCodegenImpl.kt @@ -57,7 +57,11 @@ class SerializableCompanionCodegenImpl(private val classCodegen: ImplementationB // create singleton lambda class val lambdaType = - createSingletonLambda("serializer\$1", classCodegen, companionDescriptor.getKSerializer().defaultType) { lambdaCodegen -> + createSingletonLambda( + "serializer\$1", + classCodegen, + companionDescriptor.getKSerializer().defaultType + ) { lambdaCodegen, expressionCodegen -> val serializerDescriptor = requireNotNull( findTypeSerializer( serializableDescriptor.module, @@ -65,6 +69,7 @@ class SerializableCompanionCodegenImpl(private val classCodegen: ImplementationB ) ) stackValueSerializerInstance( + expressionCodegen, lambdaCodegen, serializableDescriptor.module, serializableDescriptor.defaultType, @@ -106,8 +111,9 @@ class SerializableCompanionCodegenImpl(private val classCodegen: ImplementationB serializableDescriptor.toSimpleType() ) ) - classCodegen.generateMethod(methodDescriptor) { _, _ -> + classCodegen.generateMethod(methodDescriptor) { _, expressionCodegen -> stackValueSerializerInstance( + expressionCodegen, classCodegen, serializableDescriptor.module, serializableDescriptor.defaultType, diff --git a/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/jvm/SerializerCodegenImpl.kt b/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/jvm/SerializerCodegenImpl.kt index 0ed7dcdd9a9..20af9f2366c 100644 --- a/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/jvm/SerializerCodegenImpl.kt +++ b/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/jvm/SerializerCodegenImpl.kt @@ -8,7 +8,6 @@ package org.jetbrains.kotlinx.serialization.compiler.backend.jvm import org.jetbrains.kotlin.codegen.* import org.jetbrains.kotlin.descriptors.* import org.jetbrains.kotlin.descriptors.annotations.Annotated -import org.jetbrains.kotlin.psi.ValueArgument import org.jetbrains.kotlin.resolve.jvm.AsmTypes import org.jetbrains.kotlin.resolve.jvm.diagnostics.OtherOrigin import org.jetbrains.kotlinx.serialization.compiler.backend.common.SerializerCodegen @@ -37,7 +36,7 @@ open class SerializerCodegenImpl( companion object { fun generateSerializerExtensions(codegen: ImplementationBodyCodegen, metadataPlugin: SerializationDescriptorSerializerPlugin?) { val serializableClass = getSerializableClassDescriptorBySerializer(codegen.descriptor) ?: return - val serializerCodegen = if (serializableClass.isInternallySerializableEnum()) { + val serializerCodegen = if (serializableClass.isEnumWithLegacyGeneratedSerializer()) { SerializerForEnumsCodegen(codegen, serializableClass) } else { SerializerCodegenImpl(codegen, serializableClass, metadataPlugin) @@ -144,30 +143,6 @@ open class SerializerCodegenImpl( expr.generateSerialDescriptor(0, true) } - private fun ExpressionCodegen.generateSyntheticAnnotationOnStack( - annotationClass: ClassDescriptor, - args: List, - ctorParams: List - ) { - val implType = - codegen.typeMapper.mapType(annotationClass).internalName + "\$" + SerialEntityNames.IMPL_NAME.identifier - with(v) { - // new Annotation$Impl(...) - anew(Type.getObjectType(implType)) - dup() - val sb = StringBuilder("(") - for (i in ctorParams.indices) { - val decl = args[i] - val desc = ctorParams[i] - val valAsmType = codegen.typeMapper.mapType(desc.type) - this@generateSyntheticAnnotationOnStack.gen(decl.getArgumentExpression(), valAsmType) - sb.append(valAsmType.descriptor) - } - sb.append(")V") - invokespecial(implType, "", sb.toString(), false) - } - } - // use null to put value on stack, use number to store it to var protected fun InstructionAdapter.stackSerialClassDesc(classDescVar: Int?) { if (staticDescriptor) @@ -192,7 +167,7 @@ open class SerializerCodegenImpl( } override fun generateChildSerializersGetter(function: FunctionDescriptor) { - codegen.generateMethod(function) { _, _ -> + codegen.generateMethod(function) { _, expressionCodegen -> val size = serializableProperties.size iconst(size) newarray(kSerializerType) @@ -202,6 +177,7 @@ open class SerializerCodegenImpl( val prop = serializableProperties[i] assert( stackValueSerializerInstanceFromSerializerWithoutSti( + expressionCodegen, codegen, prop, this@SerializerCodegenImpl @@ -335,7 +311,7 @@ open class SerializerCodegenImpl( propVar = propsStartVar for ((index, property) in serializableProperties.withIndex()) { val propertyType = codegen.typeMapper.mapType(property.type) - callReadProperty(property, propertyType, index, inputVar, descVar, propVar) + callReadProperty(expressionCodegen, property, propertyType, index, inputVar, descVar, propVar) propVar += propertyType.size } // set all bit masks to true @@ -373,7 +349,7 @@ open class SerializerCodegenImpl( if (!property.transient) { // labelI: visitLabel(labels[labelNum + 1]) - callReadProperty(property, propertyType, index, inputVar, descVar, propVar) + callReadProperty(expressionCodegen, property, propertyType, index, inputVar, descVar, propVar) // mark read bit in mask // bitMask = bitMask | 1 << index @@ -449,6 +425,7 @@ open class SerializerCodegenImpl( } private fun InstructionAdapter.callReadProperty( + expressionCodegen: ExpressionCodegen, property: SerializableProperty, propertyType: Type, index: Int, @@ -462,7 +439,7 @@ open class SerializerCodegenImpl( iconst(index) val sti = getSerialTypeInfo(property, propertyType) - val useSerializer = stackValueSerializerInstanceFromSerializer(codegen, sti, this@SerializerCodegenImpl) + val useSerializer = stackValueSerializerInstanceFromSerializer(expressionCodegen, codegen, sti, this@SerializerCodegenImpl) val unknownSer = (!useSerializer && sti.elementMethodPrefix.isEmpty()) if (unknownSer) { aconst(codegen.typeMapper.mapType(property.type)) diff --git a/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/diagnostic/SerializationPluginDeclarationChecker.kt b/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/diagnostic/SerializationPluginDeclarationChecker.kt index 77f1b8f4a9d..817bdf80ff7 100644 --- a/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/diagnostic/SerializationPluginDeclarationChecker.kt +++ b/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/diagnostic/SerializationPluginDeclarationChecker.kt @@ -36,6 +36,8 @@ import org.jetbrains.kotlinx.serialization.compiler.resolve.* internal val SERIALIZABLE_PROPERTIES: WritableSlice = Slices.createSimpleSlice() open class SerializationPluginDeclarationChecker : DeclarationChecker { + private var useLegacyEnumSerializerCached: Boolean? = null + final override fun check(declaration: KtDeclaration, descriptor: DeclarationDescriptor, context: DeclarationCheckerContext) { if (descriptor !is ClassDescriptor) return @@ -139,8 +141,13 @@ open class SerializationPluginDeclarationChecker : DeclarationChecker { } } + private fun ClassDescriptor.useLegacyGeneratedEnumSerializer(): Boolean { + return useLegacyEnumSerializerCached ?: useGeneratedEnumSerializer.also { useLegacyEnumSerializerCached = it } + } + private fun canBeSerializedInternally(descriptor: ClassDescriptor, declaration: KtDeclaration, trace: BindingTrace): Boolean { - if (descriptor.isSerializableEnumWithMissingSerializer()) { + // if enum has meta or SerialInfo annotation on a class or entries and used plugin-generated serializer + if (descriptor.isSerializableEnumWithMissingSerializer() && descriptor.useLegacyGeneratedEnumSerializer()) { val declarationToReport = declaration.modifierList ?: declaration trace.report(SerializationErrors.EXPLICIT_SERIALIZABLE_IS_REQUIRED.on(declarationToReport)) return false @@ -187,7 +194,7 @@ open class SerializationPluginDeclarationChecker : DeclarationChecker { } // check that we can instantiate supertype - if (!descriptor.isSerializableEnum()) { // enums are inherited from java.lang.Enum and can't be inherited from other classes + if (descriptor.kind != ClassKind.ENUM_CLASS) { // 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.reportOnSerializableOrMetaAnnotation(descriptor, SerializationErrors.NON_SERIALIZABLE_PARENT_MUST_HAVE_NOARG_CTOR) @@ -429,4 +436,4 @@ open class SerializationPluginDeclarationChecker : DeclarationChecker { } internal val ClassDescriptor.serializableAnnotationIsUseless: Boolean - get() = hasSerializableOrMetaAnnotationWithoutArgs && !isInternalSerializable && !hasCompanionObjectAsSerializer && !isSerializableEnum() && !isSealedSerializableInterface + get() = hasSerializableOrMetaAnnotationWithoutArgs && !isInternalSerializable && !hasCompanionObjectAsSerializer && kind != ClassKind.ENUM_CLASS && !isSealedSerializableInterface diff --git a/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/resolve/KSerializationUtil.kt b/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/resolve/KSerializationUtil.kt index 6ae8b4bbeb7..5081a254c8d 100644 --- a/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/resolve/KSerializationUtil.kt +++ b/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/resolve/KSerializationUtil.kt @@ -23,6 +23,8 @@ 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.SerialEntityNames.ENUM_SERIALIZER_FACTORY_FUNC_NAME +import org.jetbrains.kotlinx.serialization.compiler.resolve.SerialEntityNames.MARKED_ENUM_SERIALIZER_FACTORY_FUNC_NAME 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 @@ -124,11 +126,20 @@ internal val ClassDescriptor.isInternalSerializable: Boolean //todo normal check internal fun ClassDescriptor.isSerializableEnum(): Boolean = kind == ClassKind.ENUM_CLASS && hasSerializableOrMetaAnnotation +internal fun ClassDescriptor.isEnumWithLegacyGeneratedSerializer(): Boolean = isInternallySerializableEnum() && useGeneratedEnumSerializer + internal fun ClassDescriptor.isInternallySerializableEnum(): Boolean = kind == ClassKind.ENUM_CLASS && hasSerializableOrMetaAnnotationWithoutArgs internal val ClassDescriptor.shouldHaveGeneratedSerializer: Boolean - get() = (isInternalSerializable && (modality == Modality.FINAL || modality == Modality.OPEN)) || isInternallySerializableEnum() + get() = (isInternalSerializable && (modality == Modality.FINAL || modality == Modality.OPEN)) + || isEnumWithLegacyGeneratedSerializer() + +internal val ClassDescriptor.useGeneratedEnumSerializer: Boolean + get() { + val functions = module.getPackage(SerializationPackages.internalPackageFqName).memberScope.getFunctionNames() + return !functions.contains(ENUM_SERIALIZER_FACTORY_FUNC_NAME) || !functions.contains(MARKED_ENUM_SERIALIZER_FACTORY_FUNC_NAME) + } internal fun ClassDescriptor.enumEntries(): List { check(this.kind == ClassKind.ENUM_CLASS) @@ -138,6 +149,13 @@ internal fun ClassDescriptor.enumEntries(): List { .toList() } +// check enum or its elements has any SerialInfo annotation +internal fun ClassDescriptor.isEnumWithSerialInfoAnnotation(): Boolean { + if (kind != ClassKind.ENUM_CLASS) return false + if (annotations.hasAnySerialAnnotation) return true + return enumEntries().any { (it.annotations.hasAnySerialAnnotation) } +} + internal val Annotations.hasAnySerialAnnotation: Boolean get() = serialNameValue != null || any { it.annotationClass?.isSerialInfoAnnotation == true } @@ -242,6 +260,7 @@ internal fun ClassDescriptor.needSerializerFactory(): Boolean { if (!(this.platform?.isNative() == true || this.platform.isJs())) return false val serializableClass = getSerializableClassDescriptorByCompanion(this) ?: return false if (serializableClass.isSerializableObject) return true + if (serializableClass.isSerializableEnum()) return true if (serializableClass.isAbstractOrSealedSerializableClass()) return true if (serializableClass.isSealedSerializableInterface) return true if (serializableClass.declaredTypeParameters.isEmpty()) return false diff --git a/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/resolve/NamingConventions.kt b/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/resolve/NamingConventions.kt index 14f14010ab2..abaa4246299 100644 --- a/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/resolve/NamingConventions.kt +++ b/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/resolve/NamingConventions.kt @@ -78,6 +78,7 @@ object SerialEntityNames { const val SERIAL_DESCRIPTOR_FOR_INLINE = "InlineClassDescriptor" const val PLUGIN_EXCEPTIONS_FILE = "PluginExceptions" + const val ENUMS_FILE = "Enums" //exceptions const val SERIAL_EXC = "SerializationException" @@ -94,11 +95,16 @@ object SerialEntityNames { val SERIALIZER_PROVIDER_NAME = Name.identifier("serializer") val SINGLE_MASK_FIELD_MISSING_FUNC_NAME = Name.identifier("throwMissingFieldException") val ARRAY_MASK_FIELD_MISSING_FUNC_NAME = Name.identifier("throwArrayMissingFieldException") + val ENUM_SERIALIZER_FACTORY_FUNC_NAME = Name.identifier("createSimpleEnumSerializer") + val MARKED_ENUM_SERIALIZER_FACTORY_FUNC_NAME = Name.identifier("createMarkedEnumSerializer") val SINGLE_MASK_FIELD_MISSING_FUNC_FQ = SerializationPackages.internalPackageFqName.child(SINGLE_MASK_FIELD_MISSING_FUNC_NAME) val ARRAY_MASK_FIELD_MISSING_FUNC_FQ = SerializationPackages.internalPackageFqName.child(ARRAY_MASK_FIELD_MISSING_FUNC_NAME) val CACHED_SERIALIZER_PROPERTY_NAME = Name.identifier(CACHED_SERIALIZER_PROPERTY) val CACHED_DESCRIPTOR_FIELD_NAME = Name.identifier(CACHED_DESCRIPTOR_FIELD) + val ENUM_SERIALIZER_FACTORY_FUNC_FQ = SerializationPackages.internalPackageFqName.child(ENUM_SERIALIZER_FACTORY_FUNC_NAME) + val MARKED_ENUM_SERIALIZER_FACTORY_FUNC_FQ = SerializationPackages.internalPackageFqName.child(MARKED_ENUM_SERIALIZER_FACTORY_FUNC_NAME) + // parameters val dummyParamName = Name.identifier("serializationConstructorMarker") internal const val typeArgPrefix = "typeSerial" diff --git a/plugins/kotlin-serialization/kotlin-serialization-compiler/testData/diagnostics/SerializableEnums.kt b/plugins/kotlin-serialization/kotlin-serialization-compiler/testData/diagnostics/SerializableEnums.kt index 84bd938c941..b58904aa427 100644 --- a/plugins/kotlin-serialization/kotlin-serialization-compiler/testData/diagnostics/SerializableEnums.kt +++ b/plugins/kotlin-serialization/kotlin-serialization-compiler/testData/diagnostics/SerializableEnums.kt @@ -21,4 +21,4 @@ object EnumSerializer: KSerializer { } @Serializable -data class EnumUsage(val s: SimpleEnum, val m: MarkedNameEnum, val e: ExplicitlyMarkedEnum) \ No newline at end of file +data class EnumUsage(val s: SimpleEnum, val m: MarkedNameEnum, val e: ExplicitlyMarkedEnum)