[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:
committed by
Space Team
parent
3124154aef
commit
c9b8160f1e
+2
@@ -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);
|
||||
|
||||
|
||||
+30
@@ -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 =
|
||||
|
||||
+6
@@ -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",
|
||||
|
||||
+1
@@ -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>()
|
||||
|
||||
|
||||
+28
@@ -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
|
||||
|
||||
+6
@@ -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()
|
||||
}
|
||||
|
||||
+1
-1
@@ -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|>()
|
||||
}
|
||||
|
||||
+1
-1
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user