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:
Leonid Startsev
2019-05-30 20:05:30 +03:00
parent dc79b99dd2
commit 3792c44378
4 changed files with 67 additions and 43 deletions
@@ -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
@@ -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);
@@ -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
)
}
@@ -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,