Report an error on @Serializable inline classes and missing @Transient initializers

This commit is contained in:
Leonid Startsev
2019-05-29 17:10:05 +03:00
parent e6d96f1de9
commit fe1f36bc02
6 changed files with 74 additions and 9 deletions
@@ -18,7 +18,6 @@ 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
@@ -182,20 +181,23 @@ fun findEnumTypeSerializer(module: ModuleDescriptor, kType: KotlinType): ClassDe
return if (classDescriptor.kind == ClassKind.ENUM_CLASS) module.findClassAcrossModuleDependencies(enumSerializerId) else null
}
fun KtPureClassOrObject.bodyPropertiesDescriptorsMap(bindingContext: BindingContext): Map<PropertyDescriptor, KtProperty> = declarations
internal fun KtPureClassOrObject.bodyPropertiesDescriptorsMap(
bindingContext: BindingContext,
filterUninitialized: Boolean = true
): Map<PropertyDescriptor, KtProperty> = declarations
.asSequence()
.filterIsInstance<KtProperty>()
// can filter here because it's impossible to create body property w/ backing field w/o explicit delegating or initializing
.filter { it.delegateExpressionOrInitializer != null }
.filter { if (filterUninitialized) it.delegateExpressionOrInitializer != null else true }
.associateBy { (bindingContext[BindingContext.DECLARATION_TO_DESCRIPTOR, it] as? PropertyDescriptor)!! }
fun KtPureClassOrObject.primaryPropertiesDescriptorsMap(bindingContext: BindingContext): Map<PropertyDescriptor, KtParameter> =
internal fun KtPureClassOrObject.primaryConstructorPropertiesDescriptorsMap(bindingContext: BindingContext): Map<PropertyDescriptor, KtParameter> =
primaryConstructorParameters
.asSequence()
.filter { it.hasValOrVar() }
.associateBy { bindingContext[BindingContext.PRIMARY_CONSTRUCTOR_PARAMETER, it]!! }
fun KtPureClassOrObject.anonymousInitializers() = declarations
internal fun KtPureClassOrObject.anonymousInitializers() = declarations
.asSequence()
.filterIsInstance<KtAnonymousInitializer>()
.mapNotNull { it.body }
@@ -32,7 +32,7 @@ import org.jetbrains.kotlin.types.KotlinType
import org.jetbrains.kotlin.types.typeUtil.isTypeParameter
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.primaryPropertiesDescriptorsMap
import org.jetbrains.kotlinx.serialization.compiler.backend.common.primaryConstructorPropertiesDescriptorsMap
import org.jetbrains.kotlinx.serialization.compiler.backend.jvm.referenceArraySerializerId
import org.jetbrains.kotlinx.serialization.compiler.resolve.*
@@ -175,7 +175,7 @@ fun TranslationContext.buildInitializersRemapping(
superClass: ClassDescriptor?
): Map<PropertyDescriptor, KtExpression?> {
val myMap = (forClass.bodyPropertiesDescriptorsMap(bindingContext()).mapValues { it.value.delegateExpressionOrInitializer } +
forClass.primaryPropertiesDescriptorsMap(bindingContext()).mapValues { it.value.defaultValue })
forClass.primaryConstructorPropertiesDescriptorsMap(bindingContext()).mapValues { it.value.defaultValue })
val parentPsi = superClass?.takeIf { it.isInternalSerializable }?.findPsi() as? KtPureClassOrObject ?: return myMap
val parentMap = buildInitializersRemapping(parentPsi, superClass.getSuperClassNotAny())
return myMap + parentMap
@@ -57,7 +57,7 @@ class SerializableCodegenImpl(
private val descToProps = classCodegen.myClass.bodyPropertiesDescriptorsMap(classCodegen.bindingContext)
private val paramsToProps: Map<PropertyDescriptor, KtParameter> =
classCodegen.myClass.primaryPropertiesDescriptorsMap(classCodegen.bindingContext)
classCodegen.myClass.primaryConstructorPropertiesDescriptorsMap(classCodegen.bindingContext)
private fun getProp(prop: SerializableProperty) = descToProps[prop.descriptor]
private fun getParam(prop: SerializableProperty) = paramsToProps[prop.descriptor]
@@ -16,12 +16,16 @@ import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.resolve.BindingTrace
import org.jetbrains.kotlin.resolve.checkers.DeclarationChecker
import org.jetbrains.kotlin.resolve.checkers.DeclarationCheckerContext
import org.jetbrains.kotlin.resolve.hasBackingField
import org.jetbrains.kotlin.resolve.isInlineClassType
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.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.*
internal val SERIALIZABLE_PROPERTIES: WritableSlice<ClassDescriptor, SerializableProperties> = Slices.createSimpleSlice()
@@ -33,10 +37,17 @@ class SerializationPluginDeclarationChecker : DeclarationChecker {
checkCanBeSerializedInternally(descriptor, context.trace)
val props = buildSerializableProperties(descriptor, context.trace) ?: return
checkTransients(declaration as KtPureClassOrObject, context.trace)
analyzePropertiesSerializers(context.trace, descriptor, props.serializableProperties)
}
private fun checkCanBeSerializedInternally(descriptor: ClassDescriptor, trace: BindingTrace) {
if (!descriptor.annotations.hasAnnotation(SerializationAnnotations.serializableAnnotationFqName)) return
if (descriptor.isInline) {
trace.reportOnSerializableAnnotation(descriptor, SerializationErrors.INLINE_CLASSES_NOT_SUPPORTED)
return
}
if (!descriptor.hasSerializableAnnotationWithoutArgs) return
if (!descriptor.isInternalSerializable && !descriptor.hasCompanionObjectAsSerializer) {
@@ -73,6 +84,36 @@ class SerializationPluginDeclarationChecker : DeclarationChecker {
return props
}
private fun checkTransients(declaration: KtPureClassOrObject, trace: BindingTrace) {
val propertiesMap: Map<PropertyDescriptor, KtDeclaration> =
declaration.bodyPropertiesDescriptorsMap(
trace.bindingContext,
filterUninitialized = false
) + declaration.primaryConstructorPropertiesDescriptorsMap(trace.bindingContext)
propertiesMap.forEach { (descriptor, declaration) ->
val isInitialized = declarationHasInitializer(declaration)
val isMarkedTransient = descriptor.annotations.serialTransient
val hasBackingField = descriptor.hasBackingField(trace.bindingContext)
if (!hasBackingField && isMarkedTransient)
trace.reportFromPlugin(
SerializationErrors.TRANSIENT_IS_REDUNDANT.on(declaration),
SerializationPluginErrorsRendering
)
if (isMarkedTransient && !isInitialized && hasBackingField)
trace.reportFromPlugin(
SerializationErrors.TRANSIENT_MISSING_INITIALIZER.on(declaration),
SerializationPluginErrorsRendering
)
}
}
private fun declarationHasInitializer(declaration: KtDeclaration): Boolean = when (declaration) {
is KtParameter -> declaration.hasDefaultValue()
is KtProperty -> declaration.hasDelegateExpressionOrInitializer()
else -> false
}
private fun analyzePropertiesSerializers(trace: BindingTrace, serializableClass: ClassDescriptor, props: List<SerializableProperty>) {
val generatorContextForAnalysis = object : AbstractSerialGenerator(trace.bindingContext, serializableClass) {}
props.forEach {
@@ -106,6 +147,12 @@ class SerializationPluginDeclarationChecker : DeclarationChecker {
) {
if (type.genericIndex != null) return
val element = ktType.typeElement ?: return
if (type.isInlineClassType()) {
trace.reportFromPlugin(
SerializationErrors.INLINE_CLASSES_NOT_SUPPORTED.on(element),
SerializationPluginErrorsRendering
)
}
val serializer = findTypeSerializerOrContext(module, type)
if (serializer != null) {
checkSerializerNullability(type, serializer.defaultType, element, trace)
@@ -138,7 +185,7 @@ class SerializationPluginDeclarationChecker : DeclarationChecker {
findSerializableAnnotationDeclaration()?.let(report)
}
private fun BindingTrace.reportOnSerializableAnnotation(descriptor: ClassDescriptor, error: DiagnosticFactory0<KtAnnotationEntry>) {
private fun BindingTrace.reportOnSerializableAnnotation(descriptor: ClassDescriptor, error: DiagnosticFactory0<in KtAnnotationEntry>) {
descriptor.safeReport { e ->
reportFromPlugin(
error.on(e),
@@ -13,13 +13,17 @@ import org.jetbrains.kotlin.psi.KtAnnotationEntry;
import org.jetbrains.kotlin.types.KotlinType;
import static org.jetbrains.kotlin.diagnostics.Severity.ERROR;
import static org.jetbrains.kotlin.diagnostics.Severity.WARNING;
public interface SerializationErrors {
DiagnosticFactory0<KtAnnotationEntry> SERIALIZABLE_ANNOTATION_IGNORED = DiagnosticFactory0.create(ERROR);
DiagnosticFactory0<PsiElement> INLINE_CLASSES_NOT_SUPPORTED = 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);
DiagnosticFactory0<PsiElement> TRANSIENT_MISSING_INITIALIZER = DiagnosticFactory0.create(ERROR);
DiagnosticFactory0<PsiElement> TRANSIENT_IS_REDUNDANT = DiagnosticFactory0.create(WARNING);
@SuppressWarnings("UnusedDeclaration")
Object _initializer = new Object() {
@@ -38,5 +38,17 @@ object SerializationPluginErrorsRendering : DefaultErrorMessages.Extension {
"This type is not-nullable and therefore can not be serialized with serializer for nullable type ''{0}''",
Renderers.RENDER_TYPE
)
MAP.put(
SerializationErrors.TRANSIENT_MISSING_INITIALIZER,
"This property is marked is @Transient and therefore must have an initializing expression"
)
MAP.put(
SerializationErrors.TRANSIENT_IS_REDUNDANT,
"This property does not have backing field which makes it non-serializable and therefore @Transient is redundant"
)
MAP.put(
SerializationErrors.INLINE_CLASSES_NOT_SUPPORTED,
"Inline classes are not supported by serialization framework yet."
)
}
}