[FE] Type-safety refactoring: Make areCompatibleClassScopes to return Incompatible.WeakIncompatible
Review: https://jetbrains.team/p/kt/reviews/11039/timeline For StrongIncompatible `actual` declaration is considered as overload and error reports on expected declaration. For WeakIncompatible the error is reported straight away Before the refactoring `areCompatibleClassScopes` returned just `Incompatible`. It is bad because StrongIncompatible isn't possible for classes (classes can't be overloaded). Now all class incompatibilities are weak. The commit has a minor impact on observable behavior (cases where we reported the compilation problems are still reported but on another elements): - We no longer report type parameter class incompatibilities on expect declaration, we report them only on actuals (it happened because all WeakIncompatible are reported only on actuals) - In a sense, Java implicit actualization was the only way to "overload" classes (it would be a redeclaration compilation problem, so it doesn't count as a valid "overload"). And since type parameters incompatibility was StrongIncompatible for classes, we counted them as "overloads" and didn't report incompatibility problems on Kotlin class. Now we do report. (see implicitJavaActualization_multipleActuals)
This commit is contained in:
+5
-2
@@ -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
|
||||
|
||||
+32
-35
@@ -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<Pair<DeclarationSymbolMarker, Map<Incompatible<*>, List<DeclarationSymbolMarker?>>>>()
|
||||
|
||||
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<TypeParameterSymbolMarker>,
|
||||
actualTypeParameterSymbols: List<TypeParameterSymbolMarker>,
|
||||
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<TypeParameterSymbolMarker>,
|
||||
actualTypeParameterSymbols: List<TypeParameterSymbolMarker>,
|
||||
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<TypeParameterSymbolMarker>,
|
||||
actualTypeParameterSymbols: List<TypeParameterSymbolMarker>,
|
||||
): Incompatible.WeakIncompatible<*>? {
|
||||
|
||||
Vendored
+2
-2
@@ -1,7 +1,7 @@
|
||||
// MODULE: m1-common
|
||||
// FILE: common.kt
|
||||
|
||||
expect class <!IMPLICIT_JVM_ACTUALIZATION{JVM}!>Foo<!>(i: Int) {
|
||||
expect class <!AMBIGUOUS_ACTUALS{JVM}, IMPLICIT_JVM_ACTUALIZATION{JVM}!>Foo<!>(i: Int) {
|
||||
fun <!AMBIGUOUS_ACTUALS{JVM}!>foo<!>()
|
||||
}
|
||||
|
||||
@@ -15,6 +15,6 @@ public class Foo {
|
||||
|
||||
// FILE: jvm.kt
|
||||
|
||||
class Foo<T>(t: T) {
|
||||
class <!ACTUAL_MISSING!>Foo<!><T>(t: T) {
|
||||
fun foo() {}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// MODULE: m1-common
|
||||
// FILE: common.kt
|
||||
expect class SomeClass<!NO_ACTUAL_FOR_EXPECT{JVM}!><T><!> {
|
||||
expect class SomeClass<T> {
|
||||
fun foo()
|
||||
}
|
||||
|
||||
|
||||
@@ -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 <main> for JVM
|
||||
The following declaration is incompatible because number of type parameters is different:
|
||||
public final actual class C1<A, Extra>
|
||||
|
||||
expect class C1<A>
|
||||
^
|
||||
compiler/testData/multiplatform/incompatibleClasses/common.kt:16:16: error: expected class 'C3' has no actual declaration in module <main> for JVM
|
||||
The following declaration is incompatible because upper bounds of type parameters are different:
|
||||
public final actual class C3<D, E : D?>
|
||||
|
||||
expect class C3<D, E : D>
|
||||
^
|
||||
compiler/testData/multiplatform/incompatibleClasses/common.kt:18:16: error: expected class 'C4' has no actual declaration in module <main> for JVM
|
||||
The following declaration is incompatible because upper bounds of type parameters are different:
|
||||
public actual typealias C4<F> = C4Impl<F>
|
||||
|
||||
expect class C4<F>
|
||||
^
|
||||
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
|
||||
|
||||
+12
-2
@@ -25,7 +25,12 @@ sealed class ExpectActualCompatibility<out D> {
|
||||
object ParameterShape : StrongIncompatible<Nothing>("parameter shapes are different (extension vs non-extension)")
|
||||
|
||||
object ParameterCount : StrongIncompatible<Nothing>("number of value parameters is different")
|
||||
object TypeParameterCount : StrongIncompatible<Nothing>("number of type parameters is different")
|
||||
|
||||
// FunctionTypeParameterCount is strong because functions can be overloaded by type parameter count
|
||||
object FunctionTypeParameterCount : StrongIncompatible<Nothing>("number of type parameters is different")
|
||||
|
||||
// ClassTypeParameterCount is weak because classes cannot be overloaded
|
||||
object ClassTypeParameterCount : WeakIncompatible<Nothing>(FunctionTypeParameterCount.reason)
|
||||
|
||||
object ParameterTypes : StrongIncompatible<Nothing>("parameter types are different")
|
||||
object ReturnType : StrongIncompatible<Nothing>("return type is different")
|
||||
@@ -79,7 +84,12 @@ sealed class ExpectActualCompatibility<out D> {
|
||||
object Modality : WeakIncompatible<Nothing>("modality is different")
|
||||
object Visibility : WeakIncompatible<Nothing>("visibility is different")
|
||||
|
||||
object TypeParameterUpperBounds : StrongIncompatible<Nothing>("upper bounds of type parameters are different")
|
||||
// FunctionTypeParameterUpperBounds is weak because functions can be overloaded by type parameter upper bounds
|
||||
object FunctionTypeParameterUpperBounds : StrongIncompatible<Nothing>("upper bounds of type parameters are different")
|
||||
|
||||
// ClassTypeParameterUpperBounds is strong because classes cannot be overloaded
|
||||
object ClassTypeParameterUpperBounds : WeakIncompatible<Nothing>(FunctionTypeParameterUpperBounds.reason)
|
||||
|
||||
object TypeParameterVariance : WeakIncompatible<Nothing>("declaration-site variances of type parameters are different")
|
||||
object TypeParameterReified : WeakIncompatible<Nothing>(
|
||||
"some type parameter is reified in one declaration and non-reified in the other"
|
||||
|
||||
Reference in New Issue
Block a user