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 1580ca5817d..ff6da658f97 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,8 @@ 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.symbols.KtAnonymousFunctionSymbol +import org.jetbrains.kotlin.idea.frontend.api.symbols.KtConstructorSymbol import org.jetbrains.kotlin.idea.refactoring.fqName.getKotlinFqName import org.jetbrains.kotlin.lexer.KtTokens import org.jetbrains.kotlin.psi.* @@ -55,15 +57,16 @@ internal class FunctionCallHighlightingVisitor( private fun getTextAttributesForCal(callInfo: CallInfo): TextAttributesKey? = when { callInfo.isSuspendCall -> Colors.SUSPEND_FUNCTION_CALL - callInfo is ConstructorCallInfo -> - Colors.CONSTRUCTOR_CALL - callInfo is SimpleKtFunctionCallInfo -> when { - callInfo.targetFunction.getKotlinFqName() == KOTLIN_SUSPEND_BUILT_IN_FUNCTION_FQ_NAME ->Colors.KEYWORD - callInfo.targetFunction.isExtensionDeclaration() -> Colors.EXTENSION_FUNCTION_CALL - callInfo.targetFunction.parent is KtFile -> Colors.PACKAGE_FUNCTION_CALL - else -> Colors.FUNCTION_CALL + callInfo is FunctionCallInfo -> when (val function = callInfo.targetFunction) { + is KtConstructorSymbol -> Colors.CONSTRUCTOR_CALL + is KtAnonymousFunctionSymbol -> null + is KtFunctionSymbol -> when { + function.fqName == KOTLIN_SUSPEND_BUILT_IN_FUNCTION_FQ_NAME -> Colors.KEYWORD + function.isExtension -> Colors.EXTENSION_FUNCTION_CALL + function.symbolKind == KtSymbolKind.TOP_LEVEL -> Colors.PACKAGE_FUNCTION_CALL + else -> Colors.FUNCTION_CALL + } } - callInfo is SimpleJavaFunctionCallInfo -> Colors.FUNCTION_CALL callInfo is VariableAsFunctionCallInfo -> Colors.VARIABLE_AS_FUNCTION_CALL callInfo is VariableAsFunctionLikeCallInfo -> Colors.VARIABLE_AS_FUNCTION_LIKE_CALL else -> null 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 index f7bf394a37b..638fc3b3e78 100644 --- 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 @@ -5,78 +5,26 @@ package org.jetbrains.kotlin.idea.frontend.api -import com.intellij.psi.PsiClass -import com.intellij.psi.PsiElement -import com.intellij.psi.PsiMethod -import org.jetbrains.kotlin.lexer.KtTokens -import org.jetbrains.kotlin.psi.KtClass -import org.jetbrains.kotlin.psi.KtClassOrObject -import org.jetbrains.kotlin.psi.KtConstructor -import org.jetbrains.kotlin.psi.KtNamedFunction +import org.jetbrains.kotlin.idea.frontend.api.symbols.* sealed class CallInfo { abstract val isSuspendCall: Boolean - abstract val targetFunction: PsiElement? - abstract val isJavaFunctionCall: Boolean + abstract val targetFunction: KtFunctionLikeSymbol? } -data class VariableAsFunctionCallInfo(val target: PsiElement, override val isSuspendCall: Boolean) : CallInfo() { - override val targetFunction: PsiElement? = null - override val isJavaFunctionCall: Boolean = false +data class VariableAsFunctionCallInfo(val target: KtVariableLikeSymbol, override val isSuspendCall: Boolean) : CallInfo() { + override val targetFunction: KtFunctionLikeSymbol? = null } -data class VariableAsFunctionLikeCallInfo(val target: PsiElement, val invokeFunction: KtNamedFunction) : CallInfo() { - override val isSuspendCall: Boolean get() = invokeFunction.hasModifier(KtTokens.SUSPEND_KEYWORD) - override val targetFunction: PsiElement? get() = invokeFunction - override val isJavaFunctionCall: Boolean = false +data class VariableAsFunctionLikeCallInfo(val target: KtVariableLikeSymbol, val invokeFunction: KtFunctionSymbol) : CallInfo() { + override val isSuspendCall: Boolean get() = invokeFunction.isSuspend + override val targetFunction get() = invokeFunction } -// SimpleFunctionCallInfo - -sealed class SimpleFunctionCallInfo : CallInfo() { - abstract override val targetFunction: PsiElement -} - -data class SimpleKtFunctionCallInfo(override val targetFunction: KtNamedFunction) : SimpleFunctionCallInfo() { - override val isSuspendCall: Boolean get() = targetFunction.hasModifier(KtTokens.SUSPEND_KEYWORD) - override val isJavaFunctionCall: Boolean = true -} - -data class SimpleJavaFunctionCallInfo(override val targetFunction: PsiMethod) : SimpleFunctionCallInfo() { - override val isSuspendCall: Boolean = false - override val isJavaFunctionCall: Boolean = false -} - - -// ConstructorCallInfo - -sealed class ConstructorCallInfo : CallInfo() { - final override val isSuspendCall: Boolean = false - abstract val isPrimary: Boolean -} - -data class KtExplicitConstructorCallInfo( - override val targetFunction: KtConstructor<*>, - override val isPrimary: Boolean -) : ConstructorCallInfo() { - override val isJavaFunctionCall: Boolean = false -} - -data class KtImplicitPrimaryConstructorCallInfo(val owner: KtClass) : ConstructorCallInfo() { - override val targetFunction: PsiElement? = null - override val isJavaFunctionCall: Boolean = false - override val isPrimary: Boolean = true -} - -data class JavaExplicitConstructorCallInfo( - override val targetFunction: PsiMethod, - override val isPrimary: Boolean -) : ConstructorCallInfo() { - override val isJavaFunctionCall: Boolean = true -} - -data class JavaImplicitPrimaryConstructorCallInfo(val owner: PsiClass) : ConstructorCallInfo() { - override val targetFunction: PsiElement? = null - override val isJavaFunctionCall: Boolean = true - override val isPrimary: Boolean = true -} +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-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/FirAnalysisSession.kt b/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/FirAnalysisSession.kt index da25929b60b..273e6c208b9 100644 --- a/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/FirAnalysisSession.kt +++ b/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/FirAnalysisSession.kt @@ -6,10 +6,6 @@ package org.jetbrains.kotlin.idea.frontend.api.fir import com.intellij.openapi.project.Project -import com.intellij.psi.PsiClass -import com.intellij.psi.PsiElement -import com.intellij.psi.PsiField -import com.intellij.psi.PsiMethod import org.jetbrains.kotlin.builtins.KotlinBuiltIns import org.jetbrains.kotlin.diagnostics.Diagnostic import org.jetbrains.kotlin.fir.declarations.* @@ -25,7 +21,7 @@ import org.jetbrains.kotlin.idea.fir.* import org.jetbrains.kotlin.idea.fir.low.level.api.LowLevelFirApiFacade import org.jetbrains.kotlin.idea.frontend.api.* import org.jetbrains.kotlin.idea.references.FirReferenceResolveHelper -import org.jetbrains.kotlin.idea.references.FirReferenceResolveHelper.toTargetPsi +import org.jetbrains.kotlin.idea.references.FirReferenceResolveHelper.toTargetSymbol import org.jetbrains.kotlin.name.ClassId import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.psi.* @@ -35,6 +31,8 @@ class FirAnalysisSession( ) : FrontendAnalysisSession(project) { constructor(element: KtElement) : this(element.project) + internal val symbolBuilder = KtSymbolByFirBuilder(validityToken) + init { assertIsValid() } @@ -114,55 +112,39 @@ class FirAnalysisSession( private fun resolveCall(firCall: FirFunctionCall, callExpression: KtExpression): CallInfo? { val session = callExpression.session - val resolvedFunctionPsi = firCall.calleeReference.toTargetPsi(session) + val resolvedFunctionSymbol = firCall.calleeReference.toTargetSymbol(session, symbolBuilder) val resolvedCalleeSymbol = (firCall.calleeReference as? FirResolvedNamedReference)?.resolvedSymbol return when { resolvedCalleeSymbol is FirConstructorSymbol -> { val fir = resolvedCalleeSymbol.fir - when (resolvedFunctionPsi) { - is KtClass -> KtImplicitPrimaryConstructorCallInfo(resolvedFunctionPsi) - is PsiClass -> JavaImplicitPrimaryConstructorCallInfo(resolvedFunctionPsi) - is KtConstructor<*> -> KtExplicitConstructorCallInfo(resolvedFunctionPsi, fir.isPrimary) - is PsiMethod -> JavaExplicitConstructorCallInfo(resolvedFunctionPsi, fir.isPrimary) - else -> { - val classId = resolvedCalleeSymbol.callableId.classId - val firClass = classId?.let(session.firSymbolProvider::getClassLikeSymbolByFqName) - when (val psiClass = firClass?.fir?.findPsi(callExpression.project)) { - is PsiClass -> JavaImplicitPrimaryConstructorCallInfo(psiClass) - is KtClass -> KtImplicitPrimaryConstructorCallInfo(psiClass) - else -> null - } - } - } + FunctionCallInfo(symbolBuilder.buildFirConstructorSymbol(fir)) } firCall.dispatchReceiver is FirQualifiedAccessExpression && firCall.isImplicitFunctionCall() -> { val target = with(FirReferenceResolveHelper) { val calleeReference = (firCall.dispatchReceiver as FirQualifiedAccessExpression).calleeReference - calleeReference.toTargetPsi(session) + calleeReference.toTargetSymbol(session, symbolBuilder) } when (target) { null -> null - is KtValVarKeywordOwner, is PsiField -> { + is KtVariableLikeSymbol -> { val functionSymbol = (firCall.calleeReference as? FirResolvedNamedReference)?.resolvedSymbol as? FirNamedFunctionSymbol when (functionSymbol?.callableId) { null -> null in kotlinFunctionInvokeCallableIds -> VariableAsFunctionCallInfo(target, functionSymbol.fir.isSuspend) - else -> (resolvedFunctionPsi as? KtNamedFunction)?.let { VariableAsFunctionLikeCallInfo(target, it) } + else -> (resolvedFunctionSymbol as? KtSimpleFunctionSymbol) + ?.let { VariableAsFunctionLikeCallInfo(target, it) } } } - else -> resolvedFunctionPsi?.asSimpleFunctionCall() + else -> resolvedFunctionSymbol?.asSimpleFunctionCall() } } - else -> resolvedFunctionPsi?.asSimpleFunctionCall() + else -> resolvedFunctionSymbol?.asSimpleFunctionCall() } } - private fun PsiElement.asSimpleFunctionCall() = when (this) { - is KtNamedFunction -> SimpleKtFunctionCallInfo(this) - is PsiMethod -> SimpleJavaFunctionCallInfo(this) - else -> null - } + private fun KtSymbol.asSimpleFunctionCall() = + (this as? KtSimpleFunctionSymbol)?.let(::FunctionCallInfo) private fun forEachSuperClass(firClass: FirClass<*>, action: (FirResolvedTypeRef) -> Unit) { firClass.superTypeRefs.forEach { superType -> diff --git a/idea/idea-frontend-fir/testData/analysisSession/resolveCall/functionCallInTheSameFile.kt b/idea/idea-frontend-fir/testData/analysisSession/resolveCall/functionCallInTheSameFile.kt index 0d64c6ea5e8..dcea8d939c6 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: SimpleKtFunctionCallInfo: targetFunction = function(a: Int): IMPLICIT_TYPE \ No newline at end of file +// CALL: FunctionCallInfo: 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 925fafed6f6..53eab0c512e 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: SimpleKtFunctionCallInfo: targetFunction = function( : String, a: Int): IMPLICIT_TYPE \ No newline at end of file +// CALL: FunctionCallInfo: 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 7ec3271c80a..c5927c2c429 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: SimpleKtFunctionCallInfo: targetFunction = function( : String, a: Int): IMPLICIT_TYPE \ No newline at end of file +// CALL: FunctionCallInfo: 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 6832ad518b9..1efa530e7d5 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: KtImplicitPrimaryConstructorCallInfo: owner = Implicit constructor of A \ No newline at end of file +// CALL: FunctionCallInfo: 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 36429367409..6b30b9a4a09 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: JavaImplicitPrimaryConstructorCallInfo: owner = Implicit constructor of A \ No newline at end of file +// CALL: FunctionCallInfo: 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 391cb3e03c6..f54b3426f93 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: SimpleJavaFunctionCallInfo: targetFunction = JavaClass.javaMethod(): void \ No newline at end of file +// CALL: FunctionCallInfo: targetFunction = JavaClass.javaMethod(): kotlin.Unit \ 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 52b2df848fb..5a5383f2b8e 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, isSuspendCall = false \ No newline at end of file +// CALL: VariableAsFunctionCallInfo: target = x: kotlin.Function1, isSuspendCall = false \ 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 4e1c191f86f..31a733dc2d7 100644 --- a/idea/idea-frontend-fir/testData/analysisSession/resolveCall/variableAsFunctionLikeCall.kt +++ b/idea/idea-frontend-fir/testData/analysisSession/resolveCall/variableAsFunctionLikeCall.kt @@ -1,7 +1,7 @@ operator fun Int.invoke(): String {} -fun call(x: Int) { +fun call(x: kotlin.int) { x() } -// CALL: SimpleKtFunctionCallInfo: targetFunction = invoke( : Int): String \ No newline at end of file +// CALL: FunctionCallInfo: 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 51498c9394a..25ed4c60263 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 @@ -18,6 +18,8 @@ import com.intellij.psi.PsiParameter import com.intellij.testFramework.LightPlatformTestCase import org.intellij.plugins.relaxNG.compact.psi.util.PsiFunction import org.jetbrains.kotlin.idea.frontend.api.CallInfo +import org.jetbrains.kotlin.idea.frontend.api.TypeInfo +import org.jetbrains.kotlin.idea.frontend.api.symbols.* import org.jetbrains.kotlin.idea.refactoring.fqName.getKotlinFqName import org.jetbrains.kotlin.idea.test.KotlinLightCodeInsightTestCase import org.jetbrains.kotlin.idea.test.PluginTestCaseBase @@ -42,29 +44,31 @@ abstract class AbstractResolveCallTest : @Suppress("DEPRECATION") KotlinLightCod val elements = editor.caretModel.caretsAndSelections.map { selection -> getSingleSelectedElement(selection) } - val callInfos = executeOnPooledThreadInReadAction { + + val actualText = executeOnPooledThreadInReadAction { val analysisSession = FirAnalysisSession(file as KtFile) - elements.map { element -> + val callInfos = elements.map { element -> when (element) { is KtCallExpression -> analysisSession.resolveCall(element) is KtBinaryExpression -> analysisSession.resolveCall(element) else -> error("Selected should be either KtCallExpression or KtBinaryExpression but was $element") } } - } - if (callInfos.isEmpty()) { - error("There are should be at least one call selected") - } - val textWithoutLatestComments = run { - val rawText = File(path).readText() - """(?m)^// CALL:\s.*$""".toRegex().replace(rawText, "").trimEnd() - } - val actualText = buildString { - append(textWithoutLatestComments) - append("\n\n") - callInfos.joinTo(this, separator = "\n") { info -> - "// CALL: ${info?.stringRepresentation()}" + if (callInfos.isEmpty()) { + error("There are should be at least one call selected") + } + + val textWithoutLatestComments = run { + val rawText = File(path).readText() + """(?m)^// CALL:\s.*$""".toRegex().replace(rawText, "").trimEnd() + } + buildString { + append(textWithoutLatestComments) + append("\n\n") + callInfos.joinTo(this, separator = "\n") { info -> + "// CALL: ${info?.stringRepresentation()}" + } } } KotlinTestUtils.assertEqualsToFile(File(path), actualText) @@ -111,31 +115,22 @@ private fun File.getExternalFiles(): List { } private fun CallInfo.stringRepresentation(): String { + fun TypeInfo.render() = asDenotableTypeStringRepresentation().replace('/', '.') fun Any.stringValue(): String? = when (this) { - is PsiMethod -> buildString { - append(getKotlinFqName()!!) - @Suppress("UnstableApiUsage") - parameters.joinTo(this, prefix = "(", postfix = ")") { parameter -> - "${parameter.name}: ${(parameter as PsiParameter).typeElement!!.text}" - } - append(": ${returnTypeElement!!.text}") - } - is KtFunction -> buildString { - append(getKotlinFqName()!!) + is KtFunctionLikeSymbol -> buildString { + append(if (this@stringValue is KtFunctionSymbol) fqName else "") append("(") - receiverTypeReference?.let { receiver -> - append(" : ${receiver.text}") + (this@stringValue as? KtFunctionSymbol)?.receiverType?.let { receiver -> + append(": ${receiver.render()}") if (valueParameters.isNotEmpty()) append(", ") } - valueParameters.joinTo(this,) { parameter -> - "${parameter.name}: ${parameter.typeReference!!.text}" + valueParameters.joinTo(this) { parameter -> + "${parameter.name}: ${parameter.type.render()}" } append(")") - append(": ${typeReference?.text ?: "IMPLICIT_TYPE"}") + append(": ${type.render()}") } - is KtClass -> "Implicit constructor of ${getKotlinFqName()!!}" - is PsiClass -> "Implicit constructor of ${getKotlinFqName()!!}" - is KtParameter -> name!! + is KtParameterSymbol -> "$name: ${type.render()}" is Boolean -> toString() else -> error("unexpected parameter type ${this::class}") }