[FIR] Fixes for call conflict resolution

Let ConeCompositeConflictResolver pass the results of the previous
resolver to the next one.
Otherwise, we get false positive conflicts when a set of candidates
can't be fully reduced by one resolver but could be resolved by the
subsequent application of multiple ones.
This change makes ConeCompositeConflictResolver order-dependent and
thus, ConeOverloadConflictResolver must be invoked last, because it
must work on a pre-filtered list.

Also, let ConeEquivalentCallConflictResolver use
FirStandardOverrideChecker instead of compareCallsByUsedArguments
because it's stricter.

This all fixes a false positive overload resolution ambiguity in common
metadata compilation that is caused by stdlib using the new KMP
format.
Now stdlib metadata is in the classpath, and so declarations from the
stdlib are returned from both MetadataSymbolProvider and
KlibBasedSymbolProvider.
This isn't a problem per se because duplicate candidates are filtered
out by ConeEquivalentCallConflictResolver (K1 works analogously), but
in the case of top-level functions with generic receivers like
Collection<T>.toTypedArray, the check failed because of the direct
comparison of receiver types.

#KT-60943 Fixed
This commit is contained in:
Kirill Rakhman
2023-08-10 12:45:09 +02:00
committed by Space Team
parent 86ef313233
commit 724d527fd8
19 changed files with 55 additions and 167 deletions
@@ -21,13 +21,11 @@ object DefaultCallConflictResolverFactory : ConeCallConflictResolverFactory() {
transformerComponents: BodyResolveComponents
): ConeCompositeConflictResolver {
val specificityComparator = TypeSpecificityComparator.NONE
// NB: Please, be aware that adding might not necessarily help you because ConeOverloadConflictResolver doesn't just filter out
// less specific candidates, but leave the set the same if there are more than one same-specifity candidates.
// Thus, in that case, your new ConeCallConflictResolver might get all the candidates in that case.
// NB: Adding new resolvers is strongly discouraged because the results are order-dependent.
return ConeCompositeConflictResolver(
ConeOverloadConflictResolver(specificityComparator, components, transformerComponents),
ConeEquivalentCallConflictResolver(specificityComparator, components, transformerComponents),
ConeIntegerOperatorConflictResolver,
ConeOverloadConflictResolver(specificityComparator, components, transformerComponents),
)
}
}
@@ -25,13 +25,11 @@ object JsCallConflictResolverFactory : ConeCallConflictResolverFactory() {
transformerComponents: BodyResolveComponents
): ConeCompositeConflictResolver {
val specificityComparator = JsTypeSpecificityComparatorWithoutDelegate(components.session.typeContext)
// NB: Please, be aware that adding might not necessarily help you because ConeOverloadConflictResolver doesn't just filter out
// less specific candidates, but leave the set the same if there are more than one same-specifity candidates.
// Thus, in that case, your new ConeCallConflictResolver might get all the candidates in that case.
// NB: Adding new resolvers is strongly discouraged because the results are order-dependent.
return ConeCompositeConflictResolver(
ConeOverloadConflictResolver(specificityComparator, components, transformerComponents),
ConeEquivalentCallConflictResolver(specificityComparator, components, transformerComponents),
ConeIntegerOperatorConflictResolver,
ConeOverloadConflictResolver(specificityComparator, components, transformerComponents),
)
}
}
@@ -12,7 +12,7 @@ import org.jetbrains.kotlin.fir.resolve.BodyResolveComponents
import org.jetbrains.kotlin.fir.resolve.calls.AbstractConeCallConflictResolver
import org.jetbrains.kotlin.fir.resolve.calls.Candidate
import org.jetbrains.kotlin.fir.resolve.inference.InferenceComponents
import org.jetbrains.kotlin.fir.types.coneType
import org.jetbrains.kotlin.fir.scopes.impl.FirStandardOverrideChecker
import org.jetbrains.kotlin.fir.utils.exceptions.withFirEntry
import org.jetbrains.kotlin.resolve.calls.results.FlatSignature
import org.jetbrains.kotlin.resolve.calls.results.TypeSpecificityComparator
@@ -64,21 +64,24 @@ class ConeEquivalentCallConflictResolver(
): Boolean {
if (first.symbol.callableId != second.symbol.callableId) return false
if (first.isExpect != second.isExpect) return false
if (first.receiverParameter?.typeRef?.coneType != second.receiverParameter?.typeRef?.coneType) {
return false
}
if (first is FirVariable != second is FirVariable) {
return false
}
if (!firstCandidate.mappedArgumentsOrderRepresentation.contentEquals(secondCandidate.mappedArgumentsOrderRepresentation)) {
return false
}
val firstSignature = createFlatSignature(firstCandidate, first)
val secondSignature = createFlatSignature(secondCandidate, second)
return compareCallsByUsedArguments(firstSignature, secondSignature, discriminateGenerics = false, useOriginalSamTypes = false) &&
compareCallsByUsedArguments(secondSignature, firstSignature, discriminateGenerics = false, useOriginalSamTypes = false)
val overrideChecker = FirStandardOverrideChecker(inferenceComponents.session)
return if (first is FirProperty && second is FirProperty) {
overrideChecker.isOverriddenProperty(first, second) && overrideChecker.isOverriddenProperty(second, first)
} else if (first is FirSimpleFunction && second is FirSimpleFunction) {
overrideChecker.isOverriddenFunction(first, second) && overrideChecker.isOverriddenFunction(second, first)
} else {
false
}
}
/**
* If the candidate is a function, then the arguments
* order representation is an array containing the
@@ -99,15 +102,4 @@ class ConeEquivalentCallConflictResolver(
}
return result
}
private fun createFlatSignature(call: Candidate, declaration: FirCallableDeclaration): FlatSignature<Candidate> {
return when (declaration) {
is FirSimpleFunction -> createFlatSignature(call, declaration)
is FirConstructor -> createFlatSignature(call, declaration)
is FirVariable -> createFlatSignature(call, declaration)
else -> errorWithAttachment("Not supported: ${this::class}") {
withFirEntry("declaration", declaration)
}
}
}
}
@@ -24,14 +24,12 @@ object JvmCallConflictResolverFactory : ConeCallConflictResolverFactory() {
transformerComponents: BodyResolveComponents
): ConeCompositeConflictResolver {
val specificityComparator = JvmTypeSpecificityComparator(components.session.typeContext)
// NB: Please, be aware that adding might not necessarily help you because ConeOverloadConflictResolver doesn't just filter out
// less specific candidates, but leave the set the same if there are more than one same-specifity candidates.
// Thus, in that case, your new ConeCallConflictResolver might get all the candidates in that case.
// NB: Adding new resolvers is strongly discouraged because the results are order-dependent.
return ConeCompositeConflictResolver(
ConeOverloadConflictResolver(specificityComparator, components, transformerComponents),
ConeEquivalentCallConflictResolver(specificityComparator, components, transformerComponents),
JvmPlatformOverloadsConflictResolver(components.session),
ConeIntegerOperatorConflictResolver,
ConeOverloadConflictResolver(specificityComparator, components, transformerComponents),
)
}
}
@@ -17,7 +17,7 @@ class ConeCompositeConflictResolver(
var index = 0
while (currentCandidates.size > 1 && index < conflictResolvers.size) {
val conflictResolver = conflictResolvers[index++]
currentCandidates = conflictResolver.chooseMaximallySpecificCandidates(candidates, discriminateAbstracts)
currentCandidates = conflictResolver.chooseMaximallySpecificCandidates(currentCandidates, discriminateAbstracts)
}
return currentCandidates
}
@@ -131,13 +131,13 @@ fun main() {
val list10 = buildList {
add("one")
foo0(get(0), 0f)
<!OVERLOAD_RESOLUTION_AMBIGUITY!>foo0<!>(get(0), 0f)
}
val list11 = buildList {
add("one")
val x = get(0)
foo0(x, 0f)
<!OVERLOAD_RESOLUTION_AMBIGUITY!>foo0<!>(x, 0f)
}
val list12 = buildList {
add("one")
@@ -11,8 +11,8 @@ private val validVal = 1
private val <!REDECLARATION!>invalidProp0<!> = 1
// NB invalidFun0 and invalidProp0 are conflicting overloads, since the following is an ambiguity:
fun useInvalidFun0() = invalidFun0()
fun useInvalidProp0() = invalidProp0
fun useInvalidFun0() = <!OVERLOAD_RESOLUTION_AMBIGUITY!>invalidFun0<!>()
fun useInvalidProp0() = <!OVERLOAD_RESOLUTION_AMBIGUITY!>invalidProp0<!>
<!CONFLICTING_OVERLOADS!>private fun invalidFun1()<!> {}
<!CONFLICTING_OVERLOADS!>private fun invalidFun1()<!> {}
@@ -4,5 +4,5 @@ fun <T> f1(l: <!UNRESOLVED_REFERENCE!>List2<T><!>): T {throw Exception()} // ERR
fun <T> f1(c: Collection<T>): T{throw Exception()}
fun <T> test(l: List<T>) {
<!NEW_INFERENCE_NO_INFORMATION_FOR_PARAMETER!>f1<!>(l)
<!OVERLOAD_RESOLUTION_AMBIGUITY!>f1<!>(l)
}
@@ -1,6 +0,0 @@
fun foo(vararg t : String) = ""
fun foo(vararg t : Int) = ""
fun test() {
foo()
}
@@ -1,3 +1,4 @@
// FIR_IDENTICAL
fun foo(vararg t : String) = ""
fun foo(vararg t : Int) = ""
@@ -36,7 +36,7 @@ fun case1() {
val a: Boolean? = false
checkSubtype<Boolean?>(a)
val x4 = <!CONDITION_TYPE_MISMATCH!>a<!> && true
x4 <!OVERLOAD_RESOLUTION_AMBIGUITY!>checkType<!> { <!NONE_APPLICABLE!>check<!><Boolean>() }
x4 checkType { check<Boolean>() }
}
// TESTCASE NUMBER: 2
@@ -44,7 +44,7 @@ fun case2() {
val a: Any = false
<!DEBUG_INFO_EXPRESSION_TYPE("kotlin.Any")!>a<!>
val x4 = <!CONDITION_TYPE_MISMATCH!>a<!> && true
x4 <!OVERLOAD_RESOLUTION_AMBIGUITY!>checkType<!> { <!NONE_APPLICABLE!>check<!><Boolean>() }
x4 checkType { check<Boolean>() }
}
// TESTCASE NUMBER: 3
@@ -56,7 +56,7 @@ fun case3() {
val x3 = a1 && <!CONDITION_TYPE_MISMATCH!>a2<!>
<!DEBUG_INFO_EXPRESSION_TYPE("kotlin.Boolean")!>x3<!>
x3 <!OVERLOAD_RESOLUTION_AMBIGUITY!>checkType<!> { <!NONE_APPLICABLE!>check<!><Boolean>() }
x3 checkType { check<Boolean>() }
}
// TESTCASE NUMBER: 4
@@ -1,56 +0,0 @@
// !DIAGNOSTICS: -UNUSED_VARIABLE -ASSIGNED_BUT_NEVER_ACCESSED_VARIABLE -UNUSED_VALUE -UNUSED_PARAMETER -UNUSED_EXPRESSION
// SKIP_TXT
/*
* KOTLIN DIAGNOSTICS SPEC TEST (POSITIVE)
*
* SPEC VERSION: 0.1-313
* MAIN LINK: expressions, logical-conjunction-expression -> paragraph 2 -> sentence 1
* PRIMARY LINKS: expressions, logical-conjunction-expression -> paragraph 2 -> sentence 2
* NUMBER: 1
* DESCRIPTION: Both operands of a logical conjunction expression must have a type which is a subtype of kotlin.Boolean
* HELPERS: checkType
*/
// MODULE: libModule
// FILE: libModule/JavaClass.java
package libModule;
public class JavaClass {
public static boolean VALUE;
public static Object VALUE_OBJ = true;
public Boolean getValue ()
{ return new Boolean ("true"); }
}
// MODULE: mainModule(libModule)
// FILE: KotlinClass.kt
package mainModule
import libModule.*
import checkSubtype
import checkType
import check
fun foo() = run { false && JavaClass.VALUE && throw Exception() }
// TESTCASE NUMBER: 1
fun case1() {
val a1 = false
val a2 = JavaClass.VALUE
val a3 = foo()
val a4 = JavaClass().getValue()
val a5 = JavaClass.VALUE_OBJ
checkSubtype<Boolean>(a1)
checkSubtype<Boolean>(a2)
checkSubtype<Boolean>(a3)
checkSubtype<Boolean>(a4)
val x3 = a1 && a2 && a3 && a4 && a5 as Boolean
<!DEBUG_INFO_EXPRESSION_TYPE("kotlin.Boolean")!>x3<!>
x3 <!OVERLOAD_RESOLUTION_AMBIGUITY!>checkType<!> { <!NONE_APPLICABLE!>check<!><Boolean>()}
}
@@ -1,3 +1,4 @@
// FIR_IDENTICAL
// !DIAGNOSTICS: -UNUSED_VARIABLE -ASSIGNED_BUT_NEVER_ACCESSED_VARIABLE -UNUSED_VALUE -UNUSED_PARAMETER -UNUSED_EXPRESSION
// SKIP_TXT
@@ -36,7 +36,7 @@ fun case1() {
val a: Boolean? = false
checkSubtype<Boolean?>(a)
val x4 = <!CONDITION_TYPE_MISMATCH!>a<!> || true
x4 <!OVERLOAD_RESOLUTION_AMBIGUITY!>checkType<!> { <!NONE_APPLICABLE!>check<!><Boolean>() }
x4 checkType { check<Boolean>() }
}
// TESTCASE NUMBER: 2
@@ -44,7 +44,7 @@ fun case2() {
val a: Any = false
<!DEBUG_INFO_EXPRESSION_TYPE("kotlin.Any")!>a<!>
val x4 = <!CONDITION_TYPE_MISMATCH!>a<!> || true
x4 <!OVERLOAD_RESOLUTION_AMBIGUITY!>checkType<!> { <!NONE_APPLICABLE!>check<!><Boolean>() }
x4 checkType { check<Boolean>() }
}
// TESTCASE NUMBER: 3
@@ -56,7 +56,7 @@ fun case3() {
val x3 = a1 || <!CONDITION_TYPE_MISMATCH!>a2<!>
<!DEBUG_INFO_EXPRESSION_TYPE("kotlin.Boolean")!>x3<!>
x3 <!OVERLOAD_RESOLUTION_AMBIGUITY!>checkType<!> { <!NONE_APPLICABLE!>check<!><Boolean>() }
x3 checkType { check<Boolean>() }
}
// TESTCASE NUMBER: 4
@@ -1,56 +0,0 @@
// !DIAGNOSTICS: -UNUSED_VARIABLE -ASSIGNED_BUT_NEVER_ACCESSED_VARIABLE -UNUSED_VALUE -UNUSED_PARAMETER -UNUSED_EXPRESSION
// SKIP_TXT
/*
* KOTLIN DIAGNOSTICS SPEC TEST (POSITIVE)
*
* SPEC VERSION: 0.1-313
* MAIN LINK: expressions, logical-disjunction-expression -> paragraph 2 -> sentence 1
* PRIMARY LINKS: expressions, logical-disjunction-expression -> paragraph 2 -> sentence 2
* NUMBER: 1
* DESCRIPTION:Both operands of a logical disjunction expression must have a type which is a subtype of kotlin.Boolean
* HELPERS: checkType
*/
// MODULE: libModule
// FILE: libModule/JavaClass.java
package libModule;
public class JavaClass {
public static boolean VALUE;
public static Object VALUE_OBJ = true;
public Boolean getValue ()
{ return new Boolean ("true"); }
}
// MODULE: mainModule(libModule)
// FILE: KotlinClass.kt
package mainModule
import libModule.*
import checkSubtype
import checkType
import check
fun foo() = run { false || JavaClass.VALUE || throw Exception() }
// TESTCASE NUMBER: 1
fun case1() {
val a1 = false
val a2 = JavaClass.VALUE
val a3 = foo()
val a4 = JavaClass().getValue()
val a5 = JavaClass.VALUE_OBJ
checkSubtype<Boolean>(a1)
checkSubtype<Boolean>(a2)
checkSubtype<Boolean>(a3)
checkSubtype<Boolean>(a4)
val x3 = a1 || a2 || a3 || a4 || a5 as Boolean
<!DEBUG_INFO_EXPRESSION_TYPE("kotlin.Boolean")!>x3<!>
x3 <!OVERLOAD_RESOLUTION_AMBIGUITY!>checkType<!> { <!NONE_APPLICABLE!>check<!><Boolean>()}
}
@@ -1,3 +1,4 @@
// FIR_IDENTICAL
// !DIAGNOSTICS: -UNUSED_VARIABLE -ASSIGNED_BUT_NEVER_ACCESSED_VARIABLE -UNUSED_VALUE -UNUSED_PARAMETER -UNUSED_EXPRESSION
// SKIP_TXT
@@ -44,7 +44,11 @@ class CustomK2Tests : KGPBaseTest() {
@GradleTest
@DisplayName("Serialization plugin in common source set. KT-56911")
fun testHmppDependenciesInJsTests(gradleVersion: GradleVersion) {
project("k2-serialization-plugin-in-common-sourceset", gradleVersion) {
project(
"k2-serialization-plugin-in-common-sourceset",
gradleVersion,
buildOptions = defaultBuildOptions.copy(languageVersion = "2.0"),
) {
val taskToExecute = ":compileKotlinJs"
build(taskToExecute) {
assertTasksExecuted(taskToExecute)
@@ -155,4 +159,19 @@ class CustomK2Tests : KGPBaseTest() {
}
}
}
@GradleTest
@DisplayName("Common metadata compilation. KT-60943")
fun kt60943CommonMetadataCompilation(gradleVersion: GradleVersion) {
project(
"k2-serialization-plugin-in-common-sourceset",
gradleVersion,
buildOptions = defaultBuildOptions.copy(languageVersion = "2.0"),
) {
val taskToExecute = ":compileCommonMainKotlinMetadata"
build(taskToExecute) {
assertTasksExecuted(taskToExecute)
}
}
}
}
@@ -23,11 +23,6 @@ kotlin {
}
}
sourceSets {
all {
languageSettings.apply {
languageVersion = "2.0"
}
}
val commonMain by getting{
dependencies{
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.0-RC")