Report an error on @Serializable inline classes and missing @Transient initializers
This commit is contained in:
+7
-5
@@ -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 }
|
||||
|
||||
+2
-2
@@ -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
|
||||
|
||||
+1
-1
@@ -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]
|
||||
|
||||
+48
-1
@@ -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),
|
||||
|
||||
+4
@@ -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() {
|
||||
|
||||
+12
@@ -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."
|
||||
)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user