[FIR] Filter out matched expect candidates in `consumeCandidate`
Fix overloading resolving between regular and `DeprecationLevel.HIDDEN` candidate
^KT-69201 Fixed
diff --git a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/calls/candidate/CandidateCollector.kt b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/calls/candidate/CandidateCollector.kt
index 4190bb1..e0ad073 100644
--- a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/calls/candidate/CandidateCollector.kt
+++ b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/calls/candidate/CandidateCollector.kt
@@ -5,11 +5,15 @@
package org.jetbrains.kotlin.fir.resolve.calls.candidate
+import org.jetbrains.kotlin.fir.declarations.getSingleMatchedExpectForActualOrNull
+import org.jetbrains.kotlin.fir.declarations.utils.isActual
+import org.jetbrains.kotlin.fir.declarations.utils.isExpect
import org.jetbrains.kotlin.fir.resolve.BodyResolveComponents
import org.jetbrains.kotlin.fir.resolve.calls.ResolutionContext
import org.jetbrains.kotlin.fir.resolve.calls.ResolutionResultOverridesOtherToPreserveCompatibility
import org.jetbrains.kotlin.fir.resolve.calls.stages.ResolutionStageRunner
import org.jetbrains.kotlin.fir.resolve.calls.tower.TowerGroup
+import org.jetbrains.kotlin.fir.symbols.impl.FirCallableSymbol
import org.jetbrains.kotlin.resolve.calls.tower.ApplicabilityDetail
import org.jetbrains.kotlin.resolve.calls.tower.CandidateApplicability
import org.jetbrains.kotlin.resolve.calls.tower.CandidateApplicability.INAPPLICABLE_ARGUMENTS_MAPPING_ERROR
@@ -24,6 +28,9 @@
private val groupNumbers = mutableListOf<TowerGroup>()
private val candidates = mutableListOf<Candidate>()
+ // All matched expects should be preserved to make it possible to filter out them later when corresponding actuals are encountered
+ private val ignoringExpectCallables = mutableListOf<FirCallableSymbol<*>>()
+
var currentApplicability: CandidateApplicability = CandidateApplicability.HIDDEN
private set
@@ -32,6 +39,7 @@
fun newDataSet() {
groupNumbers.clear()
candidates.clear()
+ ignoringExpectCallables.clear()
currentApplicability = CandidateApplicability.HIDDEN
bestGroup = TowerGroup.Last
}
@@ -39,6 +47,22 @@
open fun consumeCandidate(group: TowerGroup, candidate: Candidate, context: ResolutionContext): CandidateApplicability {
val applicability = resolutionStageRunner.processCandidate(candidate, context)
+ val callableSymbol = candidate.symbol as? FirCallableSymbol<*>
+ if (callableSymbol != null) {
+ if (callableSymbol.isActual) {
+ val matchedExpectSymbol = callableSymbol.getSingleMatchedExpectForActualOrNull()
+ if (matchedExpectSymbol != null) {
+ candidates.removeAll { it.symbol === matchedExpectSymbol } // Filter out matched expects candidates
+ ignoringExpectCallables.add(matchedExpectSymbol as FirCallableSymbol<*>)
+ }
+ } else if (callableSymbol.isExpect) {
+ if (ignoringExpectCallables.any { it === callableSymbol }) {
+ // Skip the found expect because there is already a matched actual
+ return CandidateApplicability.RESOLVED_LOW_PRIORITY
+ }
+ }
+ }
+
if (applicability > currentApplicability || (applicability == currentApplicability && group < bestGroup)) {
// Only throw away previous candidates if the new one is successful. If we don't find a successful candidate, we keep all
// unsuccessful ones so that we can run all stages and pick the one with the least bad applicability.
diff --git a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/calls/overloads/ConeOverloadConflictResolver.kt b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/calls/overloads/ConeOverloadConflictResolver.kt
index bb1245e..9d60c4a 100644
--- a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/calls/overloads/ConeOverloadConflictResolver.kt
+++ b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/calls/overloads/ConeOverloadConflictResolver.kt
@@ -9,7 +9,6 @@
import org.jetbrains.kotlin.descriptors.Modality
import org.jetbrains.kotlin.fir.FirSession
import org.jetbrains.kotlin.fir.declarations.*
-import org.jetbrains.kotlin.fir.declarations.utils.isActual
import org.jetbrains.kotlin.fir.declarations.utils.isExpect
import org.jetbrains.kotlin.fir.declarations.utils.modality
import org.jetbrains.kotlin.fir.expressions.FirCallableReferenceAccess
@@ -302,9 +301,7 @@
): Candidate? {
if (candidates.size <= 1) return candidates.singleOrNull()
- val candidatesWithoutActualizedExpects = filterOutActualizedExpectCandidates(candidates)
-
- val candidateSignatures = candidatesWithoutActualizedExpects.map { candidateCall ->
+ val candidateSignatures = candidates.map { candidateCall ->
createFlatSignature(candidateCall)
}
@@ -317,23 +314,6 @@
return bestCandidatesByParameterTypes.exactMaxWith()?.origin
}
- private fun filterOutActualizedExpectCandidates(candidates: Set<Candidate>): Set<Candidate> {
- val expectForActualSymbols = candidates
- .mapNotNullTo(mutableSetOf()) {
- val callableSymbol = it.symbol as? FirCallableSymbol<*> ?: return@mapNotNullTo null
- runIf(callableSymbol.isActual) { callableSymbol.getSingleMatchedExpectForActualOrNull() }
- }
-
- return if (expectForActualSymbols.isEmpty()) {
- candidates // Optimization: in most cases, there are no expectForActualSymbols that's why filtering and allocation are not performed
- } else {
- candidates.filterTo(mutableSetOf()) { candidate ->
- val symbol = candidate.symbol
- symbol is FirCallableSymbol<*> && (!symbol.isExpect || symbol !in expectForActualSymbols)
- }
- }
- }
-
/**
* `call1` is not less specific than `call2`
*/
diff --git a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/calls/stages/ResolutionStages.kt b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/calls/stages/ResolutionStages.kt
index 7d39c82..fe0319b 100644
--- a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/calls/stages/ResolutionStages.kt
+++ b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/calls/stages/ResolutionStages.kt
@@ -697,8 +697,7 @@
internal object CheckHiddenDeclaration : ResolutionStage() {
override suspend fun check(candidate: Candidate, callInfo: CallInfo, sink: CheckerSink, context: ResolutionContext) {
val symbol = candidate.symbol as? FirCallableSymbol<*> ?: return
- /** Actual declarations are checked by [FirDeprecationChecker] */
- if (symbol.isActual) return
+
val deprecation = symbol.getDeprecation(context.session, callInfo.callSite)
val typeAliasForConstructorDeprecation by lazy(LazyThreadSafetyMode.NONE) {
(symbol as? FirConstructorSymbol)?.typeAliasForConstructor?.getDeprecation(context.session, callInfo.callSite)
diff --git a/compiler/testData/codegen/box/multiplatform/k2/regularAndDeprecatedOverloads.kt b/compiler/testData/codegen/box/multiplatform/k2/regularAndDeprecatedOverloads.kt
index 57d1511..88daa74 100644
--- a/compiler/testData/codegen/box/multiplatform/k2/regularAndDeprecatedOverloads.kt
+++ b/compiler/testData/codegen/box/multiplatform/k2/regularAndDeprecatedOverloads.kt
@@ -29,12 +29,12 @@
"Make it matchable with expect `f` function (K2) but decrease priority to avoid `OVERLOAD_RESOLUTION_AMBIGUITY` when calling it from platform source-set",
level = DeprecationLevel.HIDDEN
)
-actual fun <T> Array<T>.f(): String = <!OVERLOAD_RESOLUTION_AMBIGUITY!>f<!>()
+actual fun <T> Array<T>.f(): String = f()
fun <T> Array<out T>.f() = "OK"
fun box(): String {
if (Array(1) { _ -> ""}.g() != "OK") return "FAIL"
- return Array(0) { _ -> "" }.<!OVERLOAD_RESOLUTION_AMBIGUITY!>f<!>()
+ return Array(0) { _ -> "" }.f()
}