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 }