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(
+1 -1
View File
@@ -18,6 +18,6 @@ fun test() : Unit {
val s = "" as Any
("" as String?)?.length
(data@("" as String?))?.length
(@MustBeDocumented()( "" as String?))?.length
(<!WRONG_ANNOTATION_TARGET!>@MustBeDocumented()<!>( "" as String?))?.length
Unit
}
@@ -33,8 +33,8 @@ fun foo(arg: Int) {
// Function expression too
val f = @FunAnn fun(): Int { return 42 }
// But here, f and gav should be annotated instead
bar(@FunAnn f)
bar(@FunAnn ::gav)
bar(<!WRONG_ANNOTATION_TARGET!>@FunAnn<!> f)
bar(<!WRONG_ANNOTATION_TARGET!>@FunAnn<!> ::gav)
// Function expression, ok
fast(f)
}
@@ -1,13 +0,0 @@
@Target(AnnotationTarget.FUNCTION)
annotation class FunAnn
@Target(AnnotationTarget.EXPRESSION)
@Retention(AnnotationRetention.SOURCE)
annotation class ExprAnn
fun foo(): Int {
var x = 5
@FunAnn ++x
@ExprAnn ++x
return x
}
@@ -1,3 +1,4 @@
// FIR_IDENTICAL
@Target(AnnotationTarget.FUNCTION)
annotation class FunAnn
@@ -18,4 +18,4 @@
return local
}
@empty val z = @empty 0
@empty val z = <!WRONG_ANNOTATION_TARGET!>@empty<!> 0
@@ -4,10 +4,10 @@ annotation class base
@Retention(AnnotationRetention.SOURCE)
annotation class special
fun transform(i: Int, tr: (Int) -> Int): Int = @base @special tr(@special i)
fun transform(i: Int, tr: (Int) -> Int): Int = <!WRONG_ANNOTATION_TARGET!>@base<!> @special tr(@special i)
@base @special fun foo(i: Int): Int {
val j = @base @special i + 1
if (j == 1) return foo(@special @base 42)
val j = <!WRONG_ANNOTATION_TARGET!>@base<!> @special i + 1
if (j == 1) return foo(@special <!WRONG_ANNOTATION_TARGET!>@base<!> 42)
return transform(@special j, @base @special { @special it * 2 })
}
@@ -10,5 +10,5 @@ fun transform(i: Int, tr: (@special Int) -> Int): Int = @special tr(@special i)
fun foo(i: Int): Int {
val j = @special i + 1
if (j == 1) return foo(@special 42)
return transform(@special j, @special { i: @base Int -> @base i * 2 })
return transform(@special j, @special { i: @base Int -> <!WRONG_ANNOTATION_TARGET!>@base<!> i * 2 })
}
@@ -18,4 +18,4 @@
return local
}
@incorrect val z = @incorrect 0
@incorrect val z = <!WRONG_ANNOTATION_TARGET!>@incorrect<!> 0
@@ -75,7 +75,7 @@ import test.AnnotationTargets.*
): @fieldann @parameter Int {
@local @base @multiple @fieldann val j = i + 1
@base @multiple return j
<!WRONG_ANNOTATION_TARGET!>@base<!> <!WRONG_ANNOTATION_TARGET!>@multiple<!> return j
}
@base @method @konstructor constructor(): this(0)
@@ -0,0 +1,17 @@
/*
* KOTLIN DIAGNOSTICS SPEC TEST (POSITIVE)
*
* SPEC VERSION: 0.1-313
* PRIMARY LINKS: expressions, when-expression -> paragraph 9 -> sentence 2
* expressions, when-expression -> paragraph 5 -> sentence 1
* expressions, when-expression -> paragraph 6 -> sentence 5
* annotations, annotation-targets -> paragraph 1 -> sentence 1
*/
fun foo(a: Int) {
<!WRONG_ANNOTATION_TARGET!>@ann<!>
when (a) {
1 -> {}
}
}
annotation class ann
@@ -1,4 +1,3 @@
// FIR_IDENTICAL
/*
* KOTLIN DIAGNOSTICS SPEC TEST (POSITIVE)
*
@@ -613,6 +613,13 @@ internal val KT_DIAGNOSTIC_CONVERTER = KtDiagnosticConverterBuilder.buildConvert
token,
)
}
add(FirErrors.WRONG_ANNOTATION_TARGET) { firDiagnostic ->
WrongAnnotationTargetImpl(
firDiagnostic.a,
firDiagnostic as FirPsiDiagnostic<*>,
token,
)
}
add(FirErrors.EXPOSED_TYPEALIAS_EXPANDED_TYPE) { firDiagnostic ->
ExposedTypealiasExpandedTypeImpl(
firDiagnostic.a,
@@ -438,6 +438,11 @@ sealed class KtFirDiagnostic<PSI: PsiElement> : KtDiagnosticWithPsi<PSI> {
override val diagnosticClass get() = RestrictedRetentionForExpressionAnnotation::class
}
abstract class WrongAnnotationTarget : KtFirDiagnostic<KtAnnotationEntry>() {
override val diagnosticClass get() = WrongAnnotationTarget::class
abstract val actualTarget: String
}
abstract class ExposedTypealiasExpandedType : KtFirDiagnostic<KtNamedDeclaration>() {
override val diagnosticClass get() = ExposedTypealiasExpandedType::class
abstract val elementVisibility: EffectiveVisibility
@@ -712,6 +712,14 @@ internal class RestrictedRetentionForExpressionAnnotationImpl(
override val firDiagnostic: FirPsiDiagnostic<*> by weakRef(firDiagnostic)
}
internal class WrongAnnotationTargetImpl(
override val actualTarget: String,
firDiagnostic: FirPsiDiagnostic<*>,
override val token: ValidityToken,
) : KtFirDiagnostic.WrongAnnotationTarget(), KtAbstractFirDiagnostic<KtAnnotationEntry> {
override val firDiagnostic: FirPsiDiagnostic<*> by weakRef(firDiagnostic)
}
internal class ExposedTypealiasExpandedTypeImpl(
override val elementVisibility: EffectiveVisibility,
override val restrictingDeclaration: KtSymbol,