[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:
Nikita Bobko
2023-08-08 12:55:43 +02:00
committed by teamcity
parent 98ec13b51a
commit d4758014ec
6 changed files with 52 additions and 60 deletions
@@ -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
@@ -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<*>? {
@@ -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
@@ -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"