FIR: support WRONG_ANNOTATION_TARGET on expressions

This commit is contained in:
Mikhail Glukhikh
2021-05-13 16:12:02 +03:00
parent 92ab600081
commit d11092ae3c
23 changed files with 103 additions and 44 deletions
@@ -1,17 +1,17 @@
FILE: annotationUsedAsAnnotationArgument.kt
public final annotation class Ann : R|kotlin/Annotation| {
@R|kotlin/annotation/Target|(vararg(Q|kotlin/annotation/AnnotationTarget|.R|kotlin/annotation/AnnotationTarget.EXPRESSION|)) @R|kotlin/annotation/Retention|(Q|kotlin/annotation/AnnotationRetention|.R|kotlin/annotation/AnnotationRetention.SOURCE|) public final annotation class Ann : R|kotlin/Annotation| {
public constructor(): R|Ann| {
super<R|kotlin/Any|>()
}
}
public final annotation class Ann2 : R|kotlin/Annotation| {
@R|kotlin/annotation/Target|(vararg(Q|kotlin/annotation/AnnotationTarget|.R|kotlin/annotation/AnnotationTarget.EXPRESSION|)) @R|kotlin/annotation/Retention|(Q|kotlin/annotation/AnnotationRetention|.R|kotlin/annotation/AnnotationRetention.SOURCE|) public final annotation class Ann2 : R|kotlin/Annotation| {
public constructor(): R|Ann2| {
super<R|kotlin/Any|>()
}
}
public final annotation class Ann3 : R|kotlin/Annotation| {
@R|kotlin/annotation/Target|(vararg(Q|kotlin/annotation/AnnotationTarget|.R|kotlin/annotation/AnnotationTarget.EXPRESSION|)) @R|kotlin/annotation/Retention|(Q|kotlin/annotation/AnnotationRetention|.R|kotlin/annotation/AnnotationRetention.SOURCE|) public final annotation class Ann3 : R|kotlin/Annotation| {
public constructor(arg: R|kotlin/Int|, s: R|kotlin/String|): R|Ann3| {
super<R|kotlin/Any|>()
}
@@ -1,7 +1,15 @@
// WITH_RUNTIME
@Target(AnnotationTarget.EXPRESSION)
@Retention(AnnotationRetention.SOURCE)
annotation class Ann
@Target(AnnotationTarget.EXPRESSION)
@Retention(AnnotationRetention.SOURCE)
annotation class Ann2
@Target(AnnotationTarget.EXPRESSION)
@Retention(AnnotationRetention.SOURCE)
annotation class Ann3(val arg: Int, val s: String)
@Ann3(
@@ -1,5 +1,5 @@
FILE: annotations.kt
public final annotation class MyAnn : R|kotlin/Annotation| {
@R|kotlin/annotation/Retention|(Q|kotlin/annotation/AnnotationRetention|.R|kotlin/annotation/AnnotationRetention.SOURCE|) @R|kotlin/annotation/Target|(vararg(Q|kotlin/annotation/AnnotationTarget|.R|kotlin/annotation/AnnotationTarget.EXPRESSION|)) public final annotation class MyAnn : R|kotlin/Annotation| {
public constructor(): R|MyAnn| {
super<R|kotlin/Any|>()
}
@@ -1,3 +1,7 @@
// WITH_RUNTIME
@Retention(AnnotationRetention.SOURCE)
@Target(AnnotationTarget.EXPRESSION)
annotation class MyAnn
fun bar(x: Int) {}
@@ -173,6 +173,9 @@ object DIAGNOSTICS_LIST : DiagnosticList() {
val ANNOTATION_ON_SUPERCLASS by error<KtAnnotationEntry>()
val RESTRICTED_RETENTION_FOR_EXPRESSION_ANNOTATION by error<PsiElement>()
val WRONG_ANNOTATION_TARGET by error<KtAnnotationEntry> {
parameter<String>("actualTarget")
}
}
val EXPOSED_VISIBILITY by object : DiagnosticGroup("Exposed visibility") {
@@ -179,6 +179,7 @@ object FirErrors {
val DEPRECATED_SINCE_KOTLIN_OUTSIDE_KOTLIN_SUBPACKAGE by error0<PsiElement>(SourceElementPositioningStrategies.REFERENCED_NAME_BY_QUALIFIED)
val ANNOTATION_ON_SUPERCLASS by error0<KtAnnotationEntry>()
val RESTRICTED_RETENTION_FOR_EXPRESSION_ANNOTATION by error0<PsiElement>()
val WRONG_ANNOTATION_TARGET by error1<KtAnnotationEntry, String>()
// Exposed visibility
val EXPOSED_TYPEALIAS_EXPANDED_TYPE by error3<KtNamedDeclaration, EffectiveVisibility, FirMemberDeclaration, EffectiveVisibility>(SourceElementPositioningStrategies.DECLARATION_NAME)
@@ -9,13 +9,11 @@ import org.jetbrains.kotlin.builtins.StandardNames
import org.jetbrains.kotlin.fir.FirSession
import org.jetbrains.kotlin.fir.declarations.FirAnnotatedDeclaration
import org.jetbrains.kotlin.fir.declarations.FirRegularClass
import org.jetbrains.kotlin.fir.expressions.FirAnnotationCall
import org.jetbrains.kotlin.fir.expressions.FirQualifiedAccessExpression
import org.jetbrains.kotlin.fir.expressions.FirVarargArgumentsExpression
import org.jetbrains.kotlin.fir.expressions.argumentMapping
import org.jetbrains.kotlin.fir.expressions.*
import org.jetbrains.kotlin.fir.references.FirResolvedNamedReference
import org.jetbrains.kotlin.fir.resolve.toSymbol
import org.jetbrains.kotlin.fir.types.ConeClassLikeType
import org.jetbrains.kotlin.fir.types.FirErrorTypeRef
import org.jetbrains.kotlin.fir.types.coneType
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.name.Name
@@ -30,12 +28,8 @@ fun FirAnnotationCall.getRetention(session: FirSession): AnnotationRetention {
fun FirRegularClass.getRetention(): AnnotationRetention {
val retentionAnnotation = getRetentionAnnotation() ?: return AnnotationRetention.RUNTIME
val argumentMapping = retentionAnnotation.argumentMapping ?: return AnnotationRetention.RUNTIME
val retentionArgument = argumentMapping.keys.firstOrNull() as? FirQualifiedAccessExpression
val retentionArgument = retentionAnnotation.findSingleArgumentByName(RETENTION_PARAMETER_NAME) as? FirQualifiedAccessExpression
?: return AnnotationRetention.RUNTIME
if (argumentMapping[retentionArgument]?.name != RETENTION_PARAMETER_NAME) {
return AnnotationRetention.RUNTIME
}
val retentionName = (retentionArgument.calleeReference as? FirResolvedNamedReference)?.name?.asString()
?: return AnnotationRetention.RUNTIME
return AnnotationRetention.values().firstOrNull { it.name == retentionName } ?: AnnotationRetention.RUNTIME
@@ -54,19 +48,20 @@ private val defaultAnnotationTargets = listOf(
)
fun FirAnnotationCall.getAllowedAnnotationTargets(session: FirSession): List<AnnotationTarget> {
if (annotationTypeRef is FirErrorTypeRef) return AnnotationTarget.values().toList()
val annotationClass = (this.annotationTypeRef.coneType as? ConeClassLikeType)?.lookupTag?.toSymbol(session)?.fir as? FirRegularClass
return annotationClass?.getAllowedAnnotationTargets() ?: defaultAnnotationTargets
}
fun FirRegularClass.getAllowedAnnotationTargets(): List<AnnotationTarget> {
val targetAnnotation = getTargetAnnotation() ?: return defaultAnnotationTargets
val argumentMapping = targetAnnotation.argumentMapping ?: return defaultAnnotationTargets
val targetArgument = argumentMapping.keys.firstOrNull() as? FirVarargArgumentsExpression
?: return defaultAnnotationTargets
if (argumentMapping[targetArgument]?.name != TARGET_PARAMETER_NAME) {
return defaultAnnotationTargets
val arguments = when (val targetArgument = targetAnnotation.findSingleArgumentByName(TARGET_PARAMETER_NAME)) {
is FirVarargArgumentsExpression -> targetArgument.arguments
is FirArrayOfCall -> targetArgument.arguments
else -> return defaultAnnotationTargets
}
return targetArgument.arguments.mapNotNull { argument ->
return arguments.mapNotNull { argument ->
val targetExpression = argument as? FirQualifiedAccessExpression
val targetName = (targetExpression?.calleeReference as? FirResolvedNamedReference)?.name?.asString() ?: return@mapNotNull null
AnnotationTarget.values().firstOrNull { target -> target.name == targetName }
@@ -87,3 +82,13 @@ fun FirAnnotatedDeclaration.getAnnotationByFqName(fqName: FqName): FirAnnotation
}
}
fun FirAnnotationCall.findSingleArgumentByName(name: Name): FirExpression? {
val argumentMapping = argumentMapping
if (argumentMapping != null) {
return argumentMapping.keys.firstOrNull()?.takeIf { argumentMapping[it]?.name == name }
}
// NB: we have to consider both cases, because deserializer does not create argument mapping
val firstArgument = argumentList.arguments.firstOrNull() as? FirNamedArgumentExpression ?: return null
return firstArgument.takeIf { it.name == name }?.expression
}
@@ -5,19 +5,31 @@
package org.jetbrains.kotlin.fir.analysis.checkers.expression
import org.jetbrains.kotlin.fir.FirFakeSourceElementKind
import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext
import org.jetbrains.kotlin.fir.analysis.checkers.getAllowedAnnotationTargets
import org.jetbrains.kotlin.fir.analysis.checkers.getRetention
import org.jetbrains.kotlin.fir.analysis.diagnostics.DiagnosticReporter
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors
import org.jetbrains.kotlin.fir.analysis.diagnostics.reportOn
import org.jetbrains.kotlin.fir.analysis.diagnostics.withSuppressedDiagnostics
import org.jetbrains.kotlin.fir.declarations.FirDeclaration
import org.jetbrains.kotlin.fir.expressions.FirBlock
import org.jetbrains.kotlin.fir.expressions.FirExpression
import org.jetbrains.kotlin.fir.expressions.FirStatement
object FirExpressionAnnotationChecker : FirBasicExpressionChecker() {
override fun check(expression: FirStatement, context: CheckerContext, reporter: DiagnosticReporter) {
// Declarations are checked separately
// See KT-33658 about annotations on non-expression statements
if (expression is FirDeclaration ||
expression !is FirExpression ||
expression is FirBlock && expression.source?.kind == FirFakeSourceElementKind.DesugaredForLoop
) return
for (annotation in expression.annotations) {
withSuppressedDiagnostics(annotation, context) {
if (AnnotationTarget.EXPRESSION !in annotation.getAllowedAnnotationTargets(context.session)) {
reporter.reportOn(annotation.source, FirErrors.WRONG_ANNOTATION_TARGET, "expression", context)
}
}
}
}
@@ -328,6 +328,7 @@ import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.VARIANCE_ON_TYPE_
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.VAR_ANNOTATION_PARAMETER
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.VAR_OVERRIDDEN_BY_VAL
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.VAR_TYPE_MISMATCH_ON_OVERRIDE
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.WRONG_ANNOTATION_TARGET
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.WRONG_GETTER_RETURN_TYPE
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.WRONG_INVOCATION_KIND
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.WRONG_MODIFIER_TARGET
@@ -476,6 +477,7 @@ class FirDefaultErrorMessages : DefaultErrorMessages.Extension {
"DeprecatedSinceKotlin annotation cannot be used outside 'kotlin' subpackages"
)
map.put(ANNOTATION_ON_SUPERCLASS, "Annotations on superclass are meaningless")
map.put(WRONG_ANNOTATION_TARGET, "This annotation is not applicable to target ''{0}''", TO_STRING);
// Exposed visibility group // #
map.put(