Analysis API: Fix issues related to implicit invoke calls:

- Correctly set explicit receiver value.
- Restore original function call from FirImplicitFunctionCall (i.e.,
calls implicitly resolved to `invoke`) to get the correct name for
getting all candidates.
- Collect candidates at all tower levels.

Also make order of candidate calls in tests deterministic.
This commit is contained in:
Mark Punzalan
2022-01-31 17:58:32 +00:00
committed by Ilya Kirillov
parent ace826c570
commit 9b9da94a09
30 changed files with 763 additions and 109 deletions
@@ -42,7 +42,10 @@ fun FirFunctionCall.isImplicitFunctionCall(): Boolean {
calleeReference.getCandidateSymbols().any(FirBasedSymbol<*>::isInvokeFunction)
}
private fun FirBasedSymbol<*>.isInvokeFunction() =
/**
* Returns `true` if the symbol is for a function named `invoke`.
*/
internal fun FirBasedSymbol<*>.isInvokeFunction() =
(this as? FirNamedFunctionSymbol)?.fir?.name == OperatorNameConventions.INVOKE
fun FirFunctionCall.getCalleeSymbol(): FirBasedSymbol<*>? =
@@ -9,11 +9,13 @@ import org.jetbrains.kotlin.analysis.api.calls.*
import org.jetbrains.kotlin.analysis.api.diagnostics.KtNonBoundToPsiErrorDiagnostic
import org.jetbrains.kotlin.analysis.api.fir.KtFirAnalysisSession
import org.jetbrains.kotlin.analysis.api.fir.getCandidateSymbols
import org.jetbrains.kotlin.analysis.api.fir.isInvokeFunction
import org.jetbrains.kotlin.analysis.api.fir.symbols.KtFirArrayOfSymbolProvider.arrayOf
import org.jetbrains.kotlin.analysis.api.fir.symbols.KtFirArrayOfSymbolProvider.arrayOfSymbol
import org.jetbrains.kotlin.analysis.api.fir.symbols.KtFirArrayOfSymbolProvider.arrayTypeToArrayOfCall
import org.jetbrains.kotlin.analysis.api.fir.symbols.KtFirFunctionSymbol
import org.jetbrains.kotlin.analysis.api.impl.barebone.parentOfType
import org.jetbrains.kotlin.analysis.api.impl.barebone.parentsOfType
import org.jetbrains.kotlin.analysis.api.impl.base.components.AbstractKtCallResolver
import org.jetbrains.kotlin.analysis.api.symbols.*
import org.jetbrains.kotlin.analysis.api.tokens.ValidityToken
@@ -21,14 +23,16 @@ import org.jetbrains.kotlin.analysis.api.types.KtSubstitutor
import org.jetbrains.kotlin.analysis.api.withValidityAssertion
import org.jetbrains.kotlin.analysis.low.level.api.fir.api.getOrBuildFir
import org.jetbrains.kotlin.analysis.low.level.api.fir.api.getOrBuildFirFile
import org.jetbrains.kotlin.analysis.low.level.api.fir.api.resolveToFirSymbol
import org.jetbrains.kotlin.diagnostics.KtDiagnostic
import org.jetbrains.kotlin.fir.*
import org.jetbrains.kotlin.fir.analysis.checkers.toRegularClassSymbol
import org.jetbrains.kotlin.fir.declarations.FirDeclaration
import org.jetbrains.kotlin.fir.declarations.FirFile
import org.jetbrains.kotlin.fir.declarations.FirResolvePhase
import org.jetbrains.kotlin.fir.declarations.FirValueParameter
import org.jetbrains.kotlin.fir.expressions.*
import org.jetbrains.kotlin.fir.expressions.builder.buildFunctionCall
import org.jetbrains.kotlin.fir.expressions.impl.FirNoReceiverExpression
import org.jetbrains.kotlin.fir.references.FirErrorNamedReference
import org.jetbrains.kotlin.fir.references.FirNamedReference
import org.jetbrains.kotlin.fir.references.FirResolvedNamedReference
@@ -38,6 +42,7 @@ import org.jetbrains.kotlin.fir.resolve.calls.AbstractCandidate
import org.jetbrains.kotlin.fir.resolve.calls.Candidate
import org.jetbrains.kotlin.fir.resolve.calls.ResolutionContext
import org.jetbrains.kotlin.fir.resolve.createConeDiagnosticForCandidateWithError
import org.jetbrains.kotlin.fir.resolve.dfa.unwrapSmartcastExpression
import org.jetbrains.kotlin.fir.resolve.diagnostics.ConeDiagnosticWithCandidates
import org.jetbrains.kotlin.fir.resolve.diagnostics.ConeHiddenCandidateError
import org.jetbrains.kotlin.fir.resolve.fullyExpandedType
@@ -52,8 +57,10 @@ import org.jetbrains.kotlin.fir.symbols.SymbolInternals
import org.jetbrains.kotlin.fir.symbols.impl.*
import org.jetbrains.kotlin.fir.types.*
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.KtPsiUtil.deparenthesize
import org.jetbrains.kotlin.resolve.calls.tasks.ExplicitReceiverKind
import org.jetbrains.kotlin.toKtPsiSourceElement
import org.jetbrains.kotlin.util.OperatorNameConventions
import org.jetbrains.kotlin.util.OperatorNameConventions.EQUALS
@@ -255,59 +262,88 @@ internal class KtFirCallResolver(
handleCompoundAccessCall(psi, fir, resolveFragmentOfCall)?.let { return it }
var firstArgIsExtensionReceiver = false
var isImplicitInvoke = false
val partiallyAppliedSymbol = if (candidate != null) {
// TODO: Ideally, we should get the substitutor from the candidate. But it seems there is no way to get the substitutor from the
// candidate, `Candidate.substitutor` is not complete. maybe we can carry over the final substitutor if it's available from
// body resolve phase?
val substitutor =
(fir as? FirQualifiedAccess)?.createSubstitutorFromTypeArguments(targetSymbol) ?: KtSubstitutor.Empty(token)
KtPartiallyAppliedSymbol(
unsubstitutedKtSignature.substitute(substitutor),
candidate.dispatchReceiverValue?.receiverExpression?.toKtReceiverValue(),
candidate.extensionReceiverValue?.receiverExpression?.toKtReceiverValue(),
)
} else if (fir is FirQualifiedAccess) {
val dispatchReceiver: KtReceiverValue?
val extensionReceiver: KtReceiverValue?
if (fir is FirImplicitInvokeCall) {
val explicitReceiverPsi = when (psi) {
is KtQualifiedExpression -> (psi.selectorExpression as KtCallExpression).calleeExpression
is KtCallExpression -> psi.calleeExpression
else -> error("unexpected PSI $psi for FirImplicitInvokeCall")
} ?: error("missing calleeExpression in PSI $psi for FirImplicitInvokeCall")
// For implicit invoke, the explicit receiver is always set in FIR and this receiver is the variable or property that has
// the `invoke` member function. In this case, we use the `calleeExpression` in the `KtCallExpression` as the PSI
// representation of this receiver. Caller can then use this PSI for further call resolution, which is implemented by the
// parameter `resolveCalleeExpressionOfFunctionCall` in `toKtCallInfo`.
val explicitReceiver = KtExplicitReceiverValue(explicitReceiverPsi, false, token)
// TODO: Ideally, we should get the substitutor from the candidate. But it seems there is no way to get the substitutor from the
// candidate, `Candidate.substitutor` is not complete. maybe we can carry over the final substitutor if it's available from
// body resolve phase?
val substitutor =
(fir as? FirQualifiedAccess)?.createSubstitutorFromTypeArguments(targetSymbol) ?: KtSubstitutor.Empty(token)
// Specially handle @ExtensionFunctionType
if (fir.dispatchReceiver.typeRef.coneTypeSafe<ConeKotlinType>()?.isExtensionFunctionType == true) {
firstArgIsExtensionReceiver = true
}
fun createKtPartiallyAppliedSymbolForImplicitInvoke(
dispatchReceiver: FirExpression,
extensionReceiver: FirExpression,
explicitReceiverKind: ExplicitReceiverKind
): KtPartiallyAppliedSymbol<KtCallableSymbol, KtSignature<KtCallableSymbol>> {
isImplicitInvoke = true
val explicitReceiverPsi = when (psi) {
is KtQualifiedExpression -> (psi.selectorExpression as KtCallExpression).calleeExpression
is KtCallExpression -> psi.calleeExpression
else -> error("unexpected PSI $psi for FirImplicitInvokeCall")
} ?: error("missing calleeExpression in PSI $psi for FirImplicitInvokeCall")
// For implicit invoke, the explicit receiver is always set in FIR and this receiver is the variable or property that has
// the `invoke` member function. In this case, we use the `calleeExpression` in the `KtCallExpression` as the PSI
// representation of this receiver. Caller can then use this PSI for further call resolution, which is implemented by the
// parameter `resolveCalleeExpressionOfFunctionCall` in `toKtCallInfo`.
val explicitReceiverValue = KtExplicitReceiverValue(explicitReceiverPsi, false, token)
if (fir.explicitReceiver == fir.dispatchReceiver) {
dispatchReceiver = explicitReceiver
if (firstArgIsExtensionReceiver) {
extensionReceiver = fir.arguments.first().toKtReceiverValue()
} else {
extensionReceiver = fir.extensionReceiver.toKtReceiverValue()
}
// Specially handle @ExtensionFunctionType
if (dispatchReceiver.typeRef.coneTypeSafe<ConeKotlinType>()?.isExtensionFunctionType == true) {
firstArgIsExtensionReceiver = true
}
val dispatchReceiverValue: KtReceiverValue?
val extensionReceiverValue: KtReceiverValue?
if (explicitReceiverKind == ExplicitReceiverKind.DISPATCH_RECEIVER) {
dispatchReceiverValue = explicitReceiverValue
if (firstArgIsExtensionReceiver) {
extensionReceiverValue = (fir as FirFunctionCall).arguments.first().toKtReceiverValue()
} else {
dispatchReceiver = fir.dispatchReceiver.toKtReceiverValue()
extensionReceiver = explicitReceiver
extensionReceiverValue = extensionReceiver.toKtReceiverValue()
}
} else {
dispatchReceiver = fir.dispatchReceiver.toKtReceiverValue()
extensionReceiver = fir.extensionReceiver.toKtReceiverValue()
dispatchReceiverValue = dispatchReceiver.toKtReceiverValue()
extensionReceiverValue = explicitReceiverValue
}
val substitutor = fir.createConeSubstitutorFromTypeArguments() ?: return null
KtPartiallyAppliedSymbol(
unsubstitutedKtSignature.substitute(substitutor.toKtSubstitutor()),
dispatchReceiver,
extensionReceiver,
return KtPartiallyAppliedSymbol(
unsubstitutedKtSignature.substitute(substitutor),
dispatchReceiverValue,
extensionReceiverValue,
)
}
val partiallyAppliedSymbol = if (candidate != null) {
if (fir is FirImplicitInvokeCall ||
(fir.calleeOrCandidateName != OperatorNameConventions.INVOKE && targetSymbol.isInvokeFunction())
) {
// Implicit invoke (e.g., `x()`) will have a different callee symbol (e.g., `x`) than the candidate (e.g., `invoke`).
createKtPartiallyAppliedSymbolForImplicitInvoke(
candidate.dispatchReceiverValue?.receiverExpression ?: FirNoReceiverExpression,
candidate.extensionReceiverValue?.receiverExpression ?: FirNoReceiverExpression,
candidate.explicitReceiverKind
)
} else {
KtPartiallyAppliedSymbol(
unsubstitutedKtSignature.substitute(substitutor),
candidate.dispatchReceiverValue?.receiverExpression?.toKtReceiverValue(),
candidate.extensionReceiverValue?.receiverExpression?.toKtReceiverValue(),
)
}
} else if (fir is FirQualifiedAccess) {
if (fir is FirImplicitInvokeCall) {
val explicitReceiverKind = if (fir.explicitReceiver == fir.dispatchReceiver) {
ExplicitReceiverKind.DISPATCH_RECEIVER
} else {
ExplicitReceiverKind.EXTENSION_RECEIVER
}
createKtPartiallyAppliedSymbolForImplicitInvoke(fir.dispatchReceiver, fir.extensionReceiver, explicitReceiverKind)
} else {
KtPartiallyAppliedSymbol(
unsubstitutedKtSignature.substitute(substitutor),
fir.dispatchReceiver.toKtReceiverValue(),
fir.extensionReceiver.toKtReceiverValue()
)
}
} else {
KtPartiallyAppliedSymbol(unsubstitutedKtSignature, _dispatchReceiver = null, _extensionReceiver = null)
}
@@ -366,7 +402,7 @@ internal class KtFirCallResolver(
argumentMappingWithoutExtensionReceiver
?.createArgumentMapping(partiallyAppliedSymbol.signature as KtFunctionLikeSignature<*>)
?: LinkedHashMap(),
fir is FirImplicitInvokeCall
isImplicitInvoke
)
}
is FirExpressionWithSmartcast -> createKtCall(psi, fir.originalExpression, candidate, resolveFragmentOfCall)
@@ -673,51 +709,50 @@ internal class KtFirCallResolver(
private val resolutionContext = ResolutionContext(firSession, bodyResolveComponents, stubBodyResolveTransformer.context)
@OptIn(PrivateForInline::class)
fun getAllCandidates(functionCall: FirFunctionCall, element: KtElement): List<KtCallInfo> {
val towerContext = firResolveState.getTowerContextProvider(element.containingKtFile).getClosestAvailableParentContext(element)
towerContext?.let { bodyResolveComponents.context.replaceTowerDataContext(it) }
// Note: All candidate symbols should have the same name
val name =
functionCall.calleeReference.getCandidateSymbols().firstOrNull()?.safeAs<FirFunctionSymbol<*>>()?.name ?: return emptyList()
return bodyResolveComponents.context.withFile(firFile, bodyResolveComponents) {
val candidates = bodyResolveComponents.callResolver.collectAllCandidates(
functionCall,
name,
element.getContainingDeclarations(),
initializeBodyResolveContext(element)
// If a function call is resolved to an implicit invoke call, the FirImplicitInvokeCall will have the `invoke()` function as the
// callee and the variable as the explicit receiver. To correctly get all candidates, we need to get the original function
// call's explicit receiver (if there is any) and callee (i.e., the variable).
val unwrappedExplicitReceiver = functionCall.explicitReceiver?.unwrapSmartcastExpression()
val originalFunctionCall =
if (functionCall is FirImplicitInvokeCall && unwrappedExplicitReceiver is FirPropertyAccessExpression) {
val originalCallee = unwrappedExplicitReceiver.calleeReference.safeAs<FirNamedReference>() ?: return emptyList()
buildFunctionCall {
source = functionCall.source
annotations.addAll(functionCall.annotations)
typeArguments.addAll(functionCall.typeArguments)
explicitReceiver = unwrappedExplicitReceiver.explicitReceiver
argumentList = functionCall.argumentList
calleeReference = originalCallee
}
} else {
functionCall
}
val calleeName = originalFunctionCall.calleeOrCandidateName ?: return emptyList()
val candidates = bodyResolveComponents.context.withFile(firFile, bodyResolveComponents) {
bodyResolveComponents.callResolver.collectAllCandidates(
originalFunctionCall,
calleeName,
bodyResolveComponents.context.containers,
resolutionContext
)
candidates.mapNotNull { convertToKtCallInfo(functionCall, element, it.candidate, it.isInBestCandidates) }
}
return candidates.mapNotNull { convertToKtCallInfo(originalFunctionCall, element, it.candidate, it.isInBestCandidates) }
}
private fun KtElement.getContainingDeclarations(): List<FirDeclaration> {
fun KtElement.getContainingKtDeclaration(): KtDeclaration? =
when (val container = this.parentOfType<KtDeclaration>()) {
is KtDestructuringDeclaration -> container.parentOfType()
else -> container
}
val containingDeclarations = mutableListOf<FirDeclaration>()
var current = getContainingKtDeclaration()
while (current != null) {
val firElement = current.getOrBuildFir(firResolveState)
val firDeclaration = when (firElement) {
is FirAnonymousObjectExpression -> firElement.anonymousObject
is FirAnonymousFunctionExpression -> firElement.anonymousFunction
is FirDeclaration -> firElement
else -> error(
"Expected a FirDeclaration for KtDeclaration (type: ${current::class.simpleName}) " +
"but was ${firElement?.let { it::class.simpleName }}. KtDeclaration text:\n${current.text}"
)
}
containingDeclarations += firDeclaration
current = current.getContainingKtDeclaration()
}
return containingDeclarations.asReversed()
@OptIn(PrivateForInline::class, SymbolInternals::class)
private fun initializeBodyResolveContext(element: KtElement) {
// Set up needed context to get all candidates.
val towerContext = firResolveState.getTowerContextProvider(element.containingKtFile).getClosestAvailableParentContext(element)
towerContext?.let { bodyResolveComponents.context.replaceTowerDataContext(it) }
val containingDeclarations =
element.parentsOfType<KtDeclaration>().map { it.resolveToFirSymbol(firResolveState).fir }.toList().asReversed()
bodyResolveComponents.context.containers.addAll(containingDeclarations)
}
@OptIn(SymbolInternals::class)
private fun convertToKtCallInfo(
functionCall: FirFunctionCall,
element: KtElement,
@@ -739,6 +774,34 @@ internal class KtFirCallResolver(
}
}
private val FirResolvable.calleeOrCandidateName: Name?
get() {
val calleeReference = calleeReference
if (calleeReference !is FirNamedReference) return null
// In most cases, we can get the callee name from the callee's candidate symbols. However, there is at least one case where we
// cannot do so:
// ```
// fun x(c: Char) {}
// fun call(x: kotlin.Int) {
// operator fun Int.invoke(a: Int) {}
// operator fun Int.invoke(b: Boolean) {}
// <expr>x()</expr>
// }
// ```
// The candidates for the call will both be `invoke`. We can keep it simple by getting the name from the callee reference's PSI
// element (`x` in the above example) if possible.
return when (val psi = calleeReference.psi) {
is KtNameReferenceExpression -> psi.getReferencedNameAsName()
else -> {
// This could be KtArrayAccessExpression or KtOperationReferenceExpression.
// Note: All candidate symbols should have the same name. We go by the symbol because `originalCallee.name` will include
// the applicability if not successful.
calleeReference.getCandidateSymbols().firstOrNull()?.safeAs<FirCallableSymbol<*>>()?.name
}
}
}
private fun FirArrayOfCall.toKtCallInfo(): KtCallInfo? {
val arrayOfSymbol = with(analysisSession) {
val type = typeRef.coneTypeSafe<ConeClassLikeType>()
@@ -40,6 +40,12 @@ public class FirResolveCallTestGenerated extends AbstractResolveCallTest {
runTest("analysis/analysis-api/testData/components/callResolver/resolveCall/ambiguous.kt");
}
@Test
@TestMetadata("ambiguousImplicitInvoke.kt")
public void testAmbiguousImplicitInvoke() throws Exception {
runTest("analysis/analysis-api/testData/components/callResolver/resolveCall/ambiguousImplicitInvoke.kt");
}
@Test
@TestMetadata("ambiguousWithExplicitTypeParameters.kt")
public void testAmbiguousWithExplicitTypeParameters() throws Exception {
@@ -55,6 +55,12 @@ public class FirResolveCandidatesTestGenerated extends AbstractResolveCandidates
runTest("analysis/analysis-api/testData/components/callResolver/resolveCandidates/multipleCandidates/ambiguous.kt");
}
@Test
@TestMetadata("ambiguousImplicitInvoke.kt")
public void testAmbiguousImplicitInvoke() throws Exception {
runTest("analysis/analysis-api/testData/components/callResolver/resolveCandidates/multipleCandidates/ambiguousImplicitInvoke.kt");
}
@Test
@TestMetadata("ambiguousWithExplicitTypeParameters.kt")
public void testAmbiguousWithExplicitTypeParameters() throws Exception {
@@ -66,6 +72,18 @@ public class FirResolveCandidatesTestGenerated extends AbstractResolveCandidates
public void testAmbiguousWithInferredTypeParameters() throws Exception {
runTest("analysis/analysis-api/testData/components/callResolver/resolveCandidates/multipleCandidates/ambiguousWithInferredTypeParameters.kt");
}
@Test
@TestMetadata("implicitInvoke.kt")
public void testImplicitInvoke() throws Exception {
runTest("analysis/analysis-api/testData/components/callResolver/resolveCandidates/multipleCandidates/implicitInvoke.kt");
}
@Test
@TestMetadata("implicitInvokeWithReceiver.kt")
public void testImplicitInvokeWithReceiver() throws Exception {
runTest("analysis/analysis-api/testData/components/callResolver/resolveCandidates/multipleCandidates/implicitInvokeWithReceiver.kt");
}
}
@Nested
@@ -77,6 +95,24 @@ public class FirResolveCandidatesTestGenerated extends AbstractResolveCandidates
KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("analysis/analysis-api/testData/components/callResolver/resolveCandidates/singleCandidate"), Pattern.compile("^(.+)\\.kt$"), null, true);
}
@Test
@TestMetadata("consecutiveImplicitInvoke1.kt")
public void testConsecutiveImplicitInvoke1() throws Exception {
runTest("analysis/analysis-api/testData/components/callResolver/resolveCandidates/singleCandidate/consecutiveImplicitInvoke1.kt");
}
@Test
@TestMetadata("consecutiveImplicitInvoke2.kt")
public void testConsecutiveImplicitInvoke2() throws Exception {
runTest("analysis/analysis-api/testData/components/callResolver/resolveCandidates/singleCandidate/consecutiveImplicitInvoke2.kt");
}
@Test
@TestMetadata("consecutiveImplicitInvoke3.kt")
public void testConsecutiveImplicitInvoke3() throws Exception {
runTest("analysis/analysis-api/testData/components/callResolver/resolveCandidates/singleCandidate/consecutiveImplicitInvoke3.kt");
}
@Test
@TestMetadata("functionCall.kt")
public void testFunctionCall() throws Exception {