[FIR] Report errors on cycles in annotation parameter types

^KT-47932
This commit is contained in:
Dmitriy Novozhilov
2022-01-12 14:34:10 +03:00
committed by teamcity
parent e9ac24dc33
commit 9f870b0549
12 changed files with 109 additions and 32 deletions
@@ -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,
@@ -538,6 +538,14 @@ sealed class KtFirDiagnostic<PSI : PsiElement> : KtDiagnosticWithPsi<PSI> {
override val diagnosticClass get() = NonConstValUsedInConstantExpression::class
}
abstract class CycleInAnnotationParameterError : KtFirDiagnostic<KtParameter>() {
override val diagnosticClass get() = CycleInAnnotationParameterError::class
}
abstract class CycleInAnnotationParameterWarning : KtFirDiagnostic<KtParameter>() {
override val diagnosticClass get() = CycleInAnnotationParameterWarning::class
}
abstract class AnnotationClassConstructorCall : KtFirDiagnostic<KtCallExpression>() {
override val diagnosticClass get() = AnnotationClassConstructorCall::class
}
@@ -641,6 +641,16 @@ internal class NonConstValUsedInConstantExpressionImpl(
override val token: ValidityToken,
) : KtFirDiagnostic.NonConstValUsedInConstantExpression(), KtAbstractFirDiagnostic<KtExpression>
internal class CycleInAnnotationParameterErrorImpl(
override val firDiagnostic: KtPsiDiagnostic,
override val token: ValidityToken,
) : KtFirDiagnostic.CycleInAnnotationParameterError(), KtAbstractFirDiagnostic<KtParameter>
internal class CycleInAnnotationParameterWarningImpl(
override val firDiagnostic: KtPsiDiagnostic,
override val token: ValidityToken,
) : KtFirDiagnostic.CycleInAnnotationParameterWarning(), KtAbstractFirDiagnostic<KtParameter>
internal class AnnotationClassConstructorCallImpl(
override val firDiagnostic: KtPsiDiagnostic,
override val token: ValidityToken,
@@ -237,6 +237,7 @@ object DIAGNOSTICS_LIST : DiagnosticList("FirErrors") {
val LOCAL_ANNOTATION_CLASS_ERROR by error<KtClassOrObject>()
val MISSING_VAL_ON_ANNOTATION_PARAMETER by error<KtParameter>()
val NON_CONST_VAL_USED_IN_CONSTANT_EXPRESSION by error<KtExpression>()
val CYCLE_IN_ANNOTATION_PARAMETER by deprecationError<KtParameter>(LanguageFeature.ProhibitCyclesInAnnotations)
val ANNOTATION_CLASS_CONSTRUCTOR_CALL by error<KtCallExpression>()
val NOT_AN_ANNOTATION_CLASS by error<PsiElement> {
parameter<String>("annotationName")
@@ -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<KtClassOrObject>()
val MISSING_VAL_ON_ANNOTATION_PARAMETER by error0<KtParameter>()
val NON_CONST_VAL_USED_IN_CONSTANT_EXPRESSION by error0<KtExpression>()
val CYCLE_IN_ANNOTATION_PARAMETER by deprecationError0<KtParameter>(ProhibitCyclesInAnnotations)
val ANNOTATION_CLASS_CONSTRUCTOR_CALL by error0<KtCallExpression>()
val NOT_AN_ANNOTATION_CLASS by error1<PsiElement, String>()
val NULLABLE_TYPE_OF_ANNOTATION_MEMBER by error0<KtTypeReference>()
@@ -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
}
}
@@ -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<Y>) // error
annotation class Z1(val a: Z2, val b: Z2) // error
annotation class Z2(val value: Z1) // error
annotation class A(val x: KClass<A>) // OK
annotation class B(val x: KClass<B>) // OK
annotation class C(val b: B) // OK
@@ -1,3 +1,4 @@
// FIR_IDENTICAL
// WITH_REFLECT
// LANGUAGE: +ProhibitCyclesInAnnotations
// ISSUE: KT-47932
@@ -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<Y>) // error
annotation class Z1(val a: Z2, val b: Z2) // error
annotation class Z2(val value: Z1) // error
annotation class A(val x: KClass<A>) // OK
annotation class B(val x: KClass<B>) // OK
annotation class C(val b: B) // OK
@@ -1,3 +1,4 @@
// FIR_IDENTICAL
// WITH_REFLECT
// LANGUAGE: -ProhibitCyclesInAnnotations
// ISSUE: KT-47932
@@ -30,7 +30,7 @@ annotation class C6(val irrelevant: Double, val value: Array<A6> = [])
// Should be an error after fixing KT-47932.
@R(A7::class)
annotation class A7(val value: Array<A7>)
annotation class A7(<!CYCLE_IN_ANNOTATION_PARAMETER_WARNING!>val value: Array<A7><!>)
@@ -30,7 +30,7 @@ annotation class C6(val irrelevant: Double, val value: Array<A6> = [])
// Should be an error after fixing KT-47932.
@R(A7::class)
annotation class A7(val value: Array<A7>)
annotation class A7(<!CYCLE_IN_ANNOTATION_PARAMETER_WARNING!>val value: Array<A7><!>)