diff --git a/idea/idea-fir/src/org/jetbrains/kotlin/idea/fir/highlighter/visitors/FunctionCallHighlightingVisitor.kt b/idea/idea-fir/src/org/jetbrains/kotlin/idea/fir/highlighter/visitors/FunctionCallHighlightingVisitor.kt index 3a90c7c15d9..868fcee900b 100644 --- a/idea/idea-fir/src/org/jetbrains/kotlin/idea/fir/highlighter/visitors/FunctionCallHighlightingVisitor.kt +++ b/idea/idea-fir/src/org/jetbrains/kotlin/idea/fir/highlighter/visitors/FunctionCallHighlightingVisitor.kt @@ -8,6 +8,7 @@ package org.jetbrains.kotlin.idea.fir.highlighter.visitors import com.intellij.lang.annotation.AnnotationHolder import com.intellij.openapi.editor.colors.TextAttributesKey import org.jetbrains.kotlin.idea.frontend.api.* +import org.jetbrains.kotlin.idea.frontend.api.calls.* import org.jetbrains.kotlin.idea.frontend.api.symbols.* import org.jetbrains.kotlin.idea.frontend.api.symbols.markers.KtSymbolKind import org.jetbrains.kotlin.lexer.KtTokens @@ -20,25 +21,20 @@ internal class FunctionCallHighlightingVisitor( holder: AnnotationHolder ) : FirAfterResolveHighlightingVisitor(analysisSession, holder) { override fun visitBinaryExpression(expression: KtBinaryExpression) = with(analysisSession) { - (expression.operationReference as? KtReferenceExpression) - ?.takeIf { - // do not highlight assignment statement - (it as? KtOperationReferenceExpression)?.operationSignTokenType != KtTokens.EQ - }?.let { callee -> - expression.resolveCall() - ?.takeIf { callInfo -> - // ignore arithmetic-like operator calls - (callInfo.targetFunction as? KtFunctionSymbol)?.isOperator != true - } - ?.let { callInfo -> - getTextAttributesForCal(callInfo)?.let { attributes -> - highlightName(callee, attributes) - } - } - } + val operationReference = expression.operationReference as? KtReferenceExpression ?: return + if (operationReference.isAssignment()) return + val call = expression.resolveCall() ?: return + if (call.isErrorCall) return + if (call.isSuccessCallOf { it.isOperator }) return + getTextAttributesForCal(call)?.let { attributes -> + highlightName(operationReference, attributes) + } super.visitBinaryExpression(expression) } + private fun KtReferenceExpression.isAssignment() = + (this as? KtOperationReferenceExpression)?.operationSignTokenType == KtTokens.EQ + override fun visitCallExpression(expression: KtCallExpression) = with(analysisSession) { expression.calleeExpression ?.takeUnless { it is KtLambdaExpression } @@ -53,9 +49,9 @@ internal class FunctionCallHighlightingVisitor( super.visitCallExpression(expression) } - private fun getTextAttributesForCal(callInfo: CallInfo): TextAttributesKey? = when { - callInfo.isSuspendCall -> Colors.SUSPEND_FUNCTION_CALL - callInfo is FunctionCallInfo -> when (val function = callInfo.targetFunction) { + private fun getTextAttributesForCal(call: KtCall): TextAttributesKey? = when { + call.isSuccessCallOf { it.isSuspend } -> Colors.SUSPEND_FUNCTION_CALL + call is KtFunctionCall -> when (val function = call.targetFunction) { is KtConstructorSymbol -> Colors.CONSTRUCTOR_CALL is KtAnonymousFunctionSymbol -> null is KtFunctionSymbol -> when { @@ -66,8 +62,8 @@ internal class FunctionCallHighlightingVisitor( } else -> Colors.FUNCTION_CALL //TODO () } - callInfo is VariableAsFunctionCallInfo -> Colors.VARIABLE_AS_FUNCTION_CALL - callInfo is VariableAsFunctionLikeCallInfo -> Colors.VARIABLE_AS_FUNCTION_LIKE_CALL + call is KtFunctionalTypeVariableCall -> Colors.VARIABLE_AS_FUNCTION_CALL + call is KtVariableWithInvokeFunctionCall -> Colors.VARIABLE_AS_FUNCTION_LIKE_CALL else -> null } } \ No newline at end of file diff --git a/idea/idea-frontend-api/src/org/jetbrains/kotlin/idea/frontend/api/CallInfo.kt b/idea/idea-frontend-api/src/org/jetbrains/kotlin/idea/frontend/api/CallInfo.kt deleted file mode 100644 index 638fc3b3e78..00000000000 --- a/idea/idea-frontend-api/src/org/jetbrains/kotlin/idea/frontend/api/CallInfo.kt +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors. - * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. - */ - -package org.jetbrains.kotlin.idea.frontend.api - -import org.jetbrains.kotlin.idea.frontend.api.symbols.* - -sealed class CallInfo { - abstract val isSuspendCall: Boolean - abstract val targetFunction: KtFunctionLikeSymbol? -} - -data class VariableAsFunctionCallInfo(val target: KtVariableLikeSymbol, override val isSuspendCall: Boolean) : CallInfo() { - override val targetFunction: KtFunctionLikeSymbol? = null -} - -data class VariableAsFunctionLikeCallInfo(val target: KtVariableLikeSymbol, val invokeFunction: KtFunctionSymbol) : CallInfo() { - override val isSuspendCall: Boolean get() = invokeFunction.isSuspend - override val targetFunction get() = invokeFunction -} - -data class FunctionCallInfo(override val targetFunction: KtFunctionLikeSymbol) : CallInfo() { - override val isSuspendCall: Boolean - get() = when (targetFunction) { - is KtFunctionSymbol -> targetFunction.isSuspend - else -> false - } -} \ No newline at end of file diff --git a/idea/idea-frontend-api/src/org/jetbrains/kotlin/idea/frontend/api/KtAnalysisSession.kt b/idea/idea-frontend-api/src/org/jetbrains/kotlin/idea/frontend/api/KtAnalysisSession.kt index 6c90bb22adb..466e6625e52 100644 --- a/idea/idea-frontend-api/src/org/jetbrains/kotlin/idea/frontend/api/KtAnalysisSession.kt +++ b/idea/idea-frontend-api/src/org/jetbrains/kotlin/idea/frontend/api/KtAnalysisSession.kt @@ -6,6 +6,7 @@ package org.jetbrains.kotlin.idea.frontend.api import org.jetbrains.kotlin.diagnostics.Diagnostic +import org.jetbrains.kotlin.idea.frontend.api.calls.KtCall import org.jetbrains.kotlin.idea.frontend.api.components.* import org.jetbrains.kotlin.idea.frontend.api.scopes.* import org.jetbrains.kotlin.idea.frontend.api.symbols.* @@ -118,9 +119,9 @@ abstract class KtAnalysisSession(override val token: ValidityToken) : ValidityTo fun KtSymbolPointer.restoreSymbol(): S? = restoreSymbol(this@KtAnalysisSession) - fun KtCallExpression.resolveCall(): CallInfo? = callResolver.resolveCall(this) + fun KtCallExpression.resolveCall(): KtCall? = callResolver.resolveCall(this) - fun KtBinaryExpression.resolveCall(): CallInfo? = callResolver.resolveCall(this) + fun KtBinaryExpression.resolveCall(): KtCall? = callResolver.resolveCall(this) fun KtReference.resolveToSymbols(): Collection { check(this is KtSymbolBasedReference) { "To get reference symbol the one should be KtSymbolBasedReference" } diff --git a/idea/idea-frontend-api/src/org/jetbrains/kotlin/idea/frontend/api/calls/KtCall.kt b/idea/idea-frontend-api/src/org/jetbrains/kotlin/idea/frontend/api/calls/KtCall.kt new file mode 100644 index 00000000000..cdf7d90a585 --- /dev/null +++ b/idea/idea-frontend-api/src/org/jetbrains/kotlin/idea/frontend/api/calls/KtCall.kt @@ -0,0 +1,81 @@ +/* + * Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.idea.frontend.api.calls + +import org.jetbrains.kotlin.idea.frontend.api.diagnostics.KtDiagnostic +import org.jetbrains.kotlin.idea.frontend.api.symbols.* + +/** + * Represents direct or indirect (via invoke) function call from Kotlin code + */ +sealed class KtCall { + abstract val isErrorCall: Boolean +} + +/** + * Call using `()` of some variable of functional type, e.g., + * + * fun x(f: () -> Int) { + * f() // functional type call + * } + */ +class KtFunctionalTypeVariableCall(val target: KtVariableLikeSymbol) : KtCall() { + override val isErrorCall: Boolean get() = false +} + +/** + * Direct or indirect call of function declared by user + */ +sealed class KtDeclaredFunctionCall : KtCall() { + abstract val targetFunction: KtCallTarget + override val isErrorCall: Boolean + get() = targetFunction is KtErrorCallTarget +} + +/** + * Call using () on variable on some non-functional type, considers that `invoke` function is declared somewhere + * + * fun x(y: Int) { + * y() // variable with invoke function call + * } + * + * fun Int.invoke() {} + */ +class KtVariableWithInvokeFunctionCall( + val target: KtVariableLikeSymbol, + val invokeFunction: KtCallTarget, +) : KtDeclaredFunctionCall() { + override val targetFunction: KtCallTarget get() = invokeFunction +} + +/** + * Simple function call, e.g., + * + * x.toString() // function call + */ +data class KtFunctionCall(override val targetFunction: KtCallTarget) : KtDeclaredFunctionCall() + +/** + * Represents function(s) in which call was resolved, + * Can be success [KtSuccessCallTarget] in this case there only one such function + * Or erroneous [KtErrorCallTarget] in this case there can be any count of candidates + */ +sealed class KtCallTarget { + abstract val candidates: Collection +} + +/** + * Success call of [symbol] + */ +class KtSuccessCallTarget(val symbol: KtFunctionLikeSymbol) : KtCallTarget() { + override val candidates: Collection + get() = listOf(symbol) +} + +/** + * Function all with errors, possible candidates are [candidates] + */ +class KtErrorCallTarget(override val candidates: Collection, val diagnostic: KtDiagnostic) : KtCallTarget() \ No newline at end of file diff --git a/idea/idea-frontend-api/src/org/jetbrains/kotlin/idea/frontend/api/calls/ktCallUtils.kt b/idea/idea-frontend-api/src/org/jetbrains/kotlin/idea/frontend/api/calls/ktCallUtils.kt new file mode 100644 index 00000000000..29d37d79d61 --- /dev/null +++ b/idea/idea-frontend-api/src/org/jetbrains/kotlin/idea/frontend/api/calls/ktCallUtils.kt @@ -0,0 +1,20 @@ +/* + * Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.idea.frontend.api.calls + +import org.jetbrains.kotlin.idea.frontend.api.symbols.KtFunctionLikeSymbol + +fun KtCallTarget.getSuccessCallSymbolOrNull(): KtFunctionLikeSymbol? = when (this) { + is KtSuccessCallTarget -> symbol + is KtErrorCallTarget -> null +} + +inline fun KtCall.isSuccessCallOf(predicate: (S) -> Boolean): Boolean { + if (this !is KtFunctionCall) return false + val symbol = targetFunction.getSuccessCallSymbolOrNull() ?: return false + if (symbol !is S) return false + return predicate(symbol) +} \ No newline at end of file diff --git a/idea/idea-frontend-api/src/org/jetbrains/kotlin/idea/frontend/api/components/KtCallResolver.kt b/idea/idea-frontend-api/src/org/jetbrains/kotlin/idea/frontend/api/components/KtCallResolver.kt index 011ea9c37fb..cd213ab28fb 100644 --- a/idea/idea-frontend-api/src/org/jetbrains/kotlin/idea/frontend/api/components/KtCallResolver.kt +++ b/idea/idea-frontend-api/src/org/jetbrains/kotlin/idea/frontend/api/components/KtCallResolver.kt @@ -5,11 +5,11 @@ package org.jetbrains.kotlin.idea.frontend.api.components -import org.jetbrains.kotlin.idea.frontend.api.CallInfo +import org.jetbrains.kotlin.idea.frontend.api.calls.KtCall import org.jetbrains.kotlin.psi.KtBinaryExpression import org.jetbrains.kotlin.psi.KtCallExpression abstract class KtCallResolver : KtAnalysisSessionComponent() { - abstract fun resolveCall(call: KtCallExpression): CallInfo? - abstract fun resolveCall(call: KtBinaryExpression): CallInfo? + abstract fun resolveCall(call: KtCallExpression): KtCall? + abstract fun resolveCall(call: KtBinaryExpression): KtCall? } \ No newline at end of file diff --git a/idea/idea-frontend-api/src/org/jetbrains/kotlin/idea/frontend/api/diagnostics/KtDiagnostic.kt b/idea/idea-frontend-api/src/org/jetbrains/kotlin/idea/frontend/api/diagnostics/KtDiagnostic.kt new file mode 100644 index 00000000000..71c3d1501bd --- /dev/null +++ b/idea/idea-frontend-api/src/org/jetbrains/kotlin/idea/frontend/api/diagnostics/KtDiagnostic.kt @@ -0,0 +1,12 @@ +/* + * Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.idea.frontend.api.diagnostics + +sealed class KtDiagnostic { + abstract val message: String +} + +data class KtSimpleDiagnostic(override val message: String): KtDiagnostic() \ No newline at end of file diff --git a/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/fir/FirUtils.kt b/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/fir/FirUtils.kt index 2c8af72e351..bdbd0c52b94 100644 --- a/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/fir/FirUtils.kt +++ b/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/fir/FirUtils.kt @@ -8,8 +8,11 @@ package org.jetbrains.kotlin.idea.fir import org.jetbrains.kotlin.fir.declarations.FirDeclaration import org.jetbrains.kotlin.fir.expressions.FirFunctionCall import org.jetbrains.kotlin.fir.expressions.FirQualifiedAccessExpression +import org.jetbrains.kotlin.fir.references.FirErrorNamedReference +import org.jetbrains.kotlin.fir.references.FirNamedReference import org.jetbrains.kotlin.fir.references.FirReference import org.jetbrains.kotlin.fir.references.FirResolvedNamedReference +import org.jetbrains.kotlin.fir.resolve.diagnostics.* import org.jetbrains.kotlin.fir.symbols.FirBasedSymbol import org.jetbrains.kotlin.fir.symbols.impl.FirNamedFunctionSymbol import org.jetbrains.kotlin.idea.frontend.api.fir.KtSymbolByFirBuilder @@ -19,10 +22,12 @@ import org.jetbrains.kotlin.util.OperatorNameConventions fun FirFunctionCall.isImplicitFunctionCall(): Boolean { if (dispatchReceiver !is FirQualifiedAccessExpression) return false - val resolvedCalleeSymbol = (calleeReference as? FirResolvedNamedReference)?.resolvedSymbol - return (resolvedCalleeSymbol as? FirNamedFunctionSymbol)?.fir?.name == OperatorNameConventions.INVOKE + return calleeReference.getCandidateSymbols().any(FirBasedSymbol<*>::isInvokeFunction) } +private fun FirBasedSymbol<*>.isInvokeFunction() = + (this as? FirNamedFunctionSymbol)?.fir?.name == OperatorNameConventions.INVOKE + fun FirFunctionCall.getCalleeSymbol(): FirBasedSymbol<*>? = calleeReference.getResolvedSymbolOfNameReference() @@ -33,3 +38,19 @@ internal fun FirReference.getResolvedKtSymbolOfNameReference(builder: KtSymbolBy (getResolvedSymbolOfNameReference()?.fir as? FirDeclaration)?.let { firDeclaration -> builder.buildSymbol(firDeclaration) } + +internal fun FirErrorNamedReference.getCandidateSymbols(): Collection> = + when (val diagnostic = diagnostic) { + is ConeInapplicableCandidateError -> listOf(diagnostic.candidateSymbol) + is ConeHiddenCandidateError -> listOf(diagnostic.candidateSymbol) + is ConeAmbiguityError -> diagnostic.candidates + is ConeOperatorAmbiguityError -> diagnostic.candidates + is ConeUnsupportedCallableReferenceTarget -> listOf(diagnostic.fir.symbol) + else -> emptyList() + } + +internal fun FirNamedReference.getCandidateSymbols(): Collection> = when(this) { + is FirResolvedNamedReference -> listOf(resolvedSymbol) + is FirErrorNamedReference -> getCandidateSymbols() + else -> emptyList() +} diff --git a/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/KtSymbolByFirBuilder.kt b/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/KtSymbolByFirBuilder.kt index 4c04d4ff8d5..f9c9a4a7f86 100644 --- a/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/KtSymbolByFirBuilder.kt +++ b/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/KtSymbolByFirBuilder.kt @@ -191,7 +191,11 @@ internal class KtSymbolByFirBuilder private constructor( } - fun buildKtType(coneType: FirTypeRef): KtType = buildKtType(coneType.coneTypeUnsafe()) + fun buildKtType(coneType: FirTypeRef): KtType = + buildKtType( + coneType.coneTypeSafe() + ?: error("") + ) fun buildKtType(coneType: ConeKotlinType): KtType = typesCache.cache(coneType) { when (coneType) { diff --git a/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/components/KtFirCallResolver.kt b/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/components/KtFirCallResolver.kt index 7fc1327b083..764f97807ba 100644 --- a/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/components/KtFirCallResolver.kt +++ b/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/components/KtFirCallResolver.kt @@ -6,84 +6,102 @@ package org.jetbrains.kotlin.idea.frontend.api.fir.components import org.jetbrains.kotlin.builtins.StandardNames -import org.jetbrains.kotlin.fir.declarations.isSuspend import org.jetbrains.kotlin.fir.expressions.FirFunctionCall import org.jetbrains.kotlin.fir.expressions.FirQualifiedAccessExpression import org.jetbrains.kotlin.fir.expressions.FirSafeCallExpression +import org.jetbrains.kotlin.fir.references.FirErrorNamedReference import org.jetbrains.kotlin.fir.references.FirResolvedNamedReference import org.jetbrains.kotlin.fir.symbols.CallableId -import org.jetbrains.kotlin.fir.symbols.impl.FirConstructorSymbol import org.jetbrains.kotlin.fir.symbols.impl.FirNamedFunctionSymbol +import org.jetbrains.kotlin.idea.fir.getCandidateSymbols import org.jetbrains.kotlin.idea.fir.isImplicitFunctionCall import org.jetbrains.kotlin.idea.fir.low.level.api.api.getOrBuildFir import org.jetbrains.kotlin.idea.fir.low.level.api.api.getOrBuildFirSafe import org.jetbrains.kotlin.idea.frontend.api.* +import org.jetbrains.kotlin.idea.frontend.api.calls.* import org.jetbrains.kotlin.idea.frontend.api.components.KtCallResolver +import org.jetbrains.kotlin.idea.frontend.api.diagnostics.KtSimpleDiagnostic import org.jetbrains.kotlin.idea.frontend.api.fir.KtFirAnalysisSession -import org.jetbrains.kotlin.idea.frontend.api.symbols.KtFunctionSymbol -import org.jetbrains.kotlin.idea.frontend.api.symbols.KtSymbol -import org.jetbrains.kotlin.idea.frontend.api.symbols.KtVariableLikeSymbol +import org.jetbrains.kotlin.idea.frontend.api.fir.buildSymbol +import org.jetbrains.kotlin.idea.frontend.api.symbols.* import org.jetbrains.kotlin.idea.references.FirReferenceResolveHelper -import org.jetbrains.kotlin.idea.references.FirReferenceResolveHelper.toTargetSymbol import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.psi.KtBinaryExpression import org.jetbrains.kotlin.psi.KtCallExpression -import org.jetbrains.kotlin.psi.KtExpression internal class KtFirCallResolver( override val analysisSession: KtFirAnalysisSession, override val token: ValidityToken, ) : KtCallResolver(), KtFirAnalysisSessionComponent { - override fun resolveCall(call: KtBinaryExpression): CallInfo? = withValidityAssertion { + override fun resolveCall(call: KtBinaryExpression): KtCall? = withValidityAssertion { val firCall = call.getOrBuildFirSafe(firResolveState) ?: return null - resolveCall(firCall, call) + resolveCall(firCall) } - override fun resolveCall(call: KtCallExpression): CallInfo? = withValidityAssertion { + override fun resolveCall(call: KtCallExpression): KtCall? = withValidityAssertion { val firCall = when (val fir = call.getOrBuildFir(firResolveState)) { is FirFunctionCall -> fir is FirSafeCallExpression -> fir.regularQualifiedAccess as? FirFunctionCall else -> null } ?: return null - return resolveCall(firCall, call) + return resolveCall(firCall) } - private fun resolveCall(firCall: FirFunctionCall, callExpression: KtExpression): CallInfo? { + private fun resolveCall(firCall: FirFunctionCall): KtCall? { val session = firResolveState.rootModuleSession - val resolvedFunctionSymbol = firCall.calleeReference.toTargetSymbol(session, firSymbolBuilder) - val resolvedCalleeSymbol = (firCall.calleeReference as? FirResolvedNamedReference)?.resolvedSymbol return when { - resolvedCalleeSymbol is FirConstructorSymbol -> { - val fir = resolvedCalleeSymbol.fir - FunctionCallInfo(firSymbolBuilder.buildConstructorSymbol(fir)) - } - firCall.dispatchReceiver is FirQualifiedAccessExpression && firCall.isImplicitFunctionCall() -> { + firCall.isImplicitFunctionCall() -> { val target = with(FirReferenceResolveHelper) { val calleeReference = (firCall.dispatchReceiver as FirQualifiedAccessExpression).calleeReference - calleeReference.toTargetSymbol(session, firSymbolBuilder) + calleeReference.toTargetSymbol(session, firSymbolBuilder).singleOrNull() } when (target) { + is KtVariableLikeSymbol -> firCall.createCallByVariableLikeSymbolCall(target) null -> null - is KtVariableLikeSymbol -> { - val functionSymbol = - (firCall.calleeReference as? FirResolvedNamedReference)?.resolvedSymbol as? FirNamedFunctionSymbol - when (functionSymbol?.callableId) { - null -> null - in kotlinFunctionInvokeCallableIds -> VariableAsFunctionCallInfo(target, functionSymbol.fir.isSuspend) - else -> (resolvedFunctionSymbol as? KtFunctionSymbol) - ?.let { VariableAsFunctionLikeCallInfo(target, it) } - } - } - else -> resolvedFunctionSymbol?.asSimpleFunctionCall() + else -> firCall.asSimpleFunctionCall() } } - else -> resolvedFunctionSymbol?.asSimpleFunctionCall() + else -> firCall.asSimpleFunctionCall() } } - private fun KtSymbol.asSimpleFunctionCall() = - (this as? KtFunctionSymbol)?.let(::FunctionCallInfo) + private fun FirFunctionCall.createCallByVariableLikeSymbolCall(variableLikeSymbol: KtVariableLikeSymbol) = + when (val callReference = calleeReference) { + is FirResolvedNamedReference -> { + val functionSymbol = callReference.resolvedSymbol as? FirNamedFunctionSymbol + when (functionSymbol?.callableId) { + null -> null + in kotlinFunctionInvokeCallableIds -> KtFunctionalTypeVariableCall(variableLikeSymbol) + else -> (callReference.resolvedSymbol.fir.buildSymbol(firSymbolBuilder) as? KtFunctionSymbol) + ?.let { KtVariableWithInvokeFunctionCall(variableLikeSymbol, KtSuccessCallTarget(it)) } + } + } + is FirErrorNamedReference -> KtVariableWithInvokeFunctionCall( + variableLikeSymbol, + callReference.createErrorCallTarget() + ) + else -> error("Unexpected call reference ${callReference::class.simpleName}") + } + + private fun FirFunctionCall.asSimpleFunctionCall(): KtFunctionCall? { + val target = when (val calleeReference = calleeReference) { + is FirResolvedNamedReference -> calleeReference.getKtFunctionOrConstructorSymbol()?.let { KtSuccessCallTarget(it) } + is FirErrorNamedReference -> calleeReference.createErrorCallTarget() + else -> error("Unexpected call reference ${calleeReference::class.simpleName}") + } ?: return null + return KtFunctionCall(target) + } + + private fun FirErrorNamedReference.createErrorCallTarget(): KtErrorCallTarget = + KtErrorCallTarget( + getCandidateSymbols().mapNotNull { it.fir.buildSymbol(firSymbolBuilder) as? KtFunctionLikeSymbol }, + KtSimpleDiagnostic(diagnostic.reason) + ) + + private fun FirResolvedNamedReference.getKtFunctionOrConstructorSymbol(): KtFunctionLikeSymbol? = + resolvedSymbol.fir.buildSymbol(firSymbolBuilder) as? KtFunctionLikeSymbol + companion object { private val kotlinFunctionInvokeCallableIds = (0..23).flatMapTo(hashSetOf()) { arity -> diff --git a/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/references/FirReferenceResolveHelper.kt b/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/references/FirReferenceResolveHelper.kt index 2f5db53c68d..6fcae082c60 100644 --- a/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/references/FirReferenceResolveHelper.kt +++ b/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/references/FirReferenceResolveHelper.kt @@ -61,7 +61,7 @@ internal object FirReferenceResolveHelper { return classLikeDeclaration?.buildSymbol(symbolBuilder) } - fun FirReference.toTargetSymbol(session: FirSession, symbolBuilder: KtSymbolByFirBuilder): KtSymbol? { + fun FirReference.toTargetSymbol(session: FirSession, symbolBuilder: KtSymbolByFirBuilder): Collection { return when (this) { is FirResolvedNamedReference -> { val fir = when (val symbol = resolvedSymbol) { @@ -75,20 +75,21 @@ internal object FirReferenceResolveHelper { } else -> symbol.fir as? FirDeclaration } - fir?.buildSymbol(symbolBuilder) + listOfNotNull(fir?.buildSymbol(symbolBuilder)) } is FirResolvedCallableReference -> { - resolvedSymbol.fir.buildSymbol(symbolBuilder) + listOfNotNull(resolvedSymbol.fir.buildSymbol(symbolBuilder)) } is FirThisReference -> { - boundSymbol?.fir?.buildSymbol(symbolBuilder) + listOfNotNull(boundSymbol?.fir?.buildSymbol(symbolBuilder)) } is FirSuperReference -> { - (superTypeRef as? FirResolvedTypeRef)?.toTargetSymbol(session, symbolBuilder) + listOfNotNull((superTypeRef as? FirResolvedTypeRef)?.toTargetSymbol(session, symbolBuilder)) } - else -> { - null + is FirErrorNamedReference -> { + getCandidateSymbols().mapNotNull { it.fir.buildSymbol(symbolBuilder) } } + else -> emptyList() } } @@ -195,7 +196,7 @@ internal object FirReferenceResolveHelper { expression: KtSimpleNameExpression, session: FirSession, symbolBuilder: KtSymbolByFirBuilder - ): List { + ): Collection { val calleeReference = if (fir is FirFunctionCall && fir.isImplicitFunctionCall() @@ -207,7 +208,7 @@ internal object FirReferenceResolveHelper { // } (fir.dispatchReceiver as FirQualifiedAccessExpression).calleeReference } else fir.calleeReference - return listOfNotNull(calleeReference.toTargetSymbol(session, symbolBuilder)) + return calleeReference.toTargetSymbol(session, symbolBuilder) } private fun getSymbolsByErrorNamedReference( diff --git a/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/references/KtFirInvokeFunctionReference.kt b/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/references/KtFirInvokeFunctionReference.kt index 721f105c045..810cabc38ae 100644 --- a/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/references/KtFirInvokeFunctionReference.kt +++ b/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/references/KtFirInvokeFunctionReference.kt @@ -6,7 +6,7 @@ package org.jetbrains.kotlin.idea.references import org.jetbrains.kotlin.idea.frontend.api.KtAnalysisSession -import org.jetbrains.kotlin.idea.frontend.api.VariableAsFunctionLikeCallInfo +import org.jetbrains.kotlin.idea.frontend.api.calls.KtVariableWithInvokeFunctionCall import org.jetbrains.kotlin.idea.frontend.api.symbols.KtSymbol import org.jetbrains.kotlin.psi.KtCallExpression import org.jetbrains.kotlin.psi.KtExpression @@ -18,8 +18,8 @@ class KtFirInvokeFunctionReference(expression: KtCallExpression) : KtInvokeFunct override fun KtAnalysisSession.resolveToSymbols(): Collection { val call = expression.resolveCall() ?: return emptyList() - if (call is VariableAsFunctionLikeCallInfo) { - return listOf(call.invokeFunction) + if (call is KtVariableWithInvokeFunctionCall) { + return call.invokeFunction.candidates } return emptyList() } diff --git a/idea/idea-frontend-fir/testData/analysisSession/resolveCall/functionCallInTheSameFile.kt b/idea/idea-frontend-fir/testData/analysisSession/resolveCall/functionCallInTheSameFile.kt index dcea8d939c6..ada41cdfe38 100644 --- a/idea/idea-frontend-fir/testData/analysisSession/resolveCall/functionCallInTheSameFile.kt +++ b/idea/idea-frontend-fir/testData/analysisSession/resolveCall/functionCallInTheSameFile.kt @@ -4,4 +4,4 @@ fun call() { function(1) } -// CALL: FunctionCallInfo: targetFunction = function(a: kotlin.Int): kotlin.Unit \ No newline at end of file +// CALL: KtFunctionCall: targetFunction = function(a: kotlin.Int): kotlin.Unit \ No newline at end of file diff --git a/idea/idea-frontend-fir/testData/analysisSession/resolveCall/functionWithReceiverCall.kt b/idea/idea-frontend-fir/testData/analysisSession/resolveCall/functionWithReceiverCall.kt index 53eab0c512e..95ebd113c93 100644 --- a/idea/idea-frontend-fir/testData/analysisSession/resolveCall/functionWithReceiverCall.kt +++ b/idea/idea-frontend-fir/testData/analysisSession/resolveCall/functionWithReceiverCall.kt @@ -4,4 +4,4 @@ fun call() { "str".function(1) } -// CALL: FunctionCallInfo: targetFunction = function(: kotlin.String, a: kotlin.Int): kotlin.Unit \ No newline at end of file +// CALL: KtFunctionCall: targetFunction = function(: kotlin.String, a: kotlin.Int): kotlin.Unit \ No newline at end of file diff --git a/idea/idea-frontend-fir/testData/analysisSession/resolveCall/functionWithReceiverSafeCall.kt b/idea/idea-frontend-fir/testData/analysisSession/resolveCall/functionWithReceiverSafeCall.kt index c5927c2c429..061099b7a33 100644 --- a/idea/idea-frontend-fir/testData/analysisSession/resolveCall/functionWithReceiverSafeCall.kt +++ b/idea/idea-frontend-fir/testData/analysisSession/resolveCall/functionWithReceiverSafeCall.kt @@ -4,4 +4,4 @@ fun call() { "str"?.function(1) } -// CALL: FunctionCallInfo: targetFunction = function(: kotlin.String, a: kotlin.Int): kotlin.Unit \ No newline at end of file +// CALL: KtFunctionCall: targetFunction = function(: kotlin.String, a: kotlin.Int): kotlin.Unit \ No newline at end of file diff --git a/idea/idea-frontend-fir/testData/analysisSession/resolveCall/implicitConstuctorCall.kt b/idea/idea-frontend-fir/testData/analysisSession/resolveCall/implicitConstuctorCall.kt index 1efa530e7d5..433f0975a2f 100644 --- a/idea/idea-frontend-fir/testData/analysisSession/resolveCall/implicitConstuctorCall.kt +++ b/idea/idea-frontend-fir/testData/analysisSession/resolveCall/implicitConstuctorCall.kt @@ -4,4 +4,4 @@ fun call() { val a = A() } -// CALL: FunctionCallInfo: targetFunction = (): A \ No newline at end of file +// CALL: KtFunctionCall: targetFunction = (): A \ No newline at end of file diff --git a/idea/idea-frontend-fir/testData/analysisSession/resolveCall/implicitJavaConstuctorCall.kt b/idea/idea-frontend-fir/testData/analysisSession/resolveCall/implicitJavaConstuctorCall.kt index 6b30b9a4a09..4fa16f5b0ee 100644 --- a/idea/idea-frontend-fir/testData/analysisSession/resolveCall/implicitJavaConstuctorCall.kt +++ b/idea/idea-frontend-fir/testData/analysisSession/resolveCall/implicitJavaConstuctorCall.kt @@ -3,4 +3,4 @@ fun call() { val a = A() } -// CALL: FunctionCallInfo: targetFunction = (): A \ No newline at end of file +// CALL: KtFunctionCall: targetFunction = (): A \ No newline at end of file diff --git a/idea/idea-frontend-fir/testData/analysisSession/resolveCall/javaFunctionCall.kt b/idea/idea-frontend-fir/testData/analysisSession/resolveCall/javaFunctionCall.kt index f54b3426f93..f4b7218d2c2 100644 --- a/idea/idea-frontend-fir/testData/analysisSession/resolveCall/javaFunctionCall.kt +++ b/idea/idea-frontend-fir/testData/analysisSession/resolveCall/javaFunctionCall.kt @@ -3,4 +3,4 @@ fun call() { javaClass.javaMethod() } -// CALL: FunctionCallInfo: targetFunction = JavaClass.javaMethod(): kotlin.Unit \ No newline at end of file +// CALL: KtFunctionCall: targetFunction = JavaClass.javaMethod(): kotlin.Unit \ No newline at end of file diff --git a/idea/idea-frontend-fir/testData/analysisSession/resolveCall/simpleCallWithNonMatchingArgs.kt b/idea/idea-frontend-fir/testData/analysisSession/resolveCall/simpleCallWithNonMatchingArgs.kt new file mode 100644 index 00000000000..d545781d068 --- /dev/null +++ b/idea/idea-frontend-fir/testData/analysisSession/resolveCall/simpleCallWithNonMatchingArgs.kt @@ -0,0 +1,7 @@ +fun x() { + foo(1) +} + +fun foo(){} + +// CALL: KtFunctionCall: targetFunction = ERR \ No newline at end of file diff --git a/idea/idea-frontend-fir/testData/analysisSession/resolveCall/variableAsFunction.kt b/idea/idea-frontend-fir/testData/analysisSession/resolveCall/variableAsFunction.kt index 5a5383f2b8e..b052bf57cc4 100644 --- a/idea/idea-frontend-fir/testData/analysisSession/resolveCall/variableAsFunction.kt +++ b/idea/idea-frontend-fir/testData/analysisSession/resolveCall/variableAsFunction.kt @@ -2,4 +2,4 @@ fun call(x: (Int) -> String) { x(1) } -// CALL: VariableAsFunctionCallInfo: target = x: kotlin.Function1, isSuspendCall = false \ No newline at end of file +// CALL: KtFunctionalTypeVariableCall: target = x: kotlin.Function1 \ No newline at end of file diff --git a/idea/idea-frontend-fir/testData/analysisSession/resolveCall/variableAsFunctionLikeCall.kt b/idea/idea-frontend-fir/testData/analysisSession/resolveCall/variableAsFunctionLikeCall.kt index 31a733dc2d7..3db95ffbec2 100644 --- a/idea/idea-frontend-fir/testData/analysisSession/resolveCall/variableAsFunctionLikeCall.kt +++ b/idea/idea-frontend-fir/testData/analysisSession/resolveCall/variableAsFunctionLikeCall.kt @@ -4,4 +4,4 @@ fun call(x: kotlin.int) { x() } -// CALL: FunctionCallInfo: targetFunction = invoke(: kotlin.Int): kotlin.String \ No newline at end of file +// CALL: KtFunctionCall: targetFunction = invoke(: kotlin.Int): kotlin.String \ No newline at end of file diff --git a/idea/idea-frontend-fir/tests/org/jetbrains/kotlin/idea/frontend/api/fir/AbstractResolveCallTest.kt b/idea/idea-frontend-fir/tests/org/jetbrains/kotlin/idea/frontend/api/fir/AbstractResolveCallTest.kt index 400a1562bb6..622a493f1ec 100644 --- a/idea/idea-frontend-fir/tests/org/jetbrains/kotlin/idea/frontend/api/fir/AbstractResolveCallTest.kt +++ b/idea/idea-frontend-fir/tests/org/jetbrains/kotlin/idea/frontend/api/fir/AbstractResolveCallTest.kt @@ -11,8 +11,10 @@ import com.intellij.psi.PsiElement import com.intellij.testFramework.LightCodeInsightTestCase import org.jetbrains.kotlin.idea.addExternalTestFiles import org.jetbrains.kotlin.idea.executeOnPooledThreadInReadAction -import org.jetbrains.kotlin.idea.frontend.api.CallInfo import org.jetbrains.kotlin.idea.frontend.api.analyze +import org.jetbrains.kotlin.idea.frontend.api.calls.KtCall +import org.jetbrains.kotlin.idea.frontend.api.calls.KtErrorCallTarget +import org.jetbrains.kotlin.idea.frontend.api.calls.KtSuccessCallTarget import org.jetbrains.kotlin.idea.frontend.api.symbols.KtFunctionLikeSymbol import org.jetbrains.kotlin.idea.frontend.api.symbols.KtFunctionSymbol import org.jetbrains.kotlin.idea.frontend.api.symbols.KtParameterSymbol @@ -86,9 +88,9 @@ abstract class AbstractResolveCallTest : @Suppress("DEPRECATION") LightCodeInsig ) } -private fun CallInfo.stringRepresentation(): String { +private fun KtCall.stringRepresentation(): String { fun KtType.render() = asStringForDebugging().replace('/', '.') - fun Any.stringValue(): String? = when (this) { + fun Any.stringValue(): String = when (this) { is KtFunctionLikeSymbol -> buildString { append(if (this@stringValue is KtFunctionSymbol) callableIdIfNonLocal ?: name else "") append("(") @@ -103,6 +105,8 @@ private fun CallInfo.stringRepresentation(): String { append(": ${type.render()}") } is KtParameterSymbol -> "$name: ${type.render()}" + is KtSuccessCallTarget -> symbol.stringValue() + is KtErrorCallTarget -> "ERR<${this.diagnostic.message}, [${candidates.joinToString { it.stringValue() }}]>" is Boolean -> toString() else -> error("unexpected parameter type ${this::class}") } diff --git a/idea/idea-frontend-fir/tests/org/jetbrains/kotlin/idea/frontend/api/fir/ResolveCallTestGenerated.java b/idea/idea-frontend-fir/tests/org/jetbrains/kotlin/idea/frontend/api/fir/ResolveCallTestGenerated.java index 1ce83128cf9..676a9dd5d57 100644 --- a/idea/idea-frontend-fir/tests/org/jetbrains/kotlin/idea/frontend/api/fir/ResolveCallTestGenerated.java +++ b/idea/idea-frontend-fir/tests/org/jetbrains/kotlin/idea/frontend/api/fir/ResolveCallTestGenerated.java @@ -58,6 +58,11 @@ public class ResolveCallTestGenerated extends AbstractResolveCallTest { runTest("idea/idea-frontend-fir/testData/analysisSession/resolveCall/javaFunctionCall.kt"); } + @TestMetadata("simpleCallWithNonMatchingArgs.kt") + public void testSimpleCallWithNonMatchingArgs() throws Exception { + runTest("idea/idea-frontend-fir/testData/analysisSession/resolveCall/simpleCallWithNonMatchingArgs.kt"); + } + @TestMetadata("variableAsFunction.kt") public void testVariableAsFunction() throws Exception { runTest("idea/idea-frontend-fir/testData/analysisSession/resolveCall/variableAsFunction.kt"); diff --git a/idea/testData/resolve/references/invoke/lambdaNoParIncorrectVararg.kt b/idea/testData/resolve/references/invoke/lambdaNoParIncorrectVararg.kt index 253157780a9..cd6e79d71c2 100644 --- a/idea/testData/resolve/references/invoke/lambdaNoParIncorrectVararg.kt +++ b/idea/testData/resolve/references/invoke/lambdaNoParIncorrectVararg.kt @@ -1,5 +1,3 @@ -// IGNORE_FIR - class Foo { fun invoke(vararg a: Any) {} } diff --git a/idea/testData/resolve/references/invoke/lambdaNoParLabelIncorrectVararg.kt b/idea/testData/resolve/references/invoke/lambdaNoParLabelIncorrectVararg.kt index 6e3ba435282..0ae77948e39 100644 --- a/idea/testData/resolve/references/invoke/lambdaNoParLabelIncorrectVararg.kt +++ b/idea/testData/resolve/references/invoke/lambdaNoParLabelIncorrectVararg.kt @@ -1,5 +1,3 @@ -// IGNORE_FIR - class Foo { fun invoke(vararg a: Any) {} } diff --git a/idea/testData/resolve/references/invoke/lambdaNoParRCurlyIncorrectVararg.kt b/idea/testData/resolve/references/invoke/lambdaNoParRCurlyIncorrectVararg.kt index 1d52891066c..1dc808f0527 100644 --- a/idea/testData/resolve/references/invoke/lambdaNoParRCurlyIncorrectVararg.kt +++ b/idea/testData/resolve/references/invoke/lambdaNoParRCurlyIncorrectVararg.kt @@ -1,5 +1,3 @@ -// IGNORE_FIR - class Foo { fun invoke(vararg a: Any) {} } diff --git a/idea/testData/resolve/references/invoke/nonemptyLambdaRParIncorrectVararg.kt b/idea/testData/resolve/references/invoke/nonemptyLambdaRParIncorrectVararg.kt index 36dfb64d544..2ce67a054ed 100644 --- a/idea/testData/resolve/references/invoke/nonemptyLambdaRParIncorrectVararg.kt +++ b/idea/testData/resolve/references/invoke/nonemptyLambdaRParIncorrectVararg.kt @@ -1,5 +1,3 @@ -// IGNORE_FIR - class Foo { fun invoke(vararg a: Any) {} }