Correctly handle @Repeatable @SerialInfo annotations on classes
that were affected by deduplication of inherited serial info annotations. Prohibit combination of @InheritableSerialInfo and @Repeatable. Fixes https://github.com/Kotlin/kotlinx.serialization/issues/2099
This commit is contained in:
committed by
Space Team
parent
8b12b3f18d
commit
6ee20574e1
+5
-4
@@ -383,8 +383,8 @@ interface IrBuilderWithPluginContext {
|
||||
|
||||
fun collectSerialInfoAnnotations(irClass: IrClass): List<IrConstructorCall> {
|
||||
if (!(irClass.isInterface || irClass.hasSerializableOrMetaAnnotation())) return emptyList()
|
||||
val annotationByFq: MutableMap<FqName, IrConstructorCall> =
|
||||
irClass.annotations.associateBy { it.symbol.owner.parentAsClass.fqNameWhenAvailable!! }.toMutableMap()
|
||||
val annotationByFq: MutableMap<FqName, List<IrConstructorCall>> =
|
||||
irClass.annotations.groupBy { it.symbol.owner.parentAsClass.fqNameWhenAvailable!! }.toMutableMap()
|
||||
for (clazz in irClass.getAllSuperclasses()) {
|
||||
val annotations = clazz.annotations
|
||||
.mapNotNull {
|
||||
@@ -393,13 +393,14 @@ interface IrBuilderWithPluginContext {
|
||||
}
|
||||
annotations.forEach { (fqname, call) ->
|
||||
if (fqname !in annotationByFq) {
|
||||
annotationByFq[fqname] = call
|
||||
annotationByFq[fqname] = listOf(call)
|
||||
} else {
|
||||
// SerializationPluginDeclarationChecker already reported inconsistency
|
||||
// InheritableSerialInfo annotations can not be repeatable
|
||||
}
|
||||
}
|
||||
}
|
||||
return annotationByFq.values.toList()
|
||||
return annotationByFq.values.toList().flatten()
|
||||
}
|
||||
|
||||
fun IrBuilderWithScope.copyAnnotationsFrom(annotations: List<IrConstructorCall>): List<IrExpression> =
|
||||
|
||||
+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);
|
||||
|
||||
DiagnosticFactory0<PsiElement> INHERITABLE_SERIALINFO_CANT_BE_REPEATABLE = DiagnosticFactory0.create(ERROR);
|
||||
|
||||
DiagnosticFactory1<PsiElement, KotlinType> EXTERNAL_SERIALIZER_USELESS = DiagnosticFactory1.create(WARNING);
|
||||
|
||||
DiagnosticFactory2<PsiElement, KotlinType, KotlinType> EXTERNAL_CLASS_NOT_SERIALIZABLE = DiagnosticFactory2.create(ERROR);
|
||||
|
||||
+10
@@ -15,6 +15,7 @@ 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.load.java.JvmAnnotationNames
|
||||
import org.jetbrains.kotlin.name.FqName
|
||||
import org.jetbrains.kotlin.name.JvmNames.TRANSIENT_ANNOTATION_FQ_NAME
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
@@ -47,6 +48,7 @@ open class SerializationPluginDeclarationChecker : DeclarationChecker {
|
||||
if (descriptor !is ClassDescriptor) return
|
||||
|
||||
checkMetaSerializableApplicable(descriptor, context.trace)
|
||||
checkInheritableSerialInfoNotRepeatable(descriptor, context.trace)
|
||||
checkEnum(descriptor, declaration, context.trace)
|
||||
checkExternalSerializer(descriptor, declaration, context.trace)
|
||||
|
||||
@@ -74,6 +76,14 @@ open class SerializationPluginDeclarationChecker : DeclarationChecker {
|
||||
trace.report(SerializationErrors.META_SERIALIZABLE_NOT_APPLICABLE.on(entry))
|
||||
}
|
||||
|
||||
private fun checkInheritableSerialInfoNotRepeatable(descriptor: ClassDescriptor, trace: BindingTrace) {
|
||||
if (descriptor.kind != ClassKind.ANNOTATION_CLASS) return
|
||||
// both kotlin.Repeatable and java.lang.annotation.Repeatable
|
||||
if (!(descriptor.isAnnotatedWithKotlinRepeatable() || descriptor.annotations.hasAnnotation(JvmAnnotationNames.REPEATABLE_ANNOTATION))) return
|
||||
val inheritableAnno = descriptor.findAnnotationDeclaration(SerializationAnnotations.inheritableSerialInfoFqName) ?: return
|
||||
trace.report(SerializationErrors.INHERITABLE_SERIALINFO_CANT_BE_REPEATABLE.on(inheritableAnno))
|
||||
}
|
||||
|
||||
private fun checkExternalSerializer(classDescriptor: ClassDescriptor, declaration: KtDeclaration, trace: BindingTrace) {
|
||||
val serializableKType = classDescriptor.serializerForClass ?: return
|
||||
val serializableDescriptor = serializableKType.toClassDescriptor ?: return
|
||||
|
||||
+4
@@ -164,6 +164,10 @@ object SerializationPluginErrorsRendering : DefaultErrorMessages.Extension {
|
||||
SerializationErrors.META_SERIALIZABLE_NOT_APPLICABLE,
|
||||
"@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.INHERITABLE_SERIALINFO_CANT_BE_REPEATABLE,
|
||||
"Repeatable serial info annotations can not be inheritable. Either remove @Repeatable or use a regular @SerialInfo annotation."
|
||||
)
|
||||
|
||||
MAP.put(
|
||||
SerializationErrors.EXTERNAL_SERIALIZER_USELESS,
|
||||
|
||||
+1
@@ -46,6 +46,7 @@ object FirSerializationErrors {
|
||||
|
||||
val INCONSISTENT_INHERITABLE_SERIALINFO by error2<PsiElement, ConeKotlinType, ConeKotlinType>()
|
||||
val META_SERIALIZABLE_NOT_APPLICABLE by error0<PsiElement>()
|
||||
val INHERITABLE_SERIALINFO_CANT_BE_REPEATABLE by error0<PsiElement>()
|
||||
|
||||
val EXTERNAL_SERIALIZER_USELESS by warning1<PsiElement, FirClassSymbol<*>>()
|
||||
val EXTERNAL_CLASS_NOT_SERIALIZABLE by error2<PsiElement, FirClassSymbol<*>, ConeKotlinType>()
|
||||
|
||||
+12
@@ -9,6 +9,7 @@ import org.jetbrains.kotlin.KtSourceElement
|
||||
import org.jetbrains.kotlin.config.KotlinCompilerVersion
|
||||
import org.jetbrains.kotlin.descriptors.ClassKind
|
||||
import org.jetbrains.kotlin.diagnostics.*
|
||||
import org.jetbrains.kotlin.fir.analysis.checkers.containsRepeatableAnnotation
|
||||
import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext
|
||||
import org.jetbrains.kotlin.fir.analysis.checkers.declaration.FirClassChecker
|
||||
import org.jetbrains.kotlin.fir.analysis.checkers.isSingleFieldValueClass
|
||||
@@ -46,6 +47,7 @@ object FirSerializationPluginClassChecker : FirClassChecker() {
|
||||
with(context) {
|
||||
val classSymbol = declaration.symbol
|
||||
checkMetaSerializableApplicable(classSymbol, reporter)
|
||||
checkInheritableSerialInfoNotRepeatable(classSymbol, reporter)
|
||||
checkEnum(classSymbol, reporter)
|
||||
checkExternalSerializer(classSymbol, reporter)
|
||||
if (!canBeSerializedInternally(classSymbol, reporter)) return
|
||||
@@ -71,6 +73,16 @@ object FirSerializationPluginClassChecker : FirClassChecker() {
|
||||
reporter.reportOn(anno.source, FirSerializationErrors.META_SERIALIZABLE_NOT_APPLICABLE)
|
||||
}
|
||||
|
||||
context(CheckerContext)
|
||||
private fun checkInheritableSerialInfoNotRepeatable(classSymbol: FirClassSymbol<out FirClass>, reporter: DiagnosticReporter) {
|
||||
if (classSymbol.classKind != ClassKind.ANNOTATION_CLASS) return
|
||||
if (!classSymbol.containsRepeatableAnnotation(session)) return
|
||||
val anno = classSymbol.resolvedAnnotationsWithClassIds
|
||||
.find { it.toAnnotationClassId(session) == SerializationAnnotations.inheritableSerialInfoClassId }
|
||||
?: return
|
||||
reporter.reportOn(anno.source, FirSerializationErrors.INHERITABLE_SERIALINFO_CANT_BE_REPEATABLE)
|
||||
}
|
||||
|
||||
context(CheckerContext)
|
||||
@Suppress("IncorrectFormatting") // KTIJ-22227
|
||||
private fun checkExternalSerializer(classSymbol: FirClassSymbol<*>, reporter: DiagnosticReporter) {
|
||||
|
||||
+4
@@ -155,6 +155,10 @@ object KtDefaultErrorMessagesSerialization : BaseDiagnosticRendererFactory() {
|
||||
FirSerializationErrors.META_SERIALIZABLE_NOT_APPLICABLE,
|
||||
"@MetaSerializable annotation can be used only on top-level annotation classes."
|
||||
)
|
||||
put(
|
||||
FirSerializationErrors.INHERITABLE_SERIALINFO_CANT_BE_REPEATABLE,
|
||||
"Repeatable serial info annotations can not be inheritable. Either remove @Repeatable or use a regular @SerialInfo annotation."
|
||||
)
|
||||
|
||||
put(
|
||||
FirSerializationErrors.EXTERNAL_SERIALIZER_USELESS,
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
// TARGET_BACKEND: JVM_IR
|
||||
|
||||
// WITH_STDLIB
|
||||
// FULL_JDK
|
||||
|
||||
import kotlinx.serialization.*
|
||||
|
||||
@SerialInfo
|
||||
@Target(AnnotationTarget.CLASS, AnnotationTarget.PROPERTY)
|
||||
@Repeatable
|
||||
annotation class RepeatableSerialInfo(val value: Int)
|
||||
|
||||
@SerialInfo
|
||||
@Target(AnnotationTarget.CLASS, AnnotationTarget.PROPERTY)
|
||||
@java.lang.annotation.Repeatable(JavaRepeatableContainer::class)
|
||||
annotation class JavaRepeatable(val value2: Int)
|
||||
|
||||
@Target(AnnotationTarget.CLASS, AnnotationTarget.PROPERTY)
|
||||
annotation class JavaRepeatableContainer(val value: Array<JavaRepeatable>)
|
||||
|
||||
@Serializable
|
||||
@RepeatableSerialInfo(1)
|
||||
@RepeatableSerialInfo(2)
|
||||
@RepeatableSerialInfo(3)
|
||||
@JavaRepeatable(2)
|
||||
@JavaRepeatable(3)
|
||||
data class RepeatableSerialInfoClass(
|
||||
@RepeatableSerialInfo(4) @RepeatableSerialInfo(5) @JavaRepeatable(6) @JavaRepeatable(7) val name: String = "Some Name"
|
||||
)
|
||||
|
||||
fun List<Annotation>.sum(): Int = filterIsInstance<RepeatableSerialInfo>().sumOf { it.value }
|
||||
fun List<Annotation>.sumJava(): Int = filterIsInstance<JavaRepeatable>().sumOf { it.value2 }
|
||||
|
||||
fun box(): String {
|
||||
val d = RepeatableSerialInfoClass.serializer().descriptor
|
||||
if (d.annotations.sum() != 6) return "Incorrect number of RepeatableSerialInfo on class: ${d.annotations}"
|
||||
if (d.annotations.sumJava() != 5) return "Incorrect number of JavaRepeatable on class: ${d.annotations}"
|
||||
if (d.getElementAnnotations(0).sum() != 9) return "Incorrect number of RepeatableSerialInfo on property: ${d.getElementAnnotations(0)}"
|
||||
if (d.getElementAnnotations(0).sumJava() != 13) return "Incorrect number of JavaRepeatable on property: ${d.getElementAnnotations(0)}"
|
||||
return "OK"
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
// FIR_DISABLE_LAZY_RESOLVE_CHECKS
|
||||
// FIR_IDENTICAL
|
||||
// SKIP_TXT
|
||||
|
||||
// WITH_STDLIB
|
||||
// FULL_JDK
|
||||
|
||||
import kotlinx.serialization.*
|
||||
|
||||
<!INHERITABLE_SERIALINFO_CANT_BE_REPEATABLE!>@InheritableSerialInfo<!>
|
||||
@Target(AnnotationTarget.CLASS, AnnotationTarget.PROPERTY)
|
||||
@Repeatable
|
||||
annotation class RepeatableSerialInfo(val value: Int)
|
||||
|
||||
<!INHERITABLE_SERIALINFO_CANT_BE_REPEATABLE!>@InheritableSerialInfo<!>
|
||||
@Target(AnnotationTarget.CLASS, AnnotationTarget.PROPERTY)
|
||||
@java.lang.annotation.Repeatable(JavaRepeatableContainer::class)
|
||||
annotation class JavaRepeatable(val value2: Int)
|
||||
|
||||
@Target(AnnotationTarget.CLASS, AnnotationTarget.PROPERTY)
|
||||
annotation class JavaRepeatableContainer(val value: Array<JavaRepeatable>)
|
||||
|
||||
+6
@@ -159,6 +159,12 @@ public class SerializationFirLightTreeBlackBoxTestGenerated extends AbstractSeri
|
||||
runTest("plugins/kotlinx-serialization/testData/boxIr/multipleGenericsPolymorphic.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("repeatableSerialInfo.kt")
|
||||
public void testRepeatableSerialInfo() throws Exception {
|
||||
runTest("plugins/kotlinx-serialization/testData/boxIr/repeatableSerialInfo.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("sealedInterfaces.kt")
|
||||
public void testSealedInterfaces() throws Exception {
|
||||
|
||||
+6
@@ -122,6 +122,12 @@ public class SerializationFirPsiDiagnosticTestGenerated extends AbstractSerializ
|
||||
runTest("plugins/kotlinx-serialization/testData/diagnostics/ParamIsNotProperty.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("repeatableSerialInfo.kt")
|
||||
public void testRepeatableSerialInfo() throws Exception {
|
||||
runTest("plugins/kotlinx-serialization/testData/diagnostics/repeatableSerialInfo.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("SerializableEnums.kt")
|
||||
public void testSerializableEnums() throws Exception {
|
||||
|
||||
+6
@@ -157,6 +157,12 @@ public class SerializationIrBoxTestGenerated extends AbstractSerializationIrBoxT
|
||||
runTest("plugins/kotlinx-serialization/testData/boxIr/multipleGenericsPolymorphic.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("repeatableSerialInfo.kt")
|
||||
public void testRepeatableSerialInfo() throws Exception {
|
||||
runTest("plugins/kotlinx-serialization/testData/boxIr/repeatableSerialInfo.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("sealedInterfaces.kt")
|
||||
public void testSealedInterfaces() throws Exception {
|
||||
|
||||
+6
@@ -120,6 +120,12 @@ public class SerializationPluginDiagnosticTestGenerated extends AbstractSerializ
|
||||
runTest("plugins/kotlinx-serialization/testData/diagnostics/ParamIsNotProperty.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("repeatableSerialInfo.kt")
|
||||
public void testRepeatableSerialInfo() throws Exception {
|
||||
runTest("plugins/kotlinx-serialization/testData/diagnostics/repeatableSerialInfo.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("SerializableEnums.kt")
|
||||
public void testSerializableEnums() throws Exception {
|
||||
|
||||
Reference in New Issue
Block a user