Implement diagnostic about nullable serializer for non-nullable type

This commit is contained in:
Leonid Startsev
2019-05-28 16:49:02 +03:00
parent 6895184b84
commit e6d96f1de9
4 changed files with 29 additions and 13 deletions
@@ -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)
}
@@ -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() {
@@ -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
)
}
}
@@ -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