FIR IDE: use symbols for call resolve

This commit is contained in:
Ilya Kirillov
2020-06-24 16:46:18 +03:00
parent 82866176fb
commit 749afbd141
12 changed files with 75 additions and 147 deletions
@@ -8,6 +8,8 @@ package org.jetbrains.kotlin.idea.fir.highlighter.visitors
import com.intellij.lang.annotation.AnnotationHolder import com.intellij.lang.annotation.AnnotationHolder
import com.intellij.openapi.editor.colors.TextAttributesKey import com.intellij.openapi.editor.colors.TextAttributesKey
import org.jetbrains.kotlin.idea.frontend.api.* 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.idea.refactoring.fqName.getKotlinFqName
import org.jetbrains.kotlin.lexer.KtTokens import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.*
@@ -55,15 +57,16 @@ internal class FunctionCallHighlightingVisitor(
private fun getTextAttributesForCal(callInfo: CallInfo): TextAttributesKey? = when { private fun getTextAttributesForCal(callInfo: CallInfo): TextAttributesKey? = when {
callInfo.isSuspendCall -> Colors.SUSPEND_FUNCTION_CALL callInfo.isSuspendCall -> Colors.SUSPEND_FUNCTION_CALL
callInfo is ConstructorCallInfo -> callInfo is FunctionCallInfo -> when (val function = callInfo.targetFunction) {
Colors.CONSTRUCTOR_CALL is KtConstructorSymbol -> Colors.CONSTRUCTOR_CALL
callInfo is SimpleKtFunctionCallInfo -> when { is KtAnonymousFunctionSymbol -> null
callInfo.targetFunction.getKotlinFqName() == KOTLIN_SUSPEND_BUILT_IN_FUNCTION_FQ_NAME ->Colors.KEYWORD is KtFunctionSymbol -> when {
callInfo.targetFunction.isExtensionDeclaration() -> Colors.EXTENSION_FUNCTION_CALL function.fqName == KOTLIN_SUSPEND_BUILT_IN_FUNCTION_FQ_NAME -> Colors.KEYWORD
callInfo.targetFunction.parent is KtFile -> Colors.PACKAGE_FUNCTION_CALL function.isExtension -> Colors.EXTENSION_FUNCTION_CALL
else -> Colors.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 VariableAsFunctionCallInfo -> Colors.VARIABLE_AS_FUNCTION_CALL
callInfo is VariableAsFunctionLikeCallInfo -> Colors.VARIABLE_AS_FUNCTION_LIKE_CALL callInfo is VariableAsFunctionLikeCallInfo -> Colors.VARIABLE_AS_FUNCTION_LIKE_CALL
else -> null else -> null
@@ -5,78 +5,26 @@
package org.jetbrains.kotlin.idea.frontend.api package org.jetbrains.kotlin.idea.frontend.api
import com.intellij.psi.PsiClass import org.jetbrains.kotlin.idea.frontend.api.symbols.*
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
sealed class CallInfo { sealed class CallInfo {
abstract val isSuspendCall: Boolean abstract val isSuspendCall: Boolean
abstract val targetFunction: PsiElement? abstract val targetFunction: KtFunctionLikeSymbol?
abstract val isJavaFunctionCall: Boolean
} }
data class VariableAsFunctionCallInfo(val target: PsiElement, override val isSuspendCall: Boolean) : CallInfo() { data class VariableAsFunctionCallInfo(val target: KtVariableLikeSymbol, override val isSuspendCall: Boolean) : CallInfo() {
override val targetFunction: PsiElement? = null override val targetFunction: KtFunctionLikeSymbol? = null
override val isJavaFunctionCall: Boolean = false
} }
data class VariableAsFunctionLikeCallInfo(val target: PsiElement, val invokeFunction: KtNamedFunction) : CallInfo() { data class VariableAsFunctionLikeCallInfo(val target: KtVariableLikeSymbol, val invokeFunction: KtFunctionSymbol) : CallInfo() {
override val isSuspendCall: Boolean get() = invokeFunction.hasModifier(KtTokens.SUSPEND_KEYWORD) override val isSuspendCall: Boolean get() = invokeFunction.isSuspend
override val targetFunction: PsiElement? get() = invokeFunction override val targetFunction get() = invokeFunction
override val isJavaFunctionCall: Boolean = false
} }
// SimpleFunctionCallInfo data class FunctionCallInfo(override val targetFunction: KtFunctionLikeSymbol) : CallInfo() {
override val isSuspendCall: Boolean
sealed class SimpleFunctionCallInfo : CallInfo() { get() = when (targetFunction) {
abstract override val targetFunction: PsiElement is KtFunctionSymbol -> targetFunction.isSuspend
} else -> false
}
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
} }
@@ -6,10 +6,6 @@
package org.jetbrains.kotlin.idea.frontend.api.fir package org.jetbrains.kotlin.idea.frontend.api.fir
import com.intellij.openapi.project.Project 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.builtins.KotlinBuiltIns
import org.jetbrains.kotlin.diagnostics.Diagnostic import org.jetbrains.kotlin.diagnostics.Diagnostic
import org.jetbrains.kotlin.fir.declarations.* 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.fir.low.level.api.LowLevelFirApiFacade
import org.jetbrains.kotlin.idea.frontend.api.* import org.jetbrains.kotlin.idea.frontend.api.*
import org.jetbrains.kotlin.idea.references.FirReferenceResolveHelper 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.ClassId
import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.*
@@ -35,6 +31,8 @@ class FirAnalysisSession(
) : FrontendAnalysisSession(project) { ) : FrontendAnalysisSession(project) {
constructor(element: KtElement) : this(element.project) constructor(element: KtElement) : this(element.project)
internal val symbolBuilder = KtSymbolByFirBuilder(validityToken)
init { init {
assertIsValid() assertIsValid()
} }
@@ -114,55 +112,39 @@ class FirAnalysisSession(
private fun resolveCall(firCall: FirFunctionCall, callExpression: KtExpression): CallInfo? { private fun resolveCall(firCall: FirFunctionCall, callExpression: KtExpression): CallInfo? {
val session = callExpression.session val session = callExpression.session
val resolvedFunctionPsi = firCall.calleeReference.toTargetPsi(session) val resolvedFunctionSymbol = firCall.calleeReference.toTargetSymbol(session, symbolBuilder)
val resolvedCalleeSymbol = (firCall.calleeReference as? FirResolvedNamedReference)?.resolvedSymbol val resolvedCalleeSymbol = (firCall.calleeReference as? FirResolvedNamedReference)?.resolvedSymbol
return when { return when {
resolvedCalleeSymbol is FirConstructorSymbol -> { resolvedCalleeSymbol is FirConstructorSymbol -> {
val fir = resolvedCalleeSymbol.fir val fir = resolvedCalleeSymbol.fir
when (resolvedFunctionPsi) { FunctionCallInfo(symbolBuilder.buildFirConstructorSymbol(fir))
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
}
}
}
} }
firCall.dispatchReceiver is FirQualifiedAccessExpression && firCall.isImplicitFunctionCall() -> { firCall.dispatchReceiver is FirQualifiedAccessExpression && firCall.isImplicitFunctionCall() -> {
val target = with(FirReferenceResolveHelper) { val target = with(FirReferenceResolveHelper) {
val calleeReference = (firCall.dispatchReceiver as FirQualifiedAccessExpression).calleeReference val calleeReference = (firCall.dispatchReceiver as FirQualifiedAccessExpression).calleeReference
calleeReference.toTargetPsi(session) calleeReference.toTargetSymbol(session, symbolBuilder)
} }
when (target) { when (target) {
null -> null null -> null
is KtValVarKeywordOwner, is PsiField -> { is KtVariableLikeSymbol -> {
val functionSymbol = val functionSymbol =
(firCall.calleeReference as? FirResolvedNamedReference)?.resolvedSymbol as? FirNamedFunctionSymbol (firCall.calleeReference as? FirResolvedNamedReference)?.resolvedSymbol as? FirNamedFunctionSymbol
when (functionSymbol?.callableId) { when (functionSymbol?.callableId) {
null -> null null -> null
in kotlinFunctionInvokeCallableIds -> VariableAsFunctionCallInfo(target, functionSymbol.fir.isSuspend) 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) { private fun KtSymbol.asSimpleFunctionCall() =
is KtNamedFunction -> SimpleKtFunctionCallInfo(this) (this as? KtSimpleFunctionSymbol)?.let(::FunctionCallInfo)
is PsiMethod -> SimpleJavaFunctionCallInfo(this)
else -> null
}
private fun forEachSuperClass(firClass: FirClass<*>, action: (FirResolvedTypeRef) -> Unit) { private fun forEachSuperClass(firClass: FirClass<*>, action: (FirResolvedTypeRef) -> Unit) {
firClass.superTypeRefs.forEach { superType -> firClass.superTypeRefs.forEach { superType ->
@@ -4,4 +4,4 @@ fun call() {
<selection>function(1)</selection> <selection>function(1)</selection>
} }
// CALL: SimpleKtFunctionCallInfo: targetFunction = function(a: Int): IMPLICIT_TYPE // CALL: FunctionCallInfo: targetFunction = function(a: kotlin.Int): kotlin.Unit
@@ -4,4 +4,4 @@ fun call() {
"str".<selection>function(1)</selection> "str".<selection>function(1)</selection>
} }
// CALL: SimpleKtFunctionCallInfo: targetFunction = function(<receiver> : String, a: Int): IMPLICIT_TYPE // CALL: FunctionCallInfo: targetFunction = function(<receiver>: kotlin.String, a: kotlin.Int): kotlin.Unit
@@ -4,4 +4,4 @@ fun call() {
"str"?.<selection>function(1)</selection> "str"?.<selection>function(1)</selection>
} }
// CALL: SimpleKtFunctionCallInfo: targetFunction = function(<receiver> : String, a: Int): IMPLICIT_TYPE // CALL: FunctionCallInfo: targetFunction = function(<receiver>: kotlin.String, a: kotlin.Int): kotlin.Unit
@@ -4,4 +4,4 @@ fun call() {
val a = <selection>A()</selection> val a = <selection>A()</selection>
} }
// CALL: KtImplicitPrimaryConstructorCallInfo: owner = Implicit constructor of A // CALL: FunctionCallInfo: targetFunction = <constructor>(): A
@@ -3,4 +3,4 @@ fun call() {
val a = <selection>A()</selection> val a = <selection>A()</selection>
} }
// CALL: JavaImplicitPrimaryConstructorCallInfo: owner = Implicit constructor of A // CALL: FunctionCallInfo: targetFunction = <constructor>(): A
@@ -3,4 +3,4 @@ fun call() {
javaClass.<selection>javaMethod()</selection> javaClass.<selection>javaMethod()</selection>
} }
// CALL: SimpleJavaFunctionCallInfo: targetFunction = JavaClass.javaMethod(): void // CALL: FunctionCallInfo: targetFunction = JavaClass.javaMethod(): kotlin.Unit
@@ -2,4 +2,4 @@ fun call(x: (Int) -> String) {
<selection>x(1)</selection> <selection>x(1)</selection>
} }
// CALL: VariableAsFunctionCallInfo: target = x, isSuspendCall = false // CALL: VariableAsFunctionCallInfo: target = x: kotlin.Function1<kotlin.Int, kotlin.String>, isSuspendCall = false
@@ -1,7 +1,7 @@
operator fun Int.invoke(): String {} operator fun Int.invoke(): String {}
fun call(x: Int) { fun call(x: kotlin.int) {
<selection>x()</selection> <selection>x()</selection>
} }
// CALL: SimpleKtFunctionCallInfo: targetFunction = invoke(<receiver> : Int): String // CALL: FunctionCallInfo: targetFunction = invoke(<receiver>: kotlin.Int): kotlin.String
@@ -18,6 +18,8 @@ import com.intellij.psi.PsiParameter
import com.intellij.testFramework.LightPlatformTestCase import com.intellij.testFramework.LightPlatformTestCase
import org.intellij.plugins.relaxNG.compact.psi.util.PsiFunction import org.intellij.plugins.relaxNG.compact.psi.util.PsiFunction
import org.jetbrains.kotlin.idea.frontend.api.CallInfo 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.refactoring.fqName.getKotlinFqName
import org.jetbrains.kotlin.idea.test.KotlinLightCodeInsightTestCase import org.jetbrains.kotlin.idea.test.KotlinLightCodeInsightTestCase
import org.jetbrains.kotlin.idea.test.PluginTestCaseBase import org.jetbrains.kotlin.idea.test.PluginTestCaseBase
@@ -42,29 +44,31 @@ abstract class AbstractResolveCallTest : @Suppress("DEPRECATION") KotlinLightCod
val elements = editor.caretModel.caretsAndSelections.map { selection -> val elements = editor.caretModel.caretsAndSelections.map { selection ->
getSingleSelectedElement(selection) getSingleSelectedElement(selection)
} }
val callInfos = executeOnPooledThreadInReadAction {
val actualText = executeOnPooledThreadInReadAction {
val analysisSession = FirAnalysisSession(file as KtFile) val analysisSession = FirAnalysisSession(file as KtFile)
elements.map { element -> val callInfos = elements.map { element ->
when (element) { when (element) {
is KtCallExpression -> analysisSession.resolveCall(element) is KtCallExpression -> analysisSession.resolveCall(element)
is KtBinaryExpression -> analysisSession.resolveCall(element) is KtBinaryExpression -> analysisSession.resolveCall(element)
else -> error("Selected should be either KtCallExpression or KtBinaryExpression but was $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 { if (callInfos.isEmpty()) {
val rawText = File(path).readText() error("There are should be at least one call selected")
"""(?m)^// CALL:\s.*$""".toRegex().replace(rawText, "").trimEnd() }
}
val actualText = buildString { val textWithoutLatestComments = run {
append(textWithoutLatestComments) val rawText = File(path).readText()
append("\n\n") """(?m)^// CALL:\s.*$""".toRegex().replace(rawText, "").trimEnd()
callInfos.joinTo(this, separator = "\n") { info -> }
"// CALL: ${info?.stringRepresentation()}" buildString {
append(textWithoutLatestComments)
append("\n\n")
callInfos.joinTo(this, separator = "\n") { info ->
"// CALL: ${info?.stringRepresentation()}"
}
} }
} }
KotlinTestUtils.assertEqualsToFile(File(path), actualText) KotlinTestUtils.assertEqualsToFile(File(path), actualText)
@@ -111,31 +115,22 @@ private fun File.getExternalFiles(): List<File> {
} }
private fun CallInfo.stringRepresentation(): String { private fun CallInfo.stringRepresentation(): String {
fun TypeInfo.render() = asDenotableTypeStringRepresentation().replace('/', '.')
fun Any.stringValue(): String? = when (this) { fun Any.stringValue(): String? = when (this) {
is PsiMethod -> buildString { is KtFunctionLikeSymbol -> buildString {
append(getKotlinFqName()!!) append(if (this@stringValue is KtFunctionSymbol) fqName else "<constructor>")
@Suppress("UnstableApiUsage")
parameters.joinTo(this, prefix = "(", postfix = ")") { parameter ->
"${parameter.name}: ${(parameter as PsiParameter).typeElement!!.text}"
}
append(": ${returnTypeElement!!.text}")
}
is KtFunction -> buildString {
append(getKotlinFqName()!!)
append("(") append("(")
receiverTypeReference?.let { receiver -> (this@stringValue as? KtFunctionSymbol)?.receiverType?.let { receiver ->
append("<receiver> : ${receiver.text}") append("<receiver>: ${receiver.render()}")
if (valueParameters.isNotEmpty()) append(", ") if (valueParameters.isNotEmpty()) append(", ")
} }
valueParameters.joinTo(this,) { parameter -> valueParameters.joinTo(this) { parameter ->
"${parameter.name}: ${parameter.typeReference!!.text}" "${parameter.name}: ${parameter.type.render()}"
} }
append(")") append(")")
append(": ${typeReference?.text ?: "IMPLICIT_TYPE"}") append(": ${type.render()}")
} }
is KtClass -> "Implicit constructor of ${getKotlinFqName()!!}" is KtParameterSymbol -> "$name: ${type.render()}"
is PsiClass -> "Implicit constructor of ${getKotlinFqName()!!}"
is KtParameter -> name!!
is Boolean -> toString() is Boolean -> toString()
else -> error("unexpected parameter type ${this::class}") else -> error("unexpected parameter type ${this::class}")
} }