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:
Leonid Startsev
2023-03-17 16:57:31 +01:00
committed by Space Team
parent 8b12b3f18d
commit 6ee20574e1
13 changed files with 125 additions and 4 deletions
@@ -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> =
@@ -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);
@@ -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
@@ -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,
@@ -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>()
@@ -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) {
@@ -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>)
@@ -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 {
@@ -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 {
@@ -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 {
@@ -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 {