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 b1689955a59..0cf63f6c222 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 @@ -18,6 +18,7 @@ package org.jetbrains.kotlinx.serialization.compiler.backend.common import com.intellij.psi.PsiElement import org.jetbrains.kotlin.builtins.KotlinBuiltIns +import org.jetbrains.kotlin.codegen.CompilationException import org.jetbrains.kotlin.descriptors.* import org.jetbrains.kotlin.descriptors.annotations.Annotations import org.jetbrains.kotlin.js.descriptorUtils.getJetTypeFqName @@ -104,20 +105,32 @@ fun analyzeSpecialSerializers( else -> null } -fun AbstractSerialGenerator.findTypeSerializerOrContext( +fun AbstractSerialGenerator.findTypeSerializerOrContextUnchecked( module: ModuleDescriptor, - kType: KotlinType, - sourceElement: PsiElement? = null + kType: KotlinType ): ClassDescriptor? { val annotations = kType.annotations if (kType.isTypeParameter()) return null - if (kType.isMarkedNullable) return findTypeSerializerOrContext(module, kType.makeNotNullable(), sourceElement) + if (kType.isMarkedNullable) return findTypeSerializerOrContextUnchecked(module, kType.makeNotNullable()) annotations.serializableWith(module)?.let { return it.toClassDescriptor } additionalSerializersInScopeOfCurrentFile[kType]?.let { return it } if (kType in contextualKClassListInCurrentFile) return module.getClassFromSerializationPackage(SpecialBuiltins.contextSerializer) return analyzeSpecialSerializers(module, annotations) ?: findTypeSerializer(module, kType) } +fun AbstractSerialGenerator.findTypeSerializerOrContext( + module: ModuleDescriptor, + kType: KotlinType, + sourceElement: PsiElement? = null +): ClassDescriptor { + return findTypeSerializerOrContextUnchecked(module, kType) ?: throw CompilationException( + "Serializer for element of type $kType has not been found.\n" + + "To use context serializer as fallback, explicitly annotate element with @ContextualSerialization", + null, + sourceElement + ) +} + fun findTypeSerializer(module: ModuleDescriptor, kType: KotlinType): ClassDescriptor? { val userOverride = kType.overridenSerializer if (userOverride != null) return userOverride.toClassDescriptor 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 1127ed7811a..a5693283302 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 @@ -8,6 +8,7 @@ package org.jetbrains.kotlinx.serialization.compiler.diagnostic; import com.intellij.psi.PsiElement; import org.jetbrains.kotlin.diagnostics.DiagnosticFactory0; import org.jetbrains.kotlin.diagnostics.DiagnosticFactory1; +import org.jetbrains.kotlin.diagnostics.DiagnosticFactory2; import org.jetbrains.kotlin.diagnostics.Errors; import org.jetbrains.kotlin.psi.KtAnnotationEntry; import org.jetbrains.kotlin.types.KotlinType; @@ -22,8 +23,8 @@ public interface SerializationErrors { DiagnosticFactory0 NON_SERIALIZABLE_PARENT_MUST_HAVE_NOARG_CTOR = DiagnosticFactory0.create(ERROR); DiagnosticFactory0 PRIMARY_CONSTRUCTOR_PARAMETER_IS_NOT_A_PROPERTY = DiagnosticFactory0.create(ERROR); DiagnosticFactory1 DUPLICATE_SERIAL_NAME = DiagnosticFactory1.create(ERROR); - DiagnosticFactory0 SERIALIZER_NOT_FOUND = DiagnosticFactory0.create(ERROR); - DiagnosticFactory1 SERIALIZER_NULLABILITY_INCOMPATIBLE = DiagnosticFactory1.create(ERROR); + DiagnosticFactory1 SERIALIZER_NOT_FOUND = DiagnosticFactory1.create(ERROR); + DiagnosticFactory2 SERIALIZER_NULLABILITY_INCOMPATIBLE = DiagnosticFactory2.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 7567c8ac3d5..a2a85ce4786 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 @@ -5,6 +5,7 @@ package org.jetbrains.kotlinx.serialization.compiler.diagnostic +import com.intellij.psi.PsiElement import org.jetbrains.kotlin.descriptors.ClassDescriptor import org.jetbrains.kotlin.descriptors.DeclarationDescriptor import org.jetbrains.kotlin.descriptors.ModuleDescriptor @@ -23,9 +24,8 @@ import org.jetbrains.kotlin.types.KotlinType import org.jetbrains.kotlin.types.typeUtil.supertypes import org.jetbrains.kotlin.util.slicedMap.Slices import org.jetbrains.kotlin.util.slicedMap.WritableSlice -import org.jetbrains.kotlinx.serialization.compiler.backend.common.AbstractSerialGenerator +import org.jetbrains.kotlinx.serialization.compiler.backend.common.* import org.jetbrains.kotlinx.serialization.compiler.backend.common.bodyPropertiesDescriptorsMap -import org.jetbrains.kotlinx.serialization.compiler.backend.common.findTypeSerializerOrContext import org.jetbrains.kotlinx.serialization.compiler.backend.common.primaryConstructorPropertiesDescriptorsMap import org.jetbrains.kotlinx.serialization.compiler.resolve.* @@ -35,30 +35,34 @@ class SerializationPluginDeclarationChecker : DeclarationChecker { override fun check(declaration: KtDeclaration, descriptor: DeclarationDescriptor, context: DeclarationCheckerContext) { if (descriptor !is ClassDescriptor) return - checkCanBeSerializedInternally(descriptor, context.trace) + if (!canBeSerializedInternally(descriptor, context.trace)) return + if (declaration !is KtPureClassOrObject) return val props = buildSerializableProperties(descriptor, context.trace) ?: return - checkTransients(declaration as KtPureClassOrObject, context.trace) + checkTransients(declaration, context.trace) analyzePropertiesSerializers(context.trace, descriptor, props.serializableProperties) } - private fun checkCanBeSerializedInternally(descriptor: ClassDescriptor, trace: BindingTrace) { - if (!descriptor.annotations.hasAnnotation(SerializationAnnotations.serializableAnnotationFqName)) return + private fun canBeSerializedInternally(descriptor: ClassDescriptor, trace: BindingTrace): Boolean { + if (!descriptor.annotations.hasAnnotation(SerializationAnnotations.serializableAnnotationFqName)) return false if (descriptor.isInline) { trace.reportOnSerializableAnnotation(descriptor, SerializationErrors.INLINE_CLASSES_NOT_SUPPORTED) - return + return false } - if (!descriptor.hasSerializableAnnotationWithoutArgs) return + if (!descriptor.hasSerializableAnnotationWithoutArgs) return false if (!descriptor.isInternalSerializable && !descriptor.hasCompanionObjectAsSerializer) { trace.reportOnSerializableAnnotation(descriptor, SerializationErrors.SERIALIZABLE_ANNOTATION_IGNORED) + return false } // check that we can instantiate supertype 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) + return false } + return true } private fun buildSerializableProperties(descriptor: ClassDescriptor, trace: BindingTrace): SerializableProperties? { @@ -123,13 +127,14 @@ class SerializationPluginDeclarationChecker : DeclarationChecker { val generatorContextForAnalysis = object : AbstractSerialGenerator(trace.bindingContext, serializableClass) {} props.forEach { val serializer = it.serializableWith?.toClassDescriptor - val ktType = (it.descriptor.findPsi() as? KtCallableDeclaration)?.typeReference ?: return@forEach + val propertyPsi = it.descriptor.findPsi() ?: return@forEach + val ktType = (propertyPsi as? KtCallableDeclaration)?.typeReference if (serializer != null) { - val element = ktType.typeElement ?: return - checkSerializerNullability(it.type, serializer.defaultType, element, trace) - generatorContextForAnalysis.checkTypeArguments(it.module, it.type, element, trace) + val element = ktType?.typeElement + checkSerializerNullability(it.type, serializer.defaultType, element, trace, propertyPsi) + generatorContextForAnalysis.checkTypeArguments(it.module, it.type, element, trace, propertyPsi) } else { - generatorContextForAnalysis.checkType(it.module, it.type, ktType, trace) + generatorContextForAnalysis.checkType(it.module, it.type, ktType, trace, propertyPsi) } } } @@ -137,33 +142,35 @@ class SerializationPluginDeclarationChecker : DeclarationChecker { private fun AbstractSerialGenerator.checkTypeArguments( module: ModuleDescriptor, type: KotlinType, - element: KtTypeElement, - trace: BindingTrace + element: KtTypeElement?, + trace: BindingTrace, + fallbackElement: PsiElement ) { - type.arguments.forEachIndexed { i, it -> checkType(module, it.type, element.typeArgumentsAsTypes[i], trace) } + type.arguments.forEachIndexed { i, it -> checkType(module, it.type, element?.typeArgumentsAsTypes?.get(i), trace, fallbackElement) } } private fun AbstractSerialGenerator.checkType( module: ModuleDescriptor, type: KotlinType, - ktType: KtTypeReference, - trace: BindingTrace + ktType: KtTypeReference?, + trace: BindingTrace, + fallbackElement: PsiElement ) { - if (type.genericIndex != null) return - val element = ktType.typeElement ?: return + if (type.genericIndex != null) return // type arguments always have serializer stored in class' field + val element = ktType?.typeElement if (type.isInlineClassType()) { trace.reportFromPlugin( - SerializationErrors.INLINE_CLASSES_NOT_SUPPORTED.on(element), + SerializationErrors.INLINE_CLASSES_NOT_SUPPORTED.on(element ?: fallbackElement), SerializationPluginErrorsRendering ) } - val serializer = findTypeSerializerOrContext(module, type) + val serializer = findTypeSerializerOrContextUnchecked(module, type) if (serializer != null) { - checkSerializerNullability(type, serializer.defaultType, element, trace) - checkTypeArguments(module, type, element, trace) + checkSerializerNullability(type, serializer.defaultType, element, trace, fallbackElement) + checkTypeArguments(module, type, element, trace, fallbackElement) } else { trace.reportFromPlugin( - SerializationErrors.SERIALIZER_NOT_FOUND.on(element), + SerializationErrors.SERIALIZER_NOT_FOUND.on(element ?: fallbackElement, type), SerializationPluginErrorsRendering ) } @@ -172,15 +179,16 @@ class SerializationPluginDeclarationChecker : DeclarationChecker { private fun checkSerializerNullability( classType: KotlinType, serializerType: KotlinType, - element: KtTypeElement, - trace: BindingTrace + element: KtTypeElement?, + trace: BindingTrace, + fallbackElement: PsiElement ) { // @Serializable annotation has proper signature so this error would be caught in type checker val castedToKSerial = serializerType.supertypes().find { isKSerializer(it) } ?: return if (!classType.isMarkedNullable && castedToKSerial.arguments.first().type.isMarkedNullable) trace.reportFromPlugin( - SerializationErrors.SERIALIZER_NULLABILITY_INCOMPATIBLE.on(element, serializerType), + SerializationErrors.SERIALIZER_NULLABILITY_INCOMPATIBLE.on(element ?: fallbackElement, serializerType, classType), SerializationPluginErrorsRendering ) } 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 db299fd62a2..e966d4ff1a8 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 @@ -16,39 +16,41 @@ object SerializationPluginErrorsRendering : DefaultErrorMessages.Extension { init { MAP.put( SerializationErrors.INLINE_CLASSES_NOT_SUPPORTED, - "Inline classes are not supported by serialization framework yet." + "Inline classes are not supported by kotlinx.serialization yet" ) MAP.put( SerializationErrors.SERIALIZABLE_ANNOTATION_IGNORED, - "@Serializable annotation would be ignored because it is impossible to serialize automatically interfaces or enums. " + + "@Serializable annotation is ignored because it is impossible to serialize automatically interfaces or enums" + "Provide serializer manually via e.g. companion object" ) MAP.put( SerializationErrors.NON_SERIALIZABLE_PARENT_MUST_HAVE_NOARG_CTOR, - "Impossible to make this class serializable because its parent is not serializable and does not have exactly one constructor without arguments" + "Impossible to make this class serializable because its parent is not serializable and does not have exactly one constructor without parameters" ) MAP.put( SerializationErrors.PRIMARY_CONSTRUCTOR_PARAMETER_IS_NOT_A_PROPERTY, - "This class is not serializable automatically because it has primary constructor parameters which are not properties." + "This class is not serializable automatically because it has primary constructor parameters of which are not properties" ) MAP.put( SerializationErrors.DUPLICATE_SERIAL_NAME, - "Serializable class has duplicate serial name of property ''{0}'', either in it or its supertypes.", + "Serializable class has duplicate serial name of property ''{0}'', either in the class itself or its supertypes", Renderers.STRING ) MAP.put( SerializationErrors.SERIALIZER_NOT_FOUND, - "Serializer has not been found for type of this property. " + - "To use context serializer as fallback, explicitly annotate element with @ContextualSerialization" + "Serializer has not been found for type ''{0}''. " + + "To use context serializer as fallback, explicitly annotate type or property with @ContextualSerialization", + Renderers.RENDER_TYPE ) MAP.put( SerializationErrors.SERIALIZER_NULLABILITY_INCOMPATIBLE, - "This type is non-nullable and therefore can not be serialized with serializer for nullable type ''{0}''", + "Type ''{1}'' is non-nullable and therefore can not be serialized with serializer for nullable type ''{0}''", + Renderers.RENDER_TYPE, Renderers.RENDER_TYPE ) MAP.put( SerializationErrors.TRANSIENT_MISSING_INITIALIZER, - "This property is marked is @Transient and therefore must have an initializing expression" + "This property is marked as @Transient and therefore must have an initializing expression" ) MAP.put( SerializationErrors.TRANSIENT_IS_REDUNDANT,