Implement diagnostic about nullable serializer for non-nullable type
This commit is contained in:
+21
-2
@@ -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)
|
||||
}
|
||||
|
||||
+2
@@ -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<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);
|
||||
|
||||
@SuppressWarnings("UnusedDeclaration")
|
||||
Object _initializer = new Object() {
|
||||
|
||||
+5
@@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
+1
-11
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user