[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:
committed by
Space Team
parent
9532172a22
commit
84ad12be57
+4
@@ -36,6 +36,9 @@ object FirSerializationErrors {
|
||||
val SERIALIZER_TYPE_INCOMPATIBLE by warning3<PsiElement, ConeKotlinType, ConeKotlinType, ConeKotlinType>()
|
||||
val ABSTRACT_SERIALIZER_TYPE by error2<PsiElement, ConeKotlinType, ConeKotlinType>()
|
||||
val LOCAL_SERIALIZER_USAGE by error1<PsiElement, ConeKotlinType>()
|
||||
val CUSTOM_SERIALIZER_PARAM_ILLEGAL_COUNT by error3<PsiElement, ConeKotlinType, ConeKotlinType, String>()
|
||||
val CUSTOM_SERIALIZER_PARAM_ILLEGAL_TYPE by error3<PsiElement, ConeKotlinType, ConeKotlinType, String>()
|
||||
|
||||
val GENERIC_ARRAY_ELEMENT_NOT_SUPPORTED by error0<PsiElement>()
|
||||
val TRANSIENT_MISSING_INITIALIZER by error0<PsiElement>()
|
||||
|
||||
@@ -52,6 +55,7 @@ object FirSerializationErrors {
|
||||
val EXTERNAL_SERIALIZER_USELESS by warning1<PsiElement, FirClassSymbol<*>>()
|
||||
val EXTERNAL_CLASS_NOT_SERIALIZABLE by error2<PsiElement, FirClassSymbol<*>, ConeKotlinType>()
|
||||
val EXTERNAL_CLASS_IN_ANOTHER_MODULE by error2<PsiElement, FirClassSymbol<*>, ConeKotlinType>()
|
||||
val EXTERNAL_SERIALIZER_NO_SUITABLE_CONSTRUCTOR by error3<PsiElement, FirClassSymbol<*>, ConeKotlinType, String>()
|
||||
|
||||
init {
|
||||
RootDiagnosticRendererFactory.registerFactory(KtDefaultErrorMessagesSerialization)
|
||||
|
||||
+102
-16
@@ -14,6 +14,9 @@ import org.jetbrains.kotlin.diagnostics.reportOn
|
||||
import org.jetbrains.kotlin.fir.analysis.checkers.*
|
||||
import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext
|
||||
import org.jetbrains.kotlin.fir.analysis.checkers.declaration.FirClassChecker
|
||||
import org.jetbrains.kotlin.fir.analysis.checkers.declaration.primaryConstructorSymbol
|
||||
import org.jetbrains.kotlin.fir.analysis.checkers.isSingleFieldValueClass
|
||||
import org.jetbrains.kotlin.fir.analysis.checkers.toRegularClassSymbol
|
||||
import org.jetbrains.kotlin.fir.declarations.*
|
||||
import org.jetbrains.kotlin.fir.declarations.annotationPlatformSupport
|
||||
import org.jetbrains.kotlin.fir.declarations.utils.*
|
||||
@@ -30,6 +33,7 @@ import org.jetbrains.kotlin.resolve.jvm.annotations.TRANSIENT_ANNOTATION_CLASS_I
|
||||
import org.jetbrains.kotlin.types.AbstractTypeChecker
|
||||
import org.jetbrains.kotlinx.serialization.compiler.diagnostic.RuntimeVersions
|
||||
import org.jetbrains.kotlinx.serialization.compiler.fir.*
|
||||
import org.jetbrains.kotlinx.serialization.compiler.fir.checkers.FirSerializationErrors.EXTERNAL_SERIALIZER_NO_SUITABLE_CONSTRUCTOR
|
||||
import org.jetbrains.kotlinx.serialization.compiler.fir.checkers.FirSerializationErrors.EXTERNAL_SERIALIZER_USELESS
|
||||
import org.jetbrains.kotlinx.serialization.compiler.fir.services.dependencySerializationInfoProvider
|
||||
import org.jetbrains.kotlinx.serialization.compiler.fir.services.findTypeSerializerOrContextUnchecked
|
||||
@@ -91,6 +95,25 @@ object FirSerializationPluginClassChecker : FirClassChecker(MppCheckerKind.Commo
|
||||
val serializableClassSymbol = serializableKType.toRegularClassSymbol(session) ?: return
|
||||
|
||||
val declarations = classSymbol.declarationSymbols
|
||||
|
||||
val parametersCount = serializableKType.typeArguments.size
|
||||
if (parametersCount > 0) {
|
||||
val hasSuitableConstructor = declarations.filterIsInstance<FirConstructorSymbol>().any { constructor ->
|
||||
constructor.valueParameterSymbols.size == parametersCount
|
||||
&& constructor.valueParameterSymbols.all { param -> param.resolvedReturnType.isKSerializer }
|
||||
}
|
||||
|
||||
if (!hasSuitableConstructor) {
|
||||
reporter.reportOn(
|
||||
classSymbol.source,
|
||||
EXTERNAL_SERIALIZER_NO_SUITABLE_CONSTRUCTOR,
|
||||
classSymbol,
|
||||
serializableKType,
|
||||
parametersCount.toString()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
val descriptorOverridden = declarations.filterIsInstance<FirPropertySymbol>().singleOrNull {
|
||||
it.name == SerialEntityNames.SERIAL_DESC_FIELD_NAME
|
||||
&& it.isOverride
|
||||
@@ -361,8 +384,12 @@ object FirSerializationPluginClassChecker : FirClassChecker(MppCheckerKind.Commo
|
||||
context(CheckerContext)
|
||||
private fun checkClassWithCustomSerializer(classSymbol: FirClassSymbol<*>, reporter: DiagnosticReporter) {
|
||||
val serializerType = classSymbol.getSerializableWith(session)?.fullyExpandedType(session) ?: return
|
||||
checkCustomSerializerMatch(classSymbol, source = null, classSymbol.defaultType(), serializerType, reporter)
|
||||
|
||||
val serializerForType = serializerType.serializerForType(session)?.fullyExpandedType(session)
|
||||
|
||||
checkCustomSerializerMatch(classSymbol, source = null, classSymbol.defaultType(), serializerType, serializerForType, reporter)
|
||||
checkCustomSerializerIsNotLocal(source = null, classSymbol, serializerType, reporter)
|
||||
checkCustomSerializerParameters(classSymbol, null, serializerType, serializerForType, reporter)
|
||||
checkCustomSerializerNotAbstract(classSymbol, source = null, serializerType, reporter)
|
||||
}
|
||||
|
||||
@@ -467,25 +494,20 @@ object FirSerializationPluginClassChecker : FirClassChecker(MppCheckerKind.Commo
|
||||
// and would not be compatible on direct comparison
|
||||
if (customSerializerType.classId in SerializersClassIds.setOfSpecialSerializers) return
|
||||
|
||||
val serializerForType = customSerializerType.serializerForType(session)?.fullyExpandedType(session)
|
||||
|
||||
checkCustomSerializerMatch(
|
||||
classSymbol,
|
||||
source = typeRef.source ?: propertySymbol.source,
|
||||
propertyType,
|
||||
customSerializerType,
|
||||
serializerForType,
|
||||
reporter
|
||||
)
|
||||
checkCustomSerializerNotAbstract(
|
||||
classSymbol,
|
||||
source = propertySymbol.serializableAnnotation(needArguments = false, session)?.source,
|
||||
customSerializerType,
|
||||
reporter
|
||||
)
|
||||
checkCustomSerializerIsNotLocal(
|
||||
source = propertySymbol.serializableAnnotation(needArguments = false, session)?.source,
|
||||
classSymbol,
|
||||
customSerializerType,
|
||||
reporter
|
||||
)
|
||||
val annotationElement = propertySymbol.serializableAnnotation(needArguments = false, session)?.source
|
||||
checkCustomSerializerNotAbstract(classSymbol, source = annotationElement, customSerializerType, reporter)
|
||||
checkCustomSerializerIsNotLocal(source = annotationElement, classSymbol, customSerializerType, reporter)
|
||||
checkCustomSerializerParameters(classSymbol, annotationElement, customSerializerType, serializerForType, reporter)
|
||||
checkSerializerNullability(propertyType, customSerializerType, source, reporter)
|
||||
} else {
|
||||
checkType(typeRef, source, reporter)
|
||||
@@ -543,8 +565,14 @@ object FirSerializationPluginClassChecker : FirClassChecker(MppCheckerKind.Commo
|
||||
if (serializer != null) {
|
||||
val classSymbol = type.toRegularClassSymbol(session) ?: return
|
||||
type.serializableWith?.fullyExpandedType(session)?.let { serializerType ->
|
||||
checkCustomSerializerMatch(classSymbol, typeSource, type, serializerType, reporter)
|
||||
val serializerForType = serializerType.serializerForType(session)?.fullyExpandedType(session)
|
||||
|
||||
checkCustomSerializerMatch(classSymbol, typeSource, type, serializerType, serializerForType, reporter)
|
||||
checkCustomSerializerIsNotLocal(typeSource, classSymbol, serializerType, reporter)
|
||||
|
||||
val annotationElement = type.customAnnotations.serializableAnnotation(session)?.source ?: typeSource
|
||||
checkCustomSerializerParameters(classSymbol, annotationElement, serializerType, serializerForType, reporter)
|
||||
checkCustomSerializerNotAbstract(classSymbol, annotationElement, serializerType, reporter)
|
||||
checkSerializerNullability(type, serializerType, typeSource, reporter)
|
||||
}
|
||||
checkTypeArguments(typeRef, typeSource, reporter)
|
||||
@@ -562,9 +590,10 @@ object FirSerializationPluginClassChecker : FirClassChecker(MppCheckerKind.Commo
|
||||
source: KtSourceElement?,
|
||||
declarationType: ConeKotlinType,
|
||||
serializerType: ConeKotlinType,
|
||||
serializerForType: ConeKotlinType?,
|
||||
reporter: DiagnosticReporter
|
||||
) {
|
||||
val serializerForType = serializerType.serializerForType(session)?.fullyExpandedType(session) ?: return
|
||||
serializerForType ?: return
|
||||
|
||||
val declarationTypeClassId = declarationType.classId
|
||||
if (declarationTypeClassId == null || declarationTypeClassId != serializerForType.classId) {
|
||||
@@ -583,7 +612,7 @@ object FirSerializationPluginClassChecker : FirClassChecker(MppCheckerKind.Commo
|
||||
containingClassSymbol: FirClassSymbol<*>,
|
||||
source: KtSourceElement?,
|
||||
serializerType: ConeKotlinType,
|
||||
reporter: DiagnosticReporter
|
||||
reporter: DiagnosticReporter,
|
||||
) {
|
||||
if (with(session) { serializerType.isAbstractOrSealedOrInterface }) {
|
||||
reporter.reportOn(
|
||||
@@ -595,6 +624,63 @@ object FirSerializationPluginClassChecker : FirClassChecker(MppCheckerKind.Commo
|
||||
}
|
||||
}
|
||||
|
||||
context(CheckerContext)
|
||||
private fun checkCustomSerializerParameters(
|
||||
containingClassSymbol: FirClassSymbol<*>,
|
||||
source: KtSourceElement?,
|
||||
serializerType: ConeKotlinType,
|
||||
serializerForType: ConeKotlinType?,
|
||||
reporter: DiagnosticReporter,
|
||||
) {
|
||||
serializerForType ?: return
|
||||
|
||||
// Do not account for @Polymorphic and @Contextual, as they are serializers for T: Any
|
||||
// and would not be compatible on direct comparison
|
||||
if (serializerType.classId in SerializersClassIds.setOfSpecialSerializers) {
|
||||
return
|
||||
}
|
||||
|
||||
val primaryConstructor = serializerType.toRegularClassSymbol(session)?.primaryConstructorSymbol(session) ?: return
|
||||
|
||||
val targetElement by lazy { source ?: containingClassSymbol.serializableOrMetaAnnotationSource }
|
||||
|
||||
val isExternalSerializer = serializerType.toRegularClassSymbol(session)?.getSerializerAnnotation(session) != null
|
||||
|
||||
if ( // for external serializer, the verification will be carried out at the definition
|
||||
!isExternalSerializer
|
||||
// it is allowed that parameters are not passed in regular serializers at all
|
||||
&& primaryConstructor.valueParameterSymbols.isNotEmpty()
|
||||
// if the parameters are still specified, then their number must match in the serializable class and constructor
|
||||
&& serializerForType.typeArguments.size != primaryConstructor.valueParameterSymbols.size
|
||||
) {
|
||||
val message = if (serializerForType.typeArguments.isNotEmpty()) {
|
||||
"expected no parameters or ${serializerForType.typeArguments.size}, but has ${primaryConstructor.valueParameterSymbols.size} parameters"
|
||||
} else {
|
||||
"expected no parameters but has ${primaryConstructor.valueParameterSymbols.size} parameters"
|
||||
}
|
||||
reporter.reportOn(
|
||||
targetElement,
|
||||
FirSerializationErrors.CUSTOM_SERIALIZER_PARAM_ILLEGAL_COUNT,
|
||||
serializerType,
|
||||
serializerForType,
|
||||
message
|
||||
)
|
||||
}
|
||||
|
||||
primaryConstructor.valueParameterSymbols.forEach { param ->
|
||||
val returnType = param.resolvedReturnType
|
||||
if (!returnType.isKSerializer) {
|
||||
reporter.reportOn(
|
||||
targetElement,
|
||||
FirSerializationErrors.CUSTOM_SERIALIZER_PARAM_ILLEGAL_TYPE,
|
||||
serializerType,
|
||||
serializerForType,
|
||||
param.name.asString()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
context(CheckerContext)
|
||||
private fun checkCustomSerializerIsNotLocal(
|
||||
source: KtSourceElement?,
|
||||
|
||||
+22
@@ -115,6 +115,20 @@ object KtDefaultErrorMessagesSerialization : BaseDiagnosticRendererFactory() {
|
||||
"Class ''{0}'' can't be used as a serializer since it is local",
|
||||
FirDiagnosticRenderers.RENDER_TYPE
|
||||
)
|
||||
put(
|
||||
FirSerializationErrors.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}",
|
||||
FirDiagnosticRenderers.RENDER_TYPE,
|
||||
FirDiagnosticRenderers.RENDER_TYPE,
|
||||
CommonRenderers.STRING
|
||||
)
|
||||
put(
|
||||
FirSerializationErrors.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''",
|
||||
FirDiagnosticRenderers.RENDER_TYPE,
|
||||
FirDiagnosticRenderers.RENDER_TYPE,
|
||||
CommonRenderers.STRING
|
||||
)
|
||||
put(
|
||||
FirSerializationErrors.TRANSIENT_MISSING_INITIALIZER,
|
||||
"This property is marked as @Transient and therefore must have an initializing expression"
|
||||
@@ -183,5 +197,13 @@ object KtDefaultErrorMessagesSerialization : BaseDiagnosticRendererFactory() {
|
||||
FirDiagnosticRenderers.SYMBOL,
|
||||
FirDiagnosticRenderers.RENDER_TYPE
|
||||
)
|
||||
|
||||
put(
|
||||
FirSerializationErrors.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",
|
||||
FirDiagnosticRenderers.SYMBOL,
|
||||
FirDiagnosticRenderers.RENDER_TYPE,
|
||||
CommonRenderers.STRING
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user