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> {
|
fun collectSerialInfoAnnotations(irClass: IrClass): List<IrConstructorCall> {
|
||||||
if (!(irClass.isInterface || irClass.hasSerializableOrMetaAnnotation())) return emptyList()
|
if (!(irClass.isInterface || irClass.hasSerializableOrMetaAnnotation())) return emptyList()
|
||||||
val annotationByFq: MutableMap<FqName, IrConstructorCall> =
|
val annotationByFq: MutableMap<FqName, List<IrConstructorCall>> =
|
||||||
irClass.annotations.associateBy { it.symbol.owner.parentAsClass.fqNameWhenAvailable!! }.toMutableMap()
|
irClass.annotations.groupBy { it.symbol.owner.parentAsClass.fqNameWhenAvailable!! }.toMutableMap()
|
||||||
for (clazz in irClass.getAllSuperclasses()) {
|
for (clazz in irClass.getAllSuperclasses()) {
|
||||||
val annotations = clazz.annotations
|
val annotations = clazz.annotations
|
||||||
.mapNotNull {
|
.mapNotNull {
|
||||||
@@ -393,13 +393,14 @@ interface IrBuilderWithPluginContext {
|
|||||||
}
|
}
|
||||||
annotations.forEach { (fqname, call) ->
|
annotations.forEach { (fqname, call) ->
|
||||||
if (fqname !in annotationByFq) {
|
if (fqname !in annotationByFq) {
|
||||||
annotationByFq[fqname] = call
|
annotationByFq[fqname] = listOf(call)
|
||||||
} else {
|
} else {
|
||||||
// SerializationPluginDeclarationChecker already reported inconsistency
|
// 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> =
|
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);
|
DiagnosticFactory2<PsiElement, KotlinType, KotlinType> INCONSISTENT_INHERITABLE_SERIALINFO = DiagnosticFactory2.create(ERROR);
|
||||||
DiagnosticFactory0<PsiElement> META_SERIALIZABLE_NOT_APPLICABLE = DiagnosticFactory0.create(WARNING);
|
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);
|
DiagnosticFactory1<PsiElement, KotlinType> EXTERNAL_SERIALIZER_USELESS = DiagnosticFactory1.create(WARNING);
|
||||||
|
|
||||||
DiagnosticFactory2<PsiElement, KotlinType, KotlinType> EXTERNAL_CLASS_NOT_SERIALIZABLE = DiagnosticFactory2.create(ERROR);
|
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.diagnostics.DiagnosticFactory0
|
||||||
import org.jetbrains.kotlin.incremental.components.NoLookupLocation
|
import org.jetbrains.kotlin.incremental.components.NoLookupLocation
|
||||||
import org.jetbrains.kotlin.js.resolve.diagnostics.findPsi
|
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.FqName
|
||||||
import org.jetbrains.kotlin.name.JvmNames.TRANSIENT_ANNOTATION_FQ_NAME
|
import org.jetbrains.kotlin.name.JvmNames.TRANSIENT_ANNOTATION_FQ_NAME
|
||||||
import org.jetbrains.kotlin.psi.*
|
import org.jetbrains.kotlin.psi.*
|
||||||
@@ -47,6 +48,7 @@ open class SerializationPluginDeclarationChecker : DeclarationChecker {
|
|||||||
if (descriptor !is ClassDescriptor) return
|
if (descriptor !is ClassDescriptor) return
|
||||||
|
|
||||||
checkMetaSerializableApplicable(descriptor, context.trace)
|
checkMetaSerializableApplicable(descriptor, context.trace)
|
||||||
|
checkInheritableSerialInfoNotRepeatable(descriptor, context.trace)
|
||||||
checkEnum(descriptor, declaration, context.trace)
|
checkEnum(descriptor, declaration, context.trace)
|
||||||
checkExternalSerializer(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))
|
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) {
|
private fun checkExternalSerializer(classDescriptor: ClassDescriptor, declaration: KtDeclaration, trace: BindingTrace) {
|
||||||
val serializableKType = classDescriptor.serializerForClass ?: return
|
val serializableKType = classDescriptor.serializerForClass ?: return
|
||||||
val serializableDescriptor = serializableKType.toClassDescriptor ?: return
|
val serializableDescriptor = serializableKType.toClassDescriptor ?: return
|
||||||
|
|||||||
+4
@@ -164,6 +164,10 @@ object SerializationPluginErrorsRendering : DefaultErrorMessages.Extension {
|
|||||||
SerializationErrors.META_SERIALIZABLE_NOT_APPLICABLE,
|
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."
|
"@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(
|
MAP.put(
|
||||||
SerializationErrors.EXTERNAL_SERIALIZER_USELESS,
|
SerializationErrors.EXTERNAL_SERIALIZER_USELESS,
|
||||||
|
|||||||
+1
@@ -46,6 +46,7 @@ object FirSerializationErrors {
|
|||||||
|
|
||||||
val INCONSISTENT_INHERITABLE_SERIALINFO by error2<PsiElement, ConeKotlinType, ConeKotlinType>()
|
val INCONSISTENT_INHERITABLE_SERIALINFO by error2<PsiElement, ConeKotlinType, ConeKotlinType>()
|
||||||
val META_SERIALIZABLE_NOT_APPLICABLE by error0<PsiElement>()
|
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_SERIALIZER_USELESS by warning1<PsiElement, FirClassSymbol<*>>()
|
||||||
val EXTERNAL_CLASS_NOT_SERIALIZABLE by error2<PsiElement, FirClassSymbol<*>, ConeKotlinType>()
|
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.config.KotlinCompilerVersion
|
||||||
import org.jetbrains.kotlin.descriptors.ClassKind
|
import org.jetbrains.kotlin.descriptors.ClassKind
|
||||||
import org.jetbrains.kotlin.diagnostics.*
|
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.context.CheckerContext
|
||||||
import org.jetbrains.kotlin.fir.analysis.checkers.declaration.FirClassChecker
|
import org.jetbrains.kotlin.fir.analysis.checkers.declaration.FirClassChecker
|
||||||
import org.jetbrains.kotlin.fir.analysis.checkers.isSingleFieldValueClass
|
import org.jetbrains.kotlin.fir.analysis.checkers.isSingleFieldValueClass
|
||||||
@@ -46,6 +47,7 @@ object FirSerializationPluginClassChecker : FirClassChecker() {
|
|||||||
with(context) {
|
with(context) {
|
||||||
val classSymbol = declaration.symbol
|
val classSymbol = declaration.symbol
|
||||||
checkMetaSerializableApplicable(classSymbol, reporter)
|
checkMetaSerializableApplicable(classSymbol, reporter)
|
||||||
|
checkInheritableSerialInfoNotRepeatable(classSymbol, reporter)
|
||||||
checkEnum(classSymbol, reporter)
|
checkEnum(classSymbol, reporter)
|
||||||
checkExternalSerializer(classSymbol, reporter)
|
checkExternalSerializer(classSymbol, reporter)
|
||||||
if (!canBeSerializedInternally(classSymbol, reporter)) return
|
if (!canBeSerializedInternally(classSymbol, reporter)) return
|
||||||
@@ -71,6 +73,16 @@ object FirSerializationPluginClassChecker : FirClassChecker() {
|
|||||||
reporter.reportOn(anno.source, FirSerializationErrors.META_SERIALIZABLE_NOT_APPLICABLE)
|
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)
|
context(CheckerContext)
|
||||||
@Suppress("IncorrectFormatting") // KTIJ-22227
|
@Suppress("IncorrectFormatting") // KTIJ-22227
|
||||||
private fun checkExternalSerializer(classSymbol: FirClassSymbol<*>, reporter: DiagnosticReporter) {
|
private fun checkExternalSerializer(classSymbol: FirClassSymbol<*>, reporter: DiagnosticReporter) {
|
||||||
|
|||||||
+4
@@ -155,6 +155,10 @@ object KtDefaultErrorMessagesSerialization : BaseDiagnosticRendererFactory() {
|
|||||||
FirSerializationErrors.META_SERIALIZABLE_NOT_APPLICABLE,
|
FirSerializationErrors.META_SERIALIZABLE_NOT_APPLICABLE,
|
||||||
"@MetaSerializable annotation can be used only on top-level annotation classes."
|
"@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(
|
put(
|
||||||
FirSerializationErrors.EXTERNAL_SERIALIZER_USELESS,
|
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");
|
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
|
@Test
|
||||||
@TestMetadata("sealedInterfaces.kt")
|
@TestMetadata("sealedInterfaces.kt")
|
||||||
public void testSealedInterfaces() throws Exception {
|
public void testSealedInterfaces() throws Exception {
|
||||||
|
|||||||
+6
@@ -122,6 +122,12 @@ public class SerializationFirPsiDiagnosticTestGenerated extends AbstractSerializ
|
|||||||
runTest("plugins/kotlinx-serialization/testData/diagnostics/ParamIsNotProperty.kt");
|
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
|
@Test
|
||||||
@TestMetadata("SerializableEnums.kt")
|
@TestMetadata("SerializableEnums.kt")
|
||||||
public void testSerializableEnums() throws Exception {
|
public void testSerializableEnums() throws Exception {
|
||||||
|
|||||||
+6
@@ -157,6 +157,12 @@ public class SerializationIrBoxTestGenerated extends AbstractSerializationIrBoxT
|
|||||||
runTest("plugins/kotlinx-serialization/testData/boxIr/multipleGenericsPolymorphic.kt");
|
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
|
@Test
|
||||||
@TestMetadata("sealedInterfaces.kt")
|
@TestMetadata("sealedInterfaces.kt")
|
||||||
public void testSealedInterfaces() throws Exception {
|
public void testSealedInterfaces() throws Exception {
|
||||||
|
|||||||
+6
@@ -120,6 +120,12 @@ public class SerializationPluginDiagnosticTestGenerated extends AbstractSerializ
|
|||||||
runTest("plugins/kotlinx-serialization/testData/diagnostics/ParamIsNotProperty.kt");
|
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
|
@Test
|
||||||
@TestMetadata("SerializableEnums.kt")
|
@TestMetadata("SerializableEnums.kt")
|
||||||
public void testSerializableEnums() throws Exception {
|
public void testSerializableEnums() throws Exception {
|
||||||
|
|||||||
Reference in New Issue
Block a user