[FIR] Report deprecations on typealias constructor calls

Typealias constructor calls are now always wrapped with synthetic
FirConstructorCalls so that the referenced typeAlias can be extracted.
Previously, it was only necessary to map type arguments.

#KT-57780 Fixed
This commit is contained in:
Kirill Rakhman
2023-07-25 09:57:47 +02:00
committed by Space Team
parent b9e5b8a087
commit 2de9480101
10 changed files with 87 additions and 60 deletions
@@ -12,13 +12,17 @@ import org.jetbrains.kotlin.diagnostics.DiagnosticReporter
import org.jetbrains.kotlin.diagnostics.reportOn import org.jetbrains.kotlin.diagnostics.reportOn
import org.jetbrains.kotlin.fir.FirElement import org.jetbrains.kotlin.fir.FirElement
import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext
import org.jetbrains.kotlin.fir.analysis.checkers.getContainingClassSymbol
import org.jetbrains.kotlin.fir.analysis.checkers.toRegularClassSymbol import org.jetbrains.kotlin.fir.analysis.checkers.toRegularClassSymbol
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors
import org.jetbrains.kotlin.fir.declarations.FutureApiDeprecationInfo import org.jetbrains.kotlin.fir.declarations.FutureApiDeprecationInfo
import org.jetbrains.kotlin.fir.analysis.checkers.isLhsOfAssignment import org.jetbrains.kotlin.fir.analysis.checkers.isLhsOfAssignment
import org.jetbrains.kotlin.fir.analysis.checkers.type.FirDeprecatedTypeChecker
import org.jetbrains.kotlin.fir.declarations.getDeprecation import org.jetbrains.kotlin.fir.declarations.getDeprecation
import org.jetbrains.kotlin.fir.expressions.* import org.jetbrains.kotlin.fir.expressions.*
import org.jetbrains.kotlin.fir.references.resolved import org.jetbrains.kotlin.fir.references.resolved
import org.jetbrains.kotlin.fir.resolve.firClassLike
import org.jetbrains.kotlin.fir.resolve.typeAliasForConstructor
import org.jetbrains.kotlin.fir.symbols.FirBasedSymbol import org.jetbrains.kotlin.fir.symbols.FirBasedSymbol
import org.jetbrains.kotlin.fir.symbols.impl.FirConstructorSymbol import org.jetbrains.kotlin.fir.symbols.impl.FirConstructorSymbol
import org.jetbrains.kotlin.fir.symbols.impl.FirTypeAliasSymbol import org.jetbrains.kotlin.fir.symbols.impl.FirTypeAliasSymbol
@@ -39,14 +43,31 @@ object FirDeprecationChecker : FirBasicExpressionChecker() {
val calleeReference = expression.calleeReference ?: return val calleeReference = expression.calleeReference ?: return
val resolvedReference = calleeReference.resolved ?: return val resolvedReference = calleeReference.resolved ?: return
val source = resolvedReference.source
val referencedSymbol = resolvedReference.resolvedSymbol val referencedSymbol = resolvedReference.resolvedSymbol
// If this is a constructor call through a typealias, we want to check deprecations on the typealias itself as well as any
// intermediary expansions.
// However, we'll already check the final expansion below, so we don't want to do it here as well to prevent duplicate diagnostics.
val typeAliasForConstructor = (referencedSymbol as? FirConstructorSymbol)?.typeAliasForConstructor
if (typeAliasForConstructor != null && expression is FirQualifiedAccessExpression) {
FirDeprecatedTypeChecker.reportDeprecationsRecursively(
typeAliasForConstructor,
source,
context,
reporter,
// We pass the containing class symbol to ignore the final expansion.
symbolToIgnore = referencedSymbol.getContainingClassSymbol(context.session)
)
}
if (expression is FirDelegatedConstructorCall) { if (expression is FirDelegatedConstructorCall) {
// Report deprecations on the constructor itself, not on the declaring class as that will be handled by FirDeprecatedTypeChecker // Report deprecations on the constructor itself, not on the declaring class as that will be handled by FirDeprecatedTypeChecker
val constructorOnlyDeprecation = referencedSymbol.getDeprecation(context.session, expression) ?: return val constructorOnlyDeprecation = referencedSymbol.getDeprecation(context.session, expression) ?: return
reportApiStatus(resolvedReference.source, referencedSymbol, null, constructorOnlyDeprecation, reporter, context) val typealiasSymbol = expression.constructedTypeRef.firClassLike(context.session)?.symbol as? FirTypeAliasSymbol
reportApiStatus(source, referencedSymbol, typealiasSymbol, constructorOnlyDeprecation, reporter, context)
} else { } else {
reportApiStatusIfNeeded(resolvedReference.source, referencedSymbol, context, reporter, callSite = expression) reportApiStatusIfNeeded(source, referencedSymbol, context, reporter, typeAliasForConstructor, callSite = expression)
} }
} }
@@ -83,7 +104,7 @@ object FirDeprecationChecker : FirBasicExpressionChecker() {
typealiasSymbol: FirTypeAliasSymbol?, typealiasSymbol: FirTypeAliasSymbol?,
deprecationInfo: DeprecationInfo, deprecationInfo: DeprecationInfo,
reporter: DiagnosticReporter, reporter: DiagnosticReporter,
context: CheckerContext context: CheckerContext,
) { ) {
if (typealiasSymbol == null) { if (typealiasSymbol == null) {
val diagnostic = when (deprecationInfo.deprecationLevel) { val diagnostic = when (deprecationInfo.deprecationLevel) {
@@ -118,7 +139,7 @@ object FirDeprecationChecker : FirBasicExpressionChecker() {
private fun getWorstDeprecation( private fun getWorstDeprecation(
callSite: FirElement?, callSite: FirElement?,
symbol: FirBasedSymbol<*>, symbol: FirBasedSymbol<*>,
context: CheckerContext context: CheckerContext,
): DeprecationInfo? { ): DeprecationInfo? {
val deprecationInfos = listOfNotNull( val deprecationInfos = listOfNotNull(
symbol.getDeprecation(context.session, callSite), symbol.getDeprecation(context.session, callSite),
@@ -13,6 +13,7 @@ import org.jetbrains.kotlin.fir.analysis.checkers.expression.FirDeprecationCheck
import org.jetbrains.kotlin.fir.declarations.FirResolvePhase import org.jetbrains.kotlin.fir.declarations.FirResolvePhase
import org.jetbrains.kotlin.fir.resolve.toSymbol import org.jetbrains.kotlin.fir.resolve.toSymbol
import org.jetbrains.kotlin.fir.symbols.SymbolInternals import org.jetbrains.kotlin.fir.symbols.SymbolInternals
import org.jetbrains.kotlin.fir.symbols.impl.FirClassLikeSymbol
import org.jetbrains.kotlin.fir.symbols.impl.FirTypeAliasSymbol import org.jetbrains.kotlin.fir.symbols.impl.FirTypeAliasSymbol
import org.jetbrains.kotlin.fir.symbols.lazyResolveToPhase import org.jetbrains.kotlin.fir.symbols.lazyResolveToPhase
import org.jetbrains.kotlin.fir.types.* import org.jetbrains.kotlin.fir.types.*
@@ -23,28 +24,58 @@ object FirDeprecatedTypeChecker : FirTypeRefChecker() {
if (source.kind is KtFakeSourceElementKind) return if (source.kind is KtFakeSourceElementKind) return
val resolved = typeRef.coneTypeSafe<ConeClassLikeType>() ?: return val resolved = typeRef.coneTypeSafe<ConeClassLikeType>() ?: return
checkType(resolved, null, source, context, reporter) checkType(resolved, null, source, context, reporter, symbolToIgnore = null)
} }
@OptIn(SymbolInternals::class)
private fun checkType( private fun checkType(
type: ConeClassLikeType, type: ConeClassLikeType,
typeAliasSymbol: FirTypeAliasSymbol?, typeAliasSymbol: FirTypeAliasSymbol?,
source: KtSourceElement, source: KtSourceElement?,
context: CheckerContext, context: CheckerContext,
reporter: DiagnosticReporter reporter: DiagnosticReporter,
symbolToIgnore: FirClassLikeSymbol<*>?,
) { ) {
val symbol = type.lookupTag.toSymbol(context.session) ?: return val symbol = type.lookupTag.toSymbol(context.session) ?: return
FirDeprecationChecker.reportApiStatusIfNeeded(source, symbol, context, reporter, typealiasSymbol = typeAliasSymbol) reportDeprecationsRecursively(symbol, typeAliasSymbol, source, context, reporter, symbolToIgnore)
}
@OptIn(SymbolInternals::class)
private fun reportDeprecationsRecursively(
symbol: FirClassLikeSymbol<*>,
// If not-null, TYPEALIAS_EXPANSION_DEPRECATION will be reported instead of DEPRECATION.
typeAliasSymbol: FirTypeAliasSymbol?,
source: KtSourceElement?,
context: CheckerContext,
reporter: DiagnosticReporter,
symbolToIgnore: FirClassLikeSymbol<*>?,
) {
if (symbol == symbolToIgnore) return
FirDeprecationChecker.reportApiStatusIfNeeded(source, symbol, context, reporter, typealiasSymbol = typeAliasSymbol)
if (symbol is FirTypeAliasSymbol) { if (symbol is FirTypeAliasSymbol) {
val typeAlias = symbol.fir val typeAlias = symbol.fir
typeAlias.lazyResolveToPhase(FirResolvePhase.TYPES) typeAlias.lazyResolveToPhase(FirResolvePhase.TYPES)
typeAlias.expandedTypeRef.coneType.forEachType { typeAlias.expandedTypeRef.coneType.forEachType {
if (it is ConeClassLikeType) checkType(it, symbol, source, context, reporter) if (it is ConeClassLikeType) checkType(it, symbol, source, context, reporter, symbolToIgnore)
} }
} }
} }
/**
* Reports deprecations on [symbol]. If [symbol] is a typealias, deprecations will be reported on the expansions recursively.
*
* @param symbolToIgnore If equal to [symbol], no deprecations on the [symbol] and its expansions will be reported.
* It's passed to recursive calls and can be used to only report deprecations on a typealias and its intermediary expansions
* but not on the final expansion if set to the fully expanded class symbol.
*/
fun reportDeprecationsRecursively(
symbol: FirClassLikeSymbol<*>,
source: KtSourceElement?,
context: CheckerContext,
reporter: DiagnosticReporter,
symbolToIgnore: FirClassLikeSymbol<*>?,
) {
reportDeprecationsRecursively(symbol, typeAliasSymbol = null, source, context, reporter, symbolToIgnore)
}
} }
@@ -857,20 +857,27 @@ class FirCallResolver(
/** A candidate in the overload candidate set. */ /** A candidate in the overload candidate set. */
data class OverloadCandidate(val candidate: Candidate, val isInBestCandidates: Boolean) data class OverloadCandidate(val candidate: Candidate, val isInBestCandidates: Boolean)
/** Used for IDE */
class AllCandidatesCollector( class AllCandidatesCollector(
components: BodyResolveComponents, components: BodyResolveComponents,
resolutionStageRunner: ResolutionStageRunner resolutionStageRunner: ResolutionStageRunner
) : CandidateCollector(components, resolutionStageRunner) { ) : CandidateCollector(components, resolutionStageRunner) {
private val allCandidatesSet = mutableSetOf<Candidate>() private val allCandidatesMap = mutableMapOf<FirBasedSymbol<*>, Candidate>()
override fun consumeCandidate(group: TowerGroup, candidate: Candidate, context: ResolutionContext): CandidateApplicability { override fun consumeCandidate(group: TowerGroup, candidate: Candidate, context: ResolutionContext): CandidateApplicability {
allCandidatesSet += candidate // Filter duplicate symbols. In the case of typealias constructor calls, we consider the original constructor for uniqueness.
val key = (candidate.symbol.fir as? FirConstructor)?.originalConstructorIfTypeAlias?.symbol
?: candidate.symbol
// To preserve the behavior of a HashSet which keeps the first added item, we use getOrPut instead of put.
// Changing this behavior breaks testData/components/callResolver/resolveCandidates/singleCandidate/functionTypeVariableCall_extensionReceiver.kt
allCandidatesMap.getOrPut(key) { candidate }
return super.consumeCandidate(group, candidate, context) return super.consumeCandidate(group, candidate, context)
} }
// We want to get candidates at all tower levels. // We want to get candidates at all tower levels.
override fun shouldStopAtTheGroup(group: TowerGroup): Boolean = false override fun shouldStopAtTheGroup(group: TowerGroup): Boolean = false
val allCandidates: List<Candidate> val allCandidates: Collection<Candidate>
get() = allCandidatesSet.toList() get() = allCandidatesMap.values
} }
@@ -182,15 +182,15 @@ private fun processConstructors(
val outerType = bodyResolveComponents.outerClassManager.outerType(type) val outerType = bodyResolveComponents.outerClassManager.outerType(type)
if (basicScope != null && if (basicScope != null) {
(matchedSymbol.fir.typeParameters.isNotEmpty() || outerType != null || type.typeArguments.isNotEmpty())
) {
TypeAliasConstructorsSubstitutingScope( TypeAliasConstructorsSubstitutingScope(
matchedSymbol, matchedSymbol,
basicScope, basicScope,
outerType outerType
) )
} else basicScope } else {
null
}
} }
is FirClassSymbol -> { is FirClassSymbol -> {
val firClass = matchedSymbol.fir as FirClass val firClass = matchedSymbol.fir as FirClass
@@ -232,7 +232,7 @@ private class TypeAliasConstructorsSubstitutingScope(
origin = FirDeclarationOrigin.Synthetic.TypeAliasConstructor origin = FirDeclarationOrigin.Synthetic.TypeAliasConstructor
this.typeParameters.clear() this.typeParameters.clear()
this.typeParameters += typeParameters.map { buildConstructedClassTypeParameterRef { symbol = it.symbol } } typeParameters.mapTo(this.typeParameters) { buildConstructedClassTypeParameterRef { symbol = it.symbol } }
if (outerType != null) { if (outerType != null) {
// If the matched symbol is a type alias, and the expanded type is a nested class, e.g., // If the matched symbol is a type alias, and the expanded type is a nested class, e.g.,
@@ -93,6 +93,9 @@ private object TypeAliasForConstructorKey : FirDeclarationDataKey()
var FirConstructor.typeAliasForConstructor: FirTypeAliasSymbol? by FirDeclarationDataRegistry.data(TypeAliasForConstructorKey) var FirConstructor.typeAliasForConstructor: FirTypeAliasSymbol? by FirDeclarationDataRegistry.data(TypeAliasForConstructorKey)
val FirConstructorSymbol.typeAliasForConstructor: FirTypeAliasSymbol?
get() = fir.typeAliasForConstructor
interface FirCodeFragmentContext { interface FirCodeFragmentContext {
val towerDataContext: FirTowerDataContext val towerDataContext: FirTowerDataContext
val variables: Map<FirBasedSymbol<*>, Set<ConeKotlinType>> val variables: Map<FirBasedSymbol<*>, Set<ConeKotlinType>>
@@ -1,17 +0,0 @@
@Deprecated("Deprecated class")
open class DeprecatedClass
open class WithDeprecatedCtor(val x: Int) {
@Deprecated("Deprecated constructor")
constructor() : this(0)
}
typealias DeprecatedClassAlias = <!DEPRECATION!>DeprecatedClass<!>
typealias WithDeprecatedCtorAlias = WithDeprecatedCtor
typealias ArrayListOfDeprecatedClass = ArrayList<<!DEPRECATION!>DeprecatedClass<!>>
class Test1 : <!TYPEALIAS_EXPANSION_DEPRECATION!>DeprecatedClassAlias<!>()
class Test2 : <!DEPRECATION!>WithDeprecatedCtorAlias<!>()
val test3 = ArrayListOfDeprecatedClass()
@@ -1,3 +1,4 @@
// FIR_IDENTICAL
@Deprecated("Deprecated class") @Deprecated("Deprecated class")
open class DeprecatedClass open class DeprecatedClass
@@ -11,7 +12,9 @@ typealias WithDeprecatedCtorAlias = WithDeprecatedCtor
typealias ArrayListOfDeprecatedClass = ArrayList<<!DEPRECATION!>DeprecatedClass<!>> typealias ArrayListOfDeprecatedClass = ArrayList<<!DEPRECATION!>DeprecatedClass<!>>
class Test1 : <!TYPEALIAS_EXPANSION_DEPRECATION!>DeprecatedClassAlias<!>() class Test1 : <!TYPEALIAS_EXPANSION_DEPRECATION!>DeprecatedClassAlias<!>()
val test1_1 = <!TYPEALIAS_EXPANSION_DEPRECATION!>DeprecatedClassAlias<!>()
class Test2 : <!TYPEALIAS_EXPANSION_DEPRECATION!>WithDeprecatedCtorAlias<!>() class Test2 : <!TYPEALIAS_EXPANSION_DEPRECATION!>WithDeprecatedCtorAlias<!>()
val test2_1 = <!TYPEALIAS_EXPANSION_DEPRECATION!>WithDeprecatedCtorAlias<!>()
val test3 = <!TYPEALIAS_EXPANSION_DEPRECATION!>ArrayListOfDeprecatedClass<!>() val test3 = <!TYPEALIAS_EXPANSION_DEPRECATION!>ArrayListOfDeprecatedClass<!>()
@@ -1,5 +1,7 @@
package package
public val test1_1: DeprecatedClassAlias /* = DeprecatedClass */
public val test2_1: WithDeprecatedCtorAlias /* = WithDeprecatedCtor */
public val test3: ArrayListOfDeprecatedClass /* = java.util.ArrayList<DeprecatedClass> */ public val test3: ArrayListOfDeprecatedClass /* = java.util.ArrayList<DeprecatedClass> */
@kotlin.Deprecated(message = "Deprecated class") public open class DeprecatedClass { @kotlin.Deprecated(message = "Deprecated class") public open class DeprecatedClass {
@@ -1,24 +0,0 @@
open class Base {
companion object
}
interface IFoo
open class CG<T>
interface IG<T>
@Deprecated("Obsolete")
typealias Obsolete = Base
@Deprecated("Obsolete")
typealias IObsolete = IFoo
fun test1(x: <!DEPRECATION!>Obsolete<!>) = x
fun test1a(x: List<<!DEPRECATION!>Obsolete<!>>) = x
val test2 = Obsolete()
val test3 = <!DEPRECATION!>Obsolete<!>
class Test4: <!DEPRECATION!>Obsolete<!>()
class Test4a: <!DEPRECATION!>IObsolete<!>
class Test4b: IG<<!DEPRECATION!>Obsolete<!>>
class Test4c: CG<<!DEPRECATION!>Obsolete<!>>()
@@ -1,3 +1,4 @@
// FIR_IDENTICAL
open class Base { open class Base {
companion object companion object
} }