FIR: report WRONG_ANNOTATION_TARGET(_WITH_USE_SITE_TARGET) on declarations

This commit is contained in:
Mikhail Glukhikh
2021-05-18 11:48:44 +03:00
parent 95f2c52659
commit 2a33a6927c
73 changed files with 394 additions and 392 deletions
@@ -12,8 +12,8 @@ annotation class Ann2
@Retention(AnnotationRetention.SOURCE)
annotation class Ann3(val arg: Int, val s: String)
@Ann3(
<!WRONG_ANNOTATION_TARGET!>@Ann3(
<!ANNOTATION_USED_AS_ANNOTATION_ARGUMENT!>@Ann3(
<!ANNOTATION_USED_AS_ANNOTATION_ARGUMENT!>@Ann<!> 5, ""
)<!> <!ANNOTATION_USED_AS_ANNOTATION_ARGUMENT!>@Ann2<!> 1, ""
) val a = 0
)<!> val a = 0
@@ -1,5 +1,5 @@
// Should be something like TYPE_MISMATCH here
@file:Some(return x)
<!WRONG_ANNOTATION_TARGET_WITH_USE_SITE_TARGET!>@file:Some(return x)<!>
const val x = 42
@@ -7,7 +7,7 @@ annotation class MyAnn
fun bar(x: Int) {}
fun foo() {
@MyAnn
<!WRONG_ANNOTATION_TARGET!>@MyAnn<!>
val x: Int
@MyAnn
x = @MyAnn 42
@@ -1,5 +1,5 @@
FILE: RedundantSetterParameterTypeChecker.kt
@R|kotlin/annotation/Target|(vararg(Q|kotlin/annotation/AnnotationTarget|.R|kotlin/annotation/AnnotationTarget.CLASS|)) public final annotation class Ann : R|kotlin/Annotation| {
@R|kotlin/annotation/Target|(vararg(Q|kotlin/annotation/AnnotationTarget|.R|kotlin/annotation/AnnotationTarget.VALUE_PARAMETER|)) public final annotation class Ann : R|kotlin/Annotation| {
public constructor(): R|Ann| {
super<R|kotlin/Any|>()
}
@@ -1,5 +1,5 @@
@Target(AnnotationTarget.CLASS)
@Target(AnnotationTarget.VALUE_PARAMETER)
annotation class Ann
<!REDECLARATION!>var x: Int
@@ -41,6 +41,6 @@ class Second(val y: Char) : @WithInt(0) First() {
constructor(): this('\n')
}
@WithInt(24)
@VeryComplex(3.14f, 6.67e-11, false, 123456789012345L, null)
<!WRONG_ANNOTATION_TARGET!>@WithInt(24)<!>
<!WRONG_ANNOTATION_TARGET!>@VeryComplex(3.14f, 6.67e-11, false, 123456789012345L, null)<!>
typealias Third = @Simple Second
@@ -176,6 +176,10 @@ object DIAGNOSTICS_LIST : DiagnosticList() {
val WRONG_ANNOTATION_TARGET by error<KtAnnotationEntry> {
parameter<String>("actualTarget")
}
val WRONG_ANNOTATION_TARGET_WITH_USE_SITE_TARGET by error<KtAnnotationEntry> {
parameter<String>("actualTarget")
parameter<String>("useSiteTarget")
}
}
val EXPOSED_VISIBILITY by object : DiagnosticGroup("Exposed visibility") {
@@ -180,6 +180,7 @@ object FirErrors {
val ANNOTATION_ON_SUPERCLASS by error0<KtAnnotationEntry>()
val RESTRICTED_RETENTION_FOR_EXPRESSION_ANNOTATION by error0<PsiElement>()
val WRONG_ANNOTATION_TARGET by error1<KtAnnotationEntry, String>()
val WRONG_ANNOTATION_TARGET_WITH_USE_SITE_TARGET by error2<KtAnnotationEntry, String, String>()
// Exposed visibility
val EXPOSED_TYPEALIAS_EXPANDED_TYPE by error3<KtNamedDeclaration, EffectiveVisibility, FirMemberDeclaration, EffectiveVisibility>(SourceElementPositioningStrategies.DECLARATION_NAME)
@@ -79,7 +79,8 @@ fun FirAnnotationCall.findSingleArgumentByName(name: Name): FirExpression? {
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
val arguments = argumentList.arguments
val firstArgument = arguments.firstOrNull() as? FirNamedArgumentExpression ?: return arguments.singleOrNull()
return firstArgument.takeIf { it.name == name }?.expression
}
@@ -5,18 +5,22 @@
package org.jetbrains.kotlin.fir.analysis.checkers.declaration
import org.jetbrains.kotlin.descriptors.ClassKind
import org.jetbrains.kotlin.descriptors.annotations.KotlinTarget
import org.jetbrains.kotlin.fir.FirFakeSourceElementKind
import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext
import org.jetbrains.kotlin.fir.analysis.checkers.context.findClosest
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.declarations.FirAnnotatedDeclaration
import org.jetbrains.kotlin.fir.declarations.FirFile
import org.jetbrains.kotlin.fir.analysis.checkers.getAllowedAnnotationTargets
import org.jetbrains.kotlin.fir.analysis.diagnostics.*
import org.jetbrains.kotlin.fir.declarations.*
import org.jetbrains.kotlin.fir.expressions.FirAnnotationCall
import org.jetbrains.kotlin.fir.expressions.argumentMapping
import org.jetbrains.kotlin.fir.resolve.fqName
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.name.StandardClassIds
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.resolve.AnnotationTargetList
import org.jetbrains.kotlin.resolve.AnnotationTargetLists
object FirAnnotationChecker : FirAnnotatedDeclarationChecker() {
private val deprecatedClassId = FqName("kotlin.Deprecated")
@@ -37,33 +41,158 @@ object FirAnnotationChecker : FirAnnotatedDeclarationChecker() {
} else if (fqName == deprecatedSinceKotlinClassId) {
deprecatedSinceKotlinCall = annotation
}
withSuppressedDiagnostics(annotation, context) {
checkAnnotationTarget(declaration, annotation, context, reporter)
}
}
if (deprecatedSinceKotlinCall != null) {
withSuppressedDiagnostics(deprecatedSinceKotlinCall, context) {
checkDeprecatedCalls(deprecatedSinceKotlinCall, deprecatedCall, context, reporter)
}
}
}
private fun checkAnnotationTarget(
declaration: FirAnnotatedDeclaration,
annotation: FirAnnotationCall,
context: CheckerContext,
reporter: DiagnosticReporter
) {
if (declaration is FirValueParameter && declaration.source?.hasValOrVar() == true) {
// This will be checked later as property
return
}
val actualTargets = getActualTargetList(declaration)
val applicableTargets = annotation.getAllowedAnnotationTargets(context.session)
val useSiteTarget = annotation.useSiteTarget
fun check(targets: List<KotlinTarget>) = targets.any {
it in applicableTargets && (useSiteTarget == null || KotlinTarget.USE_SITE_MAPPING[useSiteTarget] == it)
}
if (deprecatedSinceKotlinCall != null) {
val closestFirFile = context.findClosest<FirFile>()
if (closestFirFile != null && !closestFirFile.packageFqName.startsWith(StandardClassIds.BASE_KOTLIN_PACKAGE.shortName())) {
reporter.reportOn(
deprecatedSinceKotlinCall.source,
FirErrors.DEPRECATED_SINCE_KOTLIN_OUTSIDE_KOTLIN_SUBPACKAGE,
context
)
}
fun checkWithUseSiteTargets(): Boolean {
if (useSiteTarget == null) return false
if (deprecatedCall == null) {
reporter.reportOn(deprecatedSinceKotlinCall.source, FirErrors.DEPRECATED_SINCE_KOTLIN_WITHOUT_DEPRECATED, context)
} else {
val argumentMapping = deprecatedCall.argumentMapping ?: return
for (value in argumentMapping.values) {
if (value.name.identifier == "level") {
reporter.reportOn(
deprecatedSinceKotlinCall.source,
FirErrors.DEPRECATED_SINCE_KOTLIN_WITH_DEPRECATED_LEVEL,
context
)
break
}
val useSiteMapping = KotlinTarget.USE_SITE_MAPPING[useSiteTarget]
return actualTargets.onlyWithUseSiteTarget.any { it in applicableTargets && it == useSiteMapping }
}
if (check(actualTargets.defaultTargets) || check(actualTargets.canBeSubstituted) || checkWithUseSiteTargets()) {
return
}
val targetDescription = actualTargets.defaultTargets.firstOrNull()?.description ?: "unidentified target"
if (useSiteTarget != null) {
reporter.reportOn(
annotation.source,
FirErrors.WRONG_ANNOTATION_TARGET_WITH_USE_SITE_TARGET,
targetDescription,
useSiteTarget.renderName,
context
)
} else {
reporter.reportOn(
annotation.source,
FirErrors.WRONG_ANNOTATION_TARGET,
targetDescription,
context
)
}
}
private fun checkDeprecatedCalls(
deprecatedSinceKotlinCall: FirAnnotationCall,
deprecatedCall: FirAnnotationCall?,
context: CheckerContext,
reporter: DiagnosticReporter
) {
val closestFirFile = context.findClosest<FirFile>()
if (closestFirFile != null && !closestFirFile.packageFqName.startsWith(StandardClassIds.BASE_KOTLIN_PACKAGE.shortName())) {
reporter.reportOn(
deprecatedSinceKotlinCall.source,
FirErrors.DEPRECATED_SINCE_KOTLIN_OUTSIDE_KOTLIN_SUBPACKAGE,
context
)
}
if (deprecatedCall == null) {
reporter.reportOn(deprecatedSinceKotlinCall.source, FirErrors.DEPRECATED_SINCE_KOTLIN_WITHOUT_DEPRECATED, context)
} else {
val argumentMapping = deprecatedCall.argumentMapping ?: return
for (value in argumentMapping.values) {
if (value.name.identifier == "level") {
reporter.reportOn(
deprecatedSinceKotlinCall.source,
FirErrors.DEPRECATED_SINCE_KOTLIN_WITH_DEPRECATED_LEVEL,
context
)
break
}
}
}
}
}
private fun getActualTargetList(annotated: FirDeclaration): AnnotationTargetList {
return when (annotated) {
is FirRegularClass -> {
AnnotationTargetList(
KotlinTarget.classActualTargets(annotated.classKind, annotated.isInner, annotated.isCompanion, annotated.isLocal)
)
}
is KtDestructuringDeclarationEntry -> TargetLists.T_LOCAL_VARIABLE
is FirProperty -> {
when {
annotated.isLocal ->
if (annotated.source?.kind == FirFakeSourceElementKind.DesugaredComponentFunctionCall) {
TargetLists.T_DESTRUCTURING_DECLARATION
} else {
TargetLists.T_LOCAL_VARIABLE
}
annotated.symbol.callableId.classId != null ->
if (annotated.source?.kind == FirFakeSourceElementKind.PropertyFromParameter) {
TargetLists.T_VALUE_PARAMETER_WITH_VAL
} else {
TargetLists.T_MEMBER_PROPERTY(annotated.hasBackingField, annotated.delegate != null)
}
else ->
TargetLists.T_TOP_LEVEL_PROPERTY(annotated.hasBackingField, annotated.delegate != null)
}
}
is FirValueParameter -> TargetLists.T_VALUE_PARAMETER_WITHOUT_VAL
is FirConstructor -> TargetLists.T_CONSTRUCTOR
is FirAnonymousFunction -> {
TargetLists.T_FUNCTION_EXPRESSION
}
is FirSimpleFunction -> {
when {
annotated.isLocal -> TargetLists.T_LOCAL_FUNCTION
annotated.symbol.callableId.classId != null -> TargetLists.T_MEMBER_FUNCTION
else -> TargetLists.T_TOP_LEVEL_FUNCTION
}
}
is FirTypeAlias -> TargetLists.T_TYPEALIAS
is FirPropertyAccessor -> if (annotated.isGetter) TargetLists.T_PROPERTY_GETTER else TargetLists.T_PROPERTY_SETTER
is FirFile -> TargetLists.T_FILE
is FirTypeParameter -> TargetLists.T_TYPE_PARAMETER
is FirAnonymousInitializer -> TargetLists.T_INITIALIZER
is KtDestructuringDeclaration -> TargetLists.T_DESTRUCTURING_DECLARATION
is KtLambdaExpression -> TargetLists.T_FUNCTION_LITERAL
is FirAnonymousObject ->
if (annotated.source?.kind == FirFakeSourceElementKind.EnumInitializer) {
AnnotationTargetList(
KotlinTarget.classActualTargets(
ClassKind.ENUM_ENTRY,
isInnerClass = false,
isCompanionObject = false,
isLocalClass = false
)
)
} else {
TargetLists.T_OBJECT_LITERAL
}
else -> TargetLists.EMPTY
}
}
}
private typealias TargetLists = AnnotationTargetLists
@@ -51,15 +51,15 @@ class DeclarationCheckersDiagnosticComponent(
}
override fun visitAnonymousFunction(anonymousFunction: FirAnonymousFunction, data: CheckerContext) {
checkers.allBasicDeclarationCheckers.check(anonymousFunction, data, reporter)
checkers.allAnnotatedDeclarationCheckers.check(anonymousFunction, data, reporter)
}
override fun visitPropertyAccessor(propertyAccessor: FirPropertyAccessor, data: CheckerContext) {
checkers.allBasicDeclarationCheckers.check(propertyAccessor, data, reporter)
checkers.allAnnotatedDeclarationCheckers.check(propertyAccessor, data, reporter)
}
override fun visitValueParameter(valueParameter: FirValueParameter, data: CheckerContext) {
checkers.allBasicDeclarationCheckers.check(valueParameter, data, reporter)
checkers.allAnnotatedDeclarationCheckers.check(valueParameter, data, reporter)
}
override fun visitTypeParameter(typeParameter: FirTypeParameter, data: CheckerContext) {
@@ -329,6 +329,7 @@ import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.VAR_ANNOTATION_PA
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_ANNOTATION_TARGET_WITH_USE_SITE_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
@@ -478,6 +479,12 @@ class FirDefaultErrorMessages : DefaultErrorMessages.Extension {
)
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);
map.put(
WRONG_ANNOTATION_TARGET_WITH_USE_SITE_TARGET,
"This annotation is not applicable to target ''{0}'' and use site target ''@{1}''",
TO_STRING,
TO_STRING
);
// Exposed visibility group // #
map.put(