diff --git a/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/diagnostic/SerializationErrors.java b/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/diagnostic/SerializationErrors.java index f6c61fe6904..6f2280bc69b 100644 --- a/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/diagnostic/SerializationErrors.java +++ b/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/diagnostic/SerializationErrors.java @@ -25,6 +25,7 @@ public interface SerializationErrors { DiagnosticFactory0 NON_SERIALIZABLE_PARENT_MUST_HAVE_NOARG_CTOR = DiagnosticFactory0.create(ERROR); DiagnosticFactory0 PRIMARY_CONSTRUCTOR_PARAMETER_IS_NOT_A_PROPERTY = DiagnosticFactory0.create(ERROR); DiagnosticFactory1 DUPLICATE_SERIAL_NAME = DiagnosticFactory1.create(ERROR); + DiagnosticFactory3 DUPLICATE_SERIAL_NAME_ENUM = DiagnosticFactory3.create(ERROR); DiagnosticFactory1 SERIALIZER_NOT_FOUND = DiagnosticFactory1.create(ERROR); DiagnosticFactory2 SERIALIZER_NULLABILITY_INCOMPATIBLE = DiagnosticFactory2.create(ERROR); DiagnosticFactory3 SERIALIZER_TYPE_INCOMPATIBLE = DiagnosticFactory3.create(WARNING); diff --git a/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/diagnostic/SerializationPluginDeclarationChecker.kt b/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/diagnostic/SerializationPluginDeclarationChecker.kt index 0af26c289ad..71ffd34628d 100644 --- a/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/diagnostic/SerializationPluginDeclarationChecker.kt +++ b/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/diagnostic/SerializationPluginDeclarationChecker.kt @@ -27,9 +27,8 @@ import org.jetbrains.kotlin.types.KotlinType import org.jetbrains.kotlin.types.typeUtil.supertypes import org.jetbrains.kotlin.util.slicedMap.Slices import org.jetbrains.kotlin.util.slicedMap.WritableSlice -import org.jetbrains.kotlinx.serialization.compiler.backend.common.AbstractSerialGenerator +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.findTypeSerializerOrContextUnchecked import org.jetbrains.kotlinx.serialization.compiler.backend.common.primaryConstructorPropertiesDescriptorsMap import org.jetbrains.kotlinx.serialization.compiler.resolve.* @@ -41,6 +40,7 @@ open class SerializationPluginDeclarationChecker : DeclarationChecker { final override fun check(declaration: KtDeclaration, descriptor: DeclarationDescriptor, context: DeclarationCheckerContext) { if (descriptor !is ClassDescriptor) return + checkEnum(descriptor, declaration, context.trace) checkExternalSerializer(descriptor, declaration, context.trace) if (!canBeSerializedInternally(descriptor, declaration, context.trace)) return @@ -164,7 +164,7 @@ open class SerializationPluginDeclarationChecker : DeclarationChecker { private fun canBeSerializedInternally(descriptor: ClassDescriptor, declaration: KtDeclaration, trace: BindingTrace): Boolean { // if enum has meta or SerialInfo annotation on a class or entries and used plugin-generated serializer - if (descriptor.isSerializableEnumWithMissingSerializer() && descriptor.useLegacyGeneratedEnumSerializer()) { + if (descriptor.useLegacyGeneratedEnumSerializer() && descriptor.isSerializableEnumWithMissingSerializer()) { val declarationToReport = declaration.modifierList ?: declaration trace.report(SerializationErrors.EXPLICIT_SERIALIZABLE_IS_REQUIRED.on(declarationToReport)) return false @@ -239,6 +239,30 @@ open class SerializationPluginDeclarationChecker : DeclarationChecker { return false } + private fun checkEnum(descriptor: ClassDescriptor, declaration: KtDeclaration, trace: BindingTrace) { + if (descriptor.kind != ClassKind.ENUM_CLASS) return + + val entryBySerialName = mutableMapOf() + descriptor.enumEntries().forEach { entryDescriptor -> + val serialNameAnnotation = entryDescriptor.annotations.serialNameAnnotation + val serialName = entryDescriptor.annotations.serialNameValue ?: entryDescriptor.name.asString() + val firstEntry = entryBySerialName[serialName] + if (firstEntry != null) { + trace.report( + SerializationErrors.DUPLICATE_SERIAL_NAME_ENUM.on( + serialNameAnnotation?.findAnnotationEntry() ?: firstEntry.annotations.serialNameAnnotation?.findAnnotationEntry() + ?: declaration, + descriptor.defaultType, + serialName, + entryDescriptor.name.asString() + ) + ) + } else { + entryBySerialName[serialName] = entryDescriptor + } + } + + } private fun ClassDescriptor.isSerializableEnumWithMissingSerializer(): Boolean { if (kind != ClassKind.ENUM_CLASS) return false if (hasSerializableOrMetaAnnotation) return false diff --git a/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/diagnostic/SerializationPluginErrorsRendering.kt b/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/diagnostic/SerializationPluginErrorsRendering.kt index 8f41c4ff74e..cab7951b6b0 100644 --- a/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/diagnostic/SerializationPluginErrorsRendering.kt +++ b/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/diagnostic/SerializationPluginErrorsRendering.kt @@ -56,6 +56,13 @@ object SerializationPluginErrorsRendering : DefaultErrorMessages.Extension { "Serializable class has duplicate serial name of property ''{0}'', either in the class itself or its supertypes", CommonRenderers.STRING ) + MAP.put( + SerializationErrors.DUPLICATE_SERIAL_NAME_ENUM, + "Enum class ''{0}'' has duplicate serial name ''{1}'' in entry ''{2}''", + Renderers.RENDER_TYPE, + CommonRenderers.STRING, + CommonRenderers.STRING + ) MAP.put( SerializationErrors.SERIALIZER_NOT_FOUND, "Serializer has not been found for type ''{0}''. " + diff --git a/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/resolve/KSerializationUtil.kt b/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/resolve/KSerializationUtil.kt index 56aa214060a..3c065bfd6a2 100644 --- a/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/resolve/KSerializationUtil.kt +++ b/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/resolve/KSerializationUtil.kt @@ -88,6 +88,9 @@ internal val ClassDescriptor.isInheritableSerialInfoAnnotation: Boolean internal val Annotations.serialNameValue: String? get() = findAnnotationConstantValue(SerializationAnnotations.serialNameAnnotationFqName, "value") +internal val Annotations.serialNameAnnotation: AnnotationDescriptor? + get() = findAnnotation(SerializationAnnotations.serialNameAnnotationFqName) + internal val Annotations.serialRequired: Boolean get() = hasAnnotation(SerializationAnnotations.requiredAnnotationFqName) diff --git a/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/resolve/SearchUtils.kt b/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/resolve/SearchUtils.kt index d5095a8a0e6..a536f4e76e6 100644 --- a/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/resolve/SearchUtils.kt +++ b/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/resolve/SearchUtils.kt @@ -7,10 +7,12 @@ package org.jetbrains.kotlinx.serialization.compiler.resolve import org.jetbrains.kotlin.descriptors.* 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.name.ClassId import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.name.Name +import org.jetbrains.kotlin.psi.KtAnnotationEntry import org.jetbrains.kotlin.psi.ValueArgument import org.jetbrains.kotlin.resolve.constants.KClassValue import org.jetbrains.kotlin.resolve.descriptorUtil.classId @@ -46,10 +48,13 @@ internal fun findSerializerConstructorForTypeArgumentsSerializers( return if (!onlyIfSynthetic) ctor else ctor?.takeIf { it.kind == CallableMemberDescriptor.Kind.SYNTHESIZED } } +fun AnnotationDescriptor.findAnnotationEntry(): KtAnnotationEntry? = (this as? LazyAnnotationDescriptor)?.annotationEntry + inline fun Annotations.findAnnotationConstantValue(annotationFqName: FqName, property: String): R? = - findAnnotation(annotationFqName)?.let { annotation -> - annotation.allValueArguments.entries.singleOrNull { it.key.asString() == property }?.value?.value - } as? R + findAnnotation(annotationFqName)?.findConstantValue(property) + +inline fun AnnotationDescriptor.findConstantValue(property: String): R? = + allValueArguments.entries.singleOrNull { it.key.asString() == property }?.value?.value as? R internal fun Annotations.findAnnotationKotlinTypeValue( annotationFqName: FqName, diff --git a/plugins/kotlin-serialization/kotlin-serialization-compiler/test/org/jetbrains/kotlinx/serialization/SerializationPluginDiagnosticTestGenerated.java b/plugins/kotlin-serialization/kotlin-serialization-compiler/test/org/jetbrains/kotlinx/serialization/SerializationPluginDiagnosticTestGenerated.java index 2789c60201e..e1a4b11457b 100644 --- a/plugins/kotlin-serialization/kotlin-serialization-compiler/test/org/jetbrains/kotlinx/serialization/SerializationPluginDiagnosticTestGenerated.java +++ b/plugins/kotlin-serialization/kotlin-serialization-compiler/test/org/jetbrains/kotlinx/serialization/SerializationPluginDiagnosticTestGenerated.java @@ -34,6 +34,11 @@ public class SerializationPluginDiagnosticTestGenerated extends AbstractSerializ runTest("plugins/kotlin-serialization/kotlin-serialization-compiler/testData/diagnostics/DuplicateSerialName.kt"); } + @TestMetadata("EnumDuplicateSerialName.kt") + public void testEnumDuplicateSerialName() throws Exception { + runTest("plugins/kotlin-serialization/kotlin-serialization-compiler/testData/diagnostics/EnumDuplicateSerialName.kt"); + } + @TestMetadata("ExternalSerializers.kt") public void testExternalSerializers() throws Exception { runTest("plugins/kotlin-serialization/kotlin-serialization-compiler/testData/diagnostics/ExternalSerializers.kt"); diff --git a/plugins/kotlin-serialization/kotlin-serialization-compiler/testData/diagnostics/EnumDuplicateSerialName.kt b/plugins/kotlin-serialization/kotlin-serialization-compiler/testData/diagnostics/EnumDuplicateSerialName.kt new file mode 100644 index 00000000000..26654963040 --- /dev/null +++ b/plugins/kotlin-serialization/kotlin-serialization-compiler/testData/diagnostics/EnumDuplicateSerialName.kt @@ -0,0 +1,29 @@ +// !DIAGNOSTICS: -UNUSED_PARAMETER,-UNUSED_VARIABLE +// WITH_STDLIB +// SKIP_TXT +import kotlinx.serialization.* + + +@Serializable +enum class ImplicitlyDuplicated { + @SerialName("foo") + FIRST, + @SerialName("foo") + SECOND +} + +@Serializable +enum class ExplicitlyDuplicated { + FIRST, + SECOND, + @SerialName("FIRST") + THIRD +} + +@Serializable +enum class ReversedExplicitlyDuplicated { + @SerialName("THIRD") + FIRST, + SECOND, + THIRD +}