[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
|
endElement
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ExpectActualCompatibility.Incompatible.TypeParameterNames, ExpectActualCompatibility.Incompatible.TypeParameterCount,
|
ExpectActualCompatibility.Incompatible.TypeParameterNames,
|
||||||
ExpectActualCompatibility.Incompatible.TypeParameterUpperBounds,
|
ExpectActualCompatibility.Incompatible.FunctionTypeParameterCount,
|
||||||
|
ExpectActualCompatibility.Incompatible.ClassTypeParameterCount,
|
||||||
|
ExpectActualCompatibility.Incompatible.FunctionTypeParameterUpperBounds,
|
||||||
|
ExpectActualCompatibility.Incompatible.ClassTypeParameterUpperBounds,
|
||||||
ExpectActualCompatibility.Incompatible.TypeParameterVariance,
|
ExpectActualCompatibility.Incompatible.TypeParameterVariance,
|
||||||
ExpectActualCompatibility.Incompatible.TypeParameterReified -> {
|
ExpectActualCompatibility.Incompatible.TypeParameterReified -> {
|
||||||
(element as? KtTypeParameterListOwner)?.typeParameterList
|
(element as? KtTypeParameterListOwner)?.typeParameterList
|
||||||
|
|||||||
+32
-35
@@ -70,10 +70,19 @@ object AbstractExpectActualCompatibilityChecker {
|
|||||||
context(ExpectActualMatchingContext<*>)
|
context(ExpectActualMatchingContext<*>)
|
||||||
@Suppress("warnings")
|
@Suppress("warnings")
|
||||||
private fun areCompatibleClassifiers(
|
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,
|
expectClassSymbol: RegularClassSymbolMarker,
|
||||||
actualClassLikeSymbol: ClassLikeSymbolMarker,
|
actualClassLikeSymbol: ClassLikeSymbolMarker,
|
||||||
parentSubstitutor: TypeSubstitutorMarker?
|
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
|
// 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) {
|
require(expectClassSymbol.name == actualClassLikeSymbol.name) {
|
||||||
"This function should be invoked only for declarations with the same name: $expectClassSymbol, $actualClassLikeSymbol"
|
"This function should be invoked only for declarations with the same name: $expectClassSymbol, $actualClassLikeSymbol"
|
||||||
@@ -82,7 +91,7 @@ object AbstractExpectActualCompatibilityChecker {
|
|||||||
val actualClass = when (actualClassLikeSymbol) {
|
val actualClass = when (actualClassLikeSymbol) {
|
||||||
is RegularClassSymbolMarker -> actualClassLikeSymbol
|
is RegularClassSymbolMarker -> actualClassLikeSymbol
|
||||||
is TypeAliasSymbolMarker -> actualClassLikeSymbol.expandToRegularClass()
|
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")
|
else -> error("Incorrect actual classifier for $expectClassSymbol: $actualClassLikeSymbol")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -99,7 +108,7 @@ object AbstractExpectActualCompatibilityChecker {
|
|||||||
val expectTypeParameterSymbols = expectClassSymbol.typeParameters
|
val expectTypeParameterSymbols = expectClassSymbol.typeParameters
|
||||||
val actualTypeParameterSymbols = actualClass.typeParameters
|
val actualTypeParameterSymbols = actualClass.typeParameters
|
||||||
if (expectTypeParameterSymbols.size != actualTypeParameterSymbols.size) {
|
if (expectTypeParameterSymbols.size != actualTypeParameterSymbols.size) {
|
||||||
return Incompatible.TypeParameterCount
|
return Incompatible.ClassTypeParameterCount
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!areCompatibleModalities(expectClassSymbol.modality, actualClass.modality)) {
|
if (!areCompatibleModalities(expectClassSymbol.modality, actualClass.modality)) {
|
||||||
@@ -116,23 +125,20 @@ object AbstractExpectActualCompatibilityChecker {
|
|||||||
parentSubstitutor
|
parentSubstitutor
|
||||||
)
|
)
|
||||||
|
|
||||||
areCompatibleTypeParameters(expectTypeParameterSymbols, actualTypeParameterSymbols, substitutor).let {
|
if (!areCompatibleTypeParameterUpperBounds(expectTypeParameterSymbols, actualTypeParameterSymbols, substitutor)) {
|
||||||
if (it != ExpectActualCompatibility.Compatible) {
|
return Incompatible.ClassTypeParameterUpperBounds
|
||||||
return it
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getTypeParametersVarianceOrReifiedIncompatibility(expectTypeParameterSymbols, actualTypeParameterSymbols)
|
||||||
|
?.let { return it }
|
||||||
|
|
||||||
if (!areCompatibleSupertypes(expectClassSymbol, actualClass, substitutor)) {
|
if (!areCompatibleSupertypes(expectClassSymbol, actualClass, substitutor)) {
|
||||||
return Incompatible.Supertypes
|
return Incompatible.Supertypes
|
||||||
}
|
}
|
||||||
|
|
||||||
areCompatibleClassScopes(expectClassSymbol, actualClass, substitutor).let {
|
getClassScopesIncompatibility(expectClassSymbol, actualClass, substitutor)?.let { return it }
|
||||||
if (it != ExpectActualCompatibility.Compatible) {
|
|
||||||
return it
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return ExpectActualCompatibility.Compatible
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
context(ExpectActualMatchingContext<*>)
|
context(ExpectActualMatchingContext<*>)
|
||||||
@@ -182,11 +188,11 @@ object AbstractExpectActualCompatibilityChecker {
|
|||||||
}
|
}
|
||||||
|
|
||||||
context(ExpectActualMatchingContext<*>)
|
context(ExpectActualMatchingContext<*>)
|
||||||
private fun areCompatibleClassScopes(
|
private fun getClassScopesIncompatibility(
|
||||||
expectClassSymbol: RegularClassSymbolMarker,
|
expectClassSymbol: RegularClassSymbolMarker,
|
||||||
actualClassSymbol: RegularClassSymbolMarker,
|
actualClassSymbol: RegularClassSymbolMarker,
|
||||||
substitutor: TypeSubstitutorMarker,
|
substitutor: TypeSubstitutorMarker,
|
||||||
): ExpectActualCompatibility<*> {
|
): Incompatible.WeakIncompatible<*>? {
|
||||||
val unfulfilled = arrayListOf<Pair<DeclarationSymbolMarker, Map<Incompatible<*>, List<DeclarationSymbolMarker?>>>>()
|
val unfulfilled = arrayListOf<Pair<DeclarationSymbolMarker, Map<Incompatible<*>, List<DeclarationSymbolMarker?>>>>()
|
||||||
|
|
||||||
val actualMembersByName = actualClassSymbol.collectAllMembers(isActualDeclaration = true).groupBy { it.name }
|
val actualMembersByName = actualClassSymbol.collectAllMembers(isActualDeclaration = true).groupBy { it.name }
|
||||||
@@ -218,7 +224,7 @@ object AbstractExpectActualCompatibilityChecker {
|
|||||||
|
|
||||||
// TODO: check static scope?
|
// TODO: check static scope?
|
||||||
|
|
||||||
if (unfulfilled.isEmpty()) return ExpectActualCompatibility.Compatible
|
if (unfulfilled.isEmpty()) return null
|
||||||
|
|
||||||
return Incompatible.ClassScopes(unfulfilled)
|
return Incompatible.ClassScopes(unfulfilled)
|
||||||
}
|
}
|
||||||
@@ -329,7 +335,7 @@ object AbstractExpectActualCompatibilityChecker {
|
|||||||
val expectedTypeParameters = expectDeclaration.typeParameters
|
val expectedTypeParameters = expectDeclaration.typeParameters
|
||||||
val actualTypeParameters = actualDeclaration.typeParameters
|
val actualTypeParameters = actualDeclaration.typeParameters
|
||||||
if (expectedTypeParameters.size != actualTypeParameters.size) {
|
if (expectedTypeParameters.size != actualTypeParameters.size) {
|
||||||
return Incompatible.TypeParameterCount
|
return Incompatible.FunctionTypeParameterCount
|
||||||
}
|
}
|
||||||
|
|
||||||
val substitutor = createExpectActualTypeParameterSubstitutor(
|
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
|
return null
|
||||||
}
|
}
|
||||||
@@ -399,7 +407,7 @@ object AbstractExpectActualCompatibilityChecker {
|
|||||||
return Incompatible.Visibility
|
return Incompatible.Visibility
|
||||||
}
|
}
|
||||||
|
|
||||||
getTypeParametersWeakIncompatibility(expectedTypeParameters, actualTypeParameters)?.let { return it }
|
getTypeParametersVarianceOrReifiedIncompatibility(expectedTypeParameters, actualTypeParameters)?.let { return it }
|
||||||
|
|
||||||
if (shouldCheckAbsenceOfDefaultParamsInActual) {
|
if (shouldCheckAbsenceOfDefaultParamsInActual) {
|
||||||
// "Default parameters in actual" check is required only for functions, because only functions can have parameters
|
// "Default parameters in actual" check is required only for functions, because only functions can have parameters
|
||||||
@@ -552,22 +560,11 @@ object AbstractExpectActualCompatibilityChecker {
|
|||||||
}
|
}
|
||||||
|
|
||||||
context(ExpectActualMatchingContext<*>)
|
context(ExpectActualMatchingContext<*>)
|
||||||
private fun areCompatibleTypeParameters(
|
private fun areCompatibleTypeParameterUpperBounds(
|
||||||
expectTypeParameterSymbols: List<TypeParameterSymbolMarker>,
|
expectTypeParameterSymbols: List<TypeParameterSymbolMarker>,
|
||||||
actualTypeParameterSymbols: List<TypeParameterSymbolMarker>,
|
actualTypeParameterSymbols: List<TypeParameterSymbolMarker>,
|
||||||
substitutor: TypeSubstitutorMarker,
|
substitutor: TypeSubstitutorMarker,
|
||||||
): ExpectActualCompatibility<*> =
|
): Boolean {
|
||||||
// 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<*>? {
|
|
||||||
for (i in expectTypeParameterSymbols.indices) {
|
for (i in expectTypeParameterSymbols.indices) {
|
||||||
val expectBounds = expectTypeParameterSymbols[i].bounds
|
val expectBounds = expectTypeParameterSymbols[i].bounds
|
||||||
val actualBounds = actualTypeParameterSymbols[i].bounds
|
val actualBounds = actualTypeParameterSymbols[i].bounds
|
||||||
@@ -575,15 +572,15 @@ object AbstractExpectActualCompatibilityChecker {
|
|||||||
expectBounds.size != actualBounds.size ||
|
expectBounds.size != actualBounds.size ||
|
||||||
!areCompatibleTypeLists(expectBounds.map { substitutor.safeSubstitute(it) }, actualBounds)
|
!areCompatibleTypeLists(expectBounds.map { substitutor.safeSubstitute(it) }, actualBounds)
|
||||||
) {
|
) {
|
||||||
return Incompatible.TypeParameterUpperBounds
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return null
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
context(ExpectActualMatchingContext<*>)
|
context(ExpectActualMatchingContext<*>)
|
||||||
private fun getTypeParametersWeakIncompatibility(
|
private fun getTypeParametersVarianceOrReifiedIncompatibility(
|
||||||
expectTypeParameterSymbols: List<TypeParameterSymbolMarker>,
|
expectTypeParameterSymbols: List<TypeParameterSymbolMarker>,
|
||||||
actualTypeParameterSymbols: List<TypeParameterSymbolMarker>,
|
actualTypeParameterSymbols: List<TypeParameterSymbolMarker>,
|
||||||
): Incompatible.WeakIncompatible<*>? {
|
): Incompatible.WeakIncompatible<*>? {
|
||||||
|
|||||||
Vendored
+2
-2
@@ -1,7 +1,7 @@
|
|||||||
// MODULE: m1-common
|
// MODULE: m1-common
|
||||||
// FILE: common.kt
|
// 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<!>()
|
fun <!AMBIGUOUS_ACTUALS{JVM}!>foo<!>()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -15,6 +15,6 @@ public class Foo {
|
|||||||
|
|
||||||
// FILE: jvm.kt
|
// FILE: jvm.kt
|
||||||
|
|
||||||
class Foo<T>(t: T) {
|
class <!ACTUAL_MISSING!>Foo<!><T>(t: T) {
|
||||||
fun foo() {}
|
fun foo() {}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
// MODULE: m1-common
|
// MODULE: m1-common
|
||||||
// FILE: common.kt
|
// FILE: common.kt
|
||||||
expect class SomeClass<!NO_ACTUAL_FOR_EXPECT{JVM}!><T><!> {
|
expect class SomeClass<T> {
|
||||||
fun foo()
|
fun foo()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,24 +5,6 @@ Output:
|
|||||||
-- JVM --
|
-- JVM --
|
||||||
Exit code: COMPILATION_ERROR
|
Exit code: COMPILATION_ERROR
|
||||||
Output:
|
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
|
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):
|
The following declaration is incompatible because class kinds are different (class, interface, object, enum, annotation):
|
||||||
public final expect class PClass
|
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 ParameterShape : StrongIncompatible<Nothing>("parameter shapes are different (extension vs non-extension)")
|
||||||
|
|
||||||
object ParameterCount : StrongIncompatible<Nothing>("number of value parameters is different")
|
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 ParameterTypes : StrongIncompatible<Nothing>("parameter types are different")
|
||||||
object ReturnType : StrongIncompatible<Nothing>("return type is 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 Modality : WeakIncompatible<Nothing>("modality is different")
|
||||||
object Visibility : WeakIncompatible<Nothing>("visibility 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 TypeParameterVariance : WeakIncompatible<Nothing>("declaration-site variances of type parameters are different")
|
||||||
object TypeParameterReified : WeakIncompatible<Nothing>(
|
object TypeParameterReified : WeakIncompatible<Nothing>(
|
||||||
"some type parameter is reified in one declaration and non-reified in the other"
|
"some type parameter is reified in one declaration and non-reified in the other"
|
||||||
|
|||||||
Reference in New Issue
Block a user