[FIR] Implement DEPRECATED_SINCE_KOTLIN_WITHOUT_ARGUMENTS diagnostics, fix tests, merge and refactor annotation checkers

This commit is contained in:
Ivan Kochurkin
2021-05-11 15:21:38 +03:00
committed by TeamCityServer
parent dc99a673a5
commit 3191e0b925
14 changed files with 141 additions and 142 deletions
@@ -158,6 +158,7 @@ object DIAGNOSTICS_LIST : DiagnosticList() {
parameter<String>("specifiedVersion")
}
val DEPRECATED_SINCE_KOTLIN_WITH_UNORDERED_VERSIONS by error<PsiElement>()
val DEPRECATED_SINCE_KOTLIN_WITHOUT_ARGUMENTS by error<PsiElement>()
}
val EXPOSED_VISIBILITY by object : DiagnosticGroup("Exposed visibility") {
@@ -164,6 +164,7 @@ object FirErrors {
val ILLEGAL_KOTLIN_VERSION_STRING_VALUE by error0<KtExpression>()
val NEWER_VERSION_IN_SINCE_KOTLIN by warning1<KtExpression, String>()
val DEPRECATED_SINCE_KOTLIN_WITH_UNORDERED_VERSIONS by error0<PsiElement>()
val DEPRECATED_SINCE_KOTLIN_WITHOUT_ARGUMENTS by error0<PsiElement>()
// Exposed visibility
val EXPOSED_TYPEALIAS_EXPANDED_TYPE by error3<KtNamedDeclaration, EffectiveVisibility, FirMemberDeclaration, EffectiveVisibility>(SourceElementPositioningStrategies.DECLARATION_NAME)
@@ -5,7 +5,9 @@
package org.jetbrains.kotlin.fir.analysis.checkers.expression
import org.jetbrains.kotlin.config.ApiVersion
import org.jetbrains.kotlin.fir.FirSession
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
@@ -13,22 +15,47 @@ import org.jetbrains.kotlin.fir.analysis.diagnostics.DiagnosticReporter
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirDiagnosticFactory0
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors
import org.jetbrains.kotlin.fir.analysis.diagnostics.reportOn
import org.jetbrains.kotlin.fir.declarations.FirValueParameter
import org.jetbrains.kotlin.fir.expressions.*
import org.jetbrains.kotlin.fir.languageVersionSettings
import org.jetbrains.kotlin.fir.types.ConeClassLikeType
import org.jetbrains.kotlin.fir.types.FirResolvedTypeRef
import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.psi.KtExpression
import org.jetbrains.kotlin.resolve.RequireKotlinConstants
object FirAnnotationArgumentChecker : FirAnnotationCallChecker() {
private val deprecatedSinceKotlinClassId = ClassId.fromString("kotlin/DeprecatedSinceKotlin")
private val sinceKotlinClassId = ClassId.fromString("kotlin/SinceKotlin")
private val annotationClassIdsWithVersion = setOf(
ClassId.fromString("kotlin/internal/RequireKotlin"),
sinceKotlinClassId,
deprecatedSinceKotlinClassId
)
override fun check(expression: FirAnnotationCall, context: CheckerContext, reporter: DiagnosticReporter) {
val argumentMapping = expression.argumentMapping ?: return
val classId = ((expression.annotationTypeRef as? FirResolvedTypeRef)?.type as? ConeClassLikeType)?.lookupTag?.classId
for ((arg, _) in argumentMapping) {
val argExpression = (arg as? FirNamedArgumentExpression)?.expression ?: arg
checkAnnotationArgumentWithSubElements(argExpression, context.session, reporter, context)
checkAnnotationArgumentWithSubElements(argExpression, classId, context.session, reporter, context)
?.let { reporter.reportOn(argExpression.source, it, context) }
}
checkDeprecatedSinceKotlin(expression.source, classId, argumentMapping, context, reporter)
val args = expression.argumentList.arguments
for (arg in args) {
for (ann in arg.unwrapArgument().annotations) {
reporter.reportOn(ann.source, FirErrors.ANNOTATION_USED_AS_ANNOTATION_ARGUMENT, context)
}
}
}
private fun checkAnnotationArgumentWithSubElements(
expression: FirExpression,
classId: ClassId?,
session: FirSession,
reporter: DiagnosticReporter,
context: CheckerContext
@@ -40,7 +67,7 @@ object FirAnnotationArgumentChecker : FirAnnotationCallChecker() {
for (arg in expression.argumentList.arguments) {
val sourceForReport = arg.source
when (val err = checkAnnotationArgumentWithSubElements(arg, session, reporter, context)) {
when (val err = checkAnnotationArgumentWithSubElements(arg, classId, session, reporter, context)) {
null -> {
//DO NOTHING
}
@@ -55,11 +82,11 @@ object FirAnnotationArgumentChecker : FirAnnotationCallChecker() {
}
is FirVarargArgumentsExpression -> {
for (arg in expression.arguments)
checkAnnotationArgumentWithSubElements(arg, session, reporter, context)
checkAnnotationArgumentWithSubElements(arg, classId, session, reporter, context)
?.let { reporter.reportOn(arg.source, it, context) }
}
else ->
return when (checkConstantArguments(expression, session)) {
else -> {
val error = when (checkConstantArguments(expression, session)) {
ConstantArgumentKind.NOT_CONST -> FirErrors.ANNOTATION_ARGUMENT_MUST_BE_CONST
ConstantArgumentKind.ENUM_NOT_CONST -> FirErrors.ANNOTATION_ARGUMENT_MUST_BE_ENUM_CONST
ConstantArgumentKind.NOT_KCLASS_LITERAL -> FirErrors.ANNOTATION_ARGUMENT_MUST_BE_KCLASS_LITERAL
@@ -67,7 +94,85 @@ object FirAnnotationArgumentChecker : FirAnnotationCallChecker() {
ConstantArgumentKind.NOT_CONST_VAL_IN_CONST_EXPRESSION -> FirErrors.NON_CONST_VAL_USED_IN_CONSTANT_EXPRESSION
null -> null
}
if (error != null) {
return error
} else if (classId != null && annotationClassIdsWithVersion.contains(classId)) {
val argSource = expression.source
if (argSource != null) {
val stringValue = (expression as? FirConstExpression<*>)?.value as? String
if (stringValue != null) {
if (!stringValue.matches(RequireKotlinConstants.VERSION_REGEX)) {
reporter.reportOn(argSource, FirErrors.ILLEGAL_KOTLIN_VERSION_STRING_VALUE, context)
} else if (classId == sinceKotlinClassId) {
val version = ApiVersion.parse(stringValue)
val specified = context.session.languageVersionSettings.apiVersion
if (version != null && version > specified) {
reporter.report(
FirErrors.NEWER_VERSION_IN_SINCE_KOTLIN.on(argSource, specified.versionString),
context
)
}
}
}
}
}
}
}
return null
}
private fun checkDeprecatedSinceKotlin(
source: FirSourceElement?,
classId: ClassId?,
argumentMapping: LinkedHashMap<FirExpression, FirValueParameter>,
context: CheckerContext,
reporter: DiagnosticReporter
) {
if (classId != deprecatedSinceKotlinClassId)
return
if (argumentMapping.size == 0) {
reporter.reportOn(source, FirErrors.DEPRECATED_SINCE_KOTLIN_WITHOUT_ARGUMENTS, context)
}
var warningSince: ApiVersion? = null
var errorSince: ApiVersion? = null
var hiddenSince: ApiVersion? = null
for (argument in argumentMapping) {
val identifier = argument.value.name.identifier
if (identifier == "warningSince" || identifier == "errorSince" || identifier == "hiddenSince") {
val argKey = argument.key
val constExpression = (argKey as? FirConstExpression<*>)
?: ((argKey as? FirNamedArgumentExpression)?.expression as? FirConstExpression<*>)
val stringValue = constExpression?.value as? String
if (stringValue != null) {
val version = ApiVersion.parse(stringValue)
when (identifier) {
"warningSince" -> warningSince = version
"errorSince" -> errorSince = version
"hiddenSince" -> hiddenSince = version
}
}
}
}
var isReportDeprecatedSinceKotlinWithUnorderedVersions = false
if (warningSince != null) {
if (errorSince != null) {
isReportDeprecatedSinceKotlinWithUnorderedVersions = warningSince > errorSince
}
if (hiddenSince != null && !isReportDeprecatedSinceKotlinWithUnorderedVersions) {
isReportDeprecatedSinceKotlinWithUnorderedVersions = warningSince > hiddenSince
}
}
if (errorSince != null && hiddenSince != null && !isReportDeprecatedSinceKotlinWithUnorderedVersions) {
isReportDeprecatedSinceKotlinWithUnorderedVersions = errorSince > hiddenSince
}
if (isReportDeprecatedSinceKotlinWithUnorderedVersions) {
reporter.reportOn(source, FirErrors.DEPRECATED_SINCE_KOTLIN_WITH_UNORDERED_VERSIONS, context)
}
}
}
@@ -1,72 +0,0 @@
/*
* Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.fir.analysis.checkers.expression
import org.jetbrains.kotlin.config.ApiVersion
import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext
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.expressions.FirAnnotationCall
import org.jetbrains.kotlin.fir.expressions.FirConstExpression
import org.jetbrains.kotlin.fir.expressions.FirNamedArgumentExpression
import org.jetbrains.kotlin.fir.expressions.unwrapArgument
import org.jetbrains.kotlin.fir.languageVersionSettings
import org.jetbrains.kotlin.fir.types.ConeClassLikeType
import org.jetbrains.kotlin.fir.types.FirResolvedTypeRef
import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.resolve.RequireKotlinConstants
object FirAnnotationChecker : FirAnnotationCallChecker() {
private val deprecatedSinceKotlinClassId = ClassId.fromString("kotlin/DeprecatedSinceKotlin")
private val sinceKotlinClassId = ClassId.fromString("kotlin/SinceKotlin")
private val annotationClassIdsWithVersion = setOf(
ClassId.fromString("kotlin/internal/RequireKotlin"),
sinceKotlinClassId,
deprecatedSinceKotlinClassId
)
override fun check(expression: FirAnnotationCall, context: CheckerContext, reporter: DiagnosticReporter) {
val classId = ((expression.annotationTypeRef as? FirResolvedTypeRef)?.type as? ConeClassLikeType)?.lookupTag?.classId
if (annotationClassIdsWithVersion.contains(classId)) {
for (argIndex in expression.argumentList.arguments.withIndex()) {
if (argIndex.index > 0 && classId != deprecatedSinceKotlinClassId) {
continue
}
val arg = argIndex.value
val argSource = arg.source
if (argSource != null) {
val constExpression = (arg as? FirConstExpression<*>)
?: ((arg as? FirNamedArgumentExpression)?.expression as? FirConstExpression<*>)
val stringValue = constExpression?.value as? String
if (stringValue != null) {
if (!stringValue.matches(RequireKotlinConstants.VERSION_REGEX)) {
reporter.reportOn(argSource, FirErrors.ILLEGAL_KOTLIN_VERSION_STRING_VALUE, context)
} else if (classId == sinceKotlinClassId) {
val version = ApiVersion.parse(stringValue)
val specified = context.session.languageVersionSettings.apiVersion
if (version != null && version > specified) {
reporter.report(
FirErrors.NEWER_VERSION_IN_SINCE_KOTLIN.on(argSource, specified.versionString),
context
)
}
}
}
}
}
}
val args = expression.argumentList.arguments
for (arg in args) {
for (ann in arg.unwrapArgument().annotations) {
reporter.reportOn(ann.source, FirErrors.ANNOTATION_USED_AS_ANNOTATION_ARGUMENT, context)
}
}
}
}
@@ -86,6 +86,7 @@ import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.DELEGATE_SPECIAL_
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.DELEGATION_IN_INTERFACE
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.DELEGATION_SUPER_CALL_IN_ENUM_CONSTRUCTOR
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.DEPRECATED_MODIFIER_PAIR
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.DEPRECATED_SINCE_KOTLIN_WITHOUT_ARGUMENTS
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.DEPRECATED_SINCE_KOTLIN_WITH_UNORDERED_VERSIONS
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.DEPRECATED_TYPE_PARAMETER_SYNTAX
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.DESERIALIZATION_ERROR
@@ -433,6 +434,10 @@ class FirDefaultErrorMessages : DefaultErrorMessages.Extension {
DEPRECATED_SINCE_KOTLIN_WITH_UNORDERED_VERSIONS,
"Values of DeprecatedSinceKotlin annotation should be ordered so 'warningSince' <= 'errorSince' <= 'hiddenSince' if specified"
)
map.put(
DEPRECATED_SINCE_KOTLIN_WITHOUT_ARGUMENTS,
"DeprecatedSinceKotlin annotation should have at least one argument"
)
// Exposed visibility group // #
map.put(
@@ -143,7 +143,6 @@ private fun mapInapplicableCandidateError(
)
is UnsafeCall -> mapUnsafeCallError(diagnostic.candidate, rootCause, source, qualifiedAccessSource)
is ManyLambdaExpressionArguments -> FirErrors.MANY_LAMBDA_EXPRESSION_ARGUMENTS.on(rootCause.argument.source ?: source)
is DeprecatedSinceKotlinWithUnorderedVersions -> FirErrors.DEPRECATED_SINCE_KOTLIN_WITH_UNORDERED_VERSIONS.on(source)
else -> null
}
}.ifEmpty { listOf(FirErrors.INAPPLICABLE_CANDIDATE.on(source, diagnostic.candidate.symbol)) }
@@ -11,7 +11,6 @@ import org.jetbrains.kotlin.fir.analysis.checkers.syntax.FirAnonymousFunctionSyn
object CommonExpressionCheckers : ExpressionCheckers() {
override val annotationCallCheckers: Set<FirAnnotationCallChecker>
get() = setOf(
FirAnnotationChecker,
FirAnnotationArgumentChecker
)
@@ -102,5 +102,3 @@ class NullForNotNullType(
class ManyLambdaExpressionArguments(
val argument: FirExpression
) : ResolutionDiagnostic(INAPPLICABLE_ARGUMENTS_MAPPING_ERROR)
object DeprecatedSinceKotlinWithUnorderedVersions : ResolutionDiagnostic(INAPPLICABLE)
@@ -5,7 +5,6 @@
package org.jetbrains.kotlin.fir.resolve.calls
import org.jetbrains.kotlin.config.ApiVersion
import org.jetbrains.kotlin.descriptors.Modality
import org.jetbrains.kotlin.fir.FirSymbolOwner
import org.jetbrains.kotlin.fir.FirVisibilityChecker
@@ -157,8 +156,6 @@ internal object CheckArguments : CheckerStage() {
)
}
checkDeprecatedSinceKotlinArgs(candidate, argumentMapping, sink)
if (candidate.system.hasContradiction && callInfo.arguments.isNotEmpty()) {
sink.yieldDiagnostic(InapplicableCandidate)
}
@@ -169,57 +166,6 @@ internal object CheckArguments : CheckerStage() {
FqName("DeprecatedSinceKotlin"),
Name.identifier("DeprecatedSinceKotlin")
)
private suspend fun checkDeprecatedSinceKotlinArgs(
candidate: Candidate,
argumentMapping: LinkedHashMap<FirExpression, FirValueParameter>,
sink: CheckerSink
) {
val symbol = candidate.symbol
if (symbol is FirFunctionSymbol<*>) {
if (symbol.callableId == deprecatedSinceKotlin) {
var warningSince: ApiVersion? = null
var errorSince: ApiVersion? = null
var hiddenSince: ApiVersion? = null
for (argument in argumentMapping) {
val identifier = argument.value.name.identifier
if (identifier == "warningSince" || identifier == "errorSince" || identifier == "hiddenSince") {
val argKey = argument.key
val constExpression = (argKey as? FirConstExpression<*>)
?: ((argKey as? FirNamedArgumentExpression)?.expression as? FirConstExpression<*>)
val stringValue = constExpression?.value as? String
if (stringValue != null) {
val version = ApiVersion.parse(stringValue)
when (identifier) {
"warningSince" -> warningSince = version
"errorSince" -> errorSince = version
"hiddenSince" -> hiddenSince = version
}
}
}
}
var isReportDeprecatedSinceKotlinWithUnorderedVersions = false
if (warningSince != null) {
if (errorSince != null) {
isReportDeprecatedSinceKotlinWithUnorderedVersions = warningSince > errorSince
}
if (hiddenSince != null && !isReportDeprecatedSinceKotlinWithUnorderedVersions) {
isReportDeprecatedSinceKotlinWithUnorderedVersions = warningSince > hiddenSince
}
}
if (errorSince != null && hiddenSince != null && !isReportDeprecatedSinceKotlinWithUnorderedVersions) {
isReportDeprecatedSinceKotlinWithUnorderedVersions = errorSince > hiddenSince
}
if (isReportDeprecatedSinceKotlinWithUnorderedVersions) {
sink.yieldDiagnostic(DeprecatedSinceKotlinWithUnorderedVersions)
}
}
}
}
}
internal object EagerResolveOfCallableReferences : CheckerStage() {
@@ -5,23 +5,23 @@ package kotlin.sub
@DeprecatedSinceKotlin(warningSince = "1.0", errorSince = "1.1", hiddenSince = "1.2")
fun good() {}
@DeprecatedSinceKotlin()
<!DEPRECATED_SINCE_KOTLIN_WITHOUT_ARGUMENTS!>@DeprecatedSinceKotlin()<!>
class Clazz
@Deprecated("", level = DeprecationLevel.WARNING)
@DeprecatedSinceKotlin()
<!DEPRECATED_SINCE_KOTLIN_WITHOUT_ARGUMENTS!>@DeprecatedSinceKotlin()<!>
fun fooWarning() {}
@Deprecated("", ReplaceWith(""), DeprecationLevel.WARNING)
@DeprecatedSinceKotlin()
<!DEPRECATED_SINCE_KOTLIN_WITHOUT_ARGUMENTS!>@DeprecatedSinceKotlin()<!>
fun fooDefaultWarning() {}
@Deprecated("", level = DeprecationLevel.ERROR)
@DeprecatedSinceKotlin()
<!DEPRECATED_SINCE_KOTLIN_WITHOUT_ARGUMENTS!>@DeprecatedSinceKotlin()<!>
fun fooError() {}
@Deprecated("", level = DeprecationLevel.HIDDEN)
@DeprecatedSinceKotlin()
<!DEPRECATED_SINCE_KOTLIN_WITHOUT_ARGUMENTS!>@DeprecatedSinceKotlin()<!>
fun fooHidden() {}
@Deprecated("")
@@ -1,7 +1,7 @@
package kotlin
@Deprecated("")
@DeprecatedSinceKotlin
<!DEPRECATED_SINCE_KOTLIN_WITHOUT_ARGUMENTS!>@DeprecatedSinceKotlin<!>
fun foo() {}
fun test() {
@@ -533,6 +533,12 @@ internal val KT_DIAGNOSTIC_CONVERTER = KtDiagnosticConverterBuilder.buildConvert
token,
)
}
add(FirErrors.DEPRECATED_SINCE_KOTLIN_WITHOUT_ARGUMENTS) { firDiagnostic ->
DeprecatedSinceKotlinWithoutArgumentsImpl(
firDiagnostic as FirPsiDiagnostic<*>,
token,
)
}
add(FirErrors.EXPOSED_TYPEALIAS_EXPANDED_TYPE) { firDiagnostic ->
ExposedTypealiasExpandedTypeImpl(
firDiagnostic.a,
@@ -384,6 +384,10 @@ sealed class KtFirDiagnostic<PSI: PsiElement> : KtDiagnosticWithPsi<PSI> {
override val diagnosticClass get() = DeprecatedSinceKotlinWithUnorderedVersions::class
}
abstract class DeprecatedSinceKotlinWithoutArguments : KtFirDiagnostic<PsiElement>() {
override val diagnosticClass get() = DeprecatedSinceKotlinWithoutArguments::class
}
abstract class ExposedTypealiasExpandedType : KtFirDiagnostic<KtNamedDeclaration>() {
override val diagnosticClass get() = ExposedTypealiasExpandedType::class
abstract val elementVisibility: EffectiveVisibility
@@ -619,6 +619,13 @@ internal class DeprecatedSinceKotlinWithUnorderedVersionsImpl(
override val firDiagnostic: FirPsiDiagnostic<*> by weakRef(firDiagnostic)
}
internal class DeprecatedSinceKotlinWithoutArgumentsImpl(
firDiagnostic: FirPsiDiagnostic<*>,
override val token: ValidityToken,
) : KtFirDiagnostic.DeprecatedSinceKotlinWithoutArguments(), KtAbstractFirDiagnostic<PsiElement> {
override val firDiagnostic: FirPsiDiagnostic<*> by weakRef(firDiagnostic)
}
internal class ExposedTypealiasExpandedTypeImpl(
override val elementVisibility: EffectiveVisibility,
override val restrictingDeclaration: KtSymbol,