Make short path for non-serializable classes and other improvements:
Adjust some error messages Add 'fallbackElement' to report on when property is implicitly typed Split findTypeSerializerOrContext into checked and unchecked versions
This commit is contained in:
+17
-4
@@ -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
|
||||
|
||||
+3
-2
@@ -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<KtAnnotationEntry> NON_SERIALIZABLE_PARENT_MUST_HAVE_NOARG_CTOR = DiagnosticFactory0.create(ERROR);
|
||||
DiagnosticFactory0<KtAnnotationEntry> PRIMARY_CONSTRUCTOR_PARAMETER_IS_NOT_A_PROPERTY = DiagnosticFactory0.create(ERROR);
|
||||
DiagnosticFactory1<KtAnnotationEntry, String> DUPLICATE_SERIAL_NAME = DiagnosticFactory1.create(ERROR);
|
||||
DiagnosticFactory0<PsiElement> SERIALIZER_NOT_FOUND = DiagnosticFactory0.create(ERROR);
|
||||
DiagnosticFactory1<PsiElement, KotlinType> SERIALIZER_NULLABILITY_INCOMPATIBLE = DiagnosticFactory1.create(ERROR);
|
||||
DiagnosticFactory1<PsiElement, KotlinType> SERIALIZER_NOT_FOUND = DiagnosticFactory1.create(ERROR);
|
||||
DiagnosticFactory2<PsiElement, KotlinType, KotlinType> SERIALIZER_NULLABILITY_INCOMPATIBLE = DiagnosticFactory2.create(ERROR);
|
||||
DiagnosticFactory0<PsiElement> TRANSIENT_MISSING_INITIALIZER = DiagnosticFactory0.create(ERROR);
|
||||
|
||||
DiagnosticFactory0<PsiElement> TRANSIENT_IS_REDUNDANT = DiagnosticFactory0.create(WARNING);
|
||||
|
||||
+36
-28
@@ -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
|
||||
)
|
||||
}
|
||||
|
||||
+11
-9
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user