FIR checkers: properly handle annotations with more than one argument

This commit is contained in:
Mikhail Glukhikh
2021-06-11 19:15:02 +03:00
parent ceb527c5e4
commit af5e2f3c93
5 changed files with 20 additions and 15 deletions
@@ -32,7 +32,7 @@ fun FirAnnotationCall.getRetention(session: FirSession): AnnotationRetention {
fun FirRegularClass.getRetention(): AnnotationRetention {
val retentionAnnotation = getRetentionAnnotation() ?: return AnnotationRetention.RUNTIME
val retentionArgument = retentionAnnotation.findSingleArgumentByName(RETENTION_PARAMETER_NAME) as? FirQualifiedAccessExpression
val retentionArgument = retentionAnnotation.findArgumentByName(RETENTION_PARAMETER_NAME) as? FirQualifiedAccessExpression
?: return AnnotationRetention.RUNTIME
val retentionName = (retentionArgument.calleeReference as? FirResolvedNamedReference)?.name?.asString()
?: return AnnotationRetention.RUNTIME
@@ -51,7 +51,7 @@ fun FirAnnotationCall.getAllowedAnnotationTargets(session: FirSession): Set<Kotl
fun FirRegularClass.getAllowedAnnotationTargets(): Set<KotlinTarget> {
val targetAnnotation = getTargetAnnotation() ?: return defaultAnnotationTargets
if (targetAnnotation.argumentList.arguments.isEmpty()) return emptySet()
val arguments = targetAnnotation.findSingleArgumentByName(TARGET_PARAMETER_NAME)?.unfoldArrayOrVararg().orEmpty()
val arguments = targetAnnotation.findArgumentByName(TARGET_PARAMETER_NAME)?.unfoldArrayOrVararg().orEmpty()
return arguments.mapNotNullTo(mutableSetOf()) { argument ->
val targetExpression = argument as? FirQualifiedAccessExpression
@@ -74,15 +74,20 @@ fun FirAnnotationContainer.getAnnotationByFqName(fqName: FqName): FirAnnotationC
}
}
fun FirAnnotationCall.findSingleArgumentByName(name: Name): FirExpression? {
fun FirAnnotationCall.findArgumentByName(name: Name): FirExpression? {
val argumentMapping = argumentMapping
if (argumentMapping != null) {
return argumentMapping.keys.firstOrNull()?.takeIf { argumentMapping[it]?.name == name }?.unwrapArgument()
return argumentMapping.keys.find { argumentMapping[it]?.name == name }?.unwrapArgument()
}
// NB: we have to consider both cases, because deserializer does not create argument mapping
val arguments = argumentList.arguments
val firstArgument = arguments.firstOrNull() as? FirNamedArgumentExpression ?: return arguments.singleOrNull()
return firstArgument.takeIf { it.name == name }?.expression
for (argument in arguments) {
if (argument is FirNamedArgumentExpression && argument.name == name) {
return argument.expression
}
}
// I'm lucky today!
// TODO: this line is still needed. However it should be replaced with 'return null'
return arguments.singleOrNull()
}
fun FirExpression.extractClassesFromArgument(): List<FirRegularClassSymbol> {
@@ -92,7 +92,7 @@ private fun FirAnnotatedDeclaration.getOwnSinceKotlinVersion(session: FirSession
private fun FirAnnotatedDeclaration.loadWasExperimentalMarkerClasses(): List<FirRegularClassSymbol> {
val wasExperimental = getAnnotationByFqName(OptInNames.WAS_EXPERIMENTAL_FQ_NAME) ?: return emptyList()
val annotationClasses = wasExperimental.findSingleArgumentByName(OptInNames.WAS_EXPERIMENTAL_ANNOTATION_CLASS) ?: return emptyList()
val annotationClasses = wasExperimental.findArgumentByName(OptInNames.WAS_EXPERIMENTAL_ANNOTATION_CLASS) ?: return emptyList()
return annotationClasses.extractClassesFromArgument()
}
@@ -11,7 +11,7 @@ import org.jetbrains.kotlin.fir.FirSourceElement
import org.jetbrains.kotlin.fir.analysis.checkers.ConstantArgumentKind
import org.jetbrains.kotlin.fir.analysis.checkers.checkConstantArguments
import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext
import org.jetbrains.kotlin.fir.analysis.checkers.findSingleArgumentByName
import org.jetbrains.kotlin.fir.analysis.checkers.findArgumentByName
import org.jetbrains.kotlin.fir.analysis.diagnostics.DiagnosticReporter
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirDiagnosticFactory0
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors
@@ -136,7 +136,7 @@ object FirAnnotationArgumentChecker : FirAnnotationCallChecker() {
reporter: DiagnosticReporter
) {
if (!annotationFqNamesWithVersion.contains(fqName)) return
val versionExpression = annotationCall.findSingleArgumentByName(versionArgumentName) ?: return
val versionExpression = annotationCall.findArgumentByName(versionArgumentName) ?: return
val version = parseVersionExpressionOrReport(versionExpression, context, reporter) ?: return
if (fqName == sinceKotlinFqName) {
val specified = context.session.languageVersionSettings.apiVersion
@@ -9,7 +9,7 @@ import org.jetbrains.kotlin.config.AnalysisFlags
import org.jetbrains.kotlin.fir.FirSourceElement
import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext
import org.jetbrains.kotlin.fir.analysis.checkers.extractClassesFromArgument
import org.jetbrains.kotlin.fir.analysis.checkers.findSingleArgumentByName
import org.jetbrains.kotlin.fir.analysis.checkers.findArgumentByName
import org.jetbrains.kotlin.fir.analysis.diagnostics.DiagnosticReporter
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors
import org.jetbrains.kotlin.fir.analysis.diagnostics.reportOn
@@ -32,7 +32,7 @@ object FirOptInAnnotationCallChecker : FirAnnotationCallChecker() {
if (arguments.isEmpty()) {
reporter.reportOn(expression.source, FirErrors.USE_EXPERIMENTAL_WITHOUT_ARGUMENTS, context)
} else {
val annotationClasses = expression.findSingleArgumentByName(OptInNames.USE_EXPERIMENTAL_ANNOTATION_CLASS)
val annotationClasses = expression.findArgumentByName(OptInNames.USE_EXPERIMENTAL_ANNOTATION_CLASS)
for (classSymbol in annotationClasses?.extractClassesFromArgument().orEmpty()) {
with(FirOptInUsageBaseChecker) {
if (classSymbol.fir.loadExperimentalityForMarkerAnnotation() == null) {
@@ -5,7 +5,7 @@
package org.jetbrains.kotlin.fir.analysis.checkers.expression
import org.jetbrains.kotlin.fir.analysis.checkers.findSingleArgumentByName
import org.jetbrains.kotlin.fir.analysis.checkers.findArgumentByName
import org.jetbrains.kotlin.fir.analysis.checkers.getAnnotationByFqName
import org.jetbrains.kotlin.fir.declarations.FirRegularClass
import org.jetbrains.kotlin.fir.expressions.FirConstExpression
@@ -19,10 +19,10 @@ object FirOptInUsageBaseChecker {
internal fun FirRegularClass.loadExperimentalityForMarkerAnnotation(): Experimentality? {
val experimental = getAnnotationByFqName(OptInNames.REQUIRES_OPT_IN_FQ_NAME) ?: return null
val levelArgument = experimental.findSingleArgumentByName(LEVEL) as? FirQualifiedAccessExpression
val levelArgument = experimental.findArgumentByName(LEVEL) as? FirQualifiedAccessExpression
val levelName = (levelArgument?.calleeReference as? FirResolvedNamedReference)?.name?.asString()
val level = OptInLevel.values().firstOrNull { it.name == levelName } ?: OptInLevel.DEFAULT
val message = (experimental.findSingleArgumentByName(MESSAGE) as? FirConstExpression<*>)?.value as? String
val message = (experimental.findArgumentByName(MESSAGE) as? FirConstExpression<*>)?.value as? String
return Experimentality(symbol.classId.asSingleFqName(), level.severity, message)
}