From cca03e9586a4f6aa609ed4b6c649af3a006f7673 Mon Sep 17 00:00:00 2001 From: Leonid Startsev Date: Mon, 24 Dec 2018 17:01:25 +0300 Subject: [PATCH] Make enums and interfaces not serializable internally. However, they still can be serialized using custom companion object. Fixes https://github.com/Kotlin/kotlinx.serialization/issues/138 and https://github.com/Kotlin/kotlinx.serialization/issues/304 --- .../backend/ir/SerializableIrGenerator.kt | 14 +++++++++++++- .../backend/js/SerializableJsTranslator.kt | 18 +++++++++++++----- .../backend/jvm/SerializableCodegenImpl.kt | 7 +++++++ .../compiler/resolve/KSerializationUtil.kt | 14 +++++++++++--- .../resolve/KSerializerDescriptorResolver.kt | 13 +++++++------ 5 files changed, 51 insertions(+), 15 deletions(-) diff --git a/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/ir/SerializableIrGenerator.kt b/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/ir/SerializableIrGenerator.kt index 475730cd392..2fa1456fd92 100644 --- a/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/ir/SerializableIrGenerator.kt +++ b/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/ir/SerializableIrGenerator.kt @@ -3,6 +3,7 @@ package org.jetbrains.kotlinx.serialization.compiler.backend.ir import org.jetbrains.kotlin.backend.common.BackendContext import org.jetbrains.kotlin.backend.common.lower.irThrow import org.jetbrains.kotlin.builtins.KotlinBuiltIns +import org.jetbrains.kotlin.codegen.CompilationException import org.jetbrains.kotlin.descriptors.ClassConstructorDescriptor import org.jetbrains.kotlin.descriptors.FunctionDescriptor import org.jetbrains.kotlin.ir.builders.* @@ -12,6 +13,7 @@ import org.jetbrains.kotlin.ir.declarations.IrProperty import org.jetbrains.kotlin.ir.expressions.IrExpression import org.jetbrains.kotlin.ir.util.SymbolTable import org.jetbrains.kotlin.ir.util.TypeTranslator +import org.jetbrains.kotlin.js.resolve.diagnostics.findPsi import org.jetbrains.kotlin.resolve.BindingContext import org.jetbrains.kotlin.resolve.descriptorUtil.getSuperClassOrAny import org.jetbrains.kotlin.resolve.descriptorUtil.module @@ -19,6 +21,8 @@ import org.jetbrains.kotlin.util.OperatorNameConventions import org.jetbrains.kotlinx.serialization.compiler.backend.common.SerializableCodegen import org.jetbrains.kotlinx.serialization.compiler.resolve.SerialEntityNames.MISSING_FIELD_EXC import org.jetbrains.kotlinx.serialization.compiler.resolve.getClassFromSerializationPackage +import org.jetbrains.kotlinx.serialization.compiler.resolve.hasCompanionObjectAsSerializer +import org.jetbrains.kotlinx.serialization.compiler.resolve.hasSerializableAnnotationWithoutArgs import org.jetbrains.kotlinx.serialization.compiler.resolve.isInternalSerializable class SerializableIrGenerator( @@ -98,8 +102,16 @@ class SerializableIrGenerator( context: BackendContext, bindingContext: BindingContext ) { - if (irClass.descriptor.isInternalSerializable) + val serializableClass = irClass.descriptor + + if (serializableClass.isInternalSerializable) SerializableIrGenerator(irClass, context, bindingContext).generate() + else if (serializableClass.hasSerializableAnnotationWithoutArgs && !serializableClass.hasCompanionObjectAsSerializer) { + throw CompilationException( + "@Serializable annotation on $serializableClass would be ignored because it is impossible to serialize it automatically. " + + "Provide serializer manually via e.g. companion object", null, serializableClass.findPsi() + ) + } } } } \ No newline at end of file diff --git a/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/js/SerializableJsTranslator.kt b/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/js/SerializableJsTranslator.kt index 0e29dfe053e..f070bbb8118 100644 --- a/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/js/SerializableJsTranslator.kt +++ b/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/js/SerializableJsTranslator.kt @@ -16,11 +16,13 @@ package org.jetbrains.kotlinx.serialization.compiler.backend.js +import org.jetbrains.kotlin.codegen.CompilationException import org.jetbrains.kotlin.descriptors.ClassConstructorDescriptor import org.jetbrains.kotlin.descriptors.ClassDescriptor import org.jetbrains.kotlin.descriptors.FunctionDescriptor import org.jetbrains.kotlin.descriptors.PropertyDescriptor import org.jetbrains.kotlin.js.backend.ast.* +import org.jetbrains.kotlin.js.resolve.diagnostics.findPsi import org.jetbrains.kotlin.js.translate.context.Namer import org.jetbrains.kotlin.js.translate.context.TranslationContext import org.jetbrains.kotlin.js.translate.declaration.DeclarationBodyVisitor @@ -30,10 +32,10 @@ import org.jetbrains.kotlin.psi.KtExpression import org.jetbrains.kotlin.psi.KtPureClassOrObject import org.jetbrains.kotlinx.serialization.compiler.backend.common.SerializableCodegen import org.jetbrains.kotlinx.serialization.compiler.backend.common.anonymousInitializers -import org.jetbrains.kotlinx.serialization.compiler.backend.common.bodyPropertiesDescriptorsMap -import org.jetbrains.kotlinx.serialization.compiler.backend.common.primaryPropertiesDescriptorsMap import org.jetbrains.kotlinx.serialization.compiler.resolve.SerialEntityNames.MISSING_FIELD_EXC import org.jetbrains.kotlinx.serialization.compiler.resolve.getClassFromSerializationPackage +import org.jetbrains.kotlinx.serialization.compiler.resolve.hasCompanionObjectAsSerializer +import org.jetbrains.kotlinx.serialization.compiler.resolve.hasSerializableAnnotationWithoutArgs import org.jetbrains.kotlinx.serialization.compiler.resolve.isInternalSerializable class SerializableJsTranslator( @@ -104,12 +106,18 @@ class SerializableJsTranslator( companion object { fun translate( declaration: KtPureClassOrObject, - descriptor: ClassDescriptor, + serializableClass: ClassDescriptor, translator: DeclarationBodyVisitor, context: TranslationContext ) { - if (descriptor.isInternalSerializable) - SerializableJsTranslator(declaration, descriptor, translator, context).generate() + if (serializableClass.isInternalSerializable) + SerializableJsTranslator(declaration, serializableClass, translator, context).generate() + else if (serializableClass.hasSerializableAnnotationWithoutArgs && !serializableClass.hasCompanionObjectAsSerializer) { + throw CompilationException( + "@Serializable annotation on $serializableClass would be ignored because it is impossible to serialize it automatically. " + + "Provide serializer manually via e.g. companion object", null, serializableClass.findPsi() + ) + } } } } \ No newline at end of file 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 505d4980877..db8bce66089 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 @@ -21,6 +21,7 @@ import org.jetbrains.kotlin.descriptors.ClassConstructorDescriptor import org.jetbrains.kotlin.descriptors.ClassDescriptor import org.jetbrains.kotlin.descriptors.FunctionDescriptor import org.jetbrains.kotlin.descriptors.PropertyDescriptor +import org.jetbrains.kotlin.js.resolve.diagnostics.findPsi import org.jetbrains.kotlin.lexer.KtTokens import org.jetbrains.kotlin.psi.KtExpression import org.jetbrains.kotlin.psi.KtParameter @@ -46,6 +47,12 @@ class SerializableCodegenImpl( val serializableClass = codegen.descriptor if (serializableClass.isInternalSerializable) SerializableCodegenImpl(codegen, serializableClass).generate() + else if (serializableClass.hasSerializableAnnotationWithoutArgs && !serializableClass.hasCompanionObjectAsSerializer) { + throw CompilationException( + "@Serializable annotation on $serializableClass would be ignored because it is impossible to serialize it automatically. " + + "Provide serializer manually via e.g. companion object", null, serializableClass.findPsi() + ) + } } } 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 f7e51a58cc3..01dfcc8b31b 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 @@ -35,7 +35,9 @@ import org.jetbrains.kotlinx.serialization.compiler.resolve.SerialEntityNames.KS import org.jetbrains.kotlinx.serialization.compiler.resolve.SerializationAnnotations.serialInfoFqName import org.jetbrains.kotlinx.serialization.compiler.resolve.SerializationPackages.packageFqName -fun isAllowedToHaveAutoGeneratedSerializerMethods(classDescriptor: ClassDescriptor): Boolean { +fun isAllowedToHaveAutoGeneratedSerializerMethods(classDescriptor: ClassDescriptor, serializableClassDescriptor: ClassDescriptor): Boolean { + // don't generate automatically anything for enums or interfaces or other strange things + if (serializableClassDescriptor.kind != ClassKind.CLASS) return false // it is either GeneratedSerializer implementation // or user implementation which does not have type parameters (to be able to correctly initialize descriptor) return classDescriptor.typeConstructor.supertypes.any(::isGeneratedKSerializer) || @@ -97,6 +99,12 @@ val KotlinType?.toClassDescriptor: ClassDescriptor? val ClassDescriptor.isInternalSerializable: Boolean //todo normal checking + get() { + if (kind != ClassKind.CLASS) return false + return hasSerializableAnnotationWithoutArgs + } + +internal val ClassDescriptor.hasSerializableAnnotationWithoutArgs: Boolean get() { if (!annotations.hasAnnotation(SerializationAnnotations.serializableAnnotationFqName)) return false // If provided descriptor is lazy, carefully look at psi in order not to trigger full resolve which may be recursive. @@ -112,10 +120,10 @@ internal val ClassDescriptor?.classSerializer: KotlinType? get() = this?.let { // serializer annotation on class? serializableWith?.let { return it } + // companion object serializer? + if (hasCompanionObjectAsSerializer) return companionObjectDescriptor?.defaultType // default serializable? if (isInternalSerializable) { - // companion object serializer? - if (hasCompanionObjectAsSerializer) return companionObjectDescriptor?.defaultType // $serializer nested class return this.unsubstitutedMemberScope .getDescriptorsFiltered(nameFilter = {it == SerialEntityNames.SERIALIZER_CLASS_NAME}) diff --git a/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/resolve/KSerializerDescriptorResolver.kt b/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/resolve/KSerializerDescriptorResolver.kt index caa0816bb72..94af73f9b23 100644 --- a/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/resolve/KSerializerDescriptorResolver.kt +++ b/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/resolve/KSerializerDescriptorResolver.kt @@ -136,10 +136,11 @@ object KSerializerDescriptorResolver { name: Name, result: MutableSet ) { - // Do not auto-generate anything for user serializers - if (!isAllowedToHaveAutoGeneratedSerializerMethods(thisDescriptor)) return - val classDescriptor = getSerializableClassDescriptorBySerializer(thisDescriptor) ?: return + + // Do not auto-generate anything for user serializers + if (!isAllowedToHaveAutoGeneratedSerializerMethods(thisDescriptor, classDescriptor)) return + if (name == SerialEntityNames.SERIAL_DESC_FIELD_NAME && result.none(thisDescriptor::checkSerializableClassPropertyResult) && fromSupertypes.none { thisDescriptor.checkSerializableClassPropertyResult(it) && it.modality == Modality.FINAL } ) { @@ -171,11 +172,11 @@ object KSerializerDescriptorResolver { name: Name, result: MutableCollection ) { - // Do not auto-generate anything for user serializers - if (!isAllowedToHaveAutoGeneratedSerializerMethods(thisDescriptor)) return - val classDescriptor = getSerializableClassDescriptorBySerializer(thisDescriptor) ?: return + // Do not auto-generate anything for user serializers + if (!isAllowedToHaveAutoGeneratedSerializerMethods(thisDescriptor, classDescriptor)) return + fun shouldAddSerializerFunction(checkParameters: (FunctionDescriptor) -> Boolean): Boolean { // Add 'save' / 'load' iff there is no such declared member AND there is no such final member in supertypes return result.none(checkParameters) &&