[FIR] If callable reference can't be resolved with expected type, try resolving with Any

In positions outside of calls (e.g. property initializers)
we resolve callable references using a synthetic outer call with the
expected type as parameter type.
If this fails, we previously returned an unresolved reference.
After this commit, we additionally try to resolve the callable reference
with expected type Any.
This lets us report more precise diagnostics like type mismatches or
when multiple overloads exist NONE_APPLICABLE.

#KT-55373 Fixed
#KT-55955 Fixed
This commit is contained in:
Kirill Rakhman
2023-07-27 18:32:16 +02:00
committed by Space Team
parent 2c91ae1129
commit ade1354a84
20 changed files with 143 additions and 79 deletions
@@ -3528,6 +3528,12 @@ public class DiagnosticCompilerTestFE10TestdataTestGenerated extends AbstractDia
runTest("compiler/testData/diagnostics/tests/callableReference/kt52503.kt");
}
@Test
@TestMetadata("kt55373.kt")
public void testKt55373() throws Exception {
runTest("compiler/testData/diagnostics/tests/callableReference/kt55373.kt");
}
@Test
@TestMetadata("kt7430_wrongClassOnLHS.kt")
public void testKt7430_wrongClassOnLHS() throws Exception {
@@ -3528,6 +3528,12 @@ public class LLFirPreresolvedReversedDiagnosticCompilerFE10TestDataTestGenerated
runTest("compiler/testData/diagnostics/tests/callableReference/kt52503.kt");
}
@Test
@TestMetadata("kt55373.kt")
public void testKt55373() throws Exception {
runTest("compiler/testData/diagnostics/tests/callableReference/kt55373.kt");
}
@Test
@TestMetadata("kt7430_wrongClassOnLHS.kt")
public void testKt7430_wrongClassOnLHS() throws Exception {
@@ -21,5 +21,5 @@ FILE: leakedImplicitType.kt
public final fun R|IB|.extFun(x: R|IA|): R|kotlin/Unit| {
}
public final fun testWithExpectedType(): R|kotlin/Unit| {
lval extFun_AA_B: R|IA.(IA) -> kotlin/Unit| = Q|IB|::<Unresolved reference: extFun>#
lval extFun_AA_B: R|IA.(IA) -> kotlin/Unit| = Q|IB|::<Ambiguity: extFun, [/extFun, /extFun]>#
}
@@ -14,5 +14,5 @@ fun IA.extFun(x: IB) {}
fun IB.extFun(x: IA) {}
fun testWithExpectedType() {
val extFun_AA_B: IA.(IA) -> Unit = IB::<!UNRESOLVED_REFERENCE!>extFun<!> // extFun is unresolved, type of IB::extFun is implicit
val extFun_AA_B: IA.(IA) -> Unit = IB::<!NONE_APPLICABLE!>extFun<!> // extFun is unresolved, type of IB::extFun is implicit
}
@@ -13,7 +13,7 @@ FILE: moreSpecificAmbiguousExtensions.kt
}
public final fun testWithExpectedType(): R|kotlin/Unit| {
lval extFun_AB_A: R|IA.(IB) -> kotlin/Unit| = Q|IA|::R|/extFun|
lval extFun_AA_B: R|IA.(IA) -> kotlin/Unit| = Q|IB|::<Unresolved reference: extFun>#
lval extFun_AA_B: R|IA.(IA) -> kotlin/Unit| = Q|IB|::<Ambiguity: extFun, [/extFun, /extFun]>#
lval extFun_BB_A: R|IB.(IB) -> kotlin/Unit| = Q|IA|::R|/extFun|
lval extFun_BA_B: R|IB.(IA) -> kotlin/Unit| = Q|IB|::R|/extFun|
lval extFun_BB_B: R|IB.(IB) -> kotlin/Unit| = Q|IB|::<Ambiguity: extFun, [/extFun, /extFun]>#
@@ -12,7 +12,7 @@ fun test() {
fun testWithExpectedType() {
// NB: should be resolved to kotlin/FunctionX, not kotlin/reflect/FunctionX
val extFun_AB_A: IA.(IB) -> Unit = IA::extFun
val extFun_AA_B: IA.(IA) -> Unit = IB::<!UNRESOLVED_REFERENCE!>extFun<!>
val extFun_AA_B: IA.(IA) -> Unit = IB::<!NONE_APPLICABLE!>extFun<!>
val extFun_BB_A: IB.(IB) -> Unit = IA::extFun
val extFun_BA_B: IB.(IA) -> Unit = IB::extFun
val extFun_BB_B: IB.(IB) -> Unit = IB::<!OVERLOAD_RESOLUTION_AMBIGUITY!>extFun<!>
@@ -3528,6 +3528,12 @@ public class FirLightTreeOldFrontendDiagnosticsTestGenerated extends AbstractFir
runTest("compiler/testData/diagnostics/tests/callableReference/kt52503.kt");
}
@Test
@TestMetadata("kt55373.kt")
public void testKt55373() throws Exception {
runTest("compiler/testData/diagnostics/tests/callableReference/kt55373.kt");
}
@Test
@TestMetadata("kt7430_wrongClassOnLHS.kt")
public void testKt7430_wrongClassOnLHS() throws Exception {
@@ -3534,6 +3534,12 @@ public class FirPsiOldFrontendDiagnosticsTestGenerated extends AbstractFirPsiDia
runTest("compiler/testData/diagnostics/tests/callableReference/kt52503.kt");
}
@Test
@TestMetadata("kt55373.kt")
public void testKt55373() throws Exception {
runTest("compiler/testData/diagnostics/tests/callableReference/kt55373.kt");
}
@Test
@TestMetadata("kt7430_wrongClassOnLHS.kt")
public void testKt7430_wrongClassOnLHS() throws Exception {
@@ -26,15 +26,19 @@ import org.jetbrains.kotlin.fir.expressions.*
import org.jetbrains.kotlin.fir.expressions.builder.buildArgumentList
import org.jetbrains.kotlin.fir.expressions.builder.buildFunctionCall
import org.jetbrains.kotlin.fir.moduleData
import org.jetbrains.kotlin.fir.references.FirErrorNamedReference
import org.jetbrains.kotlin.fir.references.FirReference
import org.jetbrains.kotlin.fir.references.FirResolvedCallableReference
import org.jetbrains.kotlin.fir.references.builder.buildErrorNamedReference
import org.jetbrains.kotlin.fir.references.builder.buildResolvedErrorReference
import org.jetbrains.kotlin.fir.references.impl.FirSimpleNamedReference
import org.jetbrains.kotlin.fir.references.impl.FirStubReference
import org.jetbrains.kotlin.fir.references.isError
import org.jetbrains.kotlin.fir.resolve.BodyResolveComponents
import org.jetbrains.kotlin.fir.resolve.ResolutionMode
import org.jetbrains.kotlin.fir.resolve.calls.*
import org.jetbrains.kotlin.fir.resolve.createErrorReferenceWithExistingCandidate
import org.jetbrains.kotlin.fir.resolve.diagnostics.ConeAmbiguityError
import org.jetbrains.kotlin.fir.resolve.diagnostics.ConeInapplicableCandidateError
import org.jetbrains.kotlin.fir.resolve.providers.symbolProvider
import org.jetbrains.kotlin.fir.resolve.toErrorReference
@@ -53,6 +57,7 @@ import org.jetbrains.kotlin.name.CallableId
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.resolve.ArrayFqNames
import org.jetbrains.kotlin.resolve.calls.tasks.ExplicitReceiverKind
import org.jetbrains.kotlin.resolve.calls.tower.CandidateApplicability
import org.jetbrains.kotlin.resolve.calls.tower.isSuccess
import org.jetbrains.kotlin.types.Variance
@@ -213,7 +218,7 @@ class FirSyntheticCallGenerator(
callableReferenceAccess: FirCallableReferenceAccess,
expectedTypeRef: FirTypeRef?,
context: ResolutionContext
): FirCallableReferenceAccess? {
): FirCallableReferenceAccess {
val argumentList = buildUnaryArgumentList(callableReferenceAccess)
val parameterTypeRef =
@@ -222,6 +227,80 @@ class FirSyntheticCallGenerator(
else -> context.session.builtinTypes.anyType
}
var reference = generateCalleeReferenceWithCandidate(callableReferenceAccess, argumentList, parameterTypeRef, context)
var initialCallWasUnresolved = false
if (reference is FirErrorReferenceWithCandidate && reference.diagnostic is ConeInapplicableCandidateError) {
// If the callable reference cannot be resolved with the expected type, let's try to resolve it with any type and report
// something like INITIALIZER_TYPE_MISMATCH or NONE_APPLICABLE instead of UNRESOLVED_REFERENCE.
check(callableReferenceAccess.calleeReference is FirSimpleNamedReference && callableReferenceAccess.typeRef is FirImplicitTypeRef) {
"Expected FirCallableReferenceAccess to be unresolved."
}
reference =
generateCalleeReferenceWithCandidate(callableReferenceAccess, argumentList, context.session.builtinTypes.anyType, context)
initialCallWasUnresolved = true
}
val fakeCall = buildFunctionCall {
calleeReference = reference
this.argumentList = argumentList
}
components.callCompleter.completeCall(fakeCall, ResolutionMode.ContextIndependent)
return callableReferenceAccess.apply { updateErrorsIfNecessary(fakeCall, initialCallWasUnresolved) }
}
private fun FirCallableReferenceAccess.updateErrorsIfNecessary(fakeCall: FirFunctionCall, initialCallWasUnresolved: Boolean) {
val fakeCallCalleeReference = fakeCall.calleeReference
val calleeReference = calleeReference
if (fakeCallCalleeReference.isError()) {
(calleeReference as? FirNamedReferenceWithCandidate)
?.toErrorReference(fakeCallCalleeReference.diagnostic)
?.let { replaceCalleeReference(it) }
if (!calleeReference.isError()) {
val resolvedReference = calleeReference as? FirResolvedCallableReference
?: error("By this time the actual callable reference must have already been resolved")
replaceCalleeReference(
buildResolvedErrorReference {
this.name = resolvedReference.name
this.source = resolvedReference.source
this.resolvedSymbol = resolvedReference.resolvedSymbol
this.diagnostic = fakeCallCalleeReference.diagnostic
}
)
}
} else if (initialCallWasUnresolved && calleeReference is FirErrorNamedReference) {
// If the initial call was unresolved, we tried to resolve with target type Any.
// If there are multiple applicable overloads, the applicability of the error reference is set to RESOLVED meaning we would
// report OVERLOAD_RESOLUTION_AMBIGUITY.
// This would be misleading since the opposite is actually true - no overloads were applicable.
// To fix this, we manually set the applicability to INAPPLICABLE.
(calleeReference.diagnostic as? ConeAmbiguityError)?.let {
val newCalleeReference = buildErrorNamedReference {
source = calleeReference.source
diagnostic = ConeAmbiguityError(
it.name,
CandidateApplicability.INAPPLICABLE,
it.candidates,
)
}
replaceCalleeReference(newCalleeReference)
}
}
}
private fun generateCalleeReferenceWithCandidate(
callableReferenceAccess: FirCallableReferenceAccess,
argumentList: FirArgumentList,
parameterTypeRef: FirResolvedTypeRef,
context: ResolutionContext,
): FirNamedReferenceWithCandidate {
val callableId = SyntheticCallableId.ACCEPT_SPECIFIC_TYPE
val functionSymbol = FirSyntheticFunctionSymbol(callableId)
// fun accept(p: <parameterTypeRef>): Unit
@@ -230,45 +309,14 @@ class FirSyntheticCallGenerator(
valueParameters += parameterTypeRef.toValueParameter("reference", functionSymbol, isVararg = false)
}.build()
val reference =
generateCalleeReferenceWithCandidate(
callableReferenceAccess,
function,
argumentList,
callableId.callableName,
CallKind.SyntheticIdForCallableReferencesResolution,
context,
)
val fakeCallElement = buildFunctionCall {
calleeReference = reference
this.argumentList = argumentList
}
val result = components.callCompleter.completeCall(fakeCallElement, ResolutionMode.ContextIndependent)
val completedCallableReference = result.argument as FirCallableReferenceAccess?
val callCalleeReference = result.calleeReference
if (callCalleeReference.isError()) {
(completedCallableReference?.calleeReference as? FirNamedReferenceWithCandidate)
?.toErrorReference(callCalleeReference.diagnostic)
?.let { completedCallableReference.replaceCalleeReference(it) }
if (!callableReferenceAccess.calleeReference.isError()) {
val resolvedReference = callableReferenceAccess.calleeReference as? FirResolvedCallableReference
?: error("By this time the actual callable reference must have already been resolved")
callableReferenceAccess.replaceCalleeReference(
buildResolvedErrorReference {
this.name = resolvedReference.name
this.source = resolvedReference.source
this.resolvedSymbol = resolvedReference.resolvedSymbol
this.diagnostic = callCalleeReference.diagnostic
}
)
}
}
return completedCallableReference
return generateCalleeReferenceWithCandidate(
callableReferenceAccess,
function,
argumentList,
callableId.callableName,
CallKind.SyntheticIdForCallableReferencesResolution,
context,
)
}
private fun generateCalleeReferenceWithCandidate(
@@ -1076,7 +1076,7 @@ open class FirExpressionsResolveTransformer(transformer: FirAbstractBodyResolveT
} else {
components.syntheticCallGenerator.resolveCallableReferenceWithSyntheticOuterCall(
callableReferenceAccess, data.expectedType, resolutionContext,
) ?: callableReferenceAccess
)
}.also {
dataFlowAnalyzer.exitCallableReference(it)
}
@@ -4,9 +4,9 @@ fun bar(x: String = "K"): String = x
fun dump(dumpStrategy: String) {
val k0: kotlin.reflect.KFunction0<String> = returnAdapter(::<!UNRESOLVED_REFERENCE!>foo<!>) // Error: ADAPTED_CALLABLE_REFERENCE_AGAINST_REFLECTION_TYPE
val k1: kotlin.reflect.KFunction0<String> = ::<!UNRESOLVED_REFERENCE!>foo<!>
val k1: kotlin.reflect.KFunction0<String> = <!INITIALIZER_TYPE_MISMATCH!>::foo<!>
// Should be error here, too
val k2: kotlin.reflect.KFunction0<String> = if (dumpStrategy == "KotlinLike") ::<!UNRESOLVED_REFERENCE!>foo<!> else ::<!UNRESOLVED_REFERENCE!>bar<!>
val k2: kotlin.reflect.KFunction0<String> = <!INITIALIZER_TYPE_MISMATCH, TYPE_MISMATCH!>if (dumpStrategy == "KotlinLike") ::foo else ::bar<!>
val f0: Function0<String> = returnAdapter(::<!UNRESOLVED_REFERENCE!>foo<!>)
val f1: Function0<String> = ::foo
@@ -22,6 +22,6 @@ class A3<T> {
fun test2(): (T) -> Unit = A3<T>()::<!NEW_INFERENCE_NO_INFORMATION_FOR_PARAMETER!>a3<!>
fun test3(): (Int) -> String = A3<Int>()::a3
fun <R> test4(): (R) -> Unit = this::<!UNRESOLVED_REFERENCE!>a3<!>
fun <R> test4(): (R) -> Unit = <!RETURN_TYPE_MISMATCH!>this::<!NEW_INFERENCE_NO_INFORMATION_FOR_PARAMETER!>a3<!><!>
fun <R> test5(): (T) -> R = this::a3
}
@@ -0,0 +1,7 @@
class One
fun one() = Unit
class Two
val test: Two = <!INITIALIZER_TYPE_MISMATCH!>::One<!>
val test2: Two = <!INITIALIZER_TYPE_MISMATCH!>::one<!>
val test3: (String) -> One = <!INITIALIZER_TYPE_MISMATCH!>::One<!>
@@ -0,0 +1,7 @@
class One
fun one() = Unit
class Two
val test: Two = <!TYPE_MISMATCH!>::<!TYPE_MISMATCH!>One<!><!>
val test2: Two = <!TYPE_MISMATCH!>::<!TYPE_MISMATCH!>one<!><!>
val test3: (String) -> One = <!TYPE_MISMATCH!>::<!TYPE_MISMATCH!>One<!><!>
@@ -1,9 +0,0 @@
// !DIAGNOSTICS: -UNUSED_PARAMETER
fun foo() {}
fun foo(s: String) {}
val x1 = ::<!OVERLOAD_RESOLUTION_AMBIGUITY!>foo<!>
val x2: () -> Unit = ::foo
val x3: (String) -> Unit = ::foo
val x4: (Int) -> Unit = ::<!UNRESOLVED_REFERENCE!>foo<!>
@@ -1,3 +1,4 @@
// FIR_IDENTICAL
// !DIAGNOSTICS: -UNUSED_PARAMETER
fun foo() {}
@@ -1,21 +0,0 @@
// !CHECK_TYPE
// !DIAGNOSTICS: -UNUSED_PARAMETER -UNUSED_VARIABLE
interface IA
interface IB : IA
fun IA.extFun(x: IB) {}
fun IB.extFun(x: IA) {}
fun test() {
val extFun1 = IA::extFun
val extFun2 = IB::<!OVERLOAD_RESOLUTION_AMBIGUITY!>extFun<!>
}
fun testWithExpectedType() {
val extFun_AB_A: IA.(IB) -> Unit = IA::extFun
val extFun_AA_B: IA.(IA) -> Unit = IB::<!UNRESOLVED_REFERENCE!>extFun<!>
val extFun_BB_A: IB.(IB) -> Unit = IA::extFun
val extFun_BA_B: IB.(IA) -> Unit = IB::extFun
val extFun_BB_B: IB.(IB) -> Unit = IB::<!OVERLOAD_RESOLUTION_AMBIGUITY!>extFun<!>
}
@@ -1,3 +1,4 @@
// FIR_IDENTICAL
// !CHECK_TYPE
// !DIAGNOSTICS: -UNUSED_PARAMETER -UNUSED_VARIABLE
@@ -17,7 +17,7 @@ public class Inv<T> {
// FILE: test.kt
fun test(inv: Inv<String>) {
val m: ((String) -> String) -> Inv<String> = inv::<!UNRESOLVED_REFERENCE!>map<!>
val m: ((String) -> String) -> Inv<String> = <!INITIALIZER_TYPE_MISMATCH!>inv::<!NEW_INFERENCE_NO_INFORMATION_FOR_PARAMETER!>map<!><!>
take(inv::<!UNRESOLVED_REFERENCE!>map<!>)
}
@@ -3534,6 +3534,12 @@ public class DiagnosticTestGenerated extends AbstractDiagnosticTest {
runTest("compiler/testData/diagnostics/tests/callableReference/kt52503.kt");
}
@Test
@TestMetadata("kt55373.kt")
public void testKt55373() throws Exception {
runTest("compiler/testData/diagnostics/tests/callableReference/kt55373.kt");
}
@Test
@TestMetadata("kt7430_wrongClassOnLHS.kt")
public void testKt7430_wrongClassOnLHS() throws Exception {