diff --git a/plugins/kotlinx-serialization/kotlinx-serialization.backend/src/org/jetbrains/kotlinx/serialization/compiler/backend/ir/IrBuilderWithPluginContext.kt b/plugins/kotlinx-serialization/kotlinx-serialization.backend/src/org/jetbrains/kotlinx/serialization/compiler/backend/ir/IrBuilderWithPluginContext.kt index 2f4ade9fb4a..74784c841e4 100644 --- a/plugins/kotlinx-serialization/kotlinx-serialization.backend/src/org/jetbrains/kotlinx/serialization/compiler/backend/ir/IrBuilderWithPluginContext.kt +++ b/plugins/kotlinx-serialization/kotlinx-serialization.backend/src/org/jetbrains/kotlinx/serialization/compiler/backend/ir/IrBuilderWithPluginContext.kt @@ -383,8 +383,8 @@ interface IrBuilderWithPluginContext { fun collectSerialInfoAnnotations(irClass: IrClass): List { if (!(irClass.isInterface || irClass.hasSerializableOrMetaAnnotation())) return emptyList() - val annotationByFq: MutableMap = - irClass.annotations.associateBy { it.symbol.owner.parentAsClass.fqNameWhenAvailable!! }.toMutableMap() + val annotationByFq: MutableMap> = + 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): List = 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 9fd3c78c530..3519881a44c 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); + DiagnosticFactory0 INHERITABLE_SERIALINFO_CANT_BE_REPEATABLE = DiagnosticFactory0.create(ERROR); + DiagnosticFactory1 EXTERNAL_SERIALIZER_USELESS = DiagnosticFactory1.create(WARNING); DiagnosticFactory2 EXTERNAL_CLASS_NOT_SERIALIZABLE = 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 6dcb7d6ab85..52b231990f6 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 @@ -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 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 ee3cdc12cd0..620976d5de5 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 @@ -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, 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 7fc086ccd99..4ed8de7feb0 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 @@ -46,6 +46,7 @@ object FirSerializationErrors { val INCONSISTENT_INHERITABLE_SERIALINFO by error2() val META_SERIALIZABLE_NOT_APPLICABLE by error0() + val INHERITABLE_SERIALINFO_CANT_BE_REPEATABLE by error0() val EXTERNAL_SERIALIZER_USELESS by warning1>() val EXTERNAL_CLASS_NOT_SERIALIZABLE 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 eab61372512..3a0c375d82e 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 @@ -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, 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) { 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 20b64cb352b..f12a517ea2e 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 @@ -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, diff --git a/plugins/kotlinx-serialization/testData/boxIr/repeatableSerialInfo.kt b/plugins/kotlinx-serialization/testData/boxIr/repeatableSerialInfo.kt new file mode 100644 index 00000000000..cd013c25b15 --- /dev/null +++ b/plugins/kotlinx-serialization/testData/boxIr/repeatableSerialInfo.kt @@ -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) + +@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.sum(): Int = filterIsInstance().sumOf { it.value } +fun List.sumJava(): Int = filterIsInstance().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" +} diff --git a/plugins/kotlinx-serialization/testData/diagnostics/repeatableSerialInfo.kt b/plugins/kotlinx-serialization/testData/diagnostics/repeatableSerialInfo.kt new file mode 100644 index 00000000000..b94cb4ef2a4 --- /dev/null +++ b/plugins/kotlinx-serialization/testData/diagnostics/repeatableSerialInfo.kt @@ -0,0 +1,22 @@ +// FIR_DISABLE_LAZY_RESOLVE_CHECKS +// FIR_IDENTICAL +// SKIP_TXT + +// WITH_STDLIB +// FULL_JDK + +import kotlinx.serialization.* + +@InheritableSerialInfo +@Target(AnnotationTarget.CLASS, AnnotationTarget.PROPERTY) +@Repeatable +annotation class RepeatableSerialInfo(val value: Int) + +@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) + diff --git a/plugins/kotlinx-serialization/tests-gen/org/jetbrains/kotlinx/serialization/runners/SerializationFirLightTreeBlackBoxTestGenerated.java b/plugins/kotlinx-serialization/tests-gen/org/jetbrains/kotlinx/serialization/runners/SerializationFirLightTreeBlackBoxTestGenerated.java index 70f34f7c4a8..824d3522dd5 100644 --- a/plugins/kotlinx-serialization/tests-gen/org/jetbrains/kotlinx/serialization/runners/SerializationFirLightTreeBlackBoxTestGenerated.java +++ b/plugins/kotlinx-serialization/tests-gen/org/jetbrains/kotlinx/serialization/runners/SerializationFirLightTreeBlackBoxTestGenerated.java @@ -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 { diff --git a/plugins/kotlinx-serialization/tests-gen/org/jetbrains/kotlinx/serialization/runners/SerializationFirPsiDiagnosticTestGenerated.java b/plugins/kotlinx-serialization/tests-gen/org/jetbrains/kotlinx/serialization/runners/SerializationFirPsiDiagnosticTestGenerated.java index d24603be442..d95ddc5b6c2 100644 --- a/plugins/kotlinx-serialization/tests-gen/org/jetbrains/kotlinx/serialization/runners/SerializationFirPsiDiagnosticTestGenerated.java +++ b/plugins/kotlinx-serialization/tests-gen/org/jetbrains/kotlinx/serialization/runners/SerializationFirPsiDiagnosticTestGenerated.java @@ -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 { diff --git a/plugins/kotlinx-serialization/tests-gen/org/jetbrains/kotlinx/serialization/runners/SerializationIrBoxTestGenerated.java b/plugins/kotlinx-serialization/tests-gen/org/jetbrains/kotlinx/serialization/runners/SerializationIrBoxTestGenerated.java index ced9e702dd7..3cc1b56a8b4 100644 --- a/plugins/kotlinx-serialization/tests-gen/org/jetbrains/kotlinx/serialization/runners/SerializationIrBoxTestGenerated.java +++ b/plugins/kotlinx-serialization/tests-gen/org/jetbrains/kotlinx/serialization/runners/SerializationIrBoxTestGenerated.java @@ -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 { diff --git a/plugins/kotlinx-serialization/tests-gen/org/jetbrains/kotlinx/serialization/runners/SerializationPluginDiagnosticTestGenerated.java b/plugins/kotlinx-serialization/tests-gen/org/jetbrains/kotlinx/serialization/runners/SerializationPluginDiagnosticTestGenerated.java index b1b47bed33c..bebbc52d8e9 100644 --- a/plugins/kotlinx-serialization/tests-gen/org/jetbrains/kotlinx/serialization/runners/SerializationPluginDiagnosticTestGenerated.java +++ b/plugins/kotlinx-serialization/tests-gen/org/jetbrains/kotlinx/serialization/runners/SerializationPluginDiagnosticTestGenerated.java @@ -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 {