diff --git a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/declaration/FirExpectActualDeclarationChecker.kt b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/declaration/FirExpectActualDeclarationChecker.kt index ca9832db2f3..28fe8b1a8b3 100644 --- a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/declaration/FirExpectActualDeclarationChecker.kt +++ b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/declaration/FirExpectActualDeclarationChecker.kt @@ -163,10 +163,13 @@ object FirExpectActualDeclarationChecker : FirBasicDeclarationChecker() { actualMember.fir.expectForActual?.values?.singleOrNull()?.singleOrNull() == expectedMember } - val nonTrivialUnfulfilled = singleIncompatibility.unfulfilled.filterNot(::hasSingleActualSuspect) + val nonTrivialIncompatibleMembers = singleIncompatibility.incompatibleMembers.filterNot(::hasSingleActualSuspect) + val nonTrivialMismatchedMembers = singleIncompatibility.mismatchedMembers.filterNot(::hasSingleActualSuspect) - if (nonTrivialUnfulfilled.isNotEmpty()) { - reporter.reportOn(source, FirErrors.NO_ACTUAL_CLASS_MEMBER_FOR_EXPECTED_CLASS, symbol, nonTrivialUnfulfilled, context) + if (nonTrivialIncompatibleMembers.isNotEmpty()) { + reporter.reportOn(source, FirErrors.NO_ACTUAL_CLASS_MEMBER_FOR_EXPECTED_CLASS, symbol, nonTrivialIncompatibleMembers, context) + } else if (nonTrivialMismatchedMembers.isNotEmpty()) { + reporter.reportOn(source, FirErrors.NO_ACTUAL_CLASS_MEMBER_FOR_EXPECTED_CLASS, symbol, nonTrivialMismatchedMembers, context) } } diff --git a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/diagnostics/FirPlatformIncompatibilityDiagnosticRenderer.kt b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/diagnostics/FirPlatformIncompatibilityDiagnosticRenderer.kt index 70a8c863ee0..9defbf281e6 100644 --- a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/diagnostics/FirPlatformIncompatibilityDiagnosticRenderer.kt +++ b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/diagnostics/FirPlatformIncompatibilityDiagnosticRenderer.kt @@ -102,7 +102,8 @@ private fun StringBuilder.renderIncompatibilityInformation( append(indent) append("No actual members are found for expected members listed below:") mode.newLine(this) - renderIncompatibleClassScopes(compatibility.unfulfilled, indent, mode) + renderIncompatibleClassScopes(compatibility.mismatchedMembers, indent, mode) + renderIncompatibleClassScopes(compatibility.incompatibleMembers, indent, mode) } } } diff --git a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/transformers/mpp/FirExpectActualMatchingContextImpl.kt b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/transformers/mpp/FirExpectActualMatchingContextImpl.kt index e6410271d34..af9cc0392d4 100644 --- a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/transformers/mpp/FirExpectActualMatchingContextImpl.kt +++ b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/transformers/mpp/FirExpectActualMatchingContextImpl.kt @@ -469,7 +469,7 @@ class FirExpectActualMatchingContextImpl private constructor( override fun onMismatchedOrIncompatibleMembersFromClassScope( expectSymbol: DeclarationSymbolMarker, - actualSymbolsByIncompatibility: Map, List>, + actualSymbolsByIncompatibility: Map, List>, containingExpectClassSymbol: RegularClassSymbolMarker?, containingActualClassSymbol: RegularClassSymbolMarker? ) { diff --git a/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/actualizer/ExpectActualCollector.kt b/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/actualizer/ExpectActualCollector.kt index 96ec4c4ca93..bfa1f3bd025 100644 --- a/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/actualizer/ExpectActualCollector.kt +++ b/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/actualizer/ExpectActualCollector.kt @@ -219,7 +219,7 @@ private class ExpectActualLinkCollector : IrElementVisitor, context: MatchingContext ) { - AbstractExpectActualChecker.matchSingleExpectTopLevelDeclarationAgainstPotentialActuals( + AbstractExpectActualChecker.checkSingleExpectTopLevelDeclarationAgainstPotentialActuals( expectSymbol, actualSymbols, context, @@ -260,7 +260,7 @@ private class ExpectActualLinkCollector : IrElementVisitor, List>, + actualSymbolsByIncompatibility: Map, List>, containingExpectClassSymbol: RegularClassSymbolMarker?, containingActualClassSymbol: RegularClassSymbolMarker?, ) { diff --git a/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/actualizer/IrActualizerUtils.kt b/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/actualizer/IrActualizerUtils.kt index 2c8b046c622..383300e584d 100644 --- a/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/actualizer/IrActualizerUtils.kt +++ b/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/actualizer/IrActualizerUtils.kt @@ -45,7 +45,7 @@ internal fun collectActualCallablesMatchingToSpecificExpect( matchingActuals += actualSymbol } } - AbstractExpectActualChecker.matchSingleExpectTopLevelDeclarationAgainstPotentialActuals( + AbstractExpectActualChecker.checkSingleExpectTopLevelDeclarationAgainstPotentialActuals( expectSymbol, actualSymbols, context, diff --git a/compiler/resolution.common/src/org/jetbrains/kotlin/resolve/calls/mpp/AbstractExpectActualChecker.kt b/compiler/resolution.common/src/org/jetbrains/kotlin/resolve/calls/mpp/AbstractExpectActualChecker.kt index d473cc9a9a2..f6e56a9085b 100644 --- a/compiler/resolution.common/src/org/jetbrains/kotlin/resolve/calls/mpp/AbstractExpectActualChecker.kt +++ b/compiler/resolution.common/src/org/jetbrains/kotlin/resolve/calls/mpp/AbstractExpectActualChecker.kt @@ -67,20 +67,20 @@ object AbstractExpectActualChecker { result as ExpectActualCompatibility } - fun matchSingleExpectTopLevelDeclarationAgainstPotentialActuals( + fun checkSingleExpectTopLevelDeclarationAgainstPotentialActuals( expectDeclaration: DeclarationSymbolMarker, actualDeclarations: List, context: ExpectActualMatchingContext, checkClassScopesCompatibility: Boolean, ) { with(context) { - matchSingleExpectAgainstPotentialActuals( + checkSingleExpectAgainstPotentialActuals( expectDeclaration, actualDeclarations, substitutor = null, expectClassSymbol = null, actualClassSymbol = null, - unfulfilled = null, + incompatibleMembers = null, checkClassScopesCompatibility = checkClassScopesCompatibility, ) } @@ -216,7 +216,10 @@ object AbstractExpectActualChecker { actualClassSymbol: RegularClassSymbolMarker, substitutor: TypeSubstitutorMarker, ): ExpectActualCheckingCompatibility.Incompatible<*>? { - val unfulfilled = arrayListOf, List>>>() + val mismatchedMembers = + arrayListOf>>>() + val incompatibleMembers = + arrayListOf, List>>>() val actualMembersByName = actualClassSymbol.collectAllMembers(isActualDeclaration = true).groupBy { it.name } @@ -228,15 +231,26 @@ object AbstractExpectActualChecker { expectMember is RegularClassSymbolMarker && actualMember is RegularClassSymbolMarker }.orEmpty() - matchSingleExpectAgainstPotentialActuals( + val matched = AbstractExpectActualMatcher.matchSingleExpectAgainstPotentialActuals( expectMember, actualMembers, substitutor, expectClassSymbol, actualClassSymbol, - unfulfilled, - checkClassScopesCompatibility = true, + mismatchedMembers, ) + + if (matched != null) { + checkSingleExpectAgainstPotentialActuals( + expectMember, + listOf(matched), // todo convert vector to scalar + substitutor, + expectClassSymbol, + actualClassSymbol, + incompatibleMembers, + checkClassScopesCompatibility = true, + ) + } } if (expectClassSymbol.classKind == ClassKind.ENUM_CLASS) { @@ -248,19 +262,20 @@ object AbstractExpectActualChecker { // TODO: check static scope? - if (unfulfilled.isEmpty()) return null - - return ExpectActualCheckingCompatibility.ClassScopes(unfulfilled) + return when (mismatchedMembers.isNotEmpty() || incompatibleMembers.isNotEmpty()) { + true -> ExpectActualCheckingCompatibility.ClassScopes(mismatchedMembers, incompatibleMembers) + false -> null + } } context(ExpectActualMatchingContext<*>) - private fun matchSingleExpectAgainstPotentialActuals( + private fun checkSingleExpectAgainstPotentialActuals( expectMember: DeclarationSymbolMarker, actualMembers: List, substitutor: TypeSubstitutorMarker?, expectClassSymbol: RegularClassSymbolMarker?, actualClassSymbol: RegularClassSymbolMarker?, - unfulfilled: MutableList, List>>>?, + incompatibleMembers: MutableList, List>>>?, checkClassScopesCompatibility: Boolean, ) { val mapping = actualMembers.keysToMap { actualMember -> @@ -286,7 +301,7 @@ object AbstractExpectActualChecker { } } - val incompatibilityMap = mutableMapOf, MutableList>() + val mismatchedOrIncompatibleMap = mutableMapOf, MutableList>() for ((actualMember, compatibility) in mapping) { when (compatibility) { is ExpectActualCompatibility.MatchedOrCompatible<*> -> { @@ -294,12 +309,19 @@ object AbstractExpectActualChecker { return } - is MismatchOrIncompatible<*> -> incompatibilityMap.getOrPut(compatibility) { SmartList() }.add(actualMember) + is MismatchOrIncompatible<*> -> mismatchedOrIncompatibleMap.getOrPut(compatibility) { SmartList() }.add(actualMember) } } - unfulfilled?.add(expectMember to incompatibilityMap) - onMismatchedOrIncompatibleMembersFromClassScope(expectMember, incompatibilityMap, expectClassSymbol, actualClassSymbol) + // todo it's temporary code. KT-62590 in progress + val incompatibilities = mismatchedOrIncompatibleMap + .mapNotNull { (key, value) -> + (key as? ExpectActualCheckingCompatibility.Incompatible)?.let { it to value } + } + .toMap() + + incompatibleMembers?.add(expectMember to incompatibilities) + onMismatchedOrIncompatibleMembersFromClassScope(expectMember, mismatchedOrIncompatibleMap, expectClassSymbol, actualClassSymbol) } context(ExpectActualMatchingContext<*>) diff --git a/compiler/resolution.common/src/org/jetbrains/kotlin/resolve/calls/mpp/AbstractExpectActualMatcher.kt b/compiler/resolution.common/src/org/jetbrains/kotlin/resolve/calls/mpp/AbstractExpectActualMatcher.kt index 9b883f91a36..635bce9d320 100644 --- a/compiler/resolution.common/src/org/jetbrains/kotlin/resolve/calls/mpp/AbstractExpectActualMatcher.kt +++ b/compiler/resolution.common/src/org/jetbrains/kotlin/resolve/calls/mpp/AbstractExpectActualMatcher.kt @@ -10,7 +10,6 @@ import org.jetbrains.kotlin.mpp.* import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.name.SpecialNames import org.jetbrains.kotlin.resolve.multiplatform.ExpectActualCompatibility -import org.jetbrains.kotlin.resolve.multiplatform.ExpectActualCompatibility.MismatchOrIncompatible import org.jetbrains.kotlin.resolve.multiplatform.ExpectActualMatchingCompatibility import org.jetbrains.kotlin.types.model.KotlinTypeMarker import org.jetbrains.kotlin.types.model.TypeSubstitutorMarker @@ -111,15 +110,22 @@ object AbstractExpectActualMatcher { // TODO: check static scope? } + /** + * Besides returning the matched declaration + * + * The function has an additional side effects: + * - It adds mismatched members to `mismatchedMembers` + * - It calls `onMatchedMembers` and `onMismatchedMembersFromClassScope` callbacks + */ context(ExpectActualMatchingContext<*>) - private fun matchSingleExpectAgainstPotentialActuals( + internal fun matchSingleExpectAgainstPotentialActuals( expectMember: DeclarationSymbolMarker, actualMembers: List, substitutor: TypeSubstitutorMarker?, expectClassSymbol: RegularClassSymbolMarker?, actualClassSymbol: RegularClassSymbolMarker?, - unfulfilled: MutableList, List>>>?, - ) { + unfulfilled: MutableList>>>?, + ): DeclarationSymbolMarker? { val mapping = actualMembers.keysToMap { actualMember -> when (expectMember) { is CallableSymbolMarker -> getCallablesCompatibility( @@ -137,12 +143,12 @@ object AbstractExpectActualMatcher { } } - val incompatibilityMap = mutableMapOf, MutableList>() + val incompatibilityMap = mutableMapOf>() for ((actualMember, compatibility) in mapping) { when (compatibility) { ExpectActualMatchingCompatibility.MatchedSuccessfully -> { onMatchedOrCompatibleMembers(expectMember, actualMember, expectClassSymbol, actualClassSymbol) - return + return actualMember } is ExpectActualMatchingCompatibility.Mismatch -> incompatibilityMap.getOrPut(compatibility) { SmartList() }.add(actualMember) @@ -151,6 +157,7 @@ object AbstractExpectActualMatcher { unfulfilled?.add(expectMember to incompatibilityMap) onMismatchedOrIncompatibleMembersFromClassScope(expectMember, incompatibilityMap, expectClassSymbol, actualClassSymbol) + return null } context(ExpectActualMatchingContext<*>) diff --git a/compiler/resolution.common/src/org/jetbrains/kotlin/resolve/calls/mpp/ExpectActualMatchingContext.kt b/compiler/resolution.common/src/org/jetbrains/kotlin/resolve/calls/mpp/ExpectActualMatchingContext.kt index 9942527bed3..4f6412143f4 100644 --- a/compiler/resolution.common/src/org/jetbrains/kotlin/resolve/calls/mpp/ExpectActualMatchingContext.kt +++ b/compiler/resolution.common/src/org/jetbrains/kotlin/resolve/calls/mpp/ExpectActualMatchingContext.kt @@ -174,7 +174,7 @@ interface ExpectActualMatchingContext : TypeSystemC fun onMismatchedOrIncompatibleMembersFromClassScope( expectSymbol: DeclarationSymbolMarker, - actualSymbolsByIncompatibility: Map, List>, + actualSymbolsByIncompatibility: Map, List>, containingExpectClassSymbol: RegularClassSymbolMarker?, containingActualClassSymbol: RegularClassSymbolMarker?, ) {} diff --git a/compiler/testData/diagnostics/tests/multiplatform/annotationMatching/classScopeViaTypealiasIncompatible.fir.kt b/compiler/testData/diagnostics/tests/multiplatform/annotationMatching/classScopeViaTypealiasIncompatible.fir.kt index e823fdf638a..8c5427a4c54 100644 --- a/compiler/testData/diagnostics/tests/multiplatform/annotationMatching/classScopeViaTypealiasIncompatible.fir.kt +++ b/compiler/testData/diagnostics/tests/multiplatform/annotationMatching/classScopeViaTypealiasIncompatible.fir.kt @@ -18,7 +18,8 @@ class WeakIncompatibilityImpl { fun foo(differentName: String) {} } -actual typealias WeakIncompatibility = WeakIncompatibilityImpl +// TODO: Duplicated diagnostic will be fixed in KT-62559 +actual typealias WeakIncompatibility = WeakIncompatibilityImpl class StrongIncompatibilityImpl { fun foo(p: String) {} // Different param type diff --git a/compiler/testData/diagnostics/tests/multiplatform/annotationMatching/classScopeViaTypealiasIncompatible.kt b/compiler/testData/diagnostics/tests/multiplatform/annotationMatching/classScopeViaTypealiasIncompatible.kt index 9d1b6bec5af..c4d7ec8b6f4 100644 --- a/compiler/testData/diagnostics/tests/multiplatform/annotationMatching/classScopeViaTypealiasIncompatible.kt +++ b/compiler/testData/diagnostics/tests/multiplatform/annotationMatching/classScopeViaTypealiasIncompatible.kt @@ -18,6 +18,7 @@ class WeakIncompatibilityImpl { fun foo(differentName: String) {} } +// TODO: Duplicated diagnostic will be fixed in KT-62559 actual typealias WeakIncompatibility = WeakIncompatibilityImpl class StrongIncompatibilityImpl { diff --git a/core/compiler.common/src/org/jetbrains/kotlin/resolve/multiplatform/ExpectActualCompatibility.kt b/core/compiler.common/src/org/jetbrains/kotlin/resolve/multiplatform/ExpectActualCompatibility.kt index 6eeb9a2d8df..a81a54303ed 100644 --- a/core/compiler.common/src/org/jetbrains/kotlin/resolve/multiplatform/ExpectActualCompatibility.kt +++ b/core/compiler.common/src/org/jetbrains/kotlin/resolve/multiplatform/ExpectActualCompatibility.kt @@ -5,6 +5,8 @@ package org.jetbrains.kotlin.resolve.multiplatform +import org.jetbrains.kotlin.resolve.multiplatform.ExpectActualMatchingCompatibility.Mismatch + private const val TYPE_PARAMETER_COUNT = "number of type parameters is different" // Note that the reason is used in the diagnostic output, see PlatformIncompatibilityDiagnosticRenderer @@ -82,7 +84,8 @@ sealed class ExpectActualCheckingCompatibility : ExpectActualCompatibilit object FunInterfaceModifier : Incompatible("actual declaration for fun expect interface is not a functional interface") object Supertypes : Incompatible("some supertypes are missing in the actual declaration") class ClassScopes( - val unfulfilled: List, Collection>>> + val mismatchedMembers: List>>>, + val incompatibleMembers: List, /* actuals */ Collection>>>, ) : Incompatible("some expected members have no actual ones") object EnumEntries : Incompatible("some entries from expected enum are missing in the actual enum")