[FIR] fix resolution ambiguities between weakly compatible expect and actual

There is a corresponding example inside the stdlib,
see `kotlin.text.startsWith`.

JVM and common counterpart are weakly-compatible
as the actual declaration has default arguments,
which results in `ExpectActualCompatibility.Incompatible.ActualFunctionWithDefaultParameters`

This commit allows such cases.

^KT-61732 fixed
This commit is contained in:
Ilya Kirillov
2023-09-06 10:54:12 +02:00
committed by Space Team
parent 209d59440b
commit 030250d387
8 changed files with 101 additions and 1 deletions
@@ -97,6 +97,12 @@ public class FirOldFrontendMPPDiagnosticsWithLightTreeTestGenerated extends Abst
runTest("compiler/testData/diagnostics/tests/multiplatform/arraySortFixed.kt");
}
@Test
@TestMetadata("callConflictsOnExpectAndActualWeaklyCompatible.kt")
public void testCallConflictsOnExpectAndActualWeaklyCompatible() throws Exception {
runTest("compiler/testData/diagnostics/tests/multiplatform/callConflictsOnExpectAndActualWeaklyCompatible.kt");
}
@Test
@TestMetadata("checkNoActualForExpectInLastModule.kt")
public void testCheckNoActualForExpectInLastModule() throws Exception {
@@ -97,6 +97,12 @@ public class FirOldFrontendMPPDiagnosticsWithPsiTestGenerated extends AbstractFi
runTest("compiler/testData/diagnostics/tests/multiplatform/arraySortFixed.kt");
}
@Test
@TestMetadata("callConflictsOnExpectAndActualWeaklyCompatible.kt")
public void testCallConflictsOnExpectAndActualWeaklyCompatible() throws Exception {
runTest("compiler/testData/diagnostics/tests/multiplatform/callConflictsOnExpectAndActualWeaklyCompatible.kt");
}
@Test
@TestMetadata("checkNoActualForExpectInLastModule.kt")
public void testCheckNoActualForExpectInLastModule() throws Exception {
@@ -9,6 +9,7 @@ import org.jetbrains.kotlin.descriptors.Modality
import org.jetbrains.kotlin.fir.FirSession
import org.jetbrains.kotlin.fir.declarations.FirMemberDeclaration
import org.jetbrains.kotlin.fir.declarations.getSingleExpectForActualOrNull
import org.jetbrains.kotlin.fir.declarations.getSingleCompatibleOrWeaklyIncompatibleExpectForActualOrNull
import org.jetbrains.kotlin.fir.declarations.utils.isActual
import org.jetbrains.kotlin.fir.declarations.utils.isExpect
import org.jetbrains.kotlin.fir.declarations.utils.modality
@@ -289,7 +290,10 @@ class ConeOverloadConflictResolver(
val expectForActualSymbols = candidates
.mapNotNullTo(mutableSetOf()) {
val callableSymbol = it.symbol as? FirCallableSymbol<*> ?: return@mapNotNullTo null
runIf(callableSymbol.isActual) { callableSymbol.getSingleExpectForActualOrNull() }
runIf(callableSymbol.isActual) {
callableSymbol.getSingleExpectForActualOrNull()
?: callableSymbol.getSingleCompatibleOrWeaklyIncompatibleExpectForActualOrNull()
}
}
return if (expectForActualSymbols.isEmpty()) {
@@ -11,6 +11,7 @@ import org.jetbrains.kotlin.fir.symbols.impl.FirFunctionSymbol
import org.jetbrains.kotlin.fir.symbols.impl.FirRegularClassSymbol
import org.jetbrains.kotlin.fir.symbols.lazyResolveToPhase
import org.jetbrains.kotlin.resolve.multiplatform.ExpectActualCompatibility
import org.jetbrains.kotlin.resolve.multiplatform.isCompatibleOrWeaklyIncompatible
private object ExpectForActualAttributeKey : FirDeclarationDataKey()
@@ -26,6 +27,13 @@ fun FirBasedSymbol<*>.getSingleExpectForActualOrNull(): FirBasedSymbol<*>? {
return expectForActual?.values?.singleOrNull()?.singleOrNull()
}
fun FirBasedSymbol<*>.getSingleCompatibleOrWeaklyIncompatibleExpectForActualOrNull(): FirBasedSymbol<*>? {
val expectForActual = expectForActual ?: return null
val compatibleOrWeakCompatible: List<FirBasedSymbol<*>> =
expectForActual.entries.singleOrNull { it.key.isCompatibleOrWeaklyIncompatible }?.value ?: return null
return compatibleOrWeakCompatible.singleOrNull()
}
val FirBasedSymbol<*>.expectForActual: ExpectForActualData?
get() {
lazyResolveToPhase(FirResolvePhase.EXPECT_ACTUAL_MATCHING)
@@ -0,0 +1,33 @@
// ISSUE: KT-61732
// based on of kotlin.text.startsWith from kotlin-stdlib
// MODULE: common
// TARGET_PLATFORM: Common
// FILE: common.kt
expect fun String.foo(prefix: String, ignoreCase: Boolean = false): Boolean
expect fun String.foo(prefix: String, startIndex: Int, ignoreCase: Boolean = false): Boolean
// MODULE: jvm()()(common)
// TARGET_PLATFORM: JVM
// FILE: jvm.kt
@Suppress(<!ERROR_SUPPRESSION!>"ACTUAL_FUNCTION_WITH_DEFAULT_ARGUMENTS"<!>)
actual fun String.foo(prefix: String, ignoreCase: Boolean = false): Boolean {
return true
}
@Suppress(<!ERROR_SUPPRESSION!>"ACTUAL_FUNCTION_WITH_DEFAULT_ARGUMENTS"<!>)
actual fun String.foo(prefix: String, startIndex: Int, ignoreCase: Boolean = false): Boolean {
return true
}
// MODULE: client(jvm)()()
// TARGET_PLATFORM: JVM
// FILE: client.kt
fun main() {
"".foo("")
}
@@ -0,0 +1,33 @@
// ISSUE: KT-61732
// based on of kotlin.text.startsWith from kotlin-stdlib
// MODULE: common
// TARGET_PLATFORM: Common
// FILE: common.kt
expect fun String.foo(prefix: String, ignoreCase: Boolean = false): Boolean
expect fun String.foo(prefix: String, startIndex: Int, ignoreCase: Boolean = false): Boolean
// MODULE: jvm()()(common)
// TARGET_PLATFORM: JVM
// FILE: jvm.kt
@Suppress("ACTUAL_FUNCTION_WITH_DEFAULT_ARGUMENTS")
actual fun String.foo(prefix: String, ignoreCase: Boolean = false): Boolean {
return true
}
@Suppress("ACTUAL_FUNCTION_WITH_DEFAULT_ARGUMENTS")
actual fun String.foo(prefix: String, startIndex: Int, ignoreCase: Boolean = false): Boolean {
return true
}
// MODULE: client(jvm)()()
// TARGET_PLATFORM: JVM
// FILE: client.kt
fun main() {
"".foo("")
}
@@ -23020,6 +23020,12 @@ public class DiagnosticTestGenerated extends AbstractDiagnosticTest {
runTest("compiler/testData/diagnostics/tests/multiplatform/arraySortFixed.kt");
}
@Test
@TestMetadata("callConflictsOnExpectAndActualWeaklyCompatible.kt")
public void testCallConflictsOnExpectAndActualWeaklyCompatible() throws Exception {
runTest("compiler/testData/diagnostics/tests/multiplatform/callConflictsOnExpectAndActualWeaklyCompatible.kt");
}
@Test
@TestMetadata("checkNoActualForExpectInLastModule.kt")
public void testCheckNoActualForExpectInLastModule() throws Exception {
@@ -99,6 +99,10 @@ sealed class ExpectActualCompatibility<out D> {
object Compatible : ExpectActualCompatibility<Nothing>()
}
val ExpectActualCompatibility<*>.isCompatibleOrWeaklyIncompatible: Boolean
get() = this is ExpectActualCompatibility.Compatible
|| this is ExpectActualCompatibility.Incompatible.WeakIncompatible
val ExpectActualCompatibility<*>.compatible: Boolean
get() = this == ExpectActualCompatibility.Compatible