[FIR] Bring equivalent call behavior closer to K1

#KT-61159 Fixed
This commit is contained in:
Kirill Rakhman
2023-08-14 17:01:29 +02:00
committed by Space Team
parent 7c67e9e08b
commit fa77e3952d
39 changed files with 408 additions and 126 deletions
@@ -12,13 +12,13 @@ FILE: mixingImplicitAndExplicitReceivers.kt
public final fun withThis(): R|kotlin/Unit| {
when () {
!=(this@R|/Wrapper|.R|/Wrapper.s|, Null(null)) -> {
R|/takeString|(this@R|/Wrapper|.R|/Wrapper.s|)
<Ambiguity: takeString, [/takeString, /takeString]>#(this@R|/Wrapper|.R|/Wrapper.s|)
}
}
when () {
!=(this@R|/Wrapper|.R|/Wrapper.s|, Null(null)) -> {
R|/takeString|(this@R|/Wrapper|.R|/Wrapper.s|)
<Ambiguity: takeString, [/takeString, /takeString]>#(this@R|/Wrapper|.R|/Wrapper.s|)
}
}
@@ -3,10 +3,10 @@
class Wrapper(val s: String?) {
fun withThis() {
if (s != null) {
takeString(this.s) // Should be OK
<!OVERLOAD_RESOLUTION_AMBIGUITY!>takeString<!>(this.s) // Should be OK
}
if (this.s != null) {
takeString(s) // Should be OK
<!OVERLOAD_RESOLUTION_AMBIGUITY!>takeString<!>(s) // Should be OK
}
}
}
@@ -13,7 +13,7 @@ FILE: beyoundCalls.kt
}
public final fun foo(): R|kotlin/Unit| {
lval x: R|(kotlin/String) -> kotlin/Int| = ::R|/bar|
lval y: R|kotlin/reflect/KFunction1<kotlin/String, kotlin/Int>| = ::R|/bar|
lval y: <ERROR TYPE REF: Ambiguity: bar, [/bar, /bar]> = ::<Ambiguity: bar, [/bar, /bar]>#
lval z: R|kotlin/reflect/KFunction1<kotlin/String, kotlin/Int>| = ::R|/baz|
lval w: R|(kotlin/String) -> kotlin/Int| = ::R|/foobaz<kotlin/String, kotlin/Int>|
::R|/baz|
@@ -6,7 +6,7 @@ fun <T, R> foobaz(x: T): R = TODO()
fun foo() {
val x: (String) -> Int = ::bar
val y = ::bar
val y = ::<!OVERLOAD_RESOLUTION_AMBIGUITY!>bar<!>
val z = ::baz
val w: (String) -> Int = ::foobaz
@@ -28143,6 +28143,12 @@ public class FirLightTreeOldFrontendDiagnosticsTestGenerated extends AbstractFir
runTest("compiler/testData/diagnostics/tests/resolve/overloadConflicts/allLambdas.kt");
}
@Test
@TestMetadata("equivalentCallsDifferentVisibility.kt")
public void testEquivalentCallsDifferentVisibility() throws Exception {
runTest("compiler/testData/diagnostics/tests/resolve/overloadConflicts/equivalentCallsDifferentVisibility.kt");
}
@Test
@TestMetadata("extensionReceiverAndVarargs.kt")
public void testExtensionReceiverAndVarargs() throws Exception {
@@ -28155,6 +28155,12 @@ public class FirPsiOldFrontendDiagnosticsTestGenerated extends AbstractFirPsiDia
runTest("compiler/testData/diagnostics/tests/resolve/overloadConflicts/allLambdas.kt");
}
@Test
@TestMetadata("equivalentCallsDifferentVisibility.kt")
public void testEquivalentCallsDifferentVisibility() throws Exception {
runTest("compiler/testData/diagnostics/tests/resolve/overloadConflicts/equivalentCallsDifferentVisibility.kt");
}
@Test
@TestMetadata("extensionReceiverAndVarargs.kt")
public void testExtensionReceiverAndVarargs() throws Exception {
@@ -17402,6 +17402,22 @@ public class FirLightTreeBlackBoxCodegenTestGenerated extends AbstractFirLightTr
}
}
@Nested
@TestMetadata("compiler/testData/codegen/box/equivalentCalls")
@TestDataPath("$PROJECT_ROOT")
public class EquivalentCalls {
@Test
public void testAllFilesPresentInEquivalentCalls() throws Exception {
KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/codegen/box/equivalentCalls"), Pattern.compile("^(.+)\\.kt$"), null, TargetBackend.JVM_IR, true);
}
@Test
@TestMetadata("localEquivalentWins.kt")
public void testLocalEquivalentWins() throws Exception {
runTest("compiler/testData/codegen/box/equivalentCalls/localEquivalentWins.kt");
}
}
@Nested
@TestMetadata("compiler/testData/codegen/box/evaluate")
@TestDataPath("$PROJECT_ROOT")
@@ -17402,6 +17402,22 @@ public class FirPsiBlackBoxCodegenTestGenerated extends AbstractFirPsiBlackBoxCo
}
}
@Nested
@TestMetadata("compiler/testData/codegen/box/equivalentCalls")
@TestDataPath("$PROJECT_ROOT")
public class EquivalentCalls {
@Test
public void testAllFilesPresentInEquivalentCalls() throws Exception {
KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/codegen/box/equivalentCalls"), Pattern.compile("^(.+)\\.kt$"), null, TargetBackend.JVM_IR, true);
}
@Test
@TestMetadata("localEquivalentWins.kt")
public void testLocalEquivalentWins() throws Exception {
runTest("compiler/testData/codegen/box/equivalentCalls/localEquivalentWins.kt");
}
}
@Nested
@TestMetadata("compiler/testData/codegen/box/evaluate")
@TestDataPath("$PROJECT_ROOT")
@@ -13,17 +13,19 @@ 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.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
import org.jetbrains.kotlin.utils.exceptions.errorWithAttachment
// This conflict resolver filters JVM equivalent top-level functions
// like emptyArray() from intrinsics and built-ins
/**
* Resolver that filters out equivalent calls, mainly to deduplicate multiples of the same declaration coming from different versions
* of the same dependency, e.g., multiple stdlibs.
*
* Currently, it will also consider a declaration from source and one from binary equivalent if all conditions are met for backward
* compatibility with K1.
*/
class ConeEquivalentCallConflictResolver(
specificityComparator: TypeSpecificityComparator,
inferenceComponents: InferenceComponents,
transformerComponents: BodyResolveComponents
transformerComponents: BodyResolveComponents,
) : AbstractConeCallConflictResolver(
specificityComparator,
inferenceComponents,
@@ -38,8 +40,13 @@ class ConeEquivalentCallConflictResolver(
}
private fun filterOutEquivalentCalls(candidates: Collection<Candidate>): Set<Candidate> {
// Since we can consider a declaration from source and one from binary equivalent, we need to make sure we favor the one from
// source, otherwise we might get a behavior change to K1.
// See org.jetbrains.kotlin.resolve.calls.results.OverloadingConflictResolver.filterOutEquivalentCalls.
val fromSourceFirst = candidates.sortedBy { it.symbol.fir.source == null }
val result = mutableSetOf<Candidate>()
outerLoop@ for (myCandidate in candidates) {
outerLoop@ for (myCandidate in fromSourceFirst) {
val me = myCandidate.symbol.fir
if (me is FirCallableDeclaration && me.symbol.containingClassLookupTag() == null) {
for (otherCandidate in result) {
@@ -63,6 +70,11 @@ class ConeEquivalentCallConflictResolver(
secondCandidate: Candidate
): Boolean {
if (first.symbol.callableId != second.symbol.callableId) return false
// Emulate behavior from K1 where declarations from the same module are never equivalent.
// We expect REDECLARATION or CONFLICTING_OVERLOADS to be reported in those cases.
// See a.containingDeclaration == b.containingDeclaration check in
// org.jetbrains.kotlin.resolve.DescriptorEquivalenceForOverrides.areCallableDescriptorsEquivalent.
if (first.moduleData == second.moduleData) return false
if (first.isExpect != second.isExpect) return false
if (first is FirVariable != second is FirVariable) {
return false
@@ -73,15 +85,16 @@ class ConeEquivalentCallConflictResolver(
val overrideChecker = FirStandardOverrideChecker(inferenceComponents.session)
return if (first is FirProperty && second is FirProperty) {
overrideChecker.isOverriddenProperty(first, second) && overrideChecker.isOverriddenProperty(second, first)
overrideChecker.isOverriddenProperty(first, second, ignoreVisibility = true) &&
overrideChecker.isOverriddenProperty(second, first, ignoreVisibility = true)
} else if (first is FirSimpleFunction && second is FirSimpleFunction) {
overrideChecker.isOverriddenFunction(first, second) && overrideChecker.isOverriddenFunction(second, first)
overrideChecker.isOverriddenFunction(first, second, ignoreVisibility = true) &&
overrideChecker.isOverriddenFunction(second, first, ignoreVisibility = true)
} else {
false
}
}
/**
* If the candidate is a function, then the arguments
* order representation is an array containing the
@@ -122,32 +122,44 @@ class FirStandardOverrideChecker(private val session: FirSession) : FirAbstractO
}
override fun isOverriddenFunction(overrideCandidate: FirSimpleFunction, baseDeclaration: FirSimpleFunction): Boolean {
return isOverriddenFunction(overrideCandidate, baseDeclaration, ignoreVisibility = false)
}
fun isOverriddenFunction(overrideCandidate: FirSimpleFunction, baseDeclaration: FirSimpleFunction, ignoreVisibility: Boolean): Boolean {
if (overrideCandidate.valueParameters.size != baseDeclaration.valueParameters.size) return false
val substitutor = buildTypeParametersSubstitutorIfCompatible(overrideCandidate, baseDeclaration) ?: return false
if (!commonCallableChecks(overrideCandidate, baseDeclaration, substitutor)) return false
if (!commonCallableChecks(overrideCandidate, baseDeclaration, substitutor, ignoreVisibility)) return false
return overrideCandidate.valueParameters.zip(baseDeclaration.valueParameters).all { (memberParam, selfParam) ->
isEqualTypes(memberParam.returnTypeRef, selfParam.returnTypeRef, substitutor)
}
}
override fun isOverriddenProperty(
override fun isOverriddenProperty(overrideCandidate: FirCallableDeclaration, baseDeclaration: FirProperty): Boolean {
return isOverriddenProperty(overrideCandidate, baseDeclaration, ignoreVisibility = false)
}
fun isOverriddenProperty(
overrideCandidate: FirCallableDeclaration,
baseDeclaration: FirProperty
baseDeclaration: FirProperty,
ignoreVisibility: Boolean,
): Boolean {
if (overrideCandidate !is FirProperty) return false
val substitutor = buildTypeParametersSubstitutorIfCompatible(overrideCandidate, baseDeclaration) ?: return false
return commonCallableChecks(overrideCandidate, baseDeclaration, substitutor)
return commonCallableChecks(overrideCandidate, baseDeclaration, substitutor, ignoreVisibility)
}
private fun FirStandardOverrideChecker.commonCallableChecks(
overrideCandidate: FirCallableDeclaration,
baseDeclaration: FirCallableDeclaration,
substitutor: ConeSubstitutor,
// Overload-ability is used to filter out equivalent calls (see ConeEquivalentCallConflictResolver) in which case visibility
// must be ignored.
ignoreVisibility: Boolean,
): Boolean {
if (Visibilities.isPrivate(baseDeclaration.visibility)) return false
if (!ignoreVisibility && Visibilities.isPrivate(baseDeclaration.visibility)) return false
if (overrideCandidate.contextReceivers.size != baseDeclaration.contextReceivers.size) return false
overrideCandidate.lazyResolveToPhase(FirResolvePhase.TYPES)