[KxSerialization] Added inspections on custom serializer parameters

Added inspections to check:
- custom serializer on class has as many parameters in primary constructor as the serializable class of type arguments
- all parameters in custom serializer has `KSerializer` type
- property in serializable class not parametrized by type parameter
- custom serializer on property of serializable class have no parameters in primary constructor
This commit is contained in:
Sergey.Shanshin
2023-07-28 22:03:31 +02:00
committed by Space Team
parent 9532172a22
commit 84ad12be57
15 changed files with 507 additions and 32 deletions
@@ -39,6 +39,8 @@ public interface SerializationErrors {
DiagnosticFactory2<PsiElement, KotlinType, KotlinType> ABSTRACT_SERIALIZER_TYPE = DiagnosticFactory2.create(ERROR);
DiagnosticFactory3<PsiElement, KotlinType, KotlinType, KotlinType> SERIALIZER_TYPE_INCOMPATIBLE = DiagnosticFactory3.create(WARNING);
DiagnosticFactory1<PsiElement, KotlinType> LOCAL_SERIALIZER_USAGE = DiagnosticFactory1.create(ERROR);
DiagnosticFactory3<PsiElement, KotlinType, KotlinType, String> CUSTOM_SERIALIZER_PARAM_ILLEGAL_COUNT = DiagnosticFactory3.create(ERROR);
DiagnosticFactory3<PsiElement, KotlinType, KotlinType, String> CUSTOM_SERIALIZER_PARAM_ILLEGAL_TYPE = DiagnosticFactory3.create(ERROR);
DiagnosticFactory0<PsiElement> TRANSIENT_MISSING_INITIALIZER = DiagnosticFactory0.create(ERROR);
DiagnosticFactory0<PsiElement> GENERIC_ARRAY_ELEMENT_NOT_SUPPORTED = DiagnosticFactory0.create(ERROR);
@@ -61,6 +63,8 @@ public interface SerializationErrors {
DiagnosticFactory2<PsiElement, KotlinType, KotlinType> EXTERNAL_CLASS_NOT_SERIALIZABLE = DiagnosticFactory2.create(ERROR);
DiagnosticFactory2<PsiElement, KotlinType, KotlinType> EXTERNAL_CLASS_IN_ANOTHER_MODULE = DiagnosticFactory2.create(ERROR);
DiagnosticFactory3<PsiElement, KotlinType, KotlinType, String> EXTERNAL_SERIALIZER_NO_SUITABLE_CONSTRUCTOR = DiagnosticFactory3.create(ERROR);
@SuppressWarnings("UnusedDeclaration")
Object _initializer = new Object() {
@@ -27,12 +27,14 @@ import org.jetbrains.kotlin.resolve.lazy.descriptors.LazyAnnotationDescriptor
import org.jetbrains.kotlin.resolve.source.getPsi
import org.jetbrains.kotlin.types.KotlinType
import org.jetbrains.kotlin.types.typeUtil.isEnum
import org.jetbrains.kotlin.types.typeUtil.isTypeParameter
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.*
import org.jetbrains.kotlinx.serialization.compiler.backend.common.bodyPropertiesDescriptorsMap
import org.jetbrains.kotlinx.serialization.compiler.backend.common.primaryConstructorPropertiesDescriptorsMap
import org.jetbrains.kotlinx.serialization.compiler.diagnostic.SerializationErrors.EXTERNAL_SERIALIZER_NO_SUITABLE_CONSTRUCTOR
import org.jetbrains.kotlinx.serialization.compiler.diagnostic.SerializationErrors.EXTERNAL_SERIALIZER_USELESS
import org.jetbrains.kotlinx.serialization.compiler.resolve.*
import org.jetbrains.kotlinx.serialization.compiler.resolve.SerialEntityNames.LOAD_NAME
@@ -89,6 +91,25 @@ open class SerializationPluginDeclarationChecker : DeclarationChecker {
val serializableDescriptor = serializableKType.toClassDescriptor ?: return
val props = SerializableProperties(serializableDescriptor, trace.bindingContext)
val parametersCount = serializableKType.arguments.size
if (parametersCount > 0) {
val hasSuitableConstructor = classDescriptor.constructors.any { constructor ->
constructor.valueParameters.size == parametersCount
&& constructor.valueParameters.all { param -> isKSerializer(param.type) }
}
if (!hasSuitableConstructor) {
trace.report(
EXTERNAL_SERIALIZER_NO_SUITABLE_CONSTRUCTOR.on(
declaration,
classDescriptor.defaultType,
serializableKType,
parametersCount.toString()
)
)
}
}
val descriptorOverridden = classDescriptor.unsubstitutedMemberScope
.getContributedVariables(SERIAL_DESC_FIELD_NAME, NoLookupLocation.FROM_BACKEND).singleOrNull {
it.kind != CallableMemberDescriptor.Kind.SYNTHESIZED
@@ -325,6 +346,7 @@ open class SerializationPluginDeclarationChecker : DeclarationChecker {
val annotationPsi = descriptor.findSerializableOrMetaAnnotationDeclaration()
checkCustomSerializerMatch(descriptor.module, descriptor.defaultType, descriptor, annotationPsi, trace, declaration)
checkCustomSerializerIsNotLocal(descriptor.module, descriptor, trace, declaration)
checkCustomSerializerParameters(descriptor.module, descriptor, descriptor.defaultType, annotationPsi, declaration, trace)
checkCustomSerializerNotAbstract(descriptor.module, descriptor.defaultType, descriptor, annotationPsi, trace, declaration)
}
@@ -441,14 +463,9 @@ open class SerializationPluginDeclarationChecker : DeclarationChecker {
if (serializer != null) {
val element = ktType?.typeElement
checkCustomSerializerMatch(it.module, it.type, it.descriptor, element, trace, propertyPsi)
checkCustomSerializerNotAbstract(
it.module,
it.type,
it.descriptor,
it.descriptor.findSerializableOrMetaAnnotationDeclaration(),
trace,
propertyPsi
)
val annotationPsi = it.descriptor.findSerializableOrMetaAnnotationDeclaration()
checkCustomSerializerNotAbstract(it.module, it.type, it.descriptor, annotationPsi, trace, propertyPsi)
checkCustomSerializerParameters(it.module, it.descriptor, it.type, annotationPsi, propertyPsi, trace)
checkCustomSerializerIsNotLocal(it.module, it.descriptor, trace, propertyPsi)
checkSerializerNullability(it.type, serializer.defaultType, element, trace, propertyPsi)
generatorContextForAnalysis.checkTypeArguments(it.module, it.type, element, trace, propertyPsi)
@@ -516,8 +533,14 @@ open class SerializationPluginDeclarationChecker : DeclarationChecker {
}
val serializer = findTypeSerializerOrContextUnchecked(module, type)
if (serializer != null) {
checkCustomSerializerMatch(module, type, type, element, trace, fallbackElement)
checkCustomSerializerIsNotLocal(module, type, trace, fallbackElement)
type.annotations.serializableWith(module)?.let {
checkCustomSerializerMatch(module, type, type, element, trace, fallbackElement)
checkCustomSerializerIsNotLocal(module, type, trace, fallbackElement)
val annotationElement = type.findSerializableAnnotationDeclaration()
checkCustomSerializerParameters(module, type, type, annotationElement, fallbackElement, trace)
checkCustomSerializerNotAbstract(module, type, type, annotationElement, trace, fallbackElement)
}
checkSerializerNullability(type, serializer.defaultType, element, trace, fallbackElement)
checkTypeArguments(module, type, element, trace, fallbackElement)
} else {
@@ -591,6 +614,63 @@ open class SerializationPluginDeclarationChecker : DeclarationChecker {
}
}
private fun checkCustomSerializerParameters(
module: ModuleDescriptor,
declaration: Annotated,
serializableType: KotlinType,
element: KtElement?,
fallbackElement: PsiElement,
trace: BindingTrace,
) {
val serializerType = declaration.annotations.serializableWith(module) ?: return
val serializerDescriptor = serializerType.toClassDescriptor ?: return
if (serializerDescriptor.classId in SerializersClassIds.setOfSpecialSerializers) {
return
}
val primaryConstructor = serializerDescriptor.constructors.singleOrNull { constructor -> constructor.isPrimary } ?: return
val targetElement = element ?: fallbackElement
val isExternalSerializer = serializerDescriptor.serializerForClass != null
if ( // for external serializer, the verification will be carried out at the definition
!isExternalSerializer
// it is allowed that parameters are not passed to regular serializers at all
&& primaryConstructor.valueParameters.isNotEmpty()
// if the parameters are still specified, then their number must match in the serializable class and constructor
&& primaryConstructor.valueParameters.size != serializableType.arguments.size
) {
val message = if (serializableType.arguments.isNotEmpty()) {
"expected no parameters or ${serializableType.arguments.size}, but has ${primaryConstructor.valueParameters.size} parameters"
} else {
"expected no parameters but has ${primaryConstructor.valueParameters.size} parameters"
}
trace.report(
SerializationErrors.CUSTOM_SERIALIZER_PARAM_ILLEGAL_COUNT.on(
targetElement,
serializerType,
serializableType,
message
)
)
}
primaryConstructor.valueParameters.forEach { param ->
if (!isKSerializer(param.type)) {
trace.report(
SerializationErrors.CUSTOM_SERIALIZER_PARAM_ILLEGAL_TYPE.on(
targetElement,
serializerType,
serializableType,
param.name.asString()
)
)
}
}
}
private fun checkSerializerNullability(
classType: KotlinType,
serializerType: KotlinType,
@@ -118,6 +118,20 @@ object SerializationPluginErrorsRendering : DefaultErrorMessages.Extension {
"Class ''{0}'' can't be used as a serializer since it is local",
Renderers.RENDER_TYPE
)
MAP.put(
SerializationErrors.CUSTOM_SERIALIZER_PARAM_ILLEGAL_COUNT,
"Custom serializer ''{0}'' can not be used for ''{1}'' since it has an invalid number of parameters in primary constructor: {2}",
Renderers.RENDER_TYPE,
Renderers.RENDER_TYPE,
CommonRenderers.STRING
)
MAP.put(
SerializationErrors.CUSTOM_SERIALIZER_PARAM_ILLEGAL_TYPE,
"Custom serializer ''{0}'' can not be used for ''{1}'', type of parameter ''{2}'' in serializer's primary constructor should be ''KSerializer''",
Renderers.RENDER_TYPE,
Renderers.RENDER_TYPE,
CommonRenderers.STRING
)
MAP.put(
SerializationErrors.TRANSIENT_MISSING_INITIALIZER,
"This property is marked as @Transient and therefore must have an initializing expression"
@@ -194,5 +208,13 @@ object SerializationPluginErrorsRendering : DefaultErrorMessages.Extension {
Renderers.RENDER_TYPE,
Renderers.RENDER_TYPE
)
MAP.put(
SerializationErrors.EXTERNAL_SERIALIZER_NO_SUITABLE_CONSTRUCTOR,
"Cannot generate external serializer ''{0}'': it must have a constructor with {2} value parameters, because class ''{1}'' has type parameters",
Renderers.RENDER_TYPE,
Renderers.RENDER_TYPE,
CommonRenderers.STRING
)
}
}
@@ -212,7 +212,7 @@ private val ClassDescriptor.hasSerializableAnnotationWithArgs: Boolean
return psi.valueArguments.isNotEmpty()
}
private fun Annotated.findSerializableAnnotationDeclaration(): KtAnnotationEntry? {
internal fun Annotated.findSerializableAnnotationDeclaration(): KtAnnotationEntry? {
val lazyDesc = annotations.findAnnotation(serializableAnnotationFqName) as? LazyAnnotationDescriptor
return lazyDesc?.annotationEntry
}