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 ec96f62b261..c7449466636 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 @@ -717,6 +717,18 @@ interface IrBuilderExtension { ) } + fun createClassReference(irClass: IrClass, startOffset: Int, endOffset: Int): IrClassReference { + val classType = irClass.descriptor.toSimpleType(false) + val classSymbol = irClass.symbol + return IrClassReferenceImpl( + startOffset, + endOffset, + compilerContext.irBuiltIns.kClassClass.starProjectedType, + classSymbol, + classType.approximateJvmErasure.toIrType() + ) + } + // It is impossible to use star projections right away, because Array::class differ from Array::class : // detailed information about type arguments required for class references on jvm private val KotlinType.approximateJvmErasure: KotlinType diff --git a/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/ir/SerializableCompanionIrGenerator.kt b/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/ir/SerializableCompanionIrGenerator.kt index 11c9fc5f333..da0cc3183e1 100644 --- a/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/ir/SerializableCompanionIrGenerator.kt +++ b/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/ir/SerializableCompanionIrGenerator.kt @@ -20,6 +20,7 @@ import org.jetbrains.kotlin.resolve.BindingContext import org.jetbrains.kotlin.resolve.calls.components.isVararg import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe import org.jetbrains.kotlin.resolve.descriptorUtil.module +import org.jetbrains.kotlin.util.collectionUtils.filterIsInstanceAnd import org.jetbrains.kotlinx.serialization.compiler.backend.common.SerializableCompanionCodegen import org.jetbrains.kotlinx.serialization.compiler.backend.common.findTypeSerializer import org.jetbrains.kotlinx.serialization.compiler.extensions.SerializationPluginContext @@ -58,7 +59,7 @@ class SerializableCompanionIrGenerator( ) ) ?: return - val irSerializableClass = compilerContext.referenceClass(serializableDescriptor.fqNameSafe)?.owner ?: return + val irSerializableClass = if (irClass.isCompanion) irClass.parentAsClass else irClass val serializableWithAlreadyPresent = irSerializableClass.annotations.any { it.symbol.descriptor.constructedClass.fqNameSafe == annotationMarkerClass.fqNameSafe } @@ -66,12 +67,23 @@ class SerializableCompanionIrGenerator( val annotationCtor = compilerContext.referenceConstructors(annotationMarkerClass.fqNameSafe).single { it.owner.isPrimary } val annotationType = annotationMarkerClass.defaultType.toIrType() + + val serializerIrClass = if (serializableDescriptor.isInternalSerializable) { + // internally generated serializer always declared inside serializable class + irSerializableClass.declarations + .filterIsInstanceAnd { it.name == serializer.name } + .singleOrNull() ?: throw Exception("No class with name ${serializer.fqNameSafe}") + } else { + // FIXME referenceClass not supports local classes so it should be replaced in future + compilerContext.referenceClass(serializer.fqNameSafe)!!.owner + } + + val annotationCtorCall = IrConstructorCallImpl.fromSymbolDescriptor(startOffset, endOffset, annotationType, annotationCtor).apply { - val serializerType = serializer.toSimpleType(false) putValueArgument( 0, createClassReference( - serializerType, + serializerIrClass, startOffset, endOffset ) 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 4ecb4ca49c1..ff020c276b5 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 @@ -22,6 +22,7 @@ import org.jetbrains.kotlin.resolve.BindingContext import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe import org.jetbrains.kotlin.resolve.descriptorUtil.module import org.jetbrains.kotlin.util.OperatorNameConventions +import org.jetbrains.kotlin.util.collectionUtils.filterIsInstanceAnd import org.jetbrains.kotlin.utils.getOrPutNullable import org.jetbrains.kotlinx.serialization.compiler.backend.common.* import org.jetbrains.kotlinx.serialization.compiler.diagnostic.serializableAnnotationIsUseless @@ -172,10 +173,15 @@ class SerializableIrGenerator( private fun IrBlockBodyBuilder.getStaticSerialDescriptorExpr(): IrExpression { val serializer = serializableDescriptor.classSerializer!! - val serialDescriptorGetter = compilerContext.referenceClass(serializer.fqNameSafe)?.getPropertyGetter(SERIAL_DESC_FIELD) - ?: throw Exception("No class with name ${serializer.fqNameSafe}") + // internally generated serializer always declared inside serializable class + val serializerIrClass = irClass.declarations + .filterIsInstanceAnd { it.name == serializer.name } + .singleOrNull() ?: throw Exception("No class with name ${serializer.fqNameSafe}") + + val serialDescriptorGetter = + serializerIrClass.getPropertyGetter(SERIAL_DESC_FIELD)!! return irGet( - serialDescriptorGetter.owner.returnType, + serializerIrClass.defaultType, irGetObject(serializer), serialDescriptorGetter.owner.symbol ) 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 c4b5a848c2d..54c18634d5f 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 @@ -10,6 +10,7 @@ import org.jetbrains.kotlin.backend.common.lower.DeclarationIrBuilder import org.jetbrains.kotlin.backend.common.lower.irIfThen import org.jetbrains.kotlin.backend.common.lower.irThrow import org.jetbrains.kotlin.builtins.PrimitiveType +import org.jetbrains.kotlin.codegen.CompilationException import org.jetbrains.kotlin.descriptors.* import org.jetbrains.kotlin.ir.builders.* import org.jetbrains.kotlin.ir.declarations.* @@ -22,6 +23,7 @@ import org.jetbrains.kotlin.ir.types.* import org.jetbrains.kotlin.ir.util.* import org.jetbrains.kotlin.platform.jvm.isJvm import org.jetbrains.kotlin.resolve.BindingContext +import org.jetbrains.kotlin.resolve.DescriptorUtils import org.jetbrains.kotlin.resolve.calls.components.hasDefaultValue import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe import org.jetbrains.kotlin.resolve.isInlineClass @@ -458,6 +460,15 @@ open class SerializerIrGenerator( val ctor: IrConstructorSymbol = serializableSyntheticConstructor(serializableIrClass) +irReturn(irInvoke(null, ctor, typeArgs, args)) } else { + if (DescriptorUtils.isLocal(serializerDescriptor)) { + // if the serializer is local, then the serializable class too, since they must be in the same scope + throw CompilationException( + "External serializer class `${serializerDescriptor.fqNameSafe}` is local. Local external serializers are not supported yet.", + null, + null + ) + } + generateGoldenMaskCheck(bitMasks, properties, localSerialDesc.get()) val ctor: IrConstructorSymbol = diff --git a/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/diagnostic/SerializationErrors.java b/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/diagnostic/SerializationErrors.java index 58db82bb773..7ab677384f7 100644 --- a/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/diagnostic/SerializationErrors.java +++ b/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/diagnostic/SerializationErrors.java @@ -16,7 +16,7 @@ import static org.jetbrains.kotlin.diagnostics.Severity.WARNING; public interface SerializationErrors { DiagnosticFactory2 INLINE_CLASSES_NOT_SUPPORTED = DiagnosticFactory2.create(ERROR); DiagnosticFactory0 PLUGIN_IS_NOT_ENABLED = DiagnosticFactory0.create(WARNING); - DiagnosticFactory0 LOCAL_CLASSES_NOT_SUPPORTED = DiagnosticFactory0.create(ERROR); + DiagnosticFactory0 ANONYMOUS_OBJECTS_NOT_SUPPORTED = DiagnosticFactory0.create(ERROR); DiagnosticFactory0 INNER_CLASSES_NOT_SUPPORTED = DiagnosticFactory0.create(ERROR); DiagnosticFactory0 EXPLICIT_SERIALIZABLE_IS_REQUIRED = DiagnosticFactory0.create(WARNING); @@ -28,6 +28,7 @@ public interface SerializationErrors { DiagnosticFactory1 SERIALIZER_NOT_FOUND = DiagnosticFactory1.create(ERROR); DiagnosticFactory2 SERIALIZER_NULLABILITY_INCOMPATIBLE = DiagnosticFactory2.create(ERROR); DiagnosticFactory3 SERIALIZER_TYPE_INCOMPATIBLE = DiagnosticFactory3.create(WARNING); + DiagnosticFactory1 LOCAL_SERIALIZER_USAGE = DiagnosticFactory1.create(ERROR); DiagnosticFactory0 TRANSIENT_MISSING_INITIALIZER = DiagnosticFactory0.create(ERROR); DiagnosticFactory0 TRANSIENT_IS_REDUNDANT = DiagnosticFactory0.create(WARNING); 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 c4991ffb270..af9d6371fe1 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 @@ -29,6 +29,7 @@ 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 = Slices.createSimpleSlice() @@ -110,7 +111,7 @@ open class SerializationPluginDeclarationChecker : DeclarationChecker { return false } - if (!descriptor.annotations.hasAnnotation(SerializationAnnotations.serializableAnnotationFqName)) return false + if (!descriptor.annotations.hasAnnotation(serializableAnnotationFqName)) return false if (!serializationPluginEnabledOn(descriptor)) { trace.reportOnSerializableAnnotation(descriptor, SerializationErrors.PLUGIN_IS_NOT_ENABLED) @@ -118,8 +119,8 @@ open class SerializationPluginDeclarationChecker : DeclarationChecker { } - if (DescriptorUtils.isLocal(descriptor)) { - trace.reportOnSerializableAnnotation(descriptor, SerializationErrors.LOCAL_CLASSES_NOT_SUPPORTED) + if (descriptor.isAnonymousObjectOrContained) { + trace.reportOnSerializableAnnotation(descriptor, SerializationErrors.ANONYMOUS_OBJECTS_NOT_SUPPORTED) return false } @@ -140,7 +141,11 @@ open class SerializationPluginDeclarationChecker : DeclarationChecker { } return false } - if (!descriptor.hasSerializableAnnotationWithoutArgs) return false + if (!descriptor.hasSerializableAnnotationWithoutArgs) { + // defined custom serializer + checkClassWithCustomSerializer(descriptor, declaration, trace) + return false + } if (descriptor.serializableAnnotationIsUseless) { trace.reportOnSerializableAnnotation(descriptor, SerializationErrors.SERIALIZABLE_ANNOTATION_IGNORED) @@ -158,9 +163,27 @@ open class SerializationPluginDeclarationChecker : DeclarationChecker { return true } + private fun checkClassWithCustomSerializer(descriptor: ClassDescriptor, declaration: KtDeclaration, trace: BindingTrace) { + val annotationPsi = descriptor.findSerializableAnnotationDeclaration() + checkCustomSerializerMatch(descriptor.module, descriptor.defaultType, descriptor, annotationPsi, trace, declaration) + checkCustomSerializerIsNotLocal(descriptor.module, descriptor, trace, declaration) + } + + private val ClassDescriptor.isAnonymousObjectOrContained: Boolean + get() { + var current: DeclarationDescriptor? = this + while (current != null) { + if (DescriptorUtils.isAnonymousObject(current)) { + return true + } + current = current.containingDeclaration + } + return false + } + private fun ClassDescriptor.isSerializableEnumWithMissingSerializer(): Boolean { if (kind != ClassKind.ENUM_CLASS) return false - if (annotations.hasAnnotation(SerializationAnnotations.serializableAnnotationFqName)) return false + if (annotations.hasAnnotation(serializableAnnotationFqName)) return false if (annotations.hasAnySerialAnnotation) return true return enumEntries().any { (it.annotations.hasAnySerialAnnotation) } } @@ -173,7 +196,7 @@ open class SerializationPluginDeclarationChecker : DeclarationChecker { } private fun buildSerializableProperties(descriptor: ClassDescriptor, trace: BindingTrace): SerializableProperties? { - if (!descriptor.annotations.hasAnnotation(SerializationAnnotations.serializableAnnotationFqName)) return null + if (!descriptor.annotations.hasAnnotation(serializableAnnotationFqName)) return null if (!descriptor.isInternalSerializable) return null if (descriptor.hasCompanionObjectAsSerializer) return null // customized by user @@ -234,6 +257,7 @@ open class SerializationPluginDeclarationChecker : DeclarationChecker { if (serializer != null) { val element = ktType?.typeElement checkCustomSerializerMatch(it.module, it.type, it.descriptor, element, trace, propertyPsi) + checkCustomSerializerIsNotLocal(it.module, it.descriptor, trace, propertyPsi) checkSerializerNullability(it.type, serializer.defaultType, element, trace, propertyPsi) generatorContextForAnalysis.checkTypeArguments(it.module, it.type, element, trace, propertyPsi) } else { @@ -286,6 +310,7 @@ open class SerializationPluginDeclarationChecker : DeclarationChecker { val serializer = findTypeSerializerOrContextUnchecked(module, type) if (serializer != null) { checkCustomSerializerMatch(module, type, type, element, trace, fallbackElement) + checkCustomSerializerIsNotLocal(module, type, trace, fallbackElement) checkSerializerNullability(type, serializer.defaultType, element, trace, fallbackElement) checkTypeArguments(module, type, element, trace, fallbackElement) } else { @@ -315,6 +340,27 @@ open class SerializationPluginDeclarationChecker : DeclarationChecker { ) } + private fun checkCustomSerializerIsNotLocal( + module: ModuleDescriptor, + declaration: Annotated, + trace: BindingTrace, + declarationElement: PsiElement + ) { + val serializerType = declaration.annotations.serializableWith(module) ?: return + val serializerDescriptor = serializerType.toClassDescriptor ?: return + + if (DescriptorUtils.isLocal(serializerDescriptor)) { + val element = declaration.findSerializableAnnotationDeclaration() ?: declarationElement + + trace.report( + SerializationErrors.LOCAL_SERIALIZER_USAGE.on( + element, + serializerType + ) + ) + } + } + private fun checkSerializerNullability( classType: KotlinType, serializerType: KotlinType, @@ -344,4 +390,4 @@ open class SerializationPluginDeclarationChecker : DeclarationChecker { } internal val ClassDescriptor.serializableAnnotationIsUseless: Boolean - get() = hasSerializableAnnotationWithoutArgs && !isInternalSerializable && !hasCompanionObjectAsSerializer && !isSerializableEnum() \ No newline at end of file + get() = hasSerializableAnnotationWithoutArgs && !isInternalSerializable && !hasCompanionObjectAsSerializer && !isSerializableEnum() diff --git a/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/diagnostic/SerializationPluginErrorsRendering.kt b/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/diagnostic/SerializationPluginErrorsRendering.kt index 8fb31ceeb9f..255944a71dd 100644 --- a/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/diagnostic/SerializationPluginErrorsRendering.kt +++ b/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/diagnostic/SerializationPluginErrorsRendering.kt @@ -26,8 +26,8 @@ object SerializationPluginErrorsRendering : DefaultErrorMessages.Extension { "Make sure that you've setup your buildscript correctly and re-import project." ) MAP.put( - SerializationErrors.LOCAL_CLASSES_NOT_SUPPORTED, - "Local classes and anonymous objects can not be serializable." + SerializationErrors.ANONYMOUS_OBJECTS_NOT_SUPPORTED, + "Anonymous objects or contained in it classes can not be serializable." ) MAP.put( SerializationErrors.INNER_CLASSES_NOT_SUPPORTED, @@ -74,6 +74,11 @@ object SerializationPluginErrorsRendering : DefaultErrorMessages.Extension { Renderers.RENDER_TYPE, Renderers.RENDER_TYPE ) + MAP.put( + SerializationErrors.LOCAL_SERIALIZER_USAGE, + "Class ''{0}'' can't be used as a serializer since it is local", + Renderers.RENDER_TYPE + ) MAP.put( SerializationErrors.TRANSIENT_MISSING_INITIALIZER, "This property is marked as @Transient and therefore must have an initializing expression" diff --git a/plugins/kotlin-serialization/kotlin-serialization-compiler/testData/diagnostics/LocalAndAnonymous.kt b/plugins/kotlin-serialization/kotlin-serialization-compiler/testData/diagnostics/LocalAndAnonymous.kt index be4e375a28a..f5b97b04aa0 100644 --- a/plugins/kotlin-serialization/kotlin-serialization-compiler/testData/diagnostics/LocalAndAnonymous.kt +++ b/plugins/kotlin-serialization/kotlin-serialization-compiler/testData/diagnostics/LocalAndAnonymous.kt @@ -1,17 +1,42 @@ -// !DIAGNOSTICS: -UNUSED_PARAMETER,-UNUSED_VARIABLE +// !DIAGNOSTICS: -UNUSED_PARAMETER,-UNUSED_VARIABLE,-EXPERIMENTAL_API_USAGE_ERROR,-EXPERIMENTAL_API_USAGE // WITH_RUNTIME // SKIP_TXT import kotlinx.serialization.* +import kotlinx.serialization.descriptors.* +import kotlinx.serialization.encoding.* fun container() { - @Serializable - class X + @Serializable + class X // local classes are allowed - val y = @Serializable object {} + val y = @Serializable object { + fun inObjectFun() { + @Serializable + class X // local classes in anonymous object functions are not allowed + } + } + + + class LocalSerializer : KSerializer { + override val descriptor: SerialDescriptor = buildSerialDescriptor("tmp", PrimitiveKind.INT) + override fun serialize(encoder: Encoder, value: Any?) { + encoder.encodeNull() + } + + override fun deserialize(decoder: Decoder): Any? { + return decoder.decodeNull() + } + } + + @Serializable + class WithLocalSerializerInProperty(@Serializable(with = LocalSerializer::class) val x: Any?) + + @Serializable(with = LocalSerializer::class) + data class WithLocalSerializer(val i: Int) } -val topLevelAnon = @Serializable object {} +val topLevelAnon = @Serializable object {} @Serializable class A { @Serializable class B // nested classes are allowed @@ -19,4 +44,4 @@ val topLevelAnon = @Serializable object {} @Serializable inner class C // inner classes are not @Serializable object F {} // regular named object, OK -} \ No newline at end of file +} diff --git a/plugins/kotlin-serialization/kotlin-serialization-compiler/testData/diagnostics/SerializerTypeIncompatible.kt b/plugins/kotlin-serialization/kotlin-serialization-compiler/testData/diagnostics/SerializerTypeIncompatible.kt index d604bcbb3c4..be69e658592 100644 --- a/plugins/kotlin-serialization/kotlin-serialization-compiler/testData/diagnostics/SerializerTypeIncompatible.kt +++ b/plugins/kotlin-serialization/kotlin-serialization-compiler/testData/diagnostics/SerializerTypeIncompatible.kt @@ -14,6 +14,9 @@ object BazSerializer: KSerializer @Serializer(forClass = Baz::class) object NullableBazSerializer: KSerializer +@Serializable(with = BazSerializer::class) +class Biz(val i: Int) + @Serializable class Foo(@Serializable(with = BazSerializer::class) val i: Bar)