[kxSerialization] Added diagnostic on useless Serializer annotation

Resolves Kotlin/kotlinx.serialization#2182

Merge-request: KT-MR-8718
Merged-by: Sergey Shanshin <Sergey.Shanshin@jetbrains.com>
This commit is contained in:
Sergey.Shanshin
2023-02-14 22:00:33 +00:00
committed by Space Team
parent 3124154aef
commit c9b8160f1e
10 changed files with 93 additions and 4 deletions
@@ -54,6 +54,8 @@ public interface SerializationErrors {
DiagnosticFactory2<PsiElement, KotlinType, KotlinType> INCONSISTENT_INHERITABLE_SERIALINFO = DiagnosticFactory2.create(ERROR);
DiagnosticFactory0<PsiElement> META_SERIALIZABLE_NOT_APPLICABLE = DiagnosticFactory0.create(WARNING);
DiagnosticFactory1<PsiElement, KotlinType> EXTERNAL_SERIALIZER_USELESS = DiagnosticFactory1.create(WARNING);
DiagnosticFactory2<PsiElement, KotlinType, KotlinType> EXTERNAL_CLASS_NOT_SERIALIZABLE = DiagnosticFactory2.create(ERROR);
DiagnosticFactory2<PsiElement, KotlinType, KotlinType> EXTERNAL_CLASS_IN_ANOTHER_MODULE = DiagnosticFactory2.create(ERROR);
@@ -13,6 +13,7 @@ import org.jetbrains.kotlin.descriptors.annotations.Annotated
import org.jetbrains.kotlin.descriptors.annotations.AnnotationDescriptor
import org.jetbrains.kotlin.descriptors.annotations.Annotations
import org.jetbrains.kotlin.diagnostics.DiagnosticFactory0
import org.jetbrains.kotlin.incremental.components.NoLookupLocation
import org.jetbrains.kotlin.js.resolve.diagnostics.findPsi
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.name.JvmNames.TRANSIENT_ANNOTATION_FQ_NAME
@@ -30,7 +31,11 @@ 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_USELESS
import org.jetbrains.kotlinx.serialization.compiler.resolve.*
import org.jetbrains.kotlinx.serialization.compiler.resolve.SerialEntityNames.LOAD_NAME
import org.jetbrains.kotlinx.serialization.compiler.resolve.SerialEntityNames.SAVE_NAME
import org.jetbrains.kotlinx.serialization.compiler.resolve.SerialEntityNames.SERIAL_DESC_FIELD_NAME
val SERIALIZABLE_PROPERTIES: WritableSlice<ClassDescriptor, SerializableProperties> = Slices.createSimpleSlice()
@@ -73,6 +78,31 @@ open class SerializationPluginDeclarationChecker : DeclarationChecker {
val serializableDescriptor = serializableKType.toClassDescriptor ?: return
val props = SerializableProperties(serializableDescriptor, trace.bindingContext)
val descriptorOverridden = classDescriptor.unsubstitutedMemberScope
.getContributedVariables(SERIAL_DESC_FIELD_NAME, NoLookupLocation.FROM_BACKEND).singleOrNull {
it.kind != CallableMemberDescriptor.Kind.SYNTHESIZED
} != null
val serializeOverridden = classDescriptor.unsubstitutedMemberScope
.getContributedFunctions(SAVE_NAME, NoLookupLocation.FROM_BACKEND).singleOrNull {
it.valueParameters.size == 2
&& it.overriddenDescriptors.isNotEmpty()
&& it.kind != CallableMemberDescriptor.Kind.SYNTHESIZED
} != null
val deserializeOverridden = classDescriptor.unsubstitutedMemberScope
.getContributedFunctions(LOAD_NAME, NoLookupLocation.FROM_BACKEND).singleOrNull {
it.valueParameters.size == 1
&& it.overriddenDescriptors.isNotEmpty()
&& it.kind != CallableMemberDescriptor.Kind.SYNTHESIZED
} != null
if (descriptorOverridden && serializeOverridden && deserializeOverridden) {
val entry = classDescriptor.findAnnotationDeclaration(SerializationAnnotations.serializerAnnotationFqName)
trace.report(EXTERNAL_SERIALIZER_USELESS.on(entry ?: declaration, classDescriptor.defaultType))
return
}
if (!props.isExternallySerializable) {
val entry = classDescriptor.findAnnotationDeclaration(SerializationAnnotations.serializerAnnotationFqName)
val inSameModule =
@@ -165,6 +165,12 @@ object SerializationPluginErrorsRendering : DefaultErrorMessages.Extension {
"@MetaSerializable annotation should be used only on top-level annotation classes. Usage on nested annotation classes is deprecated and will yield errors in the future."
)
MAP.put(
SerializationErrors.EXTERNAL_SERIALIZER_USELESS,
"@Serializer annotation has no effect on class ''{0}'', because all members of KSerializer are already overridden",
Renderers.RENDER_TYPE
)
MAP.put(
SerializationErrors.EXTERNAL_CLASS_NOT_SERIALIZABLE,
"Cannot generate external serializer ''{0}'': class ''{1}'' have constructor parameters which are not properties and therefore it is not serializable automatically",
@@ -47,6 +47,7 @@ object FirSerializationErrors {
val INCONSISTENT_INHERITABLE_SERIALINFO by error2<PsiElement, ConeKotlinType, ConeKotlinType>()
val META_SERIALIZABLE_NOT_APPLICABLE by error0<PsiElement>()
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>()
@@ -26,11 +26,13 @@ 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_USELESS
import org.jetbrains.kotlinx.serialization.compiler.fir.getSerializerForClass
import org.jetbrains.kotlinx.serialization.compiler.fir.services.dependencySerializationInfoProvider
import org.jetbrains.kotlinx.serialization.compiler.fir.services.findTypeSerializerOrContextUnchecked
import org.jetbrains.kotlinx.serialization.compiler.fir.services.serializablePropertiesProvider
import org.jetbrains.kotlinx.serialization.compiler.fir.services.versionReader
import org.jetbrains.kotlinx.serialization.compiler.resolve.SerialEntityNames
import org.jetbrains.kotlinx.serialization.compiler.resolve.SerializationAnnotations
import org.jetbrains.kotlinx.serialization.compiler.resolve.SerializersClassIds
@@ -74,6 +76,32 @@ object FirSerializationPluginClassChecker : FirClassChecker() {
private fun checkExternalSerializer(classSymbol: FirClassSymbol<*>, reporter: DiagnosticReporter) {
val serializableKType = classSymbol.getSerializerForClass(session) ?: return
val serializableClassSymbol = serializableKType.toRegularClassSymbol(session) ?: return
val declarations = classSymbol.declarationSymbols
val descriptorOverridden = declarations.filterIsInstance<FirPropertySymbol>().singleOrNull {
it.name == SerialEntityNames.SERIAL_DESC_FIELD_NAME
&& it.isOverride
&& it.origin == FirDeclarationOrigin.Source
} != null
val serializeOverridden = declarations.filterIsInstance<FirFunctionSymbol<*>>().singleOrNull {
it.name == SerialEntityNames.SAVE_NAME
&& it.valueParameterSymbols.size == 2
&& it.isOverride
&& it.origin == FirDeclarationOrigin.Source
} != null
val deserializeOverridden = declarations.filterIsInstance<FirFunctionSymbol<*>>().singleOrNull {
it.name == SerialEntityNames.LOAD_NAME
&& it.valueParameterSymbols.size == 1
&& it.isOverride
&& it.origin == FirDeclarationOrigin.Source
} != null
if (descriptorOverridden && serializeOverridden && deserializeOverridden) {
val source = classSymbol.getSerializerAnnotation(session)?.source ?: classSymbol.source
reporter.reportOn(source, EXTERNAL_SERIALIZER_USELESS, classSymbol)
return
}
val properties = session.serializablePropertiesProvider.getSerializablePropertiesForClass(serializableClassSymbol)
if (!properties.isExternallySerializable) {
val source = classSymbol.getSerializerAnnotation(session)?.source ?: classSymbol.source
@@ -156,6 +156,12 @@ object KtDefaultErrorMessagesSerialization : BaseDiagnosticRendererFactory() {
"@MetaSerializable annotation can be used only on top-level annotation classes."
)
put(
FirSerializationErrors.EXTERNAL_SERIALIZER_USELESS,
"@Serializer annotation has no effect on class ''{0}'', because all members of KSerializer are already overridden",
FirDiagnosticRenderers.SYMBOL,
)
put(
FirSerializationErrors.EXTERNAL_CLASS_NOT_SERIALIZABLE,
"Cannot generate external serializer ''{0}'': class ''{1}'' have constructor parameters which are not properties and therefore it is not serializable automatically",
@@ -4,8 +4,26 @@
// FILE: test.kt
import kotlinx.serialization.*
import kotlinx.serialization.descriptors.*
import kotlinx.serialization.encoding.*
class Foo(i: Int, val j: Int)
<!EXTERNAL_CLASS_NOT_SERIALIZABLE!>@Serializer(forClass = Foo::class)<!>
object ExternalSerializer
<!EXTERNAL_SERIALIZER_USELESS!>@Serializer(forClass = Foo::class)<!>
object UselessExternalSerializer : KSerializer<Foo> {
override val descriptor: SerialDescriptor
get() {
TODO()
}
override fun serialize(encoder: Encoder, value: Foo) {
TODO()
}
override fun deserialize(decoder: Decoder): Foo {
TODO()
}
}
@@ -10,8 +10,6 @@ import java.util.*
@Serializer(forClass = Date::class)
object DateSerializer : KSerializer<Date> {
override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("java.util.Date", PrimitiveKind.STRING)
override fun serialize(encoder: Encoder, value: Date) {
TODO()
}
@@ -181,7 +181,7 @@ FILE: serializerViaCompanion.kt
public final val i: R|kotlin/Int| = R|<local>/i|
public get(): R|kotlin/Int|
@R|kotlinx/serialization/Serializer|(forClass = <getClass>(Q|com/example/FullyOverridden|)) public final companion object Companion : R|kotlinx/serialization/KSerializer<com/example/FullyOverridden>| {
public final companion object Companion : R|kotlinx/serialization/KSerializer<com/example/FullyOverridden>| {
private constructor(): R|com/example/FullyOverridden.Companion| {
super<R|kotlin/Any|>()
}
@@ -67,8 +67,8 @@ data class PartiallyWithoutType(val i: Int) {
@Serializable(FullyOverridden.Companion::class)
data class FullyOverridden(val i: Int) {
@Serializer(forClass = FullyOverridden::class)
companion object : KSerializer<FullyOverridden> {
override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("FullyOverridden", PrimitiveKind.STRING)
override fun serialize(encoder: Encoder, value: FullyOverridden) {