From fe1f36bc020e9a37fef53d3455835b3666c97d34 Mon Sep 17 00:00:00 2001 From: Leonid Startsev Date: Wed, 29 May 2019 17:10:05 +0300 Subject: [PATCH] Report an error on @Serializable inline classes and missing @Transient initializers --- .../compiler/backend/common/TypeUtil.kt | 12 +++-- .../compiler/backend/js/JsCodegenUtil.kt | 4 +- .../backend/jvm/SerializableCodegenImpl.kt | 2 +- .../diagnostic/DeclarationCheckers.kt | 49 ++++++++++++++++++- .../diagnostic/SerializationErrors.java | 4 ++ .../SerializationPluginErrorsRendering.kt | 12 +++++ 6 files changed, 74 insertions(+), 9 deletions(-) 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 e2f76693487..bc03ba50693 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,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 = declarations +internal fun KtPureClassOrObject.bodyPropertiesDescriptorsMap( + bindingContext: BindingContext, + filterUninitialized: Boolean = true +): Map = declarations .asSequence() .filterIsInstance() // 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 = +internal fun KtPureClassOrObject.primaryConstructorPropertiesDescriptorsMap(bindingContext: BindingContext): Map = primaryConstructorParameters .asSequence() .filter { it.hasValOrVar() } .associateBy { bindingContext[BindingContext.PRIMARY_CONSTRUCTOR_PARAMETER, it]!! } -fun KtPureClassOrObject.anonymousInitializers() = declarations +internal fun KtPureClassOrObject.anonymousInitializers() = declarations .asSequence() .filterIsInstance() .mapNotNull { it.body } diff --git a/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/js/JsCodegenUtil.kt b/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/js/JsCodegenUtil.kt index 0bf77313acc..86ef0d1d742 100644 --- a/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/js/JsCodegenUtil.kt +++ b/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/js/JsCodegenUtil.kt @@ -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 { 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 diff --git a/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/jvm/SerializableCodegenImpl.kt b/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/jvm/SerializableCodegenImpl.kt index 64fcbc8cc14..19f2299bac4 100644 --- a/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/jvm/SerializableCodegenImpl.kt +++ b/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/jvm/SerializableCodegenImpl.kt @@ -57,7 +57,7 @@ class SerializableCodegenImpl( private val descToProps = classCodegen.myClass.bodyPropertiesDescriptorsMap(classCodegen.bindingContext) private val paramsToProps: Map = - 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] diff --git a/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/diagnostic/DeclarationCheckers.kt b/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/diagnostic/DeclarationCheckers.kt index 78ad24b4b78..7d5daa8837d 100644 --- a/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/diagnostic/DeclarationCheckers.kt +++ b/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/diagnostic/DeclarationCheckers.kt @@ -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 = 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 = + 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) { 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) { + private fun BindingTrace.reportOnSerializableAnnotation(descriptor: ClassDescriptor, error: DiagnosticFactory0) { descriptor.safeReport { e -> reportFromPlugin( error.on(e), 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 568fe69fa3c..1b0fd05a8bc 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 @@ -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 SERIALIZABLE_ANNOTATION_IGNORED = DiagnosticFactory0.create(ERROR); + DiagnosticFactory0 INLINE_CLASSES_NOT_SUPPORTED = 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); + DiagnosticFactory0 TRANSIENT_MISSING_INITIALIZER = DiagnosticFactory0.create(ERROR); + DiagnosticFactory0 TRANSIENT_IS_REDUNDANT = DiagnosticFactory0.create(WARNING); @SuppressWarnings("UnusedDeclaration") Object _initializer = new Object() { 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 959816fa2c0..32a9a526124 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 @@ -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." + ) } } \ No newline at end of file