diff --git a/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/ClassicPositioningStrategies.kt b/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/ClassicPositioningStrategies.kt index 0e82e3c391b..a8ad9872f56 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/ClassicPositioningStrategies.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/ClassicPositioningStrategies.kt @@ -64,8 +64,11 @@ object ClassicPositioningStrategies { endElement } } - ExpectActualCompatibility.Incompatible.TypeParameterNames, ExpectActualCompatibility.Incompatible.TypeParameterCount, - ExpectActualCompatibility.Incompatible.TypeParameterUpperBounds, + ExpectActualCompatibility.Incompatible.TypeParameterNames, + ExpectActualCompatibility.Incompatible.FunctionTypeParameterCount, + ExpectActualCompatibility.Incompatible.ClassTypeParameterCount, + ExpectActualCompatibility.Incompatible.FunctionTypeParameterUpperBounds, + ExpectActualCompatibility.Incompatible.ClassTypeParameterUpperBounds, ExpectActualCompatibility.Incompatible.TypeParameterVariance, ExpectActualCompatibility.Incompatible.TypeParameterReified -> { (element as? KtTypeParameterListOwner)?.typeParameterList 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 96dff05dc11..e528c12ba78 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 @@ -70,10 +70,19 @@ object AbstractExpectActualCompatibilityChecker { context(ExpectActualMatchingContext<*>) @Suppress("warnings") private fun areCompatibleClassifiers( + expectClassSymbol: RegularClassSymbolMarker, + actualClassLikeSymbol: ClassLikeSymbolMarker, + parentSubstitutor: TypeSubstitutorMarker?, + ): ExpectActualCompatibility<*> = getClassifiersIncompatibility(expectClassSymbol, actualClassLikeSymbol, parentSubstitutor) + ?: ExpectActualCompatibility.Compatible + + context(ExpectActualMatchingContext<*>) + @Suppress("warnings") + private fun getClassifiersIncompatibility( expectClassSymbol: RegularClassSymbolMarker, actualClassLikeSymbol: ClassLikeSymbolMarker, parentSubstitutor: TypeSubstitutorMarker? - ): ExpectActualCompatibility<*> { + ): ExpectActualCompatibility.Incompatible.WeakIncompatible<*>? { // 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) { "This function should be invoked only for declarations with the same name: $expectClassSymbol, $actualClassLikeSymbol" @@ -82,7 +91,7 @@ object AbstractExpectActualCompatibilityChecker { val actualClass = when (actualClassLikeSymbol) { is RegularClassSymbolMarker -> actualClassLikeSymbol is TypeAliasSymbolMarker -> actualClassLikeSymbol.expandToRegularClass() - ?: return ExpectActualCompatibility.Compatible // do not report extra error on erroneous typealias + ?: return null // do not report extra error on erroneous typealias else -> error("Incorrect actual classifier for $expectClassSymbol: $actualClassLikeSymbol") } @@ -99,7 +108,7 @@ object AbstractExpectActualCompatibilityChecker { val expectTypeParameterSymbols = expectClassSymbol.typeParameters val actualTypeParameterSymbols = actualClass.typeParameters if (expectTypeParameterSymbols.size != actualTypeParameterSymbols.size) { - return Incompatible.TypeParameterCount + return Incompatible.ClassTypeParameterCount } if (!areCompatibleModalities(expectClassSymbol.modality, actualClass.modality)) { @@ -116,23 +125,20 @@ object AbstractExpectActualCompatibilityChecker { parentSubstitutor ) - areCompatibleTypeParameters(expectTypeParameterSymbols, actualTypeParameterSymbols, substitutor).let { - if (it != ExpectActualCompatibility.Compatible) { - return it - } + if (!areCompatibleTypeParameterUpperBounds(expectTypeParameterSymbols, actualTypeParameterSymbols, substitutor)) { + return Incompatible.ClassTypeParameterUpperBounds } + getTypeParametersVarianceOrReifiedIncompatibility(expectTypeParameterSymbols, actualTypeParameterSymbols) + ?.let { return it } + if (!areCompatibleSupertypes(expectClassSymbol, actualClass, substitutor)) { return Incompatible.Supertypes } - areCompatibleClassScopes(expectClassSymbol, actualClass, substitutor).let { - if (it != ExpectActualCompatibility.Compatible) { - return it - } - } + getClassScopesIncompatibility(expectClassSymbol, actualClass, substitutor)?.let { return it } - return ExpectActualCompatibility.Compatible + return null } context(ExpectActualMatchingContext<*>) @@ -182,11 +188,11 @@ object AbstractExpectActualCompatibilityChecker { } context(ExpectActualMatchingContext<*>) - private fun areCompatibleClassScopes( + private fun getClassScopesIncompatibility( expectClassSymbol: RegularClassSymbolMarker, actualClassSymbol: RegularClassSymbolMarker, substitutor: TypeSubstitutorMarker, - ): ExpectActualCompatibility<*> { + ): Incompatible.WeakIncompatible<*>? { val unfulfilled = arrayListOf, List>>>() val actualMembersByName = actualClassSymbol.collectAllMembers(isActualDeclaration = true).groupBy { it.name } @@ -218,7 +224,7 @@ object AbstractExpectActualCompatibilityChecker { // TODO: check static scope? - if (unfulfilled.isEmpty()) return ExpectActualCompatibility.Compatible + if (unfulfilled.isEmpty()) return null return Incompatible.ClassScopes(unfulfilled) } @@ -329,7 +335,7 @@ object AbstractExpectActualCompatibilityChecker { val expectedTypeParameters = expectDeclaration.typeParameters val actualTypeParameters = actualDeclaration.typeParameters if (expectedTypeParameters.size != actualTypeParameters.size) { - return Incompatible.TypeParameterCount + return Incompatible.FunctionTypeParameterCount } val substitutor = createExpectActualTypeParameterSubstitutor( @@ -357,7 +363,9 @@ object AbstractExpectActualCompatibilityChecker { } } - getTypeParametersStrongIncompatibility(expectedTypeParameters, actualTypeParameters, substitutor)?.let { return it } + if (!areCompatibleTypeParameterUpperBounds(expectedTypeParameters, actualTypeParameters, substitutor)) { + return Incompatible.FunctionTypeParameterUpperBounds + } return null } @@ -399,7 +407,7 @@ object AbstractExpectActualCompatibilityChecker { return Incompatible.Visibility } - getTypeParametersWeakIncompatibility(expectedTypeParameters, actualTypeParameters)?.let { return it } + getTypeParametersVarianceOrReifiedIncompatibility(expectedTypeParameters, actualTypeParameters)?.let { return it } if (shouldCheckAbsenceOfDefaultParamsInActual) { // "Default parameters in actual" check is required only for functions, because only functions can have parameters @@ -552,22 +560,11 @@ object AbstractExpectActualCompatibilityChecker { } context(ExpectActualMatchingContext<*>) - private fun areCompatibleTypeParameters( + private fun areCompatibleTypeParameterUpperBounds( expectTypeParameterSymbols: List, actualTypeParameterSymbols: List, substitutor: TypeSubstitutorMarker, - ): ExpectActualCompatibility<*> = - // We must prioritize to return STRONG incompatible over WEAK incompatible (because STRONG incompatibility allows to search for overloads) - getTypeParametersStrongIncompatibility(expectTypeParameterSymbols, actualTypeParameterSymbols, substitutor) - ?: getTypeParametersWeakIncompatibility(expectTypeParameterSymbols, actualTypeParameterSymbols) - ?: ExpectActualCompatibility.Compatible - - context(ExpectActualMatchingContext<*>) - private fun getTypeParametersStrongIncompatibility( - expectTypeParameterSymbols: List, - actualTypeParameterSymbols: List, - substitutor: TypeSubstitutorMarker, - ): Incompatible.StrongIncompatible<*>? { + ): Boolean { for (i in expectTypeParameterSymbols.indices) { val expectBounds = expectTypeParameterSymbols[i].bounds val actualBounds = actualTypeParameterSymbols[i].bounds @@ -575,15 +572,15 @@ object AbstractExpectActualCompatibilityChecker { expectBounds.size != actualBounds.size || !areCompatibleTypeLists(expectBounds.map { substitutor.safeSubstitute(it) }, actualBounds) ) { - return Incompatible.TypeParameterUpperBounds + return false } } - return null + return true } context(ExpectActualMatchingContext<*>) - private fun getTypeParametersWeakIncompatibility( + private fun getTypeParametersVarianceOrReifiedIncompatibility( expectTypeParameterSymbols: List, actualTypeParameterSymbols: List, ): Incompatible.WeakIncompatible<*>? { diff --git a/compiler/testData/diagnostics/tests/multiplatform/java/implicitJavaActualization_multipleActuals.kt b/compiler/testData/diagnostics/tests/multiplatform/java/implicitJavaActualization_multipleActuals.kt index 0b506a57077..2a655f91f9d 100644 --- a/compiler/testData/diagnostics/tests/multiplatform/java/implicitJavaActualization_multipleActuals.kt +++ b/compiler/testData/diagnostics/tests/multiplatform/java/implicitJavaActualization_multipleActuals.kt @@ -1,7 +1,7 @@ // MODULE: m1-common // FILE: common.kt -expect class Foo(i: Int) { +expect class Foo(i: Int) { fun foo() } @@ -15,6 +15,6 @@ public class Foo { // FILE: jvm.kt -class Foo(t: T) { +class Foo(t: T) { fun foo() {} } diff --git a/compiler/testData/diagnostics/tests/multiplatform/kt54827.kt b/compiler/testData/diagnostics/tests/multiplatform/kt54827.kt index 66e0d6dc770..1070dd6d9bd 100644 --- a/compiler/testData/diagnostics/tests/multiplatform/kt54827.kt +++ b/compiler/testData/diagnostics/tests/multiplatform/kt54827.kt @@ -1,6 +1,6 @@ // MODULE: m1-common // FILE: common.kt -expect class SomeClass { +expect class SomeClass { fun foo() } diff --git a/compiler/testData/multiplatform/incompatibleClasses/output.txt b/compiler/testData/multiplatform/incompatibleClasses/output.txt index 9703ce14f65..bd83b800147 100644 --- a/compiler/testData/multiplatform/incompatibleClasses/output.txt +++ b/compiler/testData/multiplatform/incompatibleClasses/output.txt @@ -5,24 +5,6 @@ Output: -- JVM -- Exit code: COMPILATION_ERROR Output: -compiler/testData/multiplatform/incompatibleClasses/common.kt:14:16: error: expected class 'C1' has no actual declaration in module
for JVM -The following declaration is incompatible because number of type parameters is different: - public final actual class C1 - -expect class C1 - ^ -compiler/testData/multiplatform/incompatibleClasses/common.kt:16:16: error: expected class 'C3' has no actual declaration in module
for JVM -The following declaration is incompatible because upper bounds of type parameters are different: - public final actual class C3 - -expect class C3 - ^ -compiler/testData/multiplatform/incompatibleClasses/common.kt:18:16: error: expected class 'C4' has no actual declaration in module
for JVM -The following declaration is incompatible because upper bounds of type parameters are different: - public actual typealias C4 = C4Impl - -expect class C4 - ^ compiler/testData/multiplatform/incompatibleClasses/jvm.kt:1:8: error: actual interface 'PClass' has no corresponding expected declaration The following declaration is incompatible because class kinds are different (class, interface, object, enum, annotation): public final expect class PClass 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 8475338142a..1af72225fb8 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 @@ -25,7 +25,12 @@ sealed class ExpectActualCompatibility { object ParameterShape : StrongIncompatible("parameter shapes are different (extension vs non-extension)") object ParameterCount : StrongIncompatible("number of value parameters is different") - object TypeParameterCount : StrongIncompatible("number of type parameters is different") + + // FunctionTypeParameterCount is strong because functions can be overloaded by type parameter count + object FunctionTypeParameterCount : StrongIncompatible("number of type parameters is different") + + // ClassTypeParameterCount is weak because classes cannot be overloaded + object ClassTypeParameterCount : WeakIncompatible(FunctionTypeParameterCount.reason) object ParameterTypes : StrongIncompatible("parameter types are different") object ReturnType : StrongIncompatible("return type is different") @@ -79,7 +84,12 @@ sealed class ExpectActualCompatibility { object Modality : WeakIncompatible("modality is different") object Visibility : WeakIncompatible("visibility is different") - object TypeParameterUpperBounds : StrongIncompatible("upper bounds of type parameters are different") + // FunctionTypeParameterUpperBounds is weak because functions can be overloaded by type parameter upper bounds + object FunctionTypeParameterUpperBounds : StrongIncompatible("upper bounds of type parameters are different") + + // ClassTypeParameterUpperBounds is strong because classes cannot be overloaded + object ClassTypeParameterUpperBounds : WeakIncompatible(FunctionTypeParameterUpperBounds.reason) + object TypeParameterVariance : WeakIncompatible("declaration-site variances of type parameters are different") object TypeParameterReified : WeakIncompatible( "some type parameter is reified in one declaration and non-reified in the other"