Added inspection for duplicating serial names in enumeration entries
Resolves Kotlin/kotlinx.serialization#1852
This commit is contained in:
+1
@@ -25,6 +25,7 @@ public interface SerializationErrors {
|
||||
DiagnosticFactory0<KtAnnotationEntry> NON_SERIALIZABLE_PARENT_MUST_HAVE_NOARG_CTOR = DiagnosticFactory0.create(ERROR);
|
||||
DiagnosticFactory0<KtAnnotationEntry> PRIMARY_CONSTRUCTOR_PARAMETER_IS_NOT_A_PROPERTY = DiagnosticFactory0.create(ERROR);
|
||||
DiagnosticFactory1<KtAnnotationEntry, String> DUPLICATE_SERIAL_NAME = DiagnosticFactory1.create(ERROR);
|
||||
DiagnosticFactory3<PsiElement, KotlinType, String, String> DUPLICATE_SERIAL_NAME_ENUM = DiagnosticFactory3.create(ERROR);
|
||||
DiagnosticFactory1<PsiElement, KotlinType> SERIALIZER_NOT_FOUND = DiagnosticFactory1.create(ERROR);
|
||||
DiagnosticFactory2<PsiElement, KotlinType, KotlinType> SERIALIZER_NULLABILITY_INCOMPATIBLE = DiagnosticFactory2.create(ERROR);
|
||||
DiagnosticFactory3<PsiElement, KotlinType, KotlinType, KotlinType> SERIALIZER_TYPE_INCOMPATIBLE = DiagnosticFactory3.create(WARNING);
|
||||
|
||||
+27
-3
@@ -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<String, ClassDescriptor?>()
|
||||
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
|
||||
|
||||
+7
@@ -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}''. " +
|
||||
|
||||
+3
@@ -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)
|
||||
|
||||
|
||||
+8
-3
@@ -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 <reified R> 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 <reified R> AnnotationDescriptor.findConstantValue(property: String): R? =
|
||||
allValueArguments.entries.singleOrNull { it.key.asString() == property }?.value?.value as? R
|
||||
|
||||
internal fun Annotations.findAnnotationKotlinTypeValue(
|
||||
annotationFqName: FqName,
|
||||
|
||||
+5
@@ -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");
|
||||
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
// !DIAGNOSTICS: -UNUSED_PARAMETER,-UNUSED_VARIABLE
|
||||
// WITH_STDLIB
|
||||
// SKIP_TXT
|
||||
import kotlinx.serialization.*
|
||||
|
||||
|
||||
@Serializable
|
||||
enum class ImplicitlyDuplicated {
|
||||
@SerialName("foo")
|
||||
FIRST,
|
||||
<!DUPLICATE_SERIAL_NAME_ENUM!>@SerialName("foo")<!>
|
||||
SECOND
|
||||
}
|
||||
|
||||
@Serializable
|
||||
enum class ExplicitlyDuplicated {
|
||||
FIRST,
|
||||
SECOND,
|
||||
<!DUPLICATE_SERIAL_NAME_ENUM!>@SerialName("FIRST")<!>
|
||||
THIRD
|
||||
}
|
||||
|
||||
@Serializable
|
||||
enum class ReversedExplicitlyDuplicated {
|
||||
<!DUPLICATE_SERIAL_NAME_ENUM!>@SerialName("THIRD")<!>
|
||||
FIRST,
|
||||
SECOND,
|
||||
THIRD
|
||||
}
|
||||
Reference in New Issue
Block a user