ActualClassifierMustHasTheSameMembersAsNonFinalExpectClassifierChecker: reuse "expect-actual" matcher

In scope of: KT-22841
Review: https://jetbrains.team/p/kt/reviews/11867/timeline

Reduce complexity by reusing "expect-actual matcher" (namely
`AbstractExpectActualCompatibilityChecker.getCallablesCompatibility`)

The current solution has worse algorithmic complexity. Previously it was
O(n) in the best case, where `n` is a number of members. Now, it's
O(m^2), where `m` is number of members in one overload group. But we
prefer to have worse complexity but reuse expect-actual matcher, number
of elements in one overload group shall not be big on real world
examples.

The previous logic was non-trivial because it compared types with with
double comparison in `equals`.
This commit is contained in:
Nikita Bobko
2023-08-24 12:35:56 +02:00
committed by teamcity
parent 17089276a7
commit 3722f4d7d6
13 changed files with 123 additions and 228 deletions
@@ -1297,9 +1297,15 @@ public class FirOldFrontendMPPDiagnosticsWithLightTreeTestGenerated extends Abst
}
@Test
@TestMetadata("morePermissiveVisibilityOnActualViaTypeAlias.kt")
public void testMorePermissiveVisibilityOnActualViaTypeAlias() throws Exception {
runTest("compiler/testData/diagnostics/tests/multiplatform/headerClass/morePermissiveVisibilityOnActualViaTypeAlias.kt");
@TestMetadata("morePermissiveVisibilityOnActualViaTypeAlias_finalMember.kt")
public void testMorePermissiveVisibilityOnActualViaTypeAlias_finalMember() throws Exception {
runTest("compiler/testData/diagnostics/tests/multiplatform/headerClass/morePermissiveVisibilityOnActualViaTypeAlias_finalMember.kt");
}
@Test
@TestMetadata("morePermissiveVisibilityOnActualViaTypeAlias_openMember.kt")
public void testMorePermissiveVisibilityOnActualViaTypeAlias_openMember() throws Exception {
runTest("compiler/testData/diagnostics/tests/multiplatform/headerClass/morePermissiveVisibilityOnActualViaTypeAlias_openMember.kt");
}
@Test
@@ -1297,9 +1297,15 @@ public class FirOldFrontendMPPDiagnosticsWithPsiTestGenerated extends AbstractFi
}
@Test
@TestMetadata("morePermissiveVisibilityOnActualViaTypeAlias.kt")
public void testMorePermissiveVisibilityOnActualViaTypeAlias() throws Exception {
runTest("compiler/testData/diagnostics/tests/multiplatform/headerClass/morePermissiveVisibilityOnActualViaTypeAlias.kt");
@TestMetadata("morePermissiveVisibilityOnActualViaTypeAlias_finalMember.kt")
public void testMorePermissiveVisibilityOnActualViaTypeAlias_finalMember() throws Exception {
runTest("compiler/testData/diagnostics/tests/multiplatform/headerClass/morePermissiveVisibilityOnActualViaTypeAlias_finalMember.kt");
}
@Test
@TestMetadata("morePermissiveVisibilityOnActualViaTypeAlias_openMember.kt")
public void testMorePermissiveVisibilityOnActualViaTypeAlias_openMember() throws Exception {
runTest("compiler/testData/diagnostics/tests/multiplatform/headerClass/morePermissiveVisibilityOnActualViaTypeAlias_openMember.kt");
}
@Test
@@ -10,16 +10,16 @@ import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.diagnostics.Errors
import org.jetbrains.kotlin.incremental.components.NoLookupLocation
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.resolve.BindingTrace
import org.jetbrains.kotlin.resolve.descriptorUtil.*
import org.jetbrains.kotlin.resolve.calls.mpp.AbstractExpectActualCompatibilityChecker
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe
import org.jetbrains.kotlin.resolve.descriptorUtil.getSuperClassNotAny
import org.jetbrains.kotlin.resolve.descriptorUtil.getSuperInterfaces
import org.jetbrains.kotlin.resolve.descriptorUtil.module
import org.jetbrains.kotlin.resolve.multiplatform.*
import org.jetbrains.kotlin.resolve.scopes.MemberScope
import org.jetbrains.kotlin.resolve.source.KotlinSourceElement
import org.jetbrains.kotlin.types.KotlinType
import org.jetbrains.kotlin.types.model.TypeSubstitutorMarker
import java.util.*
import kotlin.contracts.ExperimentalContracts
import kotlin.contracts.contract
@@ -142,198 +142,71 @@ private fun calculateExpectActualScopeDiff(
parentSubstitutor = null
)
val expectClassCallables = expect.unsubstitutedMemberScope
.extractNonPrivateCallables(classTypeSubstitutor, ExpectActual.EXPECT, matchingContext)
val actualClassCallables = actual.unsubstitutedMemberScope
.extractNonPrivateCallables(classTypeSubstitutor, ExpectActual.ACTUAL, matchingContext)
.filter { it.descriptor.kind.isReal } // Filter out fake-overrides from actual because we compare list of supertypes separately anyway
val expectClassCallables = expect.unsubstitutedMemberScope.extractNonPrivateCallables()
val actualClassCallables = actual.unsubstitutedMemberScope.extractNonPrivateCallables()
.filter { it.kind.isReal } // Filter out fake-overrides from actual because we compare list of supertypes separately anyway
val nameAndKindToExpectCallable = expectClassCallables.groupBy { it.name to it.kind }
val nameAndKindToExpectCallables = expectClassCallables.groupBy { it.name to it.functionVsPropertyKind }
return (actualClassCallables - expectClassCallables).asSequence()
.flatMap { unmatchedActualCallable ->
when (val expectCallablesWithTheSameNameAndKind =
nameAndKindToExpectCallable[unmatchedActualCallable.name to unmatchedActualCallable.kind]) {
null -> listOf(ExpectActualMemberDiff.Kind.NonPrivateCallableAdded)
else -> expectCallablesWithTheSameNameAndKind.map {
calculateExpectActualMemberDiffKind(
expect = it,
actual = unmatchedActualCallable,
checkParameterNames = unmatchedActualCallable.descriptor.hasStableParameterNames()
return actualClassCallables.flatMap { actualMember ->
val potentialExpects = nameAndKindToExpectCallables[actualMember.name to actualMember.functionVsPropertyKind]
if (potentialExpects.isNullOrEmpty()) {
listOf(ExpectActualMemberDiff.Kind.NonPrivateCallableAdded)
} else {
potentialExpects
.map { expectMember ->
val substitutor = matchingContext.createExpectActualTypeParameterSubstitutor(
expectMember.typeParameters,
actualMember.typeParameters,
classTypeSubstitutor
)
AbstractExpectActualCompatibilityChecker.getCallablesCompatibility(
expectMember,
actualMember,
substitutor,
expect,
actual,
matchingContext
)
}
}.filterNotNull().map { kind -> ExpectActualMemberDiff(kind, unmatchedActualCallable.descriptor, expect) }
.takeIf { kinds -> kinds.all { it != ExpectActualCompatibility.Compatible } }
.orEmpty()
.map {
when (it) {
is ExpectActualCompatibility.Compatible -> error("Compatible was filtered out by takeIf")
is ExpectActualCompatibility.Incompatible -> it.toMemberDiffKind()
// If toMemberDiffKind returns null then some Kotlin invariants described in toMemberDiffKind no longer hold.
// We can't throw exception here because it would crash the compilation.
// Those broken invariants just needs to be reported by other checkers.
// But it's better to report some error (ExpectActualMemberDiff.Kind.NonPrivateCallableAdded in our case) to
// make sure that we don't have missed compilation errors if the invariants change
?: ExpectActualMemberDiff.Kind.NonPrivateCallableAdded
}
}
}
.toSet()
.map { kind -> ExpectActualMemberDiff(kind, actualMember, expect) }
}.toSet()
}
private fun MemberScope.extractNonPrivateCallables(
classTypeSubstitutor: TypeSubstitutorMarker,
expectActual: ExpectActual,
matchingContext: ClassicExpectActualMatchingContext,
): Set<Callable> {
private fun MemberScope.extractNonPrivateCallables(): Sequence<CallableMemberDescriptor> {
val functions =
getFunctionNames().asSequence().flatMap { getContributedFunctions(it, NoLookupLocation.WHEN_GET_ALL_DESCRIPTORS) }
val properties =
getVariableNames().asSequence().flatMap { getContributedVariables(it, NoLookupLocation.WHEN_GET_ALL_DESCRIPTORS) }
return (functions + properties).filter { !Visibilities.isPrivate(it.visibility.delegate) }
.map { descriptor -> Callable(descriptor, expectActual, classTypeSubstitutor, matchingContext) }
.toSet()
}
private data class Parameter(val name: Name, val type: KotlinType)
private enum class Kind { FUNCTION, PROPERTY }
private enum class ExpectActual { EXPECT, ACTUAL }
private data class TypeParameter(val name: Name, val upperBounds: List<KotlinType>)
private class Callable(
val descriptor: CallableMemberDescriptor,
val expectActual: ExpectActual,
val classTypeSubstitutor: TypeSubstitutorMarker,
val matchingContext: ClassicExpectActualMatchingContext,
) {
val name: Name = descriptor.name
val kind: Kind = when (descriptor) {
private val CallableMemberDescriptor.functionVsPropertyKind: Kind
get() = when (this) {
is PropertyDescriptor -> Kind.PROPERTY
is FunctionDescriptor -> Kind.FUNCTION
else -> error("Unknown kind $descriptor")
else -> error("Unknown kind $this")
}
val isVarProperty: Boolean = descriptor is PropertyDescriptor && descriptor.isVar
val isLateinitProperty: Boolean = descriptor is PropertyDescriptor && descriptor.isLateInit
val modality: Modality = descriptor.modality
val visibility: Visibility = descriptor.visibility.delegate
val setterVisibility: Visibility? = (descriptor as? PropertyDescriptor)?.setter?.visibility?.delegate
val parameters: List<Parameter> = descriptor.valueParameters.map { Parameter(it.name, it.type) }
val returnType: KotlinType = descriptor.returnType ?: error("Can't get return type")
val extensionReceiverType: KotlinType? = descriptor.extensionReceiverParameter?.type
val typeParameters: List<TypeParameter> = descriptor.typeParameters.map { TypeParameter(it.name, it.upperBounds) }
val contextReceiverTypes: List<KotlinType> = descriptor.contextReceiverParameters.map(ValueDescriptor::getType)
override fun equals(other: Any?): Boolean {
if (other !is Callable) return false
check(classTypeSubstitutor === other.classTypeSubstitutor)
check(matchingContext === other.matchingContext)
return if (expectActual == other.expectActual) {
name == other.name &&
kind == other.kind &&
isVarProperty == other.isVarProperty &&
isLateinitProperty == other.isLateinitProperty &&
modality == other.modality &&
visibility == other.visibility &&
setterVisibility == other.setterVisibility &&
parameters == other.parameters &&
returnType == other.returnType &&
extensionReceiverType == other.extensionReceiverType &&
typeParameters == other.typeParameters &&
contextReceiverTypes == other.contextReceiverTypes
} else {
val (expect, actual) = if (expectActual == ExpectActual.EXPECT) this to other else other to this
calculateExpectActualMemberDiffKind(expect, actual) == null
}
}
override fun hashCode(): Int = // Don't hash the types because type comparison is complicated
Objects.hash(
name,
kind,
isVarProperty,
isLateinitProperty,
modality,
visibility,
setterVisibility,
parameters.map(Parameter::name),
extensionReceiverType != null,
typeParameters.map(TypeParameter::name),
contextReceiverTypes.size,
)
}
private val CallableMemberDescriptor.psiIfReal: KtCallableDeclaration?
get() = takeIf { it.kind.isReal }?.source?.let { it as? KotlinSourceElement }?.psi as? KtCallableDeclaration
private fun ClassicExpectActualMatchingContext.areCompatibleWithSubstitution(
expect: KotlinType?,
actual: KotlinType?,
substitutor: TypeSubstitutorMarker,
): Boolean = areCompatibleExpectActualTypes(expect?.let { substitutor.safeSubstitute(it) }, actual)
private fun ClassicExpectActualMatchingContext.areCompatibleListWithSubstitution(
expect: List<KotlinType>,
actual: List<KotlinType>,
substitutor: TypeSubstitutorMarker,
): Boolean = expect.size == actual.size && expect.asSequence().zip(actual.asSequence())
.all { (a, b) -> areCompatibleWithSubstitution(a, b, substitutor) }
private fun ClassicExpectActualMatchingContext.areCompatibleUpperBoundsWithSubstitution(
expect: List<List<KotlinType>>,
actual: List<List<KotlinType>>,
substitutor: TypeSubstitutorMarker,
): Boolean = expect.size == actual.size && expect.asSequence().zip(actual.asSequence())
.all { (a, b) -> areCompatibleListWithSubstitution(a, b, substitutor) }
private fun calculateExpectActualMemberDiffKind(
expect: Callable,
actual: Callable,
checkParameterNames: Boolean = true,
): ExpectActualMemberDiff.Kind? {
check(expect.expectActual == ExpectActual.EXPECT)
check(actual.expectActual == ExpectActual.ACTUAL)
check(expect.classTypeSubstitutor === actual.classTypeSubstitutor)
check(expect.matchingContext === actual.matchingContext)
val substitutor = actual.matchingContext.createExpectActualTypeParameterSubstitutor(
expect.descriptor.typeParameters,
actual.descriptor.typeParameters,
actual.classTypeSubstitutor
)
with(actual.matchingContext) {
return when {
expect.name != actual.name ||
expect.kind != actual.kind ||
!areCompatibleListWithSubstitution(
expect.parameters.map(Parameter::type),
actual.parameters.map(Parameter::type),
substitutor
) ||
!areCompatibleUpperBoundsWithSubstitution(
expect.typeParameters.map(TypeParameter::upperBounds),
actual.typeParameters.map(TypeParameter::upperBounds),
substitutor
) ||
!areCompatibleWithSubstitution(
expect.extensionReceiverType,
actual.extensionReceiverType,
substitutor
) ||
!areCompatibleListWithSubstitution(
expect.contextReceiverTypes,
actual.contextReceiverTypes,
substitutor
) ->
ExpectActualMemberDiff.Kind.NonPrivateCallableAdded
expect.isVarProperty != actual.isVarProperty -> ExpectActualMemberDiff.Kind.PropertyKindChangedInOverride
expect.isLateinitProperty != actual.isLateinitProperty -> ExpectActualMemberDiff.Kind.LateinitChangedInOverride
expect.modality != actual.modality -> ExpectActualMemberDiff.Kind.ModalityChangedInOverride
expect.visibility != actual.visibility -> ExpectActualMemberDiff.Kind.VisibilityChangedInOverride
expect.setterVisibility != actual.setterVisibility -> ExpectActualMemberDiff.Kind.SetterVisibilityChangedInOverride
checkParameterNames && expect.parameters.map(Parameter::name) != actual.parameters.map(Parameter::name) ->
ExpectActualMemberDiff.Kind.ParameterNameChangedInOverride
expect.typeParameters.map(TypeParameter::name) != actual.typeParameters.map(TypeParameter::name) ->
ExpectActualMemberDiff.Kind.TypeParameterNamesChangedInOverride
!areCompatibleWithSubstitution(expect.returnType, actual.returnType, substitutor) ->
ExpectActualMemberDiff.Kind.ReturnTypeChangedInOverride
else -> null
}
}
}
private fun BindingTrace.reportIfPossible(diff: ExpectActualMemberDiff<CallableMemberDescriptor, ClassDescriptor>) {
val psi = diff.actualMember.psiIfReal ?: return
val diagnostic = when (diff.kind) {
@@ -1,16 +0,0 @@
// MODULE: m1-common
// FILE: common.kt
expect open class Foo {
fun foo()
}
// MODULE: m2-jvm()()(m1-common)
// FILE: jvm.kt
actual open class Foo {
// Hypothetically, it's more restricting than necessary. I can't see how actualizing final -> open can breaking anything.
// But technically, actual and expect scopes don't match
actual open fun foo() {
}
}
@@ -1,3 +1,4 @@
// FIR_IDENTICAL
// MODULE: m1-common
// FILE: common.kt
@@ -8,9 +9,8 @@ expect open class Foo {
// MODULE: m2-jvm()()(m1-common)
// FILE: jvm.kt
actual open <!ACTUAL_CLASSIFIER_MUST_HAVE_THE_SAME_MEMBERS_AS_NON_FINAL_EXPECT_CLASSIFIER!>class Foo<!> {
// Hypothetically, it's more restricting than necessary. I can't see how actualizing final -> open can breaking anything.
// But technically, actual and expect scopes don't match
actual <!MODALITY_CHANGED_IN_NON_FINAL_EXPECT_CLASSIFIER_ACTUALIZATION!>open<!> fun foo() {
actual open class Foo {
// final -> open is a legal modality change
actual open fun foo() {
}
}
@@ -12,6 +12,8 @@ expect open class Foo {
actual open class Foo {
actual fun foo() {}
// Expected: NON_ACTUAL_MEMBER_DECLARED_IN_EXPECT_NON_FINAL_CLASSIFIER_ACTUALIZATION.
// But it doesn't work because context receivers are not yet supported in expect actual matcher KT-61447
context(Int)
fun foo() {} // accidential override can happen with this injected fun. That's why it's prohibited
fun foo() {}
}
@@ -9,9 +9,11 @@ expect open class Foo {
// MODULE: m2-jvm()()(m1-common)
// FILE: jvm.kt
actual open <!ACTUAL_CLASSIFIER_MUST_HAVE_THE_SAME_MEMBERS_AS_NON_FINAL_EXPECT_CLASSIFIER!>class Foo<!> {
actual open class Foo {
actual fun foo() {}
// Expected: NON_ACTUAL_MEMBER_DECLARED_IN_EXPECT_NON_FINAL_CLASSIFIER_ACTUALIZATION.
// But it doesn't work because context receivers are not yet supported in expect actual matcher KT-61447
context(Int)
fun <!ACTUAL_MISSING, NON_ACTUAL_MEMBER_DECLARED_IN_EXPECT_NON_FINAL_CLASSIFIER_ACTUALIZATION!>foo<!>() {} // accidential override can happen with this injected fun. That's why it's prohibited
fun <!ACTUAL_MISSING!>foo<!>() {}
}
@@ -0,0 +1,21 @@
// !DIAGNOSTICS: -UNUSED_PARAMETER
// MODULE: m1-common
// FILE: common.kt
<!INCOMPATIBLE_MATCHING{JVM}!>expect open class Container {
<!INCOMPATIBLE_MATCHING{JVM}!>internal open fun internalFun()<!>
}<!>
// MODULE: m2-jvm()()(m1-common)
// FILE: foo/Foo.java
package foo;
public class Foo {
public void internalFun() {}
}
// FILE: jvm.kt
actual typealias <!NO_ACTUAL_CLASS_MEMBER_FOR_EXPECTED_CLASS!>Container<!> = foo.Foo
@@ -3,7 +3,7 @@
// FILE: common.kt
expect open class Container {
internal fun internalFun()
internal open fun internalFun()
}
// MODULE: m2-jvm()()(m1-common)
@@ -13,9 +13,9 @@ expect open class Container {
package foo;
public class Foo {
public final void internalFun() {}
public void internalFun() {}
}
// FILE: jvm.kt
actual typealias <!ACTUAL_CLASSIFIER_MUST_HAVE_THE_SAME_MEMBERS_AS_NON_FINAL_EXPECT_CLASSIFIER!>Container<!> = foo.Foo
actual typealias <!NO_ACTUAL_CLASS_MEMBER_FOR_EXPECTED_CLASS!>Container<!> = foo.Foo
@@ -24166,9 +24166,15 @@ public class DiagnosticTestGenerated extends AbstractDiagnosticTest {
}
@Test
@TestMetadata("morePermissiveVisibilityOnActualViaTypeAlias.kt")
public void testMorePermissiveVisibilityOnActualViaTypeAlias() throws Exception {
runTest("compiler/testData/diagnostics/tests/multiplatform/headerClass/morePermissiveVisibilityOnActualViaTypeAlias.kt");
@TestMetadata("morePermissiveVisibilityOnActualViaTypeAlias_finalMember.kt")
public void testMorePermissiveVisibilityOnActualViaTypeAlias_finalMember() throws Exception {
runTest("compiler/testData/diagnostics/tests/multiplatform/headerClass/morePermissiveVisibilityOnActualViaTypeAlias_finalMember.kt");
}
@Test
@TestMetadata("morePermissiveVisibilityOnActualViaTypeAlias_openMember.kt")
public void testMorePermissiveVisibilityOnActualViaTypeAlias_openMember() throws Exception {
runTest("compiler/testData/diagnostics/tests/multiplatform/headerClass/morePermissiveVisibilityOnActualViaTypeAlias_openMember.kt");
}
@Test
@@ -10,7 +10,7 @@ data class ExpectActualMemberDiff<out M, out C>(val kind: Kind, val actualMember
/**
* Diff kinds that are legal for fake-overrides in final `expect class`, but illegal for non-final `expect class`
*
* Also see: [toDiffKind]
* Also see: [toMemberDiffKind]
*/
enum class Kind(val rawMessage: String) {
NonPrivateCallableAdded(
@@ -52,37 +52,31 @@ data class ExpectActualMemberDiff<out M, out C>(val kind: Kind, val actualMember
}
}
/**
* This function serves a purpose of type-safe documentation. A mapping can be established between [ExpectActualCompatibility.Incompatible]
* and [ExpectActualMemberDiff.Kind]. This exhaustive when fixates this mapping, ensuring that we won't forget to add new
* [ExpectActualMemberDiff.Kind] when [ExpectActualCompatibility.Incompatible] is updated.
*/
@Suppress("unused") // The function is a documentation. That's why it's unused
private fun ExpectActualCompatibility.Incompatible<*>.toDiffKind(): ExpectActualMemberDiff.Kind? = when (this) {
fun ExpectActualCompatibility.Incompatible<*>.toMemberDiffKind(): ExpectActualMemberDiff.Kind? = when (this) {
ExpectActualCompatibility.Incompatible.CallableKind -> ExpectActualMemberDiff.Kind.NonPrivateCallableAdded
ExpectActualCompatibility.Incompatible.ParameterCount -> ExpectActualMemberDiff.Kind.NonPrivateCallableAdded
ExpectActualCompatibility.Incompatible.ParameterShape -> ExpectActualMemberDiff.Kind.NonPrivateCallableAdded
ExpectActualCompatibility.Incompatible.ParameterTypes -> ExpectActualMemberDiff.Kind.NonPrivateCallableAdded
ExpectActualCompatibility.Incompatible.ReturnType -> ExpectActualMemberDiff.Kind.ReturnTypeChangedInOverride
ExpectActualCompatibility.Incompatible.FunctionTypeParameterCount -> ExpectActualMemberDiff.Kind.NonPrivateCallableAdded
ExpectActualCompatibility.Incompatible.ClassTypeParameterCount -> null // Not applicable because ExpectActualMemberDiff is about members
ExpectActualCompatibility.Incompatible.ClassTypeParameterCount -> error("Not applicable because ExpectActualMemberDiff is about members")
ExpectActualCompatibility.Incompatible.FunctionTypeParameterUpperBounds -> ExpectActualMemberDiff.Kind.NonPrivateCallableAdded
ExpectActualCompatibility.Incompatible.ClassTypeParameterUpperBounds -> null // Not applicable because ExpectActualMemberDiff is about members
ExpectActualCompatibility.Incompatible.ClassTypeParameterUpperBounds -> error("Not applicable because ExpectActualMemberDiff is about members")
ExpectActualCompatibility.Incompatible.ActualFunctionWithDefaultParameters -> null // It's not possible to add default parameters in override
ExpectActualCompatibility.Incompatible.ClassKind -> null // Not applicable because ExpectActualMemberDiff is about members
ExpectActualCompatibility.Incompatible.ClassModifiers -> null // Not applicable because ExpectActualMemberDiff is about members
is ExpectActualCompatibility.Incompatible.ClassScopes -> null // Not applicable because ExpectActualMemberDiff is about members
ExpectActualCompatibility.Incompatible.EnumEntries -> null // Not applicable because ExpectActualMemberDiff is about members
ExpectActualCompatibility.Incompatible.FunInterfaceModifier -> null // Not applicable because ExpectActualMemberDiff is about members
ExpectActualCompatibility.Incompatible.FunctionModifiersDifferent -> null // It's not possible to override with different function modifier
ExpectActualCompatibility.Incompatible.FunctionModifiersNotSubset -> null // It's not possible to override with different function modifier
ExpectActualCompatibility.Incompatible.ClassKind -> error("Not applicable because ExpectActualMemberDiff is about members")
ExpectActualCompatibility.Incompatible.ClassModifiers -> error("Not applicable because ExpectActualMemberDiff is about members")
is ExpectActualCompatibility.Incompatible.ClassScopes -> error("Not applicable because ExpectActualMemberDiff is about members")
ExpectActualCompatibility.Incompatible.EnumEntries -> error("Not applicable because ExpectActualMemberDiff is about members")
ExpectActualCompatibility.Incompatible.FunInterfaceModifier -> error("Not applicable because ExpectActualMemberDiff is about members")
ExpectActualCompatibility.Incompatible.FunctionModifiersDifferent -> null // It's not possible to override with different function modifier (suspend)
ExpectActualCompatibility.Incompatible.FunctionModifiersNotSubset -> null // It's not possible to override with different function modifier (infix, inline, operator)
ExpectActualCompatibility.Incompatible.Modality -> ExpectActualMemberDiff.Kind.ModalityChangedInOverride
ExpectActualCompatibility.Incompatible.ParameterNames -> ExpectActualMemberDiff.Kind.ParameterNameChangedInOverride
ExpectActualCompatibility.Incompatible.PropertyConstModifier -> null // const fun can't be overridden
ExpectActualCompatibility.Incompatible.PropertyKind -> ExpectActualMemberDiff.Kind.PropertyKindChangedInOverride
ExpectActualCompatibility.Incompatible.PropertyLateinitModifier -> ExpectActualMemberDiff.Kind.LateinitChangedInOverride
ExpectActualCompatibility.Incompatible.PropertySetterVisibility -> ExpectActualMemberDiff.Kind.SetterVisibilityChangedInOverride
ExpectActualCompatibility.Incompatible.Supertypes -> null // Not applicable because ExpectActualMemberDiff is about members. But related: ACTUAL_CLASSIFIER_MUST_HAVE_THE_SAME_SUPERTYPES_AS_NON_FINAL_EXPECT_CLASSIFIER
ExpectActualCompatibility.Incompatible.Supertypes -> error("Not applicable because ExpectActualMemberDiff is about members")
ExpectActualCompatibility.Incompatible.TypeParameterNames -> ExpectActualMemberDiff.Kind.TypeParameterNamesChangedInOverride
ExpectActualCompatibility.Incompatible.TypeParameterReified -> null // inline fun can't be overridden
ExpectActualCompatibility.Incompatible.TypeParameterVariance -> null // Members are not allowed to have variance