Add checker for companion objects inside serializable class:

Warning for old FE, error in FIR

#KT-54441 Fixed

Merged-by: Leonid Startsev <leonid.startsev@jetbrains.com>
This commit is contained in:
Leonid Startsev
2022-10-20 10:18:04 +00:00
committed by Space Team
parent 3832c6a520
commit 4cf50d7d23
12 changed files with 238 additions and 8 deletions
@@ -6,6 +6,7 @@
package org.jetbrains.kotlinx.serialization.compiler.diagnostic;
import com.intellij.psi.PsiElement;
import org.jetbrains.kotlin.descriptors.ClassDescriptor;
import org.jetbrains.kotlin.diagnostics.*;
import org.jetbrains.kotlin.psi.KtAnnotationEntry;
import org.jetbrains.kotlin.types.KotlinType;
@@ -19,6 +20,13 @@ public interface SerializationErrors {
DiagnosticFactory0<PsiElement> ANONYMOUS_OBJECTS_NOT_SUPPORTED = DiagnosticFactory0.create(ERROR);
DiagnosticFactory0<PsiElement> INNER_CLASSES_NOT_SUPPORTED = DiagnosticFactory0.create(ERROR);
DiagnosticFactory1<PsiElement, ClassDescriptor> COMPANION_OBJECT_AS_CUSTOM_SERIALIZER_DEPRECATED = DiagnosticFactory1.create(WARNING);
DiagnosticFactory2<PsiElement, KotlinType, KotlinType> COMPANION_OBJECT_SERIALIZER_INSIDE_OTHER_SERIALIZABLE_CLASS = DiagnosticFactory2.create(WARNING);
DiagnosticFactory2<PsiElement, KotlinType, KotlinType> COMPANION_OBJECT_SERIALIZER_INSIDE_NON_SERIALIZABLE_CLASS = DiagnosticFactory2.create(WARNING);
DiagnosticFactory0<PsiElement> EXPLICIT_SERIALIZABLE_IS_REQUIRED = DiagnosticFactory0.create(WARNING);
DiagnosticFactory0<KtAnnotationEntry> SERIALIZABLE_ANNOTATION_IGNORED = DiagnosticFactory0.create(ERROR);
@@ -171,6 +171,8 @@ open class SerializationPluginDeclarationChecker : DeclarationChecker {
return false
}
checkCompanionSerializerDependency(descriptor, declaration, trace)
if (!descriptor.hasSerializableOrMetaAnnotation) return false
if (!serializationPluginEnabledOn(descriptor)) {
@@ -200,6 +202,7 @@ open class SerializationPluginDeclarationChecker : DeclarationChecker {
}
return false
}
if (!descriptor.hasSerializableOrMetaAnnotationWithoutArgs) {
// defined custom serializer
checkClassWithCustomSerializer(descriptor, declaration, trace)
@@ -222,6 +225,53 @@ open class SerializationPluginDeclarationChecker : DeclarationChecker {
return true
}
private fun checkCompanionSerializerDependency(descriptor: ClassDescriptor, declaration: KtDeclaration, trace: BindingTrace) {
val companionObjectDescriptor = descriptor.companionObjectDescriptor ?: return
val serializerForInCompanion = companionObjectDescriptor.serializerForClass ?: return
val serializerAnnotationSource =
companionObjectDescriptor.findAnnotationDeclaration(SerializationAnnotations.serializerAnnotationFqName)
val serializableWith = descriptor.serializableWith
if (descriptor.hasSerializableOrMetaAnnotationWithoutArgs) {
if (serializerForInCompanion == descriptor.defaultType) {
// @Serializable class Foo / @Serializer(Foo::class) companion object — prohibited due to problems with recursive resolve
descriptor.onSerializableOrMetaAnnotation {
trace.report(SerializationErrors.COMPANION_OBJECT_AS_CUSTOM_SERIALIZER_DEPRECATED.on(it, descriptor))
}
} else {
// @Serializable class Foo / @Serializer(Bar::class) companion object — prohibited as vague and confusing
trace.report(
SerializationErrors.COMPANION_OBJECT_SERIALIZER_INSIDE_OTHER_SERIALIZABLE_CLASS.on(
serializerAnnotationSource ?: declaration,
descriptor.defaultType,
serializerForInCompanion
)
)
}
} else if (serializableWith != null) {
if (serializableWith == companionObjectDescriptor.defaultType && serializerForInCompanion == descriptor.defaultType) {
// @Serializable(Foo.Companion) class Foo / @Serializer(Foo::class) companion object — the only case that is allowed
} else {
// @Serializable(anySer) class Foo / @Serializer(anyOtherClass) companion object — prohibited as vague and confusing
trace.report(
SerializationErrors.COMPANION_OBJECT_SERIALIZER_INSIDE_OTHER_SERIALIZABLE_CLASS.on(
serializerAnnotationSource ?: declaration,
descriptor.defaultType,
serializerForInCompanion
)
)
}
} else {
// (regular) class Foo / @Serializer(something) companion object - not recommended
trace.report(
SerializationErrors.COMPANION_OBJECT_SERIALIZER_INSIDE_NON_SERIALIZABLE_CLASS.on(
serializerAnnotationSource ?: declaration,
descriptor.defaultType,
serializerForInCompanion
)
)
}
}
private fun checkClassWithCustomSerializer(descriptor: ClassDescriptor, declaration: KtDeclaration, trace: BindingTrace) {
val annotationPsi = descriptor.findSerializableOrMetaAnnotationDeclaration()
checkCustomSerializerMatch(descriptor.module, descriptor.defaultType, descriptor, annotationPsi, trace, declaration)
@@ -34,6 +34,31 @@ object SerializationPluginErrorsRendering : DefaultErrorMessages.Extension {
SerializationErrors.INNER_CLASSES_NOT_SUPPORTED,
"Inner (with reference to outer this) serializable classes are not supported. Remove @Serializable annotation or 'inner' keyword."
)
MAP.put(
SerializationErrors.COMPANION_OBJECT_AS_CUSTOM_SERIALIZER_DEPRECATED,
"Class ''{0}'' has implicit custom serializer as its companion object. This behaviour is not properly reflected by @Serializable annotation without arguments and therefore is deprecated. " +
"To be able to use companion object as the ''{0}'' default serializer, please explicitly mention it in the annotation on ''{0}'': @Serializable(''{0}''.Companion::class). " +
"For more details, refer to this YouTrack ticket: https://youtrack.jetbrains.com/issue/KT-54441",
Renderers.NAME
)
MAP.put(
SerializationErrors.COMPANION_OBJECT_SERIALIZER_INSIDE_OTHER_SERIALIZABLE_CLASS,
"This class is a Companion object for @Serializable class ''{0}'', but itself is an external serializer for another class ''{1}''. " +
"Such declarations are potentially problematic and user-confusing and therefore are deprecated. " +
"Please define external serializers as non-companion, preferably top-level objects. " +
"For more details, refer to this YouTrack ticket: https://youtrack.jetbrains.com/issue/KT-54441",
Renderers.RENDER_TYPE,
Renderers.RENDER_TYPE
)
MAP.put(
SerializationErrors.COMPANION_OBJECT_SERIALIZER_INSIDE_NON_SERIALIZABLE_CLASS,
"This class is a Companion object for non-serializable class ''{0}'', but itself is an external serializer for another class ''{1}''. " +
"Such declarations are potentially problematic and user-confusing and therefore are deprecated. " +
"Please define external serializers as non-companion, preferably top-level objects. " +
"For more details, refer to this YouTrack ticket: https://youtrack.jetbrains.com/issue/KT-54441",
Renderers.RENDER_TYPE,
Renderers.RENDER_TYPE
)
MAP.put(
SerializationErrors.EXPLICIT_SERIALIZABLE_IS_REQUIRED,
"Explicit @Serializable annotation on enum class is required when @SerialName or @SerialInfo annotations are used on its members."