From 9b9da94a096b6fec35c0eaa566331c1e0db5ef52 Mon Sep 17 00:00:00 2001 From: Mark Punzalan Date: Mon, 31 Jan 2022 17:58:32 +0000 Subject: [PATCH] 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. --- .../Fe10ResolveCallTestGenerated.java | 6 + .../kotlin/analysis/api/fir/FirUtils.kt | 5 +- .../api/fir/components/KtFirCallResolver.kt | 231 +++++++++++------- .../FirResolveCallTestGenerated.java | 6 + .../FirResolveCandidatesTestGenerated.java | 36 +++ .../impl/base/test/components/testUtils.kt | 20 +- .../ambiguousImplicitInvoke.descriptors.txt | 44 ++++ .../resolveCall/ambiguousImplicitInvoke.kt | 7 + .../resolveCall/ambiguousImplicitInvoke.txt | 42 ++++ ...legatedConstructorCall_this_unresolved.txt | 34 +-- .../multipleCandidates/ambiguous.txt | 6 + .../ambiguousImplicitInvoke.kt | 7 + .../ambiguousImplicitInvoke.txt | 69 ++++++ .../ambiguousWithExplicitTypeParameters.txt | 9 + .../ambiguousWithInferredTypeParameters.txt | 9 + .../multipleCandidates/implicitInvoke.kt | 8 + .../multipleCandidates/implicitInvoke.txt | 82 +++++++ .../implicitInvokeWithReceiver.kt | 9 + .../implicitInvokeWithReceiver.txt | 56 +++++ .../consecutiveImplicitInvoke1.kt | 6 + .../consecutiveImplicitInvoke1.txt | 50 ++++ .../consecutiveImplicitInvoke2.kt | 6 + .../consecutiveImplicitInvoke2.txt | 50 ++++ .../consecutiveImplicitInvoke3.kt | 6 + .../consecutiveImplicitInvoke3.txt | 50 ++++ .../singleCandidate/variableAsFunction.txt | 4 +- .../variableAsFunctionLikeCall.txt | 4 +- .../jetbrains/kotlin/fir/FirCallResolver.kt | 3 + .../kotlin/fir/resolve/ResolveUtils.kt | 5 + .../fir/resolve/calls/CandidateCollector.kt | 2 +- 30 files changed, 763 insertions(+), 109 deletions(-) create mode 100644 analysis/analysis-api/testData/components/callResolver/resolveCall/ambiguousImplicitInvoke.descriptors.txt create mode 100644 analysis/analysis-api/testData/components/callResolver/resolveCall/ambiguousImplicitInvoke.kt create mode 100644 analysis/analysis-api/testData/components/callResolver/resolveCall/ambiguousImplicitInvoke.txt create mode 100644 analysis/analysis-api/testData/components/callResolver/resolveCandidates/multipleCandidates/ambiguousImplicitInvoke.kt create mode 100644 analysis/analysis-api/testData/components/callResolver/resolveCandidates/multipleCandidates/ambiguousImplicitInvoke.txt create mode 100644 analysis/analysis-api/testData/components/callResolver/resolveCandidates/multipleCandidates/implicitInvoke.kt create mode 100644 analysis/analysis-api/testData/components/callResolver/resolveCandidates/multipleCandidates/implicitInvoke.txt create mode 100644 analysis/analysis-api/testData/components/callResolver/resolveCandidates/multipleCandidates/implicitInvokeWithReceiver.kt create mode 100644 analysis/analysis-api/testData/components/callResolver/resolveCandidates/multipleCandidates/implicitInvokeWithReceiver.txt create mode 100644 analysis/analysis-api/testData/components/callResolver/resolveCandidates/singleCandidate/consecutiveImplicitInvoke1.kt create mode 100644 analysis/analysis-api/testData/components/callResolver/resolveCandidates/singleCandidate/consecutiveImplicitInvoke1.txt create mode 100644 analysis/analysis-api/testData/components/callResolver/resolveCandidates/singleCandidate/consecutiveImplicitInvoke2.kt create mode 100644 analysis/analysis-api/testData/components/callResolver/resolveCandidates/singleCandidate/consecutiveImplicitInvoke2.txt create mode 100644 analysis/analysis-api/testData/components/callResolver/resolveCandidates/singleCandidate/consecutiveImplicitInvoke3.kt create mode 100644 analysis/analysis-api/testData/components/callResolver/resolveCandidates/singleCandidate/consecutiveImplicitInvoke3.txt diff --git a/analysis/analysis-api-fe10/tests/org/jetbrains/kotlin/analysis/api/fe10/components/Fe10ResolveCallTestGenerated.java b/analysis/analysis-api-fe10/tests/org/jetbrains/kotlin/analysis/api/fe10/components/Fe10ResolveCallTestGenerated.java index 04a4d2b3ce3..10524f674c9 100644 --- a/analysis/analysis-api-fe10/tests/org/jetbrains/kotlin/analysis/api/fe10/components/Fe10ResolveCallTestGenerated.java +++ b/analysis/analysis-api-fe10/tests/org/jetbrains/kotlin/analysis/api/fe10/components/Fe10ResolveCallTestGenerated.java @@ -40,6 +40,12 @@ public class Fe10ResolveCallTestGenerated 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 { diff --git a/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/FirUtils.kt b/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/FirUtils.kt index 34645d5cb7b..328b9796781 100644 --- a/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/FirUtils.kt +++ b/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/FirUtils.kt @@ -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<*>? = diff --git a/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/components/KtFirCallResolver.kt b/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/components/KtFirCallResolver.kt index 8bf6e2ff259..212795c5dc1 100644 --- a/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/components/KtFirCallResolver.kt +++ b/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/components/KtFirCallResolver.kt @@ -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()?.isExtensionFunctionType == true) { - firstArgIsExtensionReceiver = true - } + fun createKtPartiallyAppliedSymbolForImplicitInvoke( + dispatchReceiver: FirExpression, + extensionReceiver: FirExpression, + explicitReceiverKind: ExplicitReceiverKind + ): KtPartiallyAppliedSymbol> { + 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()?.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 { - 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>()?.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() ?: 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 { - fun KtElement.getContainingKtDeclaration(): KtDeclaration? = - when (val container = this.parentOfType()) { - is KtDestructuringDeclaration -> container.parentOfType() - else -> container - } - - val containingDeclarations = mutableListOf() - 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().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) {} + // x() + // } + // ``` + // 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>()?.name + } + } + } + private fun FirArrayOfCall.toKtCallInfo(): KtCallInfo? { val arrayOfSymbol = with(analysisSession) { val type = typeRef.coneTypeSafe() diff --git a/analysis/analysis-api-fir/tests/org/jetbrains/kotlin/analysis/api/fir/components/FirResolveCallTestGenerated.java b/analysis/analysis-api-fir/tests/org/jetbrains/kotlin/analysis/api/fir/components/FirResolveCallTestGenerated.java index 0d3d417a16d..f617f850aa0 100644 --- a/analysis/analysis-api-fir/tests/org/jetbrains/kotlin/analysis/api/fir/components/FirResolveCallTestGenerated.java +++ b/analysis/analysis-api-fir/tests/org/jetbrains/kotlin/analysis/api/fir/components/FirResolveCallTestGenerated.java @@ -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 { diff --git a/analysis/analysis-api-fir/tests/org/jetbrains/kotlin/analysis/api/fir/components/FirResolveCandidatesTestGenerated.java b/analysis/analysis-api-fir/tests/org/jetbrains/kotlin/analysis/api/fir/components/FirResolveCandidatesTestGenerated.java index beecad97ffe..a276897f003 100644 --- a/analysis/analysis-api-fir/tests/org/jetbrains/kotlin/analysis/api/fir/components/FirResolveCandidatesTestGenerated.java +++ b/analysis/analysis-api-fir/tests/org/jetbrains/kotlin/analysis/api/fir/components/FirResolveCandidatesTestGenerated.java @@ -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 { diff --git a/analysis/analysis-api-impl-base/tests/org/jetbrains/kotlin/analysis/api/impl/base/test/components/testUtils.kt b/analysis/analysis-api-impl-base/tests/org/jetbrains/kotlin/analysis/api/impl/base/test/components/testUtils.kt index b979e53e1a6..0940a99c080 100644 --- a/analysis/analysis-api-impl-base/tests/org/jetbrains/kotlin/analysis/api/impl/base/test/components/testUtils.kt +++ b/analysis/analysis-api-impl-base/tests/org/jetbrains/kotlin/analysis/api/impl/base/test/components/testUtils.kt @@ -7,7 +7,9 @@ package org.jetbrains.kotlin.analysis.api.impl.base.test.components import com.intellij.psi.PsiElement import org.jetbrains.kotlin.analysis.api.KtAnalysisSession +import org.jetbrains.kotlin.analysis.api.calls.KtCall import org.jetbrains.kotlin.analysis.api.calls.KtCallInfo +import org.jetbrains.kotlin.analysis.api.calls.KtCallableMemberCall import org.jetbrains.kotlin.analysis.api.diagnostics.KtDiagnostic import org.jetbrains.kotlin.analysis.api.impl.base.KtMapBackedSubstitutor import org.jetbrains.kotlin.analysis.api.symbols.* @@ -80,7 +82,8 @@ internal fun KtAnalysisSession.stringRepresentation(call: KtCallInfo): String { is Name -> asString() else -> buildString { val clazz = this@stringValue::class - append(clazz.simpleName!!) + val className = clazz.simpleName!! + append(className) appendLine(":") clazz.memberProperties .filter { it.name != "token" && it.visibility == KVisibility.PUBLIC } @@ -88,9 +91,18 @@ internal fun KtAnalysisSession.stringRepresentation(call: KtCallInfo): String { val name = property.name @Suppress("UNCHECKED_CAST") - val value = - (property as KProperty1).get(this@stringValue)?.stringValue()?.indented() - "$name = $value" + val value = (property as KProperty1).get(this@stringValue)?.let { + if (className == "KtErrorCallInfo" && name == "candidateCalls") { + // The order of calls in KtErrorCallInfo.candidateCalls is non-deterministic. Sort by symbol string value. + (it as Collection).sortedWith { call1, call2 -> + if (call1 is KtCallableMemberCall<*, *> && call2 is KtCallableMemberCall<*, *>) { + call1.partiallyAppliedSymbol.stringValue().compareTo(call2.partiallyAppliedSymbol.stringValue()) + } else 0 + } + } else it + } + val valueAsString = value?.stringValue()?.indented() + "$name = $valueAsString" } } } diff --git a/analysis/analysis-api/testData/components/callResolver/resolveCall/ambiguousImplicitInvoke.descriptors.txt b/analysis/analysis-api/testData/components/callResolver/resolveCall/ambiguousImplicitInvoke.descriptors.txt new file mode 100644 index 00000000000..3bb2ae0b6b6 --- /dev/null +++ b/analysis/analysis-api/testData/components/callResolver/resolveCall/ambiguousImplicitInvoke.descriptors.txt @@ -0,0 +1,44 @@ +KtErrorCallInfo: + candidateCalls = [ + KtSimpleFunctionCall: + isImplicitInvoke = true + partiallyAppliedSymbol = KtPartiallyAppliedSymbol: + dispatchReceiver = null + extensionReceiver = KtExplicitReceiverValue: + expression = x + isSafeNavigation = false + signature = KtFunctionLikeSignature: + receiverType = kotlin.Int + returnType = kotlin.Unit + symbol = invoke(: kotlin.Int, a: kotlin.String): kotlin.Unit + valueParameters = [ + KtVariableLikeSignature: + name = a + receiverType = null + returnType = kotlin.String + symbol = a: kotlin.String + ] + argumentMapping = {}, + KtSimpleFunctionCall: + isImplicitInvoke = true + partiallyAppliedSymbol = KtPartiallyAppliedSymbol: + dispatchReceiver = null + extensionReceiver = KtExplicitReceiverValue: + expression = x + isSafeNavigation = false + signature = KtFunctionLikeSignature: + receiverType = kotlin.Int + returnType = kotlin.Unit + symbol = invoke(: kotlin.Int, b: kotlin.Boolean): kotlin.Unit + valueParameters = [ + KtVariableLikeSignature: + name = b + receiverType = null + returnType = kotlin.Boolean + symbol = b: kotlin.Boolean + ] + argumentMapping = {} + ] + diagnostic = ERROR \ No newline at end of file diff --git a/analysis/analysis-api/testData/components/callResolver/resolveCall/ambiguousImplicitInvoke.kt b/analysis/analysis-api/testData/components/callResolver/resolveCall/ambiguousImplicitInvoke.kt new file mode 100644 index 00000000000..33143f1cb62 --- /dev/null +++ b/analysis/analysis-api/testData/components/callResolver/resolveCall/ambiguousImplicitInvoke.kt @@ -0,0 +1,7 @@ +fun x(c: Char) {} + +fun call(x: kotlin.Int) { + operator fun Int.invoke(a: String) {} + operator fun Int.invoke(b: Boolean) {} + x() +} \ No newline at end of file diff --git a/analysis/analysis-api/testData/components/callResolver/resolveCall/ambiguousImplicitInvoke.txt b/analysis/analysis-api/testData/components/callResolver/resolveCall/ambiguousImplicitInvoke.txt new file mode 100644 index 00000000000..6fabb39d6df --- /dev/null +++ b/analysis/analysis-api/testData/components/callResolver/resolveCall/ambiguousImplicitInvoke.txt @@ -0,0 +1,42 @@ +KtErrorCallInfo: + candidateCalls = [ + KtSimpleFunctionCall: + isImplicitInvoke = true + partiallyAppliedSymbol = KtPartiallyAppliedSymbol: + dispatchReceiver = null + extensionReceiver = KtExplicitReceiverValue: + expression = x + isSafeNavigation = false + signature = KtFunctionLikeSignature: + receiverType = kotlin.Int + returnType = kotlin.Unit + symbol = invoke(: kotlin.Int, a: kotlin.String): kotlin.Unit + valueParameters = [ + KtVariableLikeSignature: + name = a + receiverType = null + returnType = kotlin.String + symbol = a: kotlin.String + ] + argumentMapping = {}, + KtSimpleFunctionCall: + isImplicitInvoke = true + partiallyAppliedSymbol = KtPartiallyAppliedSymbol: + dispatchReceiver = null + extensionReceiver = KtExplicitReceiverValue: + expression = x + isSafeNavigation = false + signature = KtFunctionLikeSignature: + receiverType = kotlin.Int + returnType = kotlin.Unit + symbol = invoke(: kotlin.Int, b: kotlin.Boolean): kotlin.Unit + valueParameters = [ + KtVariableLikeSignature: + name = b + receiverType = null + returnType = kotlin.Boolean + symbol = b: kotlin.Boolean + ] + argumentMapping = {} + ] + diagnostic = ERROR/invoke, /invoke]> \ No newline at end of file diff --git a/analysis/analysis-api/testData/components/callResolver/resolveCall/delegatedConstructorCall_this_unresolved.txt b/analysis/analysis-api/testData/components/callResolver/resolveCall/delegatedConstructorCall_this_unresolved.txt index e3a87692e26..56b62096a57 100644 --- a/analysis/analysis-api/testData/components/callResolver/resolveCall/delegatedConstructorCall_this_unresolved.txt +++ b/analysis/analysis-api/testData/components/callResolver/resolveCall/delegatedConstructorCall_this_unresolved.txt @@ -1,22 +1,5 @@ KtErrorCallInfo: candidateCalls = [ - KtDelegatedConstructorCall: - kind = THIS_CALL - partiallyAppliedSymbol = KtPartiallyAppliedSymbol: - dispatchReceiver = null - extensionReceiver = null - signature = KtFunctionLikeSignature: - receiverType = null - returnType = Sub - symbol = (p: kotlin.Int): Sub - valueParameters = [ - KtVariableLikeSignature: - name = p - receiverType = null - returnType = kotlin.Int - symbol = p: kotlin.Int - ] - argumentMapping = {}, KtDelegatedConstructorCall: kind = THIS_CALL partiallyAppliedSymbol = KtPartiallyAppliedSymbol: @@ -38,6 +21,23 @@ KtErrorCallInfo: returnType = kotlin.Int symbol = j: kotlin.Int ] + argumentMapping = {}, + KtDelegatedConstructorCall: + kind = THIS_CALL + partiallyAppliedSymbol = KtPartiallyAppliedSymbol: + dispatchReceiver = null + extensionReceiver = null + signature = KtFunctionLikeSignature: + receiverType = null + returnType = Sub + symbol = (p: kotlin.Int): Sub + valueParameters = [ + KtVariableLikeSignature: + name = p + receiverType = null + returnType = kotlin.Int + symbol = p: kotlin.Int + ] argumentMapping = {} ] diagnostic = ERROR diff --git a/analysis/analysis-api/testData/components/callResolver/resolveCandidates/multipleCandidates/ambiguous.txt b/analysis/analysis-api/testData/components/callResolver/resolveCandidates/multipleCandidates/ambiguous.txt index d42bc7ece8f..e28a8a58295 100644 --- a/analysis/analysis-api/testData/components/callResolver/resolveCandidates/multipleCandidates/ambiguous.txt +++ b/analysis/analysis-api/testData/components/callResolver/resolveCandidates/multipleCandidates/ambiguous.txt @@ -11,12 +11,14 @@ KtErrorCallInfo: symbol = /function(a: kotlin.Char): kotlin.Unit valueParameters = [ KtVariableLikeSignature: + name = a receiverType = null returnType = kotlin.Char symbol = a: kotlin.Char ] argumentMapping = { 1 -> (KtVariableLikeSignature: + name = a receiverType = null returnType = kotlin.Char symbol = a: kotlin.Char) @@ -37,12 +39,14 @@ KtErrorCallInfo: symbol = /function(b: kotlin.Boolean): kotlin.Unit valueParameters = [ KtVariableLikeSignature: + name = b receiverType = null returnType = kotlin.Boolean symbol = b: kotlin.Boolean ] argumentMapping = { 1 -> (KtVariableLikeSignature: + name = b receiverType = null returnType = kotlin.Boolean symbol = b: kotlin.Boolean) @@ -63,12 +67,14 @@ KtErrorCallInfo: symbol = /function(c: kotlin.String): kotlin.Unit valueParameters = [ KtVariableLikeSignature: + name = c receiverType = null returnType = kotlin.String symbol = c: kotlin.String ] argumentMapping = { 1 -> (KtVariableLikeSignature: + name = c receiverType = null returnType = kotlin.String symbol = c: kotlin.String) diff --git a/analysis/analysis-api/testData/components/callResolver/resolveCandidates/multipleCandidates/ambiguousImplicitInvoke.kt b/analysis/analysis-api/testData/components/callResolver/resolveCandidates/multipleCandidates/ambiguousImplicitInvoke.kt new file mode 100644 index 00000000000..33143f1cb62 --- /dev/null +++ b/analysis/analysis-api/testData/components/callResolver/resolveCandidates/multipleCandidates/ambiguousImplicitInvoke.kt @@ -0,0 +1,7 @@ +fun x(c: Char) {} + +fun call(x: kotlin.Int) { + operator fun Int.invoke(a: String) {} + operator fun Int.invoke(b: Boolean) {} + x() +} \ No newline at end of file diff --git a/analysis/analysis-api/testData/components/callResolver/resolveCandidates/multipleCandidates/ambiguousImplicitInvoke.txt b/analysis/analysis-api/testData/components/callResolver/resolveCandidates/multipleCandidates/ambiguousImplicitInvoke.txt new file mode 100644 index 00000000000..5439473eb9d --- /dev/null +++ b/analysis/analysis-api/testData/components/callResolver/resolveCandidates/multipleCandidates/ambiguousImplicitInvoke.txt @@ -0,0 +1,69 @@ +KtErrorCallInfo: + candidateCalls = [ + KtSimpleFunctionCall: + isImplicitInvoke = true + partiallyAppliedSymbol = KtPartiallyAppliedSymbol: + dispatchReceiver = null + extensionReceiver = KtExplicitReceiverValue: + expression = x + isSafeNavigation = false + signature = KtFunctionLikeSignature: + receiverType = kotlin.Int + returnType = kotlin.Unit + symbol = invoke(: kotlin.Int, a: kotlin.String): kotlin.Unit + valueParameters = [ + KtVariableLikeSignature: + name = a + receiverType = null + returnType = kotlin.String + symbol = a: kotlin.String + ] + argumentMapping = {} + ] + diagnostic = ERROR + +KtErrorCallInfo: + candidateCalls = [ + KtSimpleFunctionCall: + isImplicitInvoke = true + partiallyAppliedSymbol = KtPartiallyAppliedSymbol: + dispatchReceiver = null + extensionReceiver = KtExplicitReceiverValue: + expression = x + isSafeNavigation = false + signature = KtFunctionLikeSignature: + receiverType = kotlin.Int + returnType = kotlin.Unit + symbol = invoke(: kotlin.Int, b: kotlin.Boolean): kotlin.Unit + valueParameters = [ + KtVariableLikeSignature: + name = b + receiverType = null + returnType = kotlin.Boolean + symbol = b: kotlin.Boolean + ] + argumentMapping = {} + ] + diagnostic = ERROR + +KtErrorCallInfo: + candidateCalls = [ + KtSimpleFunctionCall: + isImplicitInvoke = false + partiallyAppliedSymbol = KtPartiallyAppliedSymbol: + dispatchReceiver = null + extensionReceiver = null + signature = KtFunctionLikeSignature: + receiverType = null + returnType = kotlin.Unit + symbol = /x(c: kotlin.Char): kotlin.Unit + valueParameters = [ + KtVariableLikeSignature: + name = c + receiverType = null + returnType = kotlin.Char + symbol = c: kotlin.Char + ] + argumentMapping = {} + ] + diagnostic = ERROR diff --git a/analysis/analysis-api/testData/components/callResolver/resolveCandidates/multipleCandidates/ambiguousWithExplicitTypeParameters.txt b/analysis/analysis-api/testData/components/callResolver/resolveCandidates/multipleCandidates/ambiguousWithExplicitTypeParameters.txt index 0a86d9af2a3..5fe1ca1a30c 100644 --- a/analysis/analysis-api/testData/components/callResolver/resolveCandidates/multipleCandidates/ambiguousWithExplicitTypeParameters.txt +++ b/analysis/analysis-api/testData/components/callResolver/resolveCandidates/multipleCandidates/ambiguousWithExplicitTypeParameters.txt @@ -11,16 +11,19 @@ KtErrorCallInfo: symbol = /function(t: T, a: kotlin.Char): kotlin.Unit valueParameters = [ KtVariableLikeSignature: + name = t receiverType = null returnType = kotlin.Int symbol = t: T, KtVariableLikeSignature: + name = a receiverType = null returnType = kotlin.Char symbol = a: kotlin.Char ] argumentMapping = { 1 -> (KtVariableLikeSignature: + name = t receiverType = null returnType = kotlin.Int symbol = t: T) @@ -41,16 +44,19 @@ KtErrorCallInfo: symbol = /function(u: U, b: kotlin.Boolean): kotlin.Unit valueParameters = [ KtVariableLikeSignature: + name = u receiverType = null returnType = kotlin.Int symbol = u: U, KtVariableLikeSignature: + name = b receiverType = null returnType = kotlin.Boolean symbol = b: kotlin.Boolean ] argumentMapping = { 1 -> (KtVariableLikeSignature: + name = u receiverType = null returnType = kotlin.Int symbol = u: U) @@ -71,16 +77,19 @@ KtErrorCallInfo: symbol = /function(v: V, c: kotlin.String): kotlin.Unit valueParameters = [ KtVariableLikeSignature: + name = v receiverType = null returnType = kotlin.Int symbol = v: V, KtVariableLikeSignature: + name = c receiverType = null returnType = kotlin.String symbol = c: kotlin.String ] argumentMapping = { 1 -> (KtVariableLikeSignature: + name = v receiverType = null returnType = kotlin.Int symbol = v: V) diff --git a/analysis/analysis-api/testData/components/callResolver/resolveCandidates/multipleCandidates/ambiguousWithInferredTypeParameters.txt b/analysis/analysis-api/testData/components/callResolver/resolveCandidates/multipleCandidates/ambiguousWithInferredTypeParameters.txt index 59e3940faaf..943f517bbed 100644 --- a/analysis/analysis-api/testData/components/callResolver/resolveCandidates/multipleCandidates/ambiguousWithInferredTypeParameters.txt +++ b/analysis/analysis-api/testData/components/callResolver/resolveCandidates/multipleCandidates/ambiguousWithInferredTypeParameters.txt @@ -11,16 +11,19 @@ KtErrorCallInfo: symbol = /function(t: T, a: kotlin.Char): kotlin.Unit valueParameters = [ KtVariableLikeSignature: + name = t receiverType = null returnType = T symbol = t: T, KtVariableLikeSignature: + name = a receiverType = null returnType = kotlin.Char symbol = a: kotlin.Char ] argumentMapping = { 1 -> (KtVariableLikeSignature: + name = t receiverType = null returnType = T symbol = t: T) @@ -41,16 +44,19 @@ KtErrorCallInfo: symbol = /function(u: U, b: kotlin.Boolean): kotlin.Unit valueParameters = [ KtVariableLikeSignature: + name = u receiverType = null returnType = U symbol = u: U, KtVariableLikeSignature: + name = b receiverType = null returnType = kotlin.Boolean symbol = b: kotlin.Boolean ] argumentMapping = { 1 -> (KtVariableLikeSignature: + name = u receiverType = null returnType = U symbol = u: U) @@ -71,16 +77,19 @@ KtErrorCallInfo: symbol = /function(v: V, c: kotlin.String): kotlin.Unit valueParameters = [ KtVariableLikeSignature: + name = v receiverType = null returnType = V symbol = v: V, KtVariableLikeSignature: + name = c receiverType = null returnType = kotlin.String symbol = c: kotlin.String ] argumentMapping = { 1 -> (KtVariableLikeSignature: + name = v receiverType = null returnType = V symbol = v: V) diff --git a/analysis/analysis-api/testData/components/callResolver/resolveCandidates/multipleCandidates/implicitInvoke.kt b/analysis/analysis-api/testData/components/callResolver/resolveCandidates/multipleCandidates/implicitInvoke.kt new file mode 100644 index 00000000000..21d9a07b5fa --- /dev/null +++ b/analysis/analysis-api/testData/components/callResolver/resolveCandidates/multipleCandidates/implicitInvoke.kt @@ -0,0 +1,8 @@ +operator fun Int.invoke(i: Int) {} + +fun x(c: Char) {} + +fun call(x: kotlin.Int) { + fun x(b: Boolean) {} + x(true) +} \ No newline at end of file diff --git a/analysis/analysis-api/testData/components/callResolver/resolveCandidates/multipleCandidates/implicitInvoke.txt b/analysis/analysis-api/testData/components/callResolver/resolveCandidates/multipleCandidates/implicitInvoke.txt new file mode 100644 index 00000000000..04ef1bc6baf --- /dev/null +++ b/analysis/analysis-api/testData/components/callResolver/resolveCandidates/multipleCandidates/implicitInvoke.txt @@ -0,0 +1,82 @@ +KtSuccessCallInfo: + call = KtSimpleFunctionCall: + isImplicitInvoke = false + partiallyAppliedSymbol = KtPartiallyAppliedSymbol: + dispatchReceiver = null + extensionReceiver = null + signature = KtFunctionLikeSignature: + receiverType = null + returnType = kotlin.Unit + symbol = x(b: kotlin.Boolean): kotlin.Unit + valueParameters = [ + KtVariableLikeSignature: + name = b + receiverType = null + returnType = kotlin.Boolean + symbol = b: kotlin.Boolean + ] + argumentMapping = { + true -> (KtVariableLikeSignature: + name = b + receiverType = null + returnType = kotlin.Boolean + symbol = b: kotlin.Boolean) + } + +KtErrorCallInfo: + candidateCalls = [ + KtSimpleFunctionCall: + isImplicitInvoke = false + partiallyAppliedSymbol = KtPartiallyAppliedSymbol: + dispatchReceiver = null + extensionReceiver = null + signature = KtFunctionLikeSignature: + receiverType = null + returnType = kotlin.Unit + symbol = /x(c: kotlin.Char): kotlin.Unit + valueParameters = [ + KtVariableLikeSignature: + name = c + receiverType = null + returnType = kotlin.Char + symbol = c: kotlin.Char + ] + argumentMapping = { + true -> (KtVariableLikeSignature: + name = c + receiverType = null + returnType = kotlin.Char + symbol = c: kotlin.Char) + } + ] + diagnostic = ERROR + +KtErrorCallInfo: + candidateCalls = [ + KtSimpleFunctionCall: + isImplicitInvoke = true + partiallyAppliedSymbol = KtPartiallyAppliedSymbol: + dispatchReceiver = null + extensionReceiver = KtExplicitReceiverValue: + expression = x + isSafeNavigation = false + signature = KtFunctionLikeSignature: + receiverType = kotlin.Int + returnType = kotlin.Unit + symbol = /invoke(: kotlin.Int, i: kotlin.Int): kotlin.Unit + valueParameters = [ + KtVariableLikeSignature: + name = i + receiverType = null + returnType = kotlin.Int + symbol = i: kotlin.Int + ] + argumentMapping = { + true -> (KtVariableLikeSignature: + name = i + receiverType = null + returnType = kotlin.Int + symbol = i: kotlin.Int) + } + ] + diagnostic = ERROR diff --git a/analysis/analysis-api/testData/components/callResolver/resolveCandidates/multipleCandidates/implicitInvokeWithReceiver.kt b/analysis/analysis-api/testData/components/callResolver/resolveCandidates/multipleCandidates/implicitInvokeWithReceiver.kt new file mode 100644 index 00000000000..26020eaa17d --- /dev/null +++ b/analysis/analysis-api/testData/components/callResolver/resolveCandidates/multipleCandidates/implicitInvokeWithReceiver.kt @@ -0,0 +1,9 @@ +operator fun Int.invoke(i: Int) {} + +class A(val x: Int) { + fun x(b: Boolean) {} +} + +fun call(a: A) { + a.x(1) +} \ No newline at end of file diff --git a/analysis/analysis-api/testData/components/callResolver/resolveCandidates/multipleCandidates/implicitInvokeWithReceiver.txt b/analysis/analysis-api/testData/components/callResolver/resolveCandidates/multipleCandidates/implicitInvokeWithReceiver.txt new file mode 100644 index 00000000000..478d3e673e2 --- /dev/null +++ b/analysis/analysis-api/testData/components/callResolver/resolveCandidates/multipleCandidates/implicitInvokeWithReceiver.txt @@ -0,0 +1,56 @@ +KtErrorCallInfo: + candidateCalls = [ + KtSimpleFunctionCall: + isImplicitInvoke = false + partiallyAppliedSymbol = KtPartiallyAppliedSymbol: + dispatchReceiver = KtExplicitReceiverValue: + expression = a + isSafeNavigation = false + extensionReceiver = null + signature = KtFunctionLikeSignature: + receiverType = null + returnType = kotlin.Unit + symbol = /A.x(: A, b: kotlin.Boolean): kotlin.Unit + valueParameters = [ + KtVariableLikeSignature: + name = b + receiverType = null + returnType = kotlin.Boolean + symbol = b: kotlin.Boolean + ] + argumentMapping = { + 1 -> (KtVariableLikeSignature: + name = b + receiverType = null + returnType = kotlin.Boolean + symbol = b: kotlin.Boolean) + } + ] + diagnostic = ERROR + +KtSuccessCallInfo: + call = KtSimpleFunctionCall: + isImplicitInvoke = true + partiallyAppliedSymbol = KtPartiallyAppliedSymbol: + dispatchReceiver = null + extensionReceiver = KtExplicitReceiverValue: + expression = x + isSafeNavigation = false + signature = KtFunctionLikeSignature: + receiverType = kotlin.Int + returnType = kotlin.Unit + symbol = /invoke(: kotlin.Int, i: kotlin.Int): kotlin.Unit + valueParameters = [ + KtVariableLikeSignature: + name = i + receiverType = null + returnType = kotlin.Int + symbol = i: kotlin.Int + ] + argumentMapping = { + 1 -> (KtVariableLikeSignature: + name = i + receiverType = null + returnType = kotlin.Int + symbol = i: kotlin.Int) + } diff --git a/analysis/analysis-api/testData/components/callResolver/resolveCandidates/singleCandidate/consecutiveImplicitInvoke1.kt b/analysis/analysis-api/testData/components/callResolver/resolveCandidates/singleCandidate/consecutiveImplicitInvoke1.kt new file mode 100644 index 00000000000..f804e2012b5 --- /dev/null +++ b/analysis/analysis-api/testData/components/callResolver/resolveCandidates/singleCandidate/consecutiveImplicitInvoke1.kt @@ -0,0 +1,6 @@ +operator fun Int.invoke() : Long = 1L +operator fun Long.invoke() : Double = 1.0 +operator fun Double.invoke() {} +fun test(i: Int) { + i()()() +} \ No newline at end of file diff --git a/analysis/analysis-api/testData/components/callResolver/resolveCandidates/singleCandidate/consecutiveImplicitInvoke1.txt b/analysis/analysis-api/testData/components/callResolver/resolveCandidates/singleCandidate/consecutiveImplicitInvoke1.txt new file mode 100644 index 00000000000..720bc989686 --- /dev/null +++ b/analysis/analysis-api/testData/components/callResolver/resolveCandidates/singleCandidate/consecutiveImplicitInvoke1.txt @@ -0,0 +1,50 @@ +KtSuccessCallInfo: + call = KtSimpleFunctionCall: + isImplicitInvoke = true + partiallyAppliedSymbol = KtPartiallyAppliedSymbol: + dispatchReceiver = null + extensionReceiver = KtExplicitReceiverValue: + expression = i + isSafeNavigation = false + signature = KtFunctionLikeSignature: + receiverType = kotlin.Int + returnType = kotlin.Long + symbol = /invoke(: kotlin.Int): kotlin.Long + valueParameters = [] + argumentMapping = {} + +KtErrorCallInfo: + candidateCalls = [ + KtSimpleFunctionCall: + isImplicitInvoke = true + partiallyAppliedSymbol = KtPartiallyAppliedSymbol: + dispatchReceiver = null + extensionReceiver = KtExplicitReceiverValue: + expression = i + isSafeNavigation = false + signature = KtFunctionLikeSignature: + receiverType = kotlin.Long + returnType = kotlin.Double + symbol = /invoke(: kotlin.Long): kotlin.Double + valueParameters = [] + argumentMapping = {} + ] + diagnostic = ERROR + +KtErrorCallInfo: + candidateCalls = [ + KtSimpleFunctionCall: + isImplicitInvoke = true + partiallyAppliedSymbol = KtPartiallyAppliedSymbol: + dispatchReceiver = null + extensionReceiver = KtExplicitReceiverValue: + expression = i + isSafeNavigation = false + signature = KtFunctionLikeSignature: + receiverType = kotlin.Double + returnType = kotlin.Unit + symbol = /invoke(: kotlin.Double): kotlin.Unit + valueParameters = [] + argumentMapping = {} + ] + diagnostic = ERROR \ No newline at end of file diff --git a/analysis/analysis-api/testData/components/callResolver/resolveCandidates/singleCandidate/consecutiveImplicitInvoke2.kt b/analysis/analysis-api/testData/components/callResolver/resolveCandidates/singleCandidate/consecutiveImplicitInvoke2.kt new file mode 100644 index 00000000000..aadc9e6eaac --- /dev/null +++ b/analysis/analysis-api/testData/components/callResolver/resolveCandidates/singleCandidate/consecutiveImplicitInvoke2.kt @@ -0,0 +1,6 @@ +operator fun Int.invoke() : Long = 1L +operator fun Long.invoke() : Double = 1.0 +operator fun Double.invoke() {} +fun test(i: Int) { + i()()() +} \ No newline at end of file diff --git a/analysis/analysis-api/testData/components/callResolver/resolveCandidates/singleCandidate/consecutiveImplicitInvoke2.txt b/analysis/analysis-api/testData/components/callResolver/resolveCandidates/singleCandidate/consecutiveImplicitInvoke2.txt new file mode 100644 index 00000000000..59b46c6a026 --- /dev/null +++ b/analysis/analysis-api/testData/components/callResolver/resolveCandidates/singleCandidate/consecutiveImplicitInvoke2.txt @@ -0,0 +1,50 @@ +KtErrorCallInfo: + candidateCalls = [ + KtSimpleFunctionCall: + isImplicitInvoke = true + partiallyAppliedSymbol = KtPartiallyAppliedSymbol: + dispatchReceiver = null + extensionReceiver = KtExplicitReceiverValue: + expression = i() + isSafeNavigation = false + signature = KtFunctionLikeSignature: + receiverType = kotlin.Int + returnType = kotlin.Long + symbol = /invoke(: kotlin.Int): kotlin.Long + valueParameters = [] + argumentMapping = {} + ] + diagnostic = ERROR + +KtSuccessCallInfo: + call = KtSimpleFunctionCall: + isImplicitInvoke = true + partiallyAppliedSymbol = KtPartiallyAppliedSymbol: + dispatchReceiver = null + extensionReceiver = KtExplicitReceiverValue: + expression = i() + isSafeNavigation = false + signature = KtFunctionLikeSignature: + receiverType = kotlin.Long + returnType = kotlin.Double + symbol = /invoke(: kotlin.Long): kotlin.Double + valueParameters = [] + argumentMapping = {} + +KtErrorCallInfo: + candidateCalls = [ + KtSimpleFunctionCall: + isImplicitInvoke = true + partiallyAppliedSymbol = KtPartiallyAppliedSymbol: + dispatchReceiver = null + extensionReceiver = KtExplicitReceiverValue: + expression = i() + isSafeNavigation = false + signature = KtFunctionLikeSignature: + receiverType = kotlin.Double + returnType = kotlin.Unit + symbol = /invoke(: kotlin.Double): kotlin.Unit + valueParameters = [] + argumentMapping = {} + ] + diagnostic = ERROR \ No newline at end of file diff --git a/analysis/analysis-api/testData/components/callResolver/resolveCandidates/singleCandidate/consecutiveImplicitInvoke3.kt b/analysis/analysis-api/testData/components/callResolver/resolveCandidates/singleCandidate/consecutiveImplicitInvoke3.kt new file mode 100644 index 00000000000..e8951792d3d --- /dev/null +++ b/analysis/analysis-api/testData/components/callResolver/resolveCandidates/singleCandidate/consecutiveImplicitInvoke3.kt @@ -0,0 +1,6 @@ +operator fun Int.invoke() : Long = 1L +operator fun Long.invoke() : Double = 1.0 +operator fun Double.invoke() {} +fun test(i: Int) { + i()()() +} \ No newline at end of file diff --git a/analysis/analysis-api/testData/components/callResolver/resolveCandidates/singleCandidate/consecutiveImplicitInvoke3.txt b/analysis/analysis-api/testData/components/callResolver/resolveCandidates/singleCandidate/consecutiveImplicitInvoke3.txt new file mode 100644 index 00000000000..22d60cc7122 --- /dev/null +++ b/analysis/analysis-api/testData/components/callResolver/resolveCandidates/singleCandidate/consecutiveImplicitInvoke3.txt @@ -0,0 +1,50 @@ +KtErrorCallInfo: + candidateCalls = [ + KtSimpleFunctionCall: + isImplicitInvoke = true + partiallyAppliedSymbol = KtPartiallyAppliedSymbol: + dispatchReceiver = null + extensionReceiver = KtExplicitReceiverValue: + expression = i()() + isSafeNavigation = false + signature = KtFunctionLikeSignature: + receiverType = kotlin.Int + returnType = kotlin.Long + symbol = /invoke(: kotlin.Int): kotlin.Long + valueParameters = [] + argumentMapping = {} + ] + diagnostic = ERROR + +KtErrorCallInfo: + candidateCalls = [ + KtSimpleFunctionCall: + isImplicitInvoke = true + partiallyAppliedSymbol = KtPartiallyAppliedSymbol: + dispatchReceiver = null + extensionReceiver = KtExplicitReceiverValue: + expression = i()() + isSafeNavigation = false + signature = KtFunctionLikeSignature: + receiverType = kotlin.Long + returnType = kotlin.Double + symbol = /invoke(: kotlin.Long): kotlin.Double + valueParameters = [] + argumentMapping = {} + ] + diagnostic = ERROR + +KtSuccessCallInfo: + call = KtSimpleFunctionCall: + isImplicitInvoke = true + partiallyAppliedSymbol = KtPartiallyAppliedSymbol: + dispatchReceiver = null + extensionReceiver = KtExplicitReceiverValue: + expression = i()() + isSafeNavigation = false + signature = KtFunctionLikeSignature: + receiverType = kotlin.Double + returnType = kotlin.Unit + symbol = /invoke(: kotlin.Double): kotlin.Unit + valueParameters = [] + argumentMapping = {} \ No newline at end of file diff --git a/analysis/analysis-api/testData/components/callResolver/resolveCandidates/singleCandidate/variableAsFunction.txt b/analysis/analysis-api/testData/components/callResolver/resolveCandidates/singleCandidate/variableAsFunction.txt index ee494377fdd..6120ca0fd4b 100644 --- a/analysis/analysis-api/testData/components/callResolver/resolveCandidates/singleCandidate/variableAsFunction.txt +++ b/analysis/analysis-api/testData/components/callResolver/resolveCandidates/singleCandidate/variableAsFunction.txt @@ -2,7 +2,9 @@ KtSuccessCallInfo: call = KtSimpleFunctionCall: isImplicitInvoke = true partiallyAppliedSymbol = KtPartiallyAppliedSymbol: - dispatchReceiver = null + dispatchReceiver = KtExplicitReceiverValue: + expression = x + isSafeNavigation = false extensionReceiver = null signature = KtFunctionLikeSignature: receiverType = null diff --git a/analysis/analysis-api/testData/components/callResolver/resolveCandidates/singleCandidate/variableAsFunctionLikeCall.txt b/analysis/analysis-api/testData/components/callResolver/resolveCandidates/singleCandidate/variableAsFunctionLikeCall.txt index db78693e0a0..dfce88339f1 100644 --- a/analysis/analysis-api/testData/components/callResolver/resolveCandidates/singleCandidate/variableAsFunctionLikeCall.txt +++ b/analysis/analysis-api/testData/components/callResolver/resolveCandidates/singleCandidate/variableAsFunctionLikeCall.txt @@ -3,7 +3,9 @@ KtSuccessCallInfo: isImplicitInvoke = true partiallyAppliedSymbol = KtPartiallyAppliedSymbol: dispatchReceiver = null - extensionReceiver = null + extensionReceiver = KtExplicitReceiverValue: + expression = x + isSafeNavigation = false signature = KtFunctionLikeSignature: receiverType = kotlin.Int returnType = kotlin.String diff --git a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/FirCallResolver.kt b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/FirCallResolver.kt index d03becbceee..c4c4ad21dd3 100644 --- a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/FirCallResolver.kt +++ b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/FirCallResolver.kt @@ -178,6 +178,9 @@ class FirCallResolver( return super.consumeCandidate(group, candidate, context) } + // We want to get candidates at all tower levels. + override fun shouldStopAtTheLevel(group: TowerGroup): Boolean = false + val allCandidates: List get() = allCandidatesSet.toList() } diff --git a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/ResolveUtils.kt b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/ResolveUtils.kt index 03b73198cc6..4bacf943e92 100644 --- a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/ResolveUtils.kt +++ b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/ResolveUtils.kt @@ -322,6 +322,11 @@ private inline fun BodyResolveComponents.transformExpression smartcastBuilder: () -> FirWrappedExpressionWithSmartcastBuilder, smartcastToNullBuilder: () -> FirWrappedExpressionWithSmartcastToNullBuilder ): FirWrappedExpressionWithSmartcastBuilder? { + // No need to check for smartcast if the expression was already resolved. + containingDeclarations.lastOrNull()?.let { closestDeclaration -> + if (closestDeclaration.resolvePhase >= FirResolvePhase.BODY_RESOLVE) return null + } + val (stability, typesFromSmartCast) = smartcastExtractor(expression) ?: return null val smartcastStability = stability.impliedSmartcastStability ?: if (dataFlowAnalyzer.isAccessToUnstableLocalVariable(expression)) { diff --git a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/calls/CandidateCollector.kt b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/calls/CandidateCollector.kt index cc17389556e..da5a0a3a40a 100644 --- a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/calls/CandidateCollector.kt +++ b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/calls/CandidateCollector.kt @@ -48,7 +48,7 @@ open class CandidateCollector( fun bestCandidates(): List = candidates - fun shouldStopAtTheLevel(group: TowerGroup): Boolean = + open fun shouldStopAtTheLevel(group: TowerGroup): Boolean = currentApplicability.shouldStopResolve && bestGroup < group fun isSuccess(): Boolean {