WIP! Thu 21 Sep 19:25:58 CEST 2023
diff --git a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/declaration/FirActualClassifierMustHasTheSameMembersAsNonFinalExpectClassifierChecker.kt b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/declaration/FirActualClassifierMustHasTheSameMembersAsNonFinalExpectClassifierChecker.kt
index 2e877af..dbe1e83 100644
--- a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/declaration/FirActualClassifierMustHasTheSameMembersAsNonFinalExpectClassifierChecker.kt
+++ b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/declaration/FirActualClassifierMustHasTheSameMembersAsNonFinalExpectClassifierChecker.kt
@@ -185,7 +185,8 @@
                         classTypeSubstitutor,
                         expect,
                         actual,
-                        matchingContext
+                        matchingContext,
+                        context.languageVersionSettings
                     )
                 }
                 .takeIf { kinds -> kinds.all { it != ExpectActualCompatibility.Compatible } }
diff --git a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/transformers/mpp/FirExpectActualResolver.kt b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/transformers/mpp/FirExpectActualResolver.kt
index 5294c02..25b6c27 100644
--- a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/transformers/mpp/FirExpectActualResolver.kt
+++ b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/transformers/mpp/FirExpectActualResolver.kt
@@ -17,7 +17,7 @@
 import org.jetbrains.kotlin.fir.symbols.impl.*
 import org.jetbrains.kotlin.fir.types.*
 import org.jetbrains.kotlin.mpp.CallableSymbolMarker
-import org.jetbrains.kotlin.resolve.calls.mpp.AbstractExpectActualCompatibilityChecker
+import org.jetbrains.kotlin.resolve.calls.mpp.AbstractExpectActualCallablesMatcher
 import org.jetbrains.kotlin.resolve.multiplatform.ExpectActualCompatibility
 import org.jetbrains.kotlin.utils.zipIfSizesAreEqual
 
@@ -69,7 +69,7 @@
                     candidates.filter { expectSymbol ->
                         actualSymbol != expectSymbol && expectSymbol.isExpect
                     }.groupBy { expectDeclaration ->
-                        AbstractExpectActualCompatibilityChecker.getCallablesCompatibility(
+                        AbstractExpectActualCallablesMatcher.getCallablesCompatibility(
                             expectDeclaration,
                             actualSymbol as CallableSymbolMarker,
                             parentSubstitutor,
@@ -88,13 +88,7 @@
                 is FirClassLikeSymbol<*> -> {
                     val expectClassSymbol = useSiteSession.dependenciesSymbolProvider
                         .getClassLikeSymbolByClassId(actualSymbol.classId) as? FirRegularClassSymbol ?: return emptyMap()
-                    val compatibility = AbstractExpectActualCompatibilityChecker.getClassifiersCompatibility(
-                        expectClassSymbol,
-                        actualSymbol,
-                        checkClassScopesCompatibility = true,
-                        context
-                    )
-                    mapOf(compatibility to listOf(expectClassSymbol))
+                    mapOf((ExpectActualCompatibility.Compatible as ExpectActualCompatibility<FirBasedSymbol<*>>) to listOf(expectClassSymbol))
                 }
                 else -> emptyMap()
             }
diff --git a/compiler/frontend/src/org/jetbrains/kotlin/resolve/checkers/ActualClassifierMustHasTheSameMembersAsNonFinalExpectClassifierChecker.kt b/compiler/frontend/src/org/jetbrains/kotlin/resolve/checkers/ActualClassifierMustHasTheSameMembersAsNonFinalExpectClassifierChecker.kt
index fa67e2f..ea92dcd 100644
--- a/compiler/frontend/src/org/jetbrains/kotlin/resolve/checkers/ActualClassifierMustHasTheSameMembersAsNonFinalExpectClassifierChecker.kt
+++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/checkers/ActualClassifierMustHasTheSameMembersAsNonFinalExpectClassifierChecker.kt
@@ -6,6 +6,7 @@
 package org.jetbrains.kotlin.resolve.checkers
 
 import org.jetbrains.kotlin.config.LanguageFeature
+import org.jetbrains.kotlin.config.LanguageVersionSettings
 import org.jetbrains.kotlin.descriptors.*
 import org.jetbrains.kotlin.diagnostics.Errors
 import org.jetbrains.kotlin.incremental.components.NoLookupLocation
@@ -70,7 +71,7 @@
     declaration: KtClassLikeDeclaration,
     descriptor: ClassifierDescriptorWithTypeParameters,
 ) {
-    val scopeDiff = calculateExpectActualScopeDiff(expect, actual)
+    val scopeDiff = calculateExpectActualScopeDiff(expect, actual, context.languageVersionSettings)
     if (scopeDiff.isNotEmpty()) {
         context.trace.report(
             Errors.ACTUAL_CLASSIFIER_MUST_HAVE_THE_SAME_MEMBERS_AS_NON_FINAL_EXPECT_CLASSIFIER_WARNING.on(
@@ -139,6 +140,7 @@
 private fun calculateExpectActualScopeDiff(
     expect: ClassDescriptor,
     actual: ClassDescriptor,
+    languageVersionSettings: LanguageVersionSettings,
 ): Set<ExpectActualMemberDiff<CallableMemberDescriptor, ClassDescriptor>> {
     val matchingContext = ClassicExpectActualMatchingContext(actual.module)
     val classTypeSubstitutor = (expect.declaredTypeParameters zipIfSizesAreEqual actual.declaredTypeParameters)
@@ -164,7 +166,8 @@
                         classTypeSubstitutor,
                         expect,
                         actual,
-                        matchingContext
+                        matchingContext,
+                        languageVersionSettings
                     )
                 }
                 .takeIf { kinds -> kinds.all { it != ExpectActualCompatibility.Compatible } }
diff --git a/compiler/resolution.common/src/org/jetbrains/kotlin/resolve/calls/mpp/AbstractExpectActualCallablesMatcher.kt b/compiler/resolution.common/src/org/jetbrains/kotlin/resolve/calls/mpp/AbstractExpectActualCallablesMatcher.kt
index d68690d..e0f8249 100644
--- a/compiler/resolution.common/src/org/jetbrains/kotlin/resolve/calls/mpp/AbstractExpectActualCallablesMatcher.kt
+++ b/compiler/resolution.common/src/org/jetbrains/kotlin/resolve/calls/mpp/AbstractExpectActualCallablesMatcher.kt
@@ -5,14 +5,40 @@
 
 package org.jetbrains.kotlin.resolve.calls.mpp
 
+import org.jetbrains.kotlin.config.LanguageVersionSettings
 import org.jetbrains.kotlin.descriptors.ClassKind
 import org.jetbrains.kotlin.mpp.*
+import org.jetbrains.kotlin.resolve.calls.mpp.AbstractExpectActualCompatibilityChecker.name
+import org.jetbrains.kotlin.resolve.multiplatform.ExpectActualCompatibility
 import org.jetbrains.kotlin.resolve.multiplatform.ExpectActualCompatibility.Incompatible
 import org.jetbrains.kotlin.types.model.KotlinTypeMarker
 import org.jetbrains.kotlin.types.model.TypeSubstitutorMarker
+import org.jetbrains.kotlin.utils.SmartList
+import org.jetbrains.kotlin.utils.keysToMap
 import org.jetbrains.kotlin.utils.zipIfSizesAreEqual
 
 object AbstractExpectActualCallablesMatcher {
+    fun <T : DeclarationSymbolMarker> getCallablesCompatibility(
+        expectDeclaration: CallableSymbolMarker,
+        actualDeclaration: CallableSymbolMarker,
+        parentSubstitutor: TypeSubstitutorMarker?,
+        expectContainingClass: RegularClassSymbolMarker?,
+        actualContainingClass: RegularClassSymbolMarker?,
+        context: ExpectActualMatchingContext<T>,
+    ): ExpectActualCompatibility<T> {
+        val result = with(context) {
+            getCallablesMatchingIncompatibility(
+                expectDeclaration,
+                actualDeclaration,
+                parentSubstitutor,
+                expectContainingClass,
+                actualContainingClass,
+            ) ?: ExpectActualCompatibility.Compatible
+        }
+        @Suppress("UNCHECKED_CAST")
+        return result as ExpectActualCompatibility<T>
+    }
+
     context(ExpectActualMatchingContext<*>)
     internal fun getCallablesMatchingIncompatibility(
         expectDeclaration: CallableSymbolMarker,
@@ -90,6 +116,118 @@
     }
 
     context(ExpectActualMatchingContext<*>)
+    private fun getClassScopesIncompatibility(
+        expectClassSymbol: RegularClassSymbolMarker,
+        actualClassSymbol: RegularClassSymbolMarker,
+        substitutor: TypeSubstitutorMarker,
+    ): Incompatible.ExpectActualCheckingIncompatible<*>? {
+        val unfulfilled = arrayListOf<Pair<DeclarationSymbolMarker, Map<Incompatible<*>, List<DeclarationSymbolMarker?>>>>()
+
+        val actualMembersByName = actualClassSymbol.collectAllMembers(isActualDeclaration = true).groupBy { it.name }
+
+        outer@ for (expectMember in expectClassSymbol.collectAllMembers(isActualDeclaration = false)) {
+            if (expectMember is CallableSymbolMarker && expectMember.shouldSkipMatching(expectClassSymbol)) continue
+
+            val actualMembers = actualMembersByName[expectMember.name]?.filter { actualMember ->
+                expectMember is CallableSymbolMarker && actualMember is CallableSymbolMarker ||
+                        expectMember is RegularClassSymbolMarker && actualMember is RegularClassSymbolMarker
+            }.orEmpty()
+
+            matchSingleExpectAgainstPotentialActuals(
+                expectMember,
+                actualMembers,
+                substitutor,
+                expectClassSymbol,
+                actualClassSymbol,
+                unfulfilled,
+//                checkClassScopesCompatibility = true,
+            )
+        }
+
+        if (expectClassSymbol.classKind == ClassKind.ENUM_CLASS) {
+            val aEntries = expectClassSymbol.collectEnumEntryNames()
+            val bEntries = actualClassSymbol.collectEnumEntryNames()
+
+            if (!bEntries.containsAll(aEntries)) return Incompatible.EnumEntries
+        }
+
+        // TODO: check static scope?
+
+        if (unfulfilled.isEmpty()) return null
+
+        return Incompatible.ClassScopes(unfulfilled)
+    }
+
+    fun <T : DeclarationSymbolMarker> matchSingleExpectTopLevelDeclarationAgainstPotentialActuals(
+        expectDeclaration: DeclarationSymbolMarker,
+        actualDeclarations: List<DeclarationSymbolMarker>,
+        context: ExpectActualMatchingContext<T>,
+    ) {
+        with(context) {
+            matchSingleExpectAgainstPotentialActuals(
+                expectDeclaration,
+                actualDeclarations,
+                substitutor = null,
+                expectClassSymbol = null,
+                actualClassSymbol = null,
+                unfulfilled = null,
+//                checkClassScopesCompatibility = true,
+            )
+        }
+    }
+
+    context(ExpectActualMatchingContext<*>)
+    private fun matchSingleExpectAgainstPotentialActuals(
+        expectMember: DeclarationSymbolMarker,
+        actualMembers: List<DeclarationSymbolMarker>,
+        substitutor: TypeSubstitutorMarker?,
+        expectClassSymbol: RegularClassSymbolMarker?,
+        actualClassSymbol: RegularClassSymbolMarker?,
+        unfulfilled: MutableList<Pair<DeclarationSymbolMarker, Map<Incompatible<*>, List<DeclarationSymbolMarker?>>>>?,
+//        checkClassScopesCompatibility: Boolean,
+    ) {
+        val mapping = actualMembers.keysToMap { actualMember ->
+            when (expectMember) {
+                is CallableSymbolMarker -> getCallablesMatchingIncompatibility(
+                    expectMember,
+                    actualMember as CallableSymbolMarker,
+                    substitutor,
+                    expectClassSymbol,
+                    actualClassSymbol,
+                ) ?: ExpectActualCompatibility.Compatible
+
+                is RegularClassSymbolMarker -> {
+//                    val parentSubstitutor = substitutor?.takeIf { !innerClassesCapturesOuterTypeParameters }
+//                    AbstractExpectActualCompatibilityChecker.getClassifiersCompatibility(
+//                        expectMember,
+//                        actualMember as ClassLikeSymbolMarker,
+//                        languageVersionSettings,
+//                        parentSubstitutor,
+//                        checkClassScopesCompatibility,
+//                    )
+                    ExpectActualCompatibility.Compatible
+                }
+                else -> error("Unsupported declaration: $expectMember ($actualMembers)")
+            }
+        }
+
+        val incompatibilityMap = mutableMapOf<Incompatible<*>, MutableList<DeclarationSymbolMarker>>()
+        for ((actualMember, compatibility) in mapping) {
+            when (compatibility) {
+                ExpectActualCompatibility.Compatible -> {
+                    onMatchedMembers(expectMember, actualMember, expectClassSymbol, actualClassSymbol)
+                    return
+                }
+
+                is Incompatible -> incompatibilityMap.getOrPut(compatibility) { SmartList() }.add(actualMember)
+            }
+        }
+
+        unfulfilled?.add(expectMember to incompatibilityMap)
+        onMismatchedMembersFromClassScope(expectMember, incompatibilityMap, expectClassSymbol, actualClassSymbol)
+    }
+
+    context(ExpectActualMatchingContext<*>)
     private fun valueParametersCountCompatible(
         expectDeclaration: CallableSymbolMarker,
         actualDeclaration: CallableSymbolMarker,
diff --git a/compiler/resolution.common/src/org/jetbrains/kotlin/resolve/calls/mpp/AbstractExpectActualCompatibilityChecker.kt b/compiler/resolution.common/src/org/jetbrains/kotlin/resolve/calls/mpp/AbstractExpectActualCompatibilityChecker.kt
index ab6477e..e6bedb4 100644
--- a/compiler/resolution.common/src/org/jetbrains/kotlin/resolve/calls/mpp/AbstractExpectActualCompatibilityChecker.kt
+++ b/compiler/resolution.common/src/org/jetbrains/kotlin/resolve/calls/mpp/AbstractExpectActualCompatibilityChecker.kt
@@ -5,6 +5,7 @@
 
 package org.jetbrains.kotlin.resolve.calls.mpp
 
+import org.jetbrains.kotlin.config.LanguageVersionSettings
 import org.jetbrains.kotlin.descriptors.ClassKind
 import org.jetbrains.kotlin.descriptors.Modality
 import org.jetbrains.kotlin.descriptors.Visibilities
@@ -28,9 +29,16 @@
         actualClassLikeSymbol: ClassLikeSymbolMarker,
         checkClassScopesCompatibility: Boolean,
         context: ExpectActualMatchingContext<T>,
+        languageVersionSettings: LanguageVersionSettings,
     ): ExpectActualCompatibility<T> {
         val result = with(context) {
-            getClassifiersCompatibility(expectClassSymbol, actualClassLikeSymbol, parentSubstitutor = null, checkClassScopesCompatibility)
+            getClassifiersCompatibility(
+                expectClassSymbol,
+                actualClassLikeSymbol,
+                languageVersionSettings,
+                parentSubstitutor = null,
+                checkClassScopesCompatibility
+            )
         }
         @Suppress("UNCHECKED_CAST")
         return result as ExpectActualCompatibility<T>
@@ -43,40 +51,31 @@
         expectContainingClass: RegularClassSymbolMarker?,
         actualContainingClass: RegularClassSymbolMarker?,
         context: ExpectActualMatchingContext<T>,
+        languageVersionSettings: LanguageVersionSettings,
     ): ExpectActualCompatibility<T> {
         val result = with(context) {
-            getCallablesCompatibility(expectDeclaration, actualDeclaration, parentSubstitutor, expectContainingClass, actualContainingClass)
+            getCallablesCompatibility(
+                expectDeclaration,
+                actualDeclaration,
+                parentSubstitutor,
+                expectContainingClass,
+                actualContainingClass,
+                languageVersionSettings
+            )
         }
         @Suppress("UNCHECKED_CAST")
         return result as ExpectActualCompatibility<T>
     }
 
-    fun <T : DeclarationSymbolMarker> matchSingleExpectTopLevelDeclarationAgainstPotentialActuals(
-        expectDeclaration: DeclarationSymbolMarker,
-        actualDeclarations: List<DeclarationSymbolMarker>,
-        context: ExpectActualMatchingContext<T>,
-    ) {
-        with(context) {
-            matchSingleExpectAgainstPotentialActuals(
-                expectDeclaration,
-                actualDeclarations,
-                substitutor = null,
-                expectClassSymbol = null,
-                actualClassSymbol = null,
-                unfulfilled = null,
-                checkClassScopesCompatibility = true,
-            )
-        }
-    }
-
     context(ExpectActualMatchingContext<*>)
     @Suppress("warnings")
     private fun getClassifiersCompatibility(
         expectClassSymbol: RegularClassSymbolMarker,
         actualClassLikeSymbol: ClassLikeSymbolMarker,
+        languageVersionSettings: LanguageVersionSettings,
         parentSubstitutor: TypeSubstitutorMarker?,
         checkClassScopes: Boolean,
-    ): ExpectActualCompatibility<*> = getClassifiersIncompatibility(expectClassSymbol, actualClassLikeSymbol, parentSubstitutor, checkClassScopes)
+    ): ExpectActualCompatibility<*> = getClassifiersIncompatibility(expectClassSymbol, actualClassLikeSymbol, parentSubstitutor, checkClassScopes, languageVersionSettings)
         ?: ExpectActualCompatibility.Compatible
 
     context(ExpectActualMatchingContext<*>)
@@ -86,6 +85,7 @@
         actualClassLikeSymbol: ClassLikeSymbolMarker,
         parentSubstitutor: TypeSubstitutorMarker?,
         checkClassScopesCompatibility: Boolean,
+        languageVersionSettings: LanguageVersionSettings,
     ): ExpectActualCompatibility.Incompatible.ExpectActualCheckingIncompatible<*>? {
         // Can't check FQ names here because nested expected class may be implemented via actual typealias's expansion with the other FQ name
         require(expectClassSymbol.name == actualClassLikeSymbol.name) {
@@ -141,7 +141,7 @@
         }
 
         if (checkClassScopesCompatibility) {
-            getClassScopesIncompatibility(expectClassSymbol, actualClass, substitutor)?.let { return it }
+            getClassScopesIncompatibility(expectClassSymbol, actualClass, substitutor, languageVersionSettings)?.let { return it }
         }
 
         return null
@@ -194,104 +194,13 @@
     }
 
     context(ExpectActualMatchingContext<*>)
-    private fun getClassScopesIncompatibility(
-        expectClassSymbol: RegularClassSymbolMarker,
-        actualClassSymbol: RegularClassSymbolMarker,
-        substitutor: TypeSubstitutorMarker,
-    ): Incompatible.ExpectActualCheckingIncompatible<*>? {
-        val unfulfilled = arrayListOf<Pair<DeclarationSymbolMarker, Map<Incompatible<*>, List<DeclarationSymbolMarker?>>>>()
-
-        val actualMembersByName = actualClassSymbol.collectAllMembers(isActualDeclaration = true).groupBy { it.name }
-
-        outer@ for (expectMember in expectClassSymbol.collectAllMembers(isActualDeclaration = false)) {
-            if (expectMember is CallableSymbolMarker && expectMember.shouldSkipMatching(expectClassSymbol)) continue
-
-            val actualMembers = actualMembersByName[expectMember.name]?.filter { actualMember ->
-                expectMember is CallableSymbolMarker && actualMember is CallableSymbolMarker ||
-                        expectMember is RegularClassSymbolMarker && actualMember is RegularClassSymbolMarker
-            }.orEmpty()
-
-            matchSingleExpectAgainstPotentialActuals(
-                expectMember,
-                actualMembers,
-                substitutor,
-                expectClassSymbol,
-                actualClassSymbol,
-                unfulfilled,
-                checkClassScopesCompatibility = true,
-            )
-        }
-
-        if (expectClassSymbol.classKind == ClassKind.ENUM_CLASS) {
-            val aEntries = expectClassSymbol.collectEnumEntryNames()
-            val bEntries = actualClassSymbol.collectEnumEntryNames()
-
-            if (!bEntries.containsAll(aEntries)) return Incompatible.EnumEntries
-        }
-
-        // TODO: check static scope?
-
-        if (unfulfilled.isEmpty()) return null
-
-        return Incompatible.ClassScopes(unfulfilled)
-    }
-
-    context(ExpectActualMatchingContext<*>)
-    private fun matchSingleExpectAgainstPotentialActuals(
-        expectMember: DeclarationSymbolMarker,
-        actualMembers: List<DeclarationSymbolMarker>,
-        substitutor: TypeSubstitutorMarker?,
-        expectClassSymbol: RegularClassSymbolMarker?,
-        actualClassSymbol: RegularClassSymbolMarker?,
-        unfulfilled: MutableList<Pair<DeclarationSymbolMarker, Map<Incompatible<*>, List<DeclarationSymbolMarker?>>>>?,
-        checkClassScopesCompatibility: Boolean,
-    ) {
-        val mapping = actualMembers.keysToMap { actualMember ->
-            when (expectMember) {
-                is CallableSymbolMarker -> getCallablesCompatibility(
-                    expectMember,
-                    actualMember as CallableSymbolMarker,
-                    substitutor,
-                    expectClassSymbol,
-                    actualClassSymbol
-                )
-
-                is RegularClassSymbolMarker -> {
-                    val parentSubstitutor = substitutor?.takeIf { !innerClassesCapturesOuterTypeParameters }
-                    getClassifiersCompatibility(
-                        expectMember,
-                        actualMember as ClassLikeSymbolMarker,
-                        parentSubstitutor,
-                        checkClassScopesCompatibility,
-                    )
-                }
-                else -> error("Unsupported declaration: $expectMember ($actualMembers)")
-            }
-        }
-
-        val incompatibilityMap = mutableMapOf<Incompatible<*>, MutableList<DeclarationSymbolMarker>>()
-        for ((actualMember, compatibility) in mapping) {
-            when (compatibility) {
-                ExpectActualCompatibility.Compatible -> {
-                    onMatchedMembers(expectMember, actualMember, expectClassSymbol, actualClassSymbol)
-                    return
-                }
-
-                is Incompatible -> incompatibilityMap.getOrPut(compatibility) { SmartList() }.add(actualMember)
-            }
-        }
-
-        unfulfilled?.add(expectMember to incompatibilityMap)
-        onMismatchedMembersFromClassScope(expectMember, incompatibilityMap, expectClassSymbol, actualClassSymbol)
-    }
-
-    context(ExpectActualMatchingContext<*>)
     private fun getCallablesCompatibility(
         expectDeclaration: CallableSymbolMarker,
         actualDeclaration: CallableSymbolMarker,
         parentSubstitutor: TypeSubstitutorMarker?,
         expectContainingClass: RegularClassSymbolMarker?,
         actualContainingClass: RegularClassSymbolMarker?,
+        languageVersionSettings: LanguageVersionSettings,
     ): ExpectActualCompatibility<*> {
         require(
             (expectDeclaration is ConstructorSymbolMarker && actualDeclaration is ConstructorSymbolMarker) ||
@@ -326,7 +235,8 @@
                 actualDeclaration,
                 parentSubstitutor,
                 expectContainingClass,
-                actualContainingClass
+                actualContainingClass,
+                languageVersionSettings,
             )
             ?: ExpectActualCompatibility.Compatible
     }
@@ -338,6 +248,7 @@
         parentSubstitutor: TypeSubstitutorMarker?,
         expectContainingClass: RegularClassSymbolMarker?,
         actualContainingClass: RegularClassSymbolMarker?,
+        languageVersionSettings: LanguageVersionSettings,
     ): Incompatible.ExpectActualCheckingIncompatible<*>? {
         val expectedTypeParameters = expectDeclaration.typeParameters
         val actualTypeParameters = actualDeclaration.typeParameters
@@ -377,7 +288,7 @@
             return Incompatible.Modality
         }
 
-        if (!areCompatibleCallableVisibilities(expectDeclaration.visibility, expectModality, actualDeclaration.visibility)) {
+        if (!areCompatibleCallableVisibilities(expectDeclaration.visibility, expectModality, actualDeclaration.visibility, languageVersionSettings)) {
             return Incompatible.Visibility
         }
 
@@ -418,7 +329,7 @@
                 getFunctionsIncompatibility(expectDeclaration, actualDeclaration)?.let { return it }
 
             expectDeclaration is PropertySymbolMarker && actualDeclaration is PropertySymbolMarker ->
-                getPropertiesIncompatibility(expectDeclaration, actualDeclaration)?.let { return it }
+                getPropertiesIncompatibility(expectDeclaration, actualDeclaration, languageVersionSettings)?.let { return it }
 
             expectDeclaration is EnumEntrySymbolMarker && actualDeclaration is EnumEntrySymbolMarker -> {
                 // do nothing, entries are matched only by name
@@ -480,6 +391,7 @@
         expectVisibility: Visibility,
         expectModality: Modality?,
         actualVisibility: Visibility,
+        languageVersionSettings: LanguageVersionSettings,
     ): Boolean {
         val compare = Visibilities.compare(expectVisibility, actualVisibility)
         return if (expectModality != Modality.FINAL) {
@@ -549,12 +461,14 @@
     private fun getPropertiesIncompatibility(
         expected: PropertySymbolMarker,
         actual: PropertySymbolMarker,
+        languageVersionSettings: LanguageVersionSettings,
     ): Incompatible.ExpectActualCheckingIncompatible<*>? {
         return when {
             !equalBy(expected, actual) { p -> p.isVar } -> Incompatible.PropertyKind
             !equalBy(expected, actual) { p -> p.isLateinit } -> Incompatible.PropertyLateinitModifier
             expected.isConst && !actual.isConst -> Incompatible.PropertyConstModifier
-            !arePropertySettersWithCompatibleVisibilities(expected, actual) -> Incompatible.PropertySetterVisibility
+            !arePropertySettersWithCompatibleVisibilities(expected, actual, languageVersionSettings) ->
+                Incompatible.PropertySetterVisibility
             else -> null
         }
     }
@@ -563,10 +477,16 @@
     private fun arePropertySettersWithCompatibleVisibilities(
         expected: PropertySymbolMarker,
         actual: PropertySymbolMarker,
+        languageVersionSettings: LanguageVersionSettings
     ): Boolean {
         val expectedSetter = expected.setter ?: return true
         val actualSetter = actual.setter ?: return true
-        return areCompatibleCallableVisibilities(expectedSetter.visibility, expectedSetter.modality, actualSetter.visibility)
+        return areCompatibleCallableVisibilities(
+            expectedSetter.visibility,
+            expectedSetter.modality,
+            actualSetter.visibility,
+            languageVersionSettings
+        )
     }
 
     // ---------------------------------------- Utils ----------------------------------------
diff --git a/compiler/resolution/src/org/jetbrains/kotlin/resolve/multiplatform/ExpectedActualResolver.kt b/compiler/resolution/src/org/jetbrains/kotlin/resolve/multiplatform/ExpectedActualResolver.kt
index 26e3e9c..40befb3 100644
--- a/compiler/resolution/src/org/jetbrains/kotlin/resolve/multiplatform/ExpectedActualResolver.kt
+++ b/compiler/resolution/src/org/jetbrains/kotlin/resolve/multiplatform/ExpectedActualResolver.kt
@@ -5,7 +5,9 @@
 
 package org.jetbrains.kotlin.resolve.multiplatform
 
+import org.jetbrains.kotlin.config.LanguageVersionSettings
 import org.jetbrains.kotlin.descriptors.*
+import org.jetbrains.kotlin.resolve.calls.mpp.AbstractExpectActualCallablesMatcher
 import org.jetbrains.kotlin.resolve.calls.mpp.AbstractExpectActualCompatibilityChecker
 import org.jetbrains.kotlin.resolve.descriptorUtil.classId
 import org.jetbrains.kotlin.resolve.descriptorUtil.module
@@ -29,7 +31,7 @@
                             // TODO: support non-source definitions (e.g. from Java)
                             actual.couldHaveASource
                 }.groupBy { actual ->
-                    AbstractExpectActualCompatibilityChecker.getCallablesCompatibility(
+                    AbstractExpectActualCallablesMatcher.getCallablesCompatibility(
                         expected,
                         actual,
                         parentSubstitutor = null,
@@ -40,16 +42,9 @@
                 }
             }
             is ClassDescriptor -> {
-                context.findClassifiersFromModule(expected.classId, platformModule, moduleVisibilityFilter).filter { actual ->
-                    expected != actual && !actual.isExpect && actual.couldHaveASource
-                }.groupBy { actual ->
-                    AbstractExpectActualCompatibilityChecker.getClassifiersCompatibility(
-                        expected,
-                        actual,
-                        checkClassScopesCompatibility = true,
-                        context
-                    )
-                }
+                context.findClassifiersFromModule(expected.classId, platformModule, moduleVisibilityFilter)
+                    .singleOrNull { actual -> expected != actual && !actual.isExpect && actual.couldHaveASource }
+                    ?.let { mapOf(Compatible to listOf(it)) }
             }
             else -> null
         }
@@ -71,7 +66,7 @@
                     actual,
                     candidates.filterIsInstance<CallableMemberDescriptor>(),
                     actualClass,
-                    context
+                    context,
                 )
             }
             is ClassDescriptor -> {
@@ -79,7 +74,7 @@
                     actual,
                     candidates.filterIsInstance<ClassifierDescriptorWithTypeParameters>(),
                     checkClassScopesCompatibility,
-                    context
+                    context,
                 )
             }
             else -> emptyMap()
@@ -99,8 +94,11 @@
                     is ClassifierDescriptorWithTypeParameters -> {
                         // TODO: replace with 'singleOrNull' as soon as multi-module diagnostic tests are refactored
                         val expectedClass =
-                            findExpectedForActual(container, moduleFilter, shouldCheckAbsenceOfDefaultParamsInActual)?.values
-                                ?.firstOrNull()?.firstOrNull() as? ClassDescriptor
+                            findExpectedForActual(
+                                container,
+                                moduleFilter,
+                                shouldCheckAbsenceOfDefaultParamsInActual
+                            )?.values?.firstOrNull()?.firstOrNull() as? ClassDescriptor
                         with(context) {
                             expectedClass?.getMembersForExpectClass(actual.name)?.filterIsInstance<CallableMemberDescriptor>().orEmpty()
                         }
@@ -113,7 +111,12 @@
             }
             is ClassifierDescriptorWithTypeParameters -> {
                 val candidates = context.findClassifiersFromModule(actual.classId, actual.module, moduleFilter)
-                matchActualClassAgainstPotentialExpects(actual, candidates, checkClassScopesCompatibility = true, context)
+                matchActualClassAgainstPotentialExpects(
+                    actual,
+                    candidates,
+                    checkClassScopesCompatibility = true,
+                    context,
+                )
             }
             else -> null
         }
@@ -147,13 +150,13 @@
                     }
                     else -> null
                 }
-            AbstractExpectActualCompatibilityChecker.getCallablesCompatibility(
+            AbstractExpectActualCallablesMatcher.getCallablesCompatibility(
                 expectDeclaration = declaration,
                 actualDeclaration = actual,
                 parentSubstitutor = substitutor,
                 expectContainingClass = expectedClass,
                 actualContainingClass = actualClass,
-                context
+                context,
             )
         }
     }
@@ -164,16 +167,9 @@
         checkClassScopesCompatibility: Boolean,
         context: ClassicExpectActualMatchingContext,
     ): Map<ExpectActualCompatibility<MemberDescriptor>, List<ClassifierDescriptorWithTypeParameters>> {
-        return candidates.filter { declaration ->
-            actual != declaration && declaration is ClassDescriptor && declaration.isExpect
-        }.groupBy { expected ->
-            AbstractExpectActualCompatibilityChecker.getClassifiersCompatibility(
-                expected as ClassDescriptor,
-                actual,
-                checkClassScopesCompatibility,
-                context
-            )
-        }
+        return candidates.singleOrNull { declaration -> actual != declaration && declaration is ClassDescriptor && declaration.isExpect }
+            ?.let { mapOf((Compatible as ExpectActualCompatibility<MemberDescriptor>) to listOf(it)) }
+            .orEmpty()
     }
 
     private fun CallableMemberDescriptor.findNamesakesFromModule(