From 2de9480101a6fba731b181a154d87937de712949 Mon Sep 17 00:00:00 2001 From: Kirill Rakhman Date: Tue, 25 Jul 2023 09:57:47 +0200 Subject: [PATCH] [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 --- .../expression/FirDeprecationChecker.kt | 29 +++++++++++-- .../checkers/type/FirDeprecatedTypeChecker.kt | 43 ++++++++++++++++--- .../jetbrains/kotlin/fir/FirCallResolver.kt | 15 +++++-- .../resolve/calls/ConstructorProcessing.kt | 10 ++--- .../kotlin/fir/resolve/DeclarationUtils.kt | 3 ++ .../deprecated/typealiasConstructor.fir.kt | 17 -------- .../tests/deprecated/typealiasConstructor.kt | 3 ++ .../tests/deprecated/typealiasConstructor.txt | 2 + .../tests/deprecated/typealiasUsage.fir.kt | 24 ----------- .../tests/deprecated/typealiasUsage.kt | 1 + 10 files changed, 87 insertions(+), 60 deletions(-) delete mode 100644 compiler/testData/diagnostics/tests/deprecated/typealiasConstructor.fir.kt delete mode 100644 compiler/testData/diagnostics/tests/deprecated/typealiasUsage.fir.kt diff --git a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/expression/FirDeprecationChecker.kt b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/expression/FirDeprecationChecker.kt index 30beedab1f6..b83ac40accf 100644 --- a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/expression/FirDeprecationChecker.kt +++ b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/expression/FirDeprecationChecker.kt @@ -12,13 +12,17 @@ import org.jetbrains.kotlin.diagnostics.DiagnosticReporter import org.jetbrains.kotlin.diagnostics.reportOn import org.jetbrains.kotlin.fir.FirElement 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.diagnostics.FirErrors import org.jetbrains.kotlin.fir.declarations.FutureApiDeprecationInfo 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.expressions.* 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.impl.FirConstructorSymbol import org.jetbrains.kotlin.fir.symbols.impl.FirTypeAliasSymbol @@ -39,14 +43,31 @@ object FirDeprecationChecker : FirBasicExpressionChecker() { val calleeReference = expression.calleeReference ?: return val resolvedReference = calleeReference.resolved ?: return + val source = resolvedReference.source 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) { // 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 - 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 { - 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?, deprecationInfo: DeprecationInfo, reporter: DiagnosticReporter, - context: CheckerContext + context: CheckerContext, ) { if (typealiasSymbol == null) { val diagnostic = when (deprecationInfo.deprecationLevel) { @@ -118,7 +139,7 @@ object FirDeprecationChecker : FirBasicExpressionChecker() { private fun getWorstDeprecation( callSite: FirElement?, symbol: FirBasedSymbol<*>, - context: CheckerContext + context: CheckerContext, ): DeprecationInfo? { val deprecationInfos = listOfNotNull( symbol.getDeprecation(context.session, callSite), diff --git a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/type/FirDeprecatedTypeChecker.kt b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/type/FirDeprecatedTypeChecker.kt index ed9a0a908a3..4564b6e860b 100644 --- a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/type/FirDeprecatedTypeChecker.kt +++ b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/type/FirDeprecatedTypeChecker.kt @@ -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.resolve.toSymbol 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.lazyResolveToPhase import org.jetbrains.kotlin.fir.types.* @@ -23,28 +24,58 @@ object FirDeprecatedTypeChecker : FirTypeRefChecker() { if (source.kind is KtFakeSourceElementKind) return val resolved = typeRef.coneTypeSafe() ?: return - checkType(resolved, null, source, context, reporter) + checkType(resolved, null, source, context, reporter, symbolToIgnore = null) } - @OptIn(SymbolInternals::class) private fun checkType( type: ConeClassLikeType, typeAliasSymbol: FirTypeAliasSymbol?, - source: KtSourceElement, + source: KtSourceElement?, context: CheckerContext, - reporter: DiagnosticReporter + reporter: DiagnosticReporter, + symbolToIgnore: FirClassLikeSymbol<*>?, ) { 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) { val typeAlias = symbol.fir typeAlias.lazyResolveToPhase(FirResolvePhase.TYPES) 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) + } } diff --git a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/FirCallResolver.kt b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/FirCallResolver.kt index 99a0e280e4a..37a09a26a18 100644 --- a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/FirCallResolver.kt +++ b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/FirCallResolver.kt @@ -857,20 +857,27 @@ class FirCallResolver( /** A candidate in the overload candidate set. */ data class OverloadCandidate(val candidate: Candidate, val isInBestCandidates: Boolean) +/** Used for IDE */ class AllCandidatesCollector( components: BodyResolveComponents, resolutionStageRunner: ResolutionStageRunner ) : CandidateCollector(components, resolutionStageRunner) { - private val allCandidatesSet = mutableSetOf() + private val allCandidatesMap = mutableMapOf, Candidate>() 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) } // We want to get candidates at all tower levels. override fun shouldStopAtTheGroup(group: TowerGroup): Boolean = false - val allCandidates: List - get() = allCandidatesSet.toList() + val allCandidates: Collection + get() = allCandidatesMap.values } diff --git a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/calls/ConstructorProcessing.kt b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/calls/ConstructorProcessing.kt index 0546e56f5b2..031a59e500c 100644 --- a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/calls/ConstructorProcessing.kt +++ b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/calls/ConstructorProcessing.kt @@ -182,15 +182,15 @@ private fun processConstructors( val outerType = bodyResolveComponents.outerClassManager.outerType(type) - if (basicScope != null && - (matchedSymbol.fir.typeParameters.isNotEmpty() || outerType != null || type.typeArguments.isNotEmpty()) - ) { + if (basicScope != null) { TypeAliasConstructorsSubstitutingScope( matchedSymbol, basicScope, outerType ) - } else basicScope + } else { + null + } } is FirClassSymbol -> { val firClass = matchedSymbol.fir as FirClass @@ -232,7 +232,7 @@ private class TypeAliasConstructorsSubstitutingScope( origin = FirDeclarationOrigin.Synthetic.TypeAliasConstructor this.typeParameters.clear() - this.typeParameters += typeParameters.map { buildConstructedClassTypeParameterRef { symbol = it.symbol } } + typeParameters.mapTo(this.typeParameters) { buildConstructedClassTypeParameterRef { symbol = it.symbol } } if (outerType != null) { // If the matched symbol is a type alias, and the expanded type is a nested class, e.g., diff --git a/compiler/fir/semantics/src/org/jetbrains/kotlin/fir/resolve/DeclarationUtils.kt b/compiler/fir/semantics/src/org/jetbrains/kotlin/fir/resolve/DeclarationUtils.kt index a54bfc221bb..15ffeeba01a 100644 --- a/compiler/fir/semantics/src/org/jetbrains/kotlin/fir/resolve/DeclarationUtils.kt +++ b/compiler/fir/semantics/src/org/jetbrains/kotlin/fir/resolve/DeclarationUtils.kt @@ -93,6 +93,9 @@ private object TypeAliasForConstructorKey : FirDeclarationDataKey() var FirConstructor.typeAliasForConstructor: FirTypeAliasSymbol? by FirDeclarationDataRegistry.data(TypeAliasForConstructorKey) +val FirConstructorSymbol.typeAliasForConstructor: FirTypeAliasSymbol? + get() = fir.typeAliasForConstructor + interface FirCodeFragmentContext { val towerDataContext: FirTowerDataContext val variables: Map, Set> diff --git a/compiler/testData/diagnostics/tests/deprecated/typealiasConstructor.fir.kt b/compiler/testData/diagnostics/tests/deprecated/typealiasConstructor.fir.kt deleted file mode 100644 index 82cca1fbe3d..00000000000 --- a/compiler/testData/diagnostics/tests/deprecated/typealiasConstructor.fir.kt +++ /dev/null @@ -1,17 +0,0 @@ -@Deprecated("Deprecated class") -open class DeprecatedClass - -open class WithDeprecatedCtor(val x: Int) { - @Deprecated("Deprecated constructor") - constructor() : this(0) -} - -typealias DeprecatedClassAlias = DeprecatedClass -typealias WithDeprecatedCtorAlias = WithDeprecatedCtor -typealias ArrayListOfDeprecatedClass = ArrayList<DeprecatedClass> - -class Test1 : DeprecatedClassAlias() - -class Test2 : WithDeprecatedCtorAlias() - -val test3 = ArrayListOfDeprecatedClass() diff --git a/compiler/testData/diagnostics/tests/deprecated/typealiasConstructor.kt b/compiler/testData/diagnostics/tests/deprecated/typealiasConstructor.kt index f597f4f7cb4..1f5cfb10297 100644 --- a/compiler/testData/diagnostics/tests/deprecated/typealiasConstructor.kt +++ b/compiler/testData/diagnostics/tests/deprecated/typealiasConstructor.kt @@ -1,3 +1,4 @@ +// FIR_IDENTICAL @Deprecated("Deprecated class") open class DeprecatedClass @@ -11,7 +12,9 @@ typealias WithDeprecatedCtorAlias = WithDeprecatedCtor typealias ArrayListOfDeprecatedClass = ArrayList<DeprecatedClass> class Test1 : DeprecatedClassAlias() +val test1_1 = DeprecatedClassAlias() class Test2 : WithDeprecatedCtorAlias() +val test2_1 = WithDeprecatedCtorAlias() val test3 = ArrayListOfDeprecatedClass() diff --git a/compiler/testData/diagnostics/tests/deprecated/typealiasConstructor.txt b/compiler/testData/diagnostics/tests/deprecated/typealiasConstructor.txt index 2737eabbc19..7234fc9cbc0 100644 --- a/compiler/testData/diagnostics/tests/deprecated/typealiasConstructor.txt +++ b/compiler/testData/diagnostics/tests/deprecated/typealiasConstructor.txt @@ -1,5 +1,7 @@ package +public val test1_1: DeprecatedClassAlias /* = DeprecatedClass */ +public val test2_1: WithDeprecatedCtorAlias /* = WithDeprecatedCtor */ public val test3: ArrayListOfDeprecatedClass /* = java.util.ArrayList */ @kotlin.Deprecated(message = "Deprecated class") public open class DeprecatedClass { diff --git a/compiler/testData/diagnostics/tests/deprecated/typealiasUsage.fir.kt b/compiler/testData/diagnostics/tests/deprecated/typealiasUsage.fir.kt deleted file mode 100644 index 9aa8d3c4009..00000000000 --- a/compiler/testData/diagnostics/tests/deprecated/typealiasUsage.fir.kt +++ /dev/null @@ -1,24 +0,0 @@ -open class Base { - companion object -} -interface IFoo -open class CG -interface IG - -@Deprecated("Obsolete") -typealias Obsolete = Base - -@Deprecated("Obsolete") -typealias IObsolete = IFoo - -fun test1(x: Obsolete) = x -fun test1a(x: List<Obsolete>) = x - -val test2 = Obsolete() - -val test3 = Obsolete - -class Test4: Obsolete() -class Test4a: IObsolete -class Test4b: IG<Obsolete> -class Test4c: CG<Obsolete>() diff --git a/compiler/testData/diagnostics/tests/deprecated/typealiasUsage.kt b/compiler/testData/diagnostics/tests/deprecated/typealiasUsage.kt index a679866e2c6..013a350459d 100644 --- a/compiler/testData/diagnostics/tests/deprecated/typealiasUsage.kt +++ b/compiler/testData/diagnostics/tests/deprecated/typealiasUsage.kt @@ -1,3 +1,4 @@ +// FIR_IDENTICAL open class Base { companion object }