From e6d96f1de9d5d0785cd719055b4d30cf50ab09dd Mon Sep 17 00:00:00 2001 From: Leonid Startsev Date: Tue, 28 May 2019 16:49:02 +0300 Subject: [PATCH] Implement diagnostic about nullable serializer for non-nullable type --- .../diagnostic/DeclarationCheckers.kt | 23 +++++++++++++++++-- .../diagnostic/SerializationErrors.java | 2 ++ .../SerializationPluginErrorsRendering.kt | 5 ++++ .../compiler/resolve/KSerializationUtil.kt | 12 +--------- 4 files changed, 29 insertions(+), 13 deletions(-) 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 58a1de1a264..78ad24b4b78 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 @@ -17,6 +17,7 @@ import org.jetbrains.kotlin.resolve.BindingTrace import org.jetbrains.kotlin.resolve.checkers.DeclarationChecker import org.jetbrains.kotlin.resolve.checkers.DeclarationCheckerContext 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 @@ -79,6 +80,7 @@ class SerializationPluginDeclarationChecker : DeclarationChecker { val ktType = (it.descriptor.findPsi() as? KtCallableDeclaration)?.typeReference ?: return@forEach if (serializer != null) { val element = ktType.typeElement ?: return + checkSerializerNullability(it.type, serializer.defaultType, element, trace) generatorContextForAnalysis.checkTypeArguments(it.module, it.type, element, trace) trace.record(SERIALIZER_FOR_PROPERTY, it.descriptor, serializer) } else { @@ -104,8 +106,9 @@ class SerializationPluginDeclarationChecker : DeclarationChecker { ) { if (type.genericIndex != null) return val element = ktType.typeElement ?: return - val serializerForType = findTypeSerializerOrContext(module, type) - if (serializerForType != null) { + val serializer = findTypeSerializerOrContext(module, type) + if (serializer != null) { + checkSerializerNullability(type, serializer.defaultType, element, trace) checkTypeArguments(module, type, element, trace) } else { trace.reportFromPlugin( @@ -115,6 +118,22 @@ class SerializationPluginDeclarationChecker : DeclarationChecker { } } + private fun checkSerializerNullability( + classType: KotlinType, + serializerType: KotlinType, + element: KtTypeElement, + trace: BindingTrace + ) { + // @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), + SerializationPluginErrorsRendering + ) + } + private inline fun ClassDescriptor.safeReport(report: (KtAnnotationEntry) -> Unit) { findSerializableAnnotationDeclaration()?.let(report) } 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 64d616ed8c9..568fe69fa3c 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 @@ -10,6 +10,7 @@ import org.jetbrains.kotlin.diagnostics.DiagnosticFactory0; import org.jetbrains.kotlin.diagnostics.DiagnosticFactory1; import org.jetbrains.kotlin.diagnostics.Errors; import org.jetbrains.kotlin.psi.KtAnnotationEntry; +import org.jetbrains.kotlin.types.KotlinType; import static org.jetbrains.kotlin.diagnostics.Severity.ERROR; @@ -18,6 +19,7 @@ public interface SerializationErrors { 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); @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 08ef1bfc58b..959816fa2c0 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 @@ -33,5 +33,10 @@ object SerializationPluginErrorsRendering : DefaultErrorMessages.Extension { "Serializer has not been found for type of this property. " + "To use context serializer as fallback, explicitly annotate element with @ContextualSerialization" ) + MAP.put( + SerializationErrors.SERIALIZER_NULLABILITY_INCOMPATIBLE, + "This type is not-nullable and therefore can not be serialized with serializer for nullable type ''{0}''", + Renderers.RENDER_TYPE + ) } } \ No newline at end of file diff --git a/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/resolve/KSerializationUtil.kt b/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/resolve/KSerializationUtil.kt index 9ec47fbe279..8b3c7642d55 100644 --- a/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/resolve/KSerializationUtil.kt +++ b/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/resolve/KSerializationUtil.kt @@ -160,21 +160,11 @@ internal val ClassDescriptor.hasCompanionObjectAsSerializer: Boolean internal fun ClassDescriptor.isSerializerWhichRequiersKClass() = classId in setOf(enumSerializerId, contextSerializerId, polymorphicSerializerId) -internal fun checkSerializerNullability(classType: KotlinType, serializerType: KotlinType): KotlinType { - val castedToKSerial = requireNotNull( - serializerType.supertypes().find { isKSerializer(it) }, - { "${KSERIALIZER_CLASS} is not a supertype of $serializerType" } - ) - if (!classType.isMarkedNullable && castedToKSerial.arguments.first().type.isMarkedNullable) - throw IllegalStateException("Can't serialize non-nullable field of type ${classType} with nullable serializer ${serializerType}") - return serializerType -} - // returns only user-overriden Serializer internal val KotlinType.overridenSerializer: KotlinType? get() { val desc = this.toClassDescriptor ?: return null - (desc.serializableWith)?.let { return checkSerializerNullability(this, it) } + desc.serializableWith?.let { return it } if (desc.annotations.hasAnnotation(SerializationAnnotations.polymorphicFqName)) return desc.module.getClassFromSerializationPackage(SpecialBuiltins.polymorphicSerializer).defaultType return null