diff --git a/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/diagnostics/KtFirDataClassConverters.kt b/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/diagnostics/KtFirDataClassConverters.kt index d2fa2f54455..04afa81543a 100644 --- a/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/diagnostics/KtFirDataClassConverters.kt +++ b/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/diagnostics/KtFirDataClassConverters.kt @@ -737,6 +737,18 @@ internal val KT_DIAGNOSTIC_CONVERTER = KtDiagnosticConverterBuilder.buildConvert token, ) } + add(FirErrors.CYCLE_IN_ANNOTATION_PARAMETER.errorFactory) { firDiagnostic -> + CycleInAnnotationParameterErrorImpl( + firDiagnostic as KtPsiDiagnostic, + token, + ) + } + add(FirErrors.CYCLE_IN_ANNOTATION_PARAMETER.warningFactory) { firDiagnostic -> + CycleInAnnotationParameterWarningImpl( + firDiagnostic as KtPsiDiagnostic, + token, + ) + } add(FirErrors.ANNOTATION_CLASS_CONSTRUCTOR_CALL) { firDiagnostic -> AnnotationClassConstructorCallImpl( firDiagnostic as KtPsiDiagnostic, diff --git a/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/diagnostics/KtFirDiagnostics.kt b/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/diagnostics/KtFirDiagnostics.kt index dd42c490708..042b3e298c4 100644 --- a/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/diagnostics/KtFirDiagnostics.kt +++ b/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/diagnostics/KtFirDiagnostics.kt @@ -538,6 +538,14 @@ sealed class KtFirDiagnostic : KtDiagnosticWithPsi { override val diagnosticClass get() = NonConstValUsedInConstantExpression::class } + abstract class CycleInAnnotationParameterError : KtFirDiagnostic() { + override val diagnosticClass get() = CycleInAnnotationParameterError::class + } + + abstract class CycleInAnnotationParameterWarning : KtFirDiagnostic() { + override val diagnosticClass get() = CycleInAnnotationParameterWarning::class + } + abstract class AnnotationClassConstructorCall : KtFirDiagnostic() { override val diagnosticClass get() = AnnotationClassConstructorCall::class } diff --git a/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/diagnostics/KtFirDiagnosticsImpl.kt b/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/diagnostics/KtFirDiagnosticsImpl.kt index 7d37c901947..83a400b7ccc 100644 --- a/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/diagnostics/KtFirDiagnosticsImpl.kt +++ b/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/diagnostics/KtFirDiagnosticsImpl.kt @@ -641,6 +641,16 @@ internal class NonConstValUsedInConstantExpressionImpl( override val token: ValidityToken, ) : KtFirDiagnostic.NonConstValUsedInConstantExpression(), KtAbstractFirDiagnostic +internal class CycleInAnnotationParameterErrorImpl( + override val firDiagnostic: KtPsiDiagnostic, + override val token: ValidityToken, +) : KtFirDiagnostic.CycleInAnnotationParameterError(), KtAbstractFirDiagnostic + +internal class CycleInAnnotationParameterWarningImpl( + override val firDiagnostic: KtPsiDiagnostic, + override val token: ValidityToken, +) : KtFirDiagnostic.CycleInAnnotationParameterWarning(), KtAbstractFirDiagnostic + internal class AnnotationClassConstructorCallImpl( override val firDiagnostic: KtPsiDiagnostic, override val token: ValidityToken, diff --git a/compiler/fir/checkers/checkers-component-generator/src/org/jetbrains/kotlin/fir/checkers/generator/diagnostics/FirDiagnosticsList.kt b/compiler/fir/checkers/checkers-component-generator/src/org/jetbrains/kotlin/fir/checkers/generator/diagnostics/FirDiagnosticsList.kt index 8d6b3d2e77c..b8f9c371a6f 100644 --- a/compiler/fir/checkers/checkers-component-generator/src/org/jetbrains/kotlin/fir/checkers/generator/diagnostics/FirDiagnosticsList.kt +++ b/compiler/fir/checkers/checkers-component-generator/src/org/jetbrains/kotlin/fir/checkers/generator/diagnostics/FirDiagnosticsList.kt @@ -237,6 +237,7 @@ object DIAGNOSTICS_LIST : DiagnosticList("FirErrors") { val LOCAL_ANNOTATION_CLASS_ERROR by error() val MISSING_VAL_ON_ANNOTATION_PARAMETER by error() val NON_CONST_VAL_USED_IN_CONSTANT_EXPRESSION by error() + val CYCLE_IN_ANNOTATION_PARAMETER by deprecationError(LanguageFeature.ProhibitCyclesInAnnotations) val ANNOTATION_CLASS_CONSTRUCTOR_CALL by error() val NOT_AN_ANNOTATION_CLASS by error { parameter("annotationName") diff --git a/compiler/fir/checkers/gen/org/jetbrains/kotlin/fir/analysis/diagnostics/FirErrors.kt b/compiler/fir/checkers/gen/org/jetbrains/kotlin/fir/analysis/diagnostics/FirErrors.kt index 1b806660130..ce149dbd911 100644 --- a/compiler/fir/checkers/gen/org/jetbrains/kotlin/fir/analysis/diagnostics/FirErrors.kt +++ b/compiler/fir/checkers/gen/org/jetbrains/kotlin/fir/analysis/diagnostics/FirErrors.kt @@ -13,6 +13,7 @@ import org.jetbrains.kotlin.config.LanguageFeature.ForbidExposingTypesInPrimaryC import org.jetbrains.kotlin.config.LanguageFeature.ForbidUsingExtensionPropertyTypeParameterInDelegate import org.jetbrains.kotlin.config.LanguageFeature.ProhibitAssigningSingleElementsToVarargsInNamedForm import org.jetbrains.kotlin.config.LanguageFeature.ProhibitConfusingSyntaxInWhenBranches +import org.jetbrains.kotlin.config.LanguageFeature.ProhibitCyclesInAnnotations import org.jetbrains.kotlin.config.LanguageFeature.ProhibitInvisibleAbstractMethodsInSuperclasses import org.jetbrains.kotlin.config.LanguageFeature.ProhibitNonReifiedArraysAsReifiedTypeArguments import org.jetbrains.kotlin.config.LanguageFeature.ProhibitUseSiteTargetAnnotationsOnSuperTypes @@ -222,6 +223,7 @@ object FirErrors { val LOCAL_ANNOTATION_CLASS_ERROR by error0() val MISSING_VAL_ON_ANNOTATION_PARAMETER by error0() val NON_CONST_VAL_USED_IN_CONSTANT_EXPRESSION by error0() + val CYCLE_IN_ANNOTATION_PARAMETER by deprecationError0(ProhibitCyclesInAnnotations) val ANNOTATION_CLASS_CONSTRUCTOR_CALL by error0() val NOT_AN_ANNOTATION_CLASS by error1() val NULLABLE_TYPE_OF_ANNOTATION_MEMBER by error0() diff --git a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/declaration/FirAnnotationClassDeclarationChecker.kt b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/declaration/FirAnnotationClassDeclarationChecker.kt index 927769ade09..89e7c7f97a6 100644 --- a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/declaration/FirAnnotationClassDeclarationChecker.kt +++ b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/declaration/FirAnnotationClassDeclarationChecker.kt @@ -18,10 +18,14 @@ import org.jetbrains.kotlin.fir.FirSession import org.jetbrains.kotlin.fir.analysis.checkers.* import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext import org.jetbrains.kotlin.fir.analysis.diagnostics.* +import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.CYCLE_IN_ANNOTATION_PARAMETER import org.jetbrains.kotlin.fir.declarations.* import org.jetbrains.kotlin.fir.declarations.utils.isLocal +import org.jetbrains.kotlin.fir.resolve.fullyExpandedType import org.jetbrains.kotlin.fir.resolve.toSymbol +import org.jetbrains.kotlin.fir.symbols.impl.FirConstructorSymbol import org.jetbrains.kotlin.fir.symbols.impl.FirRegularClassSymbol +import org.jetbrains.kotlin.fir.symbols.impl.FirValueParameterSymbol import org.jetbrains.kotlin.fir.types.* import org.jetbrains.kotlin.name.ClassId import org.jetbrains.kotlin.name.StandardClassIds @@ -47,6 +51,8 @@ object FirAnnotationClassDeclarationChecker : FirRegularClassChecker() { val target = declaration.getRetentionAnnotation() ?: declaration.getTargetAnnotation() ?: declaration reporter.reportOn(target.source, FirErrors.RESTRICTED_RETENTION_FOR_EXPRESSION_ANNOTATION, context) } + + checkCyclesInParameters(declaration.symbol, context, reporter) } private fun checkAnnotationClassMember(member: FirDeclaration, context: CheckerContext, reporter: DiagnosticReporter) { @@ -160,4 +166,70 @@ object FirAnnotationClassDeclarationChecker : FirRegularClassChecker() { return false } + + private fun checkCyclesInParameters(annotation: FirRegularClassSymbol, context: CheckerContext, reporter: DiagnosticReporter) { + val primaryConstructor = annotation.primaryConstructorSymbol() ?: return + val checker = CycleChecker(annotation, context.session) + for (valueParameter in primaryConstructor.valueParameterSymbols) { + if (checker.parameterHasCycle(annotation, valueParameter)) { + reporter.reportOn(valueParameter.source, CYCLE_IN_ANNOTATION_PARAMETER, context) + } + } + } + + private class CycleChecker(val targetAnnotation: FirRegularClassSymbol, val session: FirSession) { + private val visitedAnnotations = mutableSetOf(targetAnnotation) + private val annotationsWithCycle = mutableSetOf(targetAnnotation) + + fun annotationHasCycle(annotation: FirRegularClassSymbol): Boolean { + val primaryConstructor = annotation.primaryConstructorSymbol() ?: return false + for (valueParameter in primaryConstructor.valueParameterSymbols) { + if (parameterHasCycle(annotation, valueParameter)) return true + } + return false + } + + fun parameterHasCycle(ownedAnnotation: FirRegularClassSymbol, parameter: FirValueParameterSymbol): Boolean { + val returnType = parameter.resolvedReturnTypeRef.coneType + return when { + returnType.typeArguments.isNotEmpty() -> { + if (returnType.classId == StandardClassIds.KClass) return false + for (argument in returnType.typeArguments) { + if (typeHasCycle(ownedAnnotation, argument.type ?: continue)) return true + } + false + } + else -> typeHasCycle(ownedAnnotation, returnType) + } + } + + fun typeHasCycle(ownedAnnotation: FirRegularClassSymbol, type: ConeKotlinType): Boolean { + val referencedAnnotation = type.fullyExpandedType(session) + .toRegularClassSymbol(session) + ?.takeIf { it.classKind == ANNOTATION_CLASS } + ?: return false + if (!visitedAnnotations.add(referencedAnnotation)) { + return (referencedAnnotation in annotationsWithCycle).also { + if (it) { + annotationsWithCycle += ownedAnnotation + } + } + } + if (referencedAnnotation == targetAnnotation) { + annotationsWithCycle += ownedAnnotation + return true + } + return annotationHasCycle(referencedAnnotation) + } + } + + + private fun FirRegularClassSymbol.primaryConstructorSymbol(): FirConstructorSymbol? { + for (declarationSymbol in this.declarationSymbols) { + if (declarationSymbol is FirConstructorSymbol && declarationSymbol.isPrimary) { + return declarationSymbol + } + } + return null + } } diff --git a/compiler/testData/diagnostics/tests/annotations/cycleInParameters_after.fir.kt b/compiler/testData/diagnostics/tests/annotations/cycleInParameters_after.fir.kt deleted file mode 100644 index 0998cff629d..00000000000 --- a/compiler/testData/diagnostics/tests/annotations/cycleInParameters_after.fir.kt +++ /dev/null @@ -1,15 +0,0 @@ -// WITH_REFLECT -// LANGUAGE: +ProhibitCyclesInAnnotations -// ISSUE: KT-47932 - -import kotlin.reflect.KClass - -annotation class X(val value: X) // error -annotation class Y(val value: Array) // error - -annotation class Z1(val a: Z2, val b: Z2) // error -annotation class Z2(val value: Z1) // error - -annotation class A(val x: KClass) // OK -annotation class B(val x: KClass) // OK -annotation class C(val b: B) // OK \ No newline at end of file diff --git a/compiler/testData/diagnostics/tests/annotations/cycleInParameters_after.kt b/compiler/testData/diagnostics/tests/annotations/cycleInParameters_after.kt index 3d2891e8e96..6572b9cfc83 100644 --- a/compiler/testData/diagnostics/tests/annotations/cycleInParameters_after.kt +++ b/compiler/testData/diagnostics/tests/annotations/cycleInParameters_after.kt @@ -1,3 +1,4 @@ +// FIR_IDENTICAL // WITH_REFLECT // LANGUAGE: +ProhibitCyclesInAnnotations // ISSUE: KT-47932 diff --git a/compiler/testData/diagnostics/tests/annotations/cycleInParameters_before.fir.kt b/compiler/testData/diagnostics/tests/annotations/cycleInParameters_before.fir.kt deleted file mode 100644 index 9c9f5d1f0be..00000000000 --- a/compiler/testData/diagnostics/tests/annotations/cycleInParameters_before.fir.kt +++ /dev/null @@ -1,15 +0,0 @@ -// WITH_REFLECT -// LANGUAGE: -ProhibitCyclesInAnnotations -// ISSUE: KT-47932 - -import kotlin.reflect.KClass - -annotation class X(val value: X) // error -annotation class Y(val value: Array) // error - -annotation class Z1(val a: Z2, val b: Z2) // error -annotation class Z2(val value: Z1) // error - -annotation class A(val x: KClass) // OK -annotation class B(val x: KClass) // OK -annotation class C(val b: B) // OK \ No newline at end of file diff --git a/compiler/testData/diagnostics/tests/annotations/cycleInParameters_before.kt b/compiler/testData/diagnostics/tests/annotations/cycleInParameters_before.kt index 370b7b19e95..e1c8be469d2 100644 --- a/compiler/testData/diagnostics/tests/annotations/cycleInParameters_before.kt +++ b/compiler/testData/diagnostics/tests/annotations/cycleInParameters_before.kt @@ -1,3 +1,4 @@ +// FIR_IDENTICAL // WITH_REFLECT // LANGUAGE: -ProhibitCyclesInAnnotations // ISSUE: KT-47932 diff --git a/compiler/testData/diagnostics/tests/annotations/repeatable/containerArguments_1_5.kt b/compiler/testData/diagnostics/tests/annotations/repeatable/containerArguments_1_5.kt index e356520f29f..ba4363dbf02 100644 --- a/compiler/testData/diagnostics/tests/annotations/repeatable/containerArguments_1_5.kt +++ b/compiler/testData/diagnostics/tests/annotations/repeatable/containerArguments_1_5.kt @@ -30,7 +30,7 @@ annotation class C6(val irrelevant: Double, val value: Array = []) // Should be an error after fixing KT-47932. @R(A7::class) -annotation class A7(val value: Array) +annotation class A7(val value: Array) diff --git a/compiler/testData/diagnostics/tests/annotations/repeatable/containerArguments_1_6.kt b/compiler/testData/diagnostics/tests/annotations/repeatable/containerArguments_1_6.kt index 1d6c96178ca..b8e94dda5c4 100644 --- a/compiler/testData/diagnostics/tests/annotations/repeatable/containerArguments_1_6.kt +++ b/compiler/testData/diagnostics/tests/annotations/repeatable/containerArguments_1_6.kt @@ -30,7 +30,7 @@ annotation class C6(val irrelevant: Double, val value: Array = []) // Should be an error after fixing KT-47932. @R(A7::class) -annotation class A7(val value: Array) +annotation class A7(val value: Array)