diff --git a/plugins/kotlinx-serialization/kotlinx-serialization.k1/src/org/jetbrains/kotlinx/serialization/compiler/diagnostic/SerializationErrors.java b/plugins/kotlinx-serialization/kotlinx-serialization.k1/src/org/jetbrains/kotlinx/serialization/compiler/diagnostic/SerializationErrors.java index 1954bd228cc..9fd3c78c530 100644 --- a/plugins/kotlinx-serialization/kotlinx-serialization.k1/src/org/jetbrains/kotlinx/serialization/compiler/diagnostic/SerializationErrors.java +++ b/plugins/kotlinx-serialization/kotlinx-serialization.k1/src/org/jetbrains/kotlinx/serialization/compiler/diagnostic/SerializationErrors.java @@ -54,6 +54,8 @@ public interface SerializationErrors { DiagnosticFactory2 INCONSISTENT_INHERITABLE_SERIALINFO = DiagnosticFactory2.create(ERROR); DiagnosticFactory0 META_SERIALIZABLE_NOT_APPLICABLE = DiagnosticFactory0.create(WARNING); + DiagnosticFactory1 EXTERNAL_SERIALIZER_USELESS = DiagnosticFactory1.create(WARNING); + DiagnosticFactory2 EXTERNAL_CLASS_NOT_SERIALIZABLE = DiagnosticFactory2.create(ERROR); DiagnosticFactory2 EXTERNAL_CLASS_IN_ANOTHER_MODULE = DiagnosticFactory2.create(ERROR); diff --git a/plugins/kotlinx-serialization/kotlinx-serialization.k1/src/org/jetbrains/kotlinx/serialization/compiler/diagnostic/SerializationPluginDeclarationChecker.kt b/plugins/kotlinx-serialization/kotlinx-serialization.k1/src/org/jetbrains/kotlinx/serialization/compiler/diagnostic/SerializationPluginDeclarationChecker.kt index c38b4186194..2226e853658 100644 --- a/plugins/kotlinx-serialization/kotlinx-serialization.k1/src/org/jetbrains/kotlinx/serialization/compiler/diagnostic/SerializationPluginDeclarationChecker.kt +++ b/plugins/kotlinx-serialization/kotlinx-serialization.k1/src/org/jetbrains/kotlinx/serialization/compiler/diagnostic/SerializationPluginDeclarationChecker.kt @@ -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 = 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 = diff --git a/plugins/kotlinx-serialization/kotlinx-serialization.k1/src/org/jetbrains/kotlinx/serialization/compiler/diagnostic/SerializationPluginErrorsRendering.kt b/plugins/kotlinx-serialization/kotlinx-serialization.k1/src/org/jetbrains/kotlinx/serialization/compiler/diagnostic/SerializationPluginErrorsRendering.kt index 9119b1ea94d..ee3cdc12cd0 100644 --- a/plugins/kotlinx-serialization/kotlinx-serialization.k1/src/org/jetbrains/kotlinx/serialization/compiler/diagnostic/SerializationPluginErrorsRendering.kt +++ b/plugins/kotlinx-serialization/kotlinx-serialization.k1/src/org/jetbrains/kotlinx/serialization/compiler/diagnostic/SerializationPluginErrorsRendering.kt @@ -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", diff --git a/plugins/kotlinx-serialization/kotlinx-serialization.k2/src/org/jetbrains/kotlinx/serialization/compiler/fir/checkers/FirSerializationErrors.kt b/plugins/kotlinx-serialization/kotlinx-serialization.k2/src/org/jetbrains/kotlinx/serialization/compiler/fir/checkers/FirSerializationErrors.kt index 8342313cb4b..7fc086ccd99 100644 --- a/plugins/kotlinx-serialization/kotlinx-serialization.k2/src/org/jetbrains/kotlinx/serialization/compiler/fir/checkers/FirSerializationErrors.kt +++ b/plugins/kotlinx-serialization/kotlinx-serialization.k2/src/org/jetbrains/kotlinx/serialization/compiler/fir/checkers/FirSerializationErrors.kt @@ -47,6 +47,7 @@ object FirSerializationErrors { val INCONSISTENT_INHERITABLE_SERIALINFO by error2() val META_SERIALIZABLE_NOT_APPLICABLE by error0() + val EXTERNAL_SERIALIZER_USELESS by warning1>() val EXTERNAL_CLASS_NOT_SERIALIZABLE by error2, ConeKotlinType>() val EXTERNAL_CLASS_IN_ANOTHER_MODULE by error2, ConeKotlinType>() diff --git a/plugins/kotlinx-serialization/kotlinx-serialization.k2/src/org/jetbrains/kotlinx/serialization/compiler/fir/checkers/FirSerializationPluginClassChecker.kt b/plugins/kotlinx-serialization/kotlinx-serialization.k2/src/org/jetbrains/kotlinx/serialization/compiler/fir/checkers/FirSerializationPluginClassChecker.kt index 806a0c7ffc6..31b1e109663 100644 --- a/plugins/kotlinx-serialization/kotlinx-serialization.k2/src/org/jetbrains/kotlinx/serialization/compiler/fir/checkers/FirSerializationPluginClassChecker.kt +++ b/plugins/kotlinx-serialization/kotlinx-serialization.k2/src/org/jetbrains/kotlinx/serialization/compiler/fir/checkers/FirSerializationPluginClassChecker.kt @@ -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().singleOrNull { + it.name == SerialEntityNames.SERIAL_DESC_FIELD_NAME + && it.isOverride + && it.origin == FirDeclarationOrigin.Source + } != null + val serializeOverridden = declarations.filterIsInstance>().singleOrNull { + it.name == SerialEntityNames.SAVE_NAME + && it.valueParameterSymbols.size == 2 + && it.isOverride + && it.origin == FirDeclarationOrigin.Source + } != null + val deserializeOverridden = declarations.filterIsInstance>().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 diff --git a/plugins/kotlinx-serialization/kotlinx-serialization.k2/src/org/jetbrains/kotlinx/serialization/compiler/fir/checkers/KtDefaultErrorMessagesSerialization.kt b/plugins/kotlinx-serialization/kotlinx-serialization.k2/src/org/jetbrains/kotlinx/serialization/compiler/fir/checkers/KtDefaultErrorMessagesSerialization.kt index c5cc124a256..20b64cb352b 100644 --- a/plugins/kotlinx-serialization/kotlinx-serialization.k2/src/org/jetbrains/kotlinx/serialization/compiler/fir/checkers/KtDefaultErrorMessagesSerialization.kt +++ b/plugins/kotlinx-serialization/kotlinx-serialization.k2/src/org/jetbrains/kotlinx/serialization/compiler/fir/checkers/KtDefaultErrorMessagesSerialization.kt @@ -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", diff --git a/plugins/kotlinx-serialization/testData/diagnostics/ExternalSerializers.kt b/plugins/kotlinx-serialization/testData/diagnostics/ExternalSerializers.kt index 6b4b5d4e15d..4ad26b060d3 100644 --- a/plugins/kotlinx-serialization/testData/diagnostics/ExternalSerializers.kt +++ b/plugins/kotlinx-serialization/testData/diagnostics/ExternalSerializers.kt @@ -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) @Serializer(forClass = Foo::class) object ExternalSerializer + +@Serializer(forClass = Foo::class) +object UselessExternalSerializer : KSerializer { + override val descriptor: SerialDescriptor + get() { + TODO() + } + + override fun serialize(encoder: Encoder, value: Foo) { + TODO() + } + + override fun deserialize(decoder: Decoder): Foo { + TODO() + } +} diff --git a/plugins/kotlinx-serialization/testData/diagnostics/externalSerialierJava.kt b/plugins/kotlinx-serialization/testData/diagnostics/externalSerialierJava.kt index ad1109c6b4d..aa6a1c5e130 100644 --- a/plugins/kotlinx-serialization/testData/diagnostics/externalSerialierJava.kt +++ b/plugins/kotlinx-serialization/testData/diagnostics/externalSerialierJava.kt @@ -10,8 +10,6 @@ import java.util.* @Serializer(forClass = Date::class) object DateSerializer : KSerializer { - override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("java.util.Date", PrimitiveKind.STRING) - override fun serialize(encoder: Encoder, value: Date) { TODO() } diff --git a/plugins/kotlinx-serialization/testData/firMembers/serializerViaCompanion.fir.txt b/plugins/kotlinx-serialization/testData/firMembers/serializerViaCompanion.fir.txt index 340c38035bb..21201291ead 100644 --- a/plugins/kotlinx-serialization/testData/firMembers/serializerViaCompanion.fir.txt +++ b/plugins/kotlinx-serialization/testData/firMembers/serializerViaCompanion.fir.txt @@ -181,7 +181,7 @@ FILE: serializerViaCompanion.kt public final val i: R|kotlin/Int| = R|/i| public get(): R|kotlin/Int| - @R|kotlinx/serialization/Serializer|(forClass = (Q|com/example/FullyOverridden|)) public final companion object Companion : R|kotlinx/serialization/KSerializer| { + public final companion object Companion : R|kotlinx/serialization/KSerializer| { private constructor(): R|com/example/FullyOverridden.Companion| { super() } diff --git a/plugins/kotlinx-serialization/testData/firMembers/serializerViaCompanion.kt b/plugins/kotlinx-serialization/testData/firMembers/serializerViaCompanion.kt index 756c6b8fc29..08a19001e3d 100644 --- a/plugins/kotlinx-serialization/testData/firMembers/serializerViaCompanion.kt +++ b/plugins/kotlinx-serialization/testData/firMembers/serializerViaCompanion.kt @@ -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 { + override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("FullyOverridden", PrimitiveKind.STRING) override fun serialize(encoder: Encoder, value: FullyOverridden) {