[FIR] Report deprecations in qualified expressions

This commit is contained in:
Andrey Zinovyev
2021-07-16 16:54:06 +03:00
committed by TeamCityServer
parent 98bd6f5d3e
commit 8a0941a85a
32 changed files with 132 additions and 119 deletions
@@ -121,6 +121,7 @@ object CommonExpressionCheckers : ExpressionCheckers() {
get() = setOf(
FirStandaloneQualifierChecker,
FirOptInUsageQualifierChecker,
FirDeprecatedQualifierChecker,
)
override val equalityOperatorCallCheckers: Set<FirEqualityOperatorCallChecker>
@@ -7,6 +7,7 @@ package org.jetbrains.kotlin.fir.analysis.checkers.declaration
import org.jetbrains.kotlin.fir.analysis.checkers.collectEnumEntries
import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext
import org.jetbrains.kotlin.fir.analysis.checkers.expression.FirDeprecationChecker
import org.jetbrains.kotlin.fir.analysis.checkers.fullyExpandedClass
import org.jetbrains.kotlin.fir.analysis.diagnostics.DiagnosticReporter
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors
@@ -37,6 +38,7 @@ object FirImportsChecker : FirFileChecker() {
checkOperatorRename(import, context, reporter)
}
}
checkDeprecatedImport(import, context, reporter)
}
checkConflictingImports(declaration.imports, context, reporter)
}
@@ -175,4 +177,12 @@ object FirImportsChecker : FirFileChecker() {
return hasStatic || !hasIllegal
}
private fun checkDeprecatedImport(import: FirImport, context: CheckerContext, reporter: DiagnosticReporter) {
val importedFqName = import.importedFqName ?: return
if (importedFqName.isRoot || importedFqName.shortName().asString().isEmpty()) return
val classId = (import as? FirResolvedImport)?.resolvedClassId ?: ClassId.topLevel(importedFqName)
val classLike: FirRegularClassSymbol = classId.resolveToClass(context) ?: return
FirDeprecationChecker.reportDeprecationIfNeeded(import.source, classLike, null, context, reporter)
}
}
@@ -0,0 +1,24 @@
/*
* 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.fir.analysis.checkers.context.CheckerContext
import org.jetbrains.kotlin.fir.analysis.diagnostics.DiagnosticReporter
import org.jetbrains.kotlin.fir.expressions.FirResolvedQualifier
import org.jetbrains.kotlin.fir.resolve.diagnostics.ConeDeprecated
import org.jetbrains.kotlin.fir.symbols.impl.FirRegularClassSymbol
object FirDeprecatedQualifierChecker : FirResolvedQualifierChecker() {
override fun check(expression: FirResolvedQualifier, context: CheckerContext, reporter: DiagnosticReporter) {
expression.nonFatalDiagnostics.filterIsInstance<ConeDeprecated>().forEach { diagnostic ->
FirDeprecationChecker.reportDeprecation(diagnostic.source, diagnostic.symbol, diagnostic.deprecation, reporter, context)
}
if (expression.resolvedToCompanionObject) {
val companionSymbol = (expression.symbol as? FirRegularClassSymbol)?.companionObjectSymbol ?: return
FirDeprecationChecker.reportDeprecationIfNeeded(expression.source, companionSymbol, null, context, reporter)
}
}
}
@@ -17,9 +17,7 @@ import org.jetbrains.kotlin.fir.analysis.diagnostics.reportOn
import org.jetbrains.kotlin.fir.declarations.Deprecation
import org.jetbrains.kotlin.fir.declarations.DeprecationLevelValue
import org.jetbrains.kotlin.fir.declarations.getDeprecation
import org.jetbrains.kotlin.fir.expressions.FirAnnotationCall
import org.jetbrains.kotlin.fir.expressions.FirResolvable
import org.jetbrains.kotlin.fir.expressions.FirStatement
import org.jetbrains.kotlin.fir.expressions.*
import org.jetbrains.kotlin.fir.references.FirResolvedNamedReference
import org.jetbrains.kotlin.fir.symbols.FirBasedSymbol
import org.jetbrains.kotlin.fir.symbols.impl.FirConstructorSymbol
@@ -50,6 +48,16 @@ object FirDeprecationChecker : FirBasicExpressionChecker() {
reporter: DiagnosticReporter
) {
val deprecation = getWorstDeprecation(callSite, referencedSymbol, context) ?: return
reportDeprecation(source, referencedSymbol, deprecation, reporter, context)
}
internal fun reportDeprecation(
source: FirSourceElement?,
referencedSymbol: FirBasedSymbol<*>,
deprecation: Deprecation,
reporter: DiagnosticReporter,
context: CheckerContext
) {
val diagnostic = when (deprecation.level) {
DeprecationLevelValue.ERROR, DeprecationLevelValue.HIDDEN -> FirErrors.DEPRECATION_ERROR
DeprecationLevelValue.WARNING -> FirErrors.DEPRECATION
@@ -527,36 +527,42 @@ object LightTreePositioningStrategies {
endOffset: Int,
tree: FlyweightCapableTreeStructure<LighterASTNode>
): List<TextRange> {
if (node.tokenType == KtNodeTypes.CALL_EXPRESSION || node.tokenType == KtNodeTypes.CONSTRUCTOR_DELEGATION_CALL) {
return markElement(tree.referenceExpression(node, locateReferencedName) ?: node, startOffset, endOffset, tree, node)
}
if (node.tokenType == KtNodeTypes.PROPERTY_DELEGATE) {
return markElement(tree.findExpressionDeep(node) ?: node, startOffset, endOffset, tree, node)
}
if (node.tokenType == KtNodeTypes.ANNOTATION_ENTRY) {
return markElement(
tree.findDescendantByType(node, KtNodeTypes.CONSTRUCTOR_CALLEE) ?: node,
startOffset,
endOffset,
tree,
node
)
}
if (node.tokenType in nodeTypesWithOperation) {
return markElement(tree.operationReference(node) ?: node, startOffset, endOffset, tree, node)
}
if (node.tokenType == KtNodeTypes.TYPE_REFERENCE) {
val nodeToMark =
tree.findChildByType(node, KtNodeTypes.NULLABLE_TYPE)
?.let { tree.findChildByType(it, KtNodeTypes.USER_TYPE) }
?: node
return markElement(nodeToMark, startOffset, endOffset, tree, node)
}
if (node.tokenType != KtNodeTypes.DOT_QUALIFIED_EXPRESSION &&
node.tokenType != KtNodeTypes.SAFE_ACCESS_EXPRESSION &&
node.tokenType != KtNodeTypes.CALLABLE_REFERENCE_EXPRESSION
) {
return super.mark(node, startOffset, endOffset, tree)
when {
node.tokenType == KtNodeTypes.CALL_EXPRESSION || node.tokenType == KtNodeTypes.CONSTRUCTOR_DELEGATION_CALL -> {
return markElement(tree.referenceExpression(node, locateReferencedName) ?: node, startOffset, endOffset, tree, node)
}
node.tokenType == KtNodeTypes.PROPERTY_DELEGATE -> {
return markElement(tree.findExpressionDeep(node) ?: node, startOffset, endOffset, tree, node)
}
node.tokenType == KtNodeTypes.ANNOTATION_ENTRY -> {
return markElement(
tree.findDescendantByType(node, KtNodeTypes.CONSTRUCTOR_CALLEE) ?: node,
startOffset,
endOffset,
tree,
node
)
}
node.tokenType in nodeTypesWithOperation -> {
return markElement(tree.operationReference(node) ?: node, startOffset, endOffset, tree, node)
}
node.tokenType == KtNodeTypes.TYPE_REFERENCE -> {
val nodeToMark =
tree.findChildByType(node, KtNodeTypes.NULLABLE_TYPE)
?.let { tree.findChildByType(it, KtNodeTypes.USER_TYPE) }
?: node
return markElement(nodeToMark, startOffset, endOffset, tree, node)
}
node.tokenType == KtNodeTypes.IMPORT_DIRECTIVE -> {
val nodeToMark = tree.findChildByType(node, KtStubElementTypes.INSIDE_DIRECTIVE_EXPRESSIONS) ?: node
return markElement(nodeToMark, startOffset, endOffset, tree, node)
}
node.tokenType != KtNodeTypes.DOT_QUALIFIED_EXPRESSION &&
node.tokenType != KtNodeTypes.SAFE_ACCESS_EXPRESSION &&
node.tokenType != KtNodeTypes.CALLABLE_REFERENCE_EXPRESSION
-> {
return super.mark(node, startOffset, endOffset, tree)
}
}
val selector = tree.selector(node)
if (selector != null) {
@@ -254,7 +254,12 @@ class FirCallResolver(
referencedSymbol,
nameReference.source,
qualifiedAccess.typeArguments,
diagnostic
diagnostic,
nonFatalDiagnostics = extractNonFatalDiagnostics(
nameReference.source,
qualifiedAccess.explicitReceiver,
referencedSymbol
)
)
}
referencedSymbol is FirTypeParameterSymbol && referencedSymbol.fir.isReified -> {
@@ -276,6 +281,17 @@ class FirCallResolver(
return resultExpression
}
private fun extractNonFatalDiagnostics(
source: FirSourceElement?,
explicitReceiver: FirExpression?,
symbol: FirClassLikeSymbol<*>
): List<ConeDiagnostic> {
val prevDiagnostics = (explicitReceiver as? FirResolvedQualifier)?.nonFatalDiagnostics ?: emptyList()
return symbol.fir.deprecation?.forUseSite()?.let {
prevDiagnostics + ConeDeprecated(source, symbol, it)
} ?: prevDiagnostics
}
fun resolveCallableReference(
constraintSystemBuilder: ConstraintSystemBuilder,
resolvedCallableReferenceAtom: ResolvedCallableReferenceAtom,
@@ -114,7 +114,8 @@ fun BodyResolveComponents.buildResolvedQualifierForClass(
sourceElement: FirSourceElement? = null,
// TODO: Clarify if we actually need type arguments for qualifier?
typeArgumentsForQualifier: List<FirTypeProjection> = emptyList(),
diagnostic: ConeDiagnostic? = null
diagnostic: ConeDiagnostic? = null,
nonFatalDiagnostics: List<ConeDiagnostic> = emptyList()
): FirResolvedQualifier {
val classId = regularClass.classId
@@ -130,6 +131,7 @@ fun BodyResolveComponents.buildResolvedQualifierForClass(
relativeClassFqName = classId.relativeClassName
typeArguments.addAll(typeArgumentsForQualifier)
symbol = regularClass
this.nonFatalDiagnostics.addAll(nonFatalDiagnostics)
}.build().apply {
resultType = if (classId.isLocal) {
typeForQualifierByDeclaration(regularClass.fir, resultType, session)
@@ -7,6 +7,7 @@ package org.jetbrains.kotlin.fir.resolve.diagnostics
import kotlinx.collections.immutable.ImmutableList
import org.jetbrains.kotlin.fir.FirSourceElement
import org.jetbrains.kotlin.fir.declarations.Deprecation
import org.jetbrains.kotlin.fir.declarations.FirCallableDeclaration
import org.jetbrains.kotlin.fir.declarations.FirRegularClass
import org.jetbrains.kotlin.fir.diagnostics.ConeDiagnostic
@@ -147,6 +148,10 @@ class ConeUnsupportedDynamicType() : ConeDiagnostic() {
override val reason: String get() = "Dynamic types are not supported in this context"
}
class ConeDeprecated(val source: FirSourceElement?, val symbol: FirBasedSymbol<*>, val deprecation: Deprecation) : ConeDiagnostic() {
override val reason: String get() = "Deprecated: ${deprecation.message}"
}
private fun describeSymbol(symbol: FirBasedSymbol<*>): String {
return when (symbol) {
is FirClassLikeSymbol<*> -> symbol.classId.asString()
@@ -31,6 +31,7 @@ abstract class FirErrorResolvedQualifier : FirResolvedQualifier(), FirDiagnostic
abstract override val symbol: FirClassLikeSymbol<*>?
abstract override val isNullableLHSForCallableReference: Boolean
abstract override val resolvedToCompanionObject: Boolean
abstract override val nonFatalDiagnostics: List<ConeDiagnostic>
abstract override val typeArguments: List<FirTypeProjection>
abstract override val diagnostic: ConeDiagnostic
@@ -7,6 +7,7 @@ package org.jetbrains.kotlin.fir.expressions
import org.jetbrains.kotlin.fir.FirElement
import org.jetbrains.kotlin.fir.FirSourceElement
import org.jetbrains.kotlin.fir.diagnostics.ConeDiagnostic
import org.jetbrains.kotlin.fir.symbols.impl.FirClassLikeSymbol
import org.jetbrains.kotlin.fir.types.FirTypeProjection
import org.jetbrains.kotlin.fir.types.FirTypeRef
@@ -29,6 +30,7 @@ abstract class FirResolvedQualifier : FirExpression() {
abstract val symbol: FirClassLikeSymbol<*>?
abstract val isNullableLHSForCallableReference: Boolean
abstract val resolvedToCompanionObject: Boolean
abstract val nonFatalDiagnostics: List<ConeDiagnostic>
abstract val typeArguments: List<FirTypeProjection>
override fun <R, D> accept(visitor: FirVisitor<R, D>, data: D): R = visitor.visitResolvedQualifier(this, data)
@@ -7,6 +7,7 @@ package org.jetbrains.kotlin.fir.expressions.builder
import org.jetbrains.kotlin.fir.FirSourceElement
import org.jetbrains.kotlin.fir.builder.FirBuilderDsl
import org.jetbrains.kotlin.fir.diagnostics.ConeDiagnostic
import org.jetbrains.kotlin.fir.expressions.FirAnnotationCall
import org.jetbrains.kotlin.fir.expressions.FirResolvedQualifier
import org.jetbrains.kotlin.fir.symbols.impl.FirClassLikeSymbol
@@ -32,6 +33,7 @@ interface FirAbstractResolvedQualifierBuilder {
abstract var symbol: FirClassLikeSymbol<*>?
abstract var isNullableLHSForCallableReference: Boolean
abstract var resolvedToCompanionObject: Boolean
abstract val nonFatalDiagnostics: MutableList<ConeDiagnostic>
abstract val typeArguments: MutableList<FirTypeProjection>
fun build(): FirResolvedQualifier
@@ -37,6 +37,7 @@ class FirErrorResolvedQualifierBuilder : FirAbstractResolvedQualifierBuilder, Fi
override var classId: ClassId? = null
override var symbol: FirClassLikeSymbol<*>? = null
override var isNullableLHSForCallableReference: Boolean = false
override val nonFatalDiagnostics: MutableList<ConeDiagnostic> = mutableListOf()
override val typeArguments: MutableList<FirTypeProjection> = mutableListOf()
lateinit var diagnostic: ConeDiagnostic
@@ -49,6 +50,7 @@ class FirErrorResolvedQualifierBuilder : FirAbstractResolvedQualifierBuilder, Fi
classId,
symbol,
isNullableLHSForCallableReference,
nonFatalDiagnostics,
typeArguments,
diagnostic,
)
@@ -10,6 +10,7 @@ import org.jetbrains.kotlin.fir.FirSourceElement
import org.jetbrains.kotlin.fir.builder.FirAnnotationContainerBuilder
import org.jetbrains.kotlin.fir.builder.FirBuilderDsl
import org.jetbrains.kotlin.fir.declarations.FirRegularClass
import org.jetbrains.kotlin.fir.diagnostics.ConeDiagnostic
import org.jetbrains.kotlin.fir.expressions.FirAnnotationCall
import org.jetbrains.kotlin.fir.expressions.FirResolvedQualifier
import org.jetbrains.kotlin.fir.expressions.builder.FirAbstractResolvedQualifierBuilder
@@ -37,6 +38,7 @@ class FirResolvedQualifierBuilder : FirAbstractResolvedQualifierBuilder, FirAnno
override var relativeClassFqName: FqName? = null
override var symbol: FirClassLikeSymbol<*>? = null
override var isNullableLHSForCallableReference: Boolean = false
override val nonFatalDiagnostics: MutableList<ConeDiagnostic> = mutableListOf()
override val typeArguments: MutableList<FirTypeProjection> = mutableListOf()
override fun build(): FirResolvedQualifier {
@@ -48,6 +50,7 @@ class FirResolvedQualifierBuilder : FirAbstractResolvedQualifierBuilder, FirAnno
relativeClassFqName,
symbol,
isNullableLHSForCallableReference,
nonFatalDiagnostics,
typeArguments,
)
}
@@ -30,6 +30,7 @@ internal class FirErrorResolvedQualifierImpl(
override val classId: ClassId?,
override val symbol: FirClassLikeSymbol<*>?,
override var isNullableLHSForCallableReference: Boolean,
override val nonFatalDiagnostics: MutableList<ConeDiagnostic>,
override val typeArguments: MutableList<FirTypeProjection>,
override val diagnostic: ConeDiagnostic,
) : FirErrorResolvedQualifier() {
@@ -7,6 +7,7 @@ package org.jetbrains.kotlin.fir.expressions.impl
import org.jetbrains.kotlin.fir.FirSourceElement
import org.jetbrains.kotlin.fir.declarations.FirRegularClass
import org.jetbrains.kotlin.fir.diagnostics.ConeDiagnostic
import org.jetbrains.kotlin.fir.expressions.FirAnnotationCall
import org.jetbrains.kotlin.fir.expressions.FirResolvedQualifier
import org.jetbrains.kotlin.fir.symbols.impl.FirClassLikeSymbol
@@ -29,6 +30,7 @@ internal class FirResolvedQualifierImpl(
override var relativeClassFqName: FqName?,
override val symbol: FirClassLikeSymbol<*>?,
override var isNullableLHSForCallableReference: Boolean,
override val nonFatalDiagnostics: MutableList<ConeDiagnostic>,
override val typeArguments: MutableList<FirTypeProjection>,
) : FirResolvedQualifier() {
override val classId: ClassId? get() = relativeClassFqName?.let {
@@ -503,6 +503,7 @@ object NodeConfigurator : AbstractFieldConfigurator<FirTreeBuilder>(FirTreeBuild
+field("symbol", classLikeSymbolType, nullable = true)
+booleanField("isNullableLHSForCallableReference", withReplace = true)
+booleanField("resolvedToCompanionObject", withReplace = true)
+fieldList("nonFatalDiagnostics", coneDiagnosticType)
+typeArguments.withTransform()
}
@@ -978,6 +978,7 @@ object PositioningStrategies {
is KtWhenConditionInRange -> element.operationReference
is KtAnnotationEntry -> element.calleeExpression ?: element
is KtTypeReference -> (element.typeElement as? KtNullableType)?.innerType ?: element
is KtImportDirective -> element.importedReference ?: element
else -> element
}
while (locateReferencedName && result is KtParenthesizedExpression) {
@@ -1,32 +0,0 @@
class Another {
@Deprecated("Object")
companion object {
fun use() {}
const val USE = 42
}
}
fun first() {
Another.use()
Another.Companion.USE
Another.USE
}
fun useCompanion() {
val d = Another
val x = Another.Companion
Another.Companion.use()
Another.use()
}
@Deprecated("Some")
class Some {
companion object {
fun use() {}
}
}
fun some() {
Some.use()
Some.Companion.use()
}
@@ -1,3 +1,4 @@
// FIR_IDENTICAL
class Another {
@Deprecated("Object")
companion object {
@@ -10,4 +10,4 @@ public interface J {
// FILE: K.kt
import test.J
import <!DEPRECATION!>test.J<!>
@@ -1,8 +0,0 @@
import C as C2
@Deprecated("obsolete")
class C {
fun use() {}
}
fun useAlias(c : <!DEPRECATION!>C2<!>) { c.use() }
@@ -1,3 +1,4 @@
// FIR_IDENTICAL
import <!DEPRECATION!>C<!> as C2
@Deprecated("obsolete")
@@ -1,18 +0,0 @@
class TopLevel {
@Deprecated("Nested")
class Nested {
companion object {
fun use() {}
class CompanionNested2
}
class Nested2
}
}
fun useNested() {
val d = TopLevel.Nested.use()
TopLevel.Nested.Nested2()
TopLevel.Nested.<!UNRESOLVED_REFERENCE!>CompanionNested2<!>()
}
@@ -1,3 +1,4 @@
// FIR_IDENTICAL
class TopLevel {
@Deprecated("Nested")
class Nested {
@@ -1,9 +0,0 @@
@Deprecated("Object")
object Obsolete {
fun use() {}
}
fun useObject() {
Obsolete.use()
val x = Obsolete
}
@@ -1,3 +1,4 @@
// FIR_IDENTICAL
@Deprecated("Object")
object Obsolete {
fun use() {}
@@ -1,12 +0,0 @@
class Relevant {
companion object {
val value = ""
}
}
@Deprecated("Use Relevant")
typealias Obsolete = Relevant
fun test1() = Obsolete
fun test2() = Obsolete.value
fun test3() = Obsolete.toString()
@@ -1,3 +1,4 @@
// FIR_IDENTICAL
class Relevant {
companion object {
val value = ""
@@ -16,7 +16,7 @@ fun test1a(x: List<<!DEPRECATION!>Obsolete<!>>) = x
val test2 = Obsolete()
val test3 = Obsolete
val test3 = <!DEPRECATION!>Obsolete<!>
class Test4: <!DEPRECATION!>Obsolete<!>()
class Test4a: <!DEPRECATION!>IObsolete<!>
@@ -7,4 +7,4 @@ interface A
// FILE: B.kt
import test.A
import <!DEPRECATION!>test.A<!>
@@ -28,7 +28,7 @@ fun test(a: <!UNRESOLVED_REFERENCE!>A<!>) {
}
// FILE: explicitlyImportP1.kt
import p1.A
import <!DEPRECATION_ERROR!>p1.A<!>
import p2.*
fun test(a: <!DEPRECATION_ERROR!>A<!>) {
@@ -25,7 +25,7 @@ class A(val v3: Unit)
// MODULE: m4(m1, m2, m3)
// FILE: oneExplicitImportOtherStars.kt
import p1.*
import p2.A
import <!DEPRECATION_ERROR!>p2.A<!>
import p3.*
fun test(a: <!DEPRECATION_ERROR!>A<!>) {