[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()
 }