FIR IDE: implement completion for variables with functional types with receivers

This commit is contained in:
Ilya Kirillov
2021-06-09 14:35:19 +02:00
committed by teamcityserver
parent 167917cf07
commit 915c8b7996
22 changed files with 234 additions and 88 deletions
@@ -6,4 +6,3 @@ fun C.test(foo: C.() -> Unit) {
}
// EXIST: { lookupString: "foo", itemText: "foo", tailText: null, typeText: "C.() -> Unit" }
// ABSENT: { itemText: "foo", typeText: "Unit" }
@@ -1,3 +1,4 @@
// FIR_COMPARISON
interface I
interface J
@@ -1,3 +1,4 @@
// FIR_COMPARISON
fun String.foo() {
val v = ::xxx_<caret>
}
@@ -1,3 +1,4 @@
// FIR_COMPARISON
fun test(i: Int, foo: Int.() -> Char) {
i.<caret>
}
@@ -1,3 +1,4 @@
// FIR_COMPARISON
fun test(i: Int, foo: Int.() -> Char) {
i.foo()<caret>
}
@@ -1,3 +1,4 @@
// FIR_COMPARISON
fun test(i: Int, foo: Int.(String) -> Char) {
i.<caret>
}
@@ -1,3 +1,4 @@
// FIR_COMPARISON
fun test(i: Int, foo: Int.(String) -> Char) {
i.foo(<caret>)
}
@@ -6,8 +6,9 @@
package org.jetbrains.kotlin.idea.completion.checkers
import org.jetbrains.kotlin.idea.frontend.api.KtAnalysisSession
import org.jetbrains.kotlin.idea.frontend.api.components.KtExtensionApplicabilityResult
import org.jetbrains.kotlin.idea.frontend.api.symbols.KtCallableSymbol
internal fun interface ExtensionApplicabilityChecker {
fun KtAnalysisSession.isApplicable(symbol: KtCallableSymbol): Boolean
fun KtAnalysisSession.isApplicable(symbol: KtCallableSymbol): KtExtensionApplicabilityResult
}
@@ -10,24 +10,49 @@ import org.jetbrains.kotlin.idea.completion.checkers.ExtensionApplicabilityCheck
import org.jetbrains.kotlin.idea.completion.context.FirBasicCompletionContext
import org.jetbrains.kotlin.idea.completion.context.FirNameReferencePositionContext
import org.jetbrains.kotlin.idea.completion.contributors.helpers.insertSymbolAndInvokeCompletion
import org.jetbrains.kotlin.idea.completion.lookups.CallableInsertionOptions
import org.jetbrains.kotlin.idea.completion.lookups.CallableInsertionStrategy
import org.jetbrains.kotlin.idea.fir.HLIndexHelper
import org.jetbrains.kotlin.idea.frontend.api.KtAnalysisSession
import org.jetbrains.kotlin.idea.frontend.api.components.KtExtensionApplicabilityResult
import org.jetbrains.kotlin.idea.frontend.api.components.KtScopeContext
import org.jetbrains.kotlin.idea.frontend.api.scopes.KtCompositeScope
import org.jetbrains.kotlin.idea.frontend.api.scopes.KtScope
import org.jetbrains.kotlin.idea.frontend.api.symbols.*
import org.jetbrains.kotlin.idea.frontend.api.types.KtClassType
import org.jetbrains.kotlin.idea.frontend.api.types.KtNonErrorClassType
import org.jetbrains.kotlin.idea.frontend.api.types.KtFunctionalType
import org.jetbrains.kotlin.idea.frontend.api.types.KtType
import org.jetbrains.kotlin.psi.KtExpression
import org.jetbrains.kotlin.idea.completion.lookups.detectImportStrategy
internal open class FirCallableCompletionContributor(
basicContext: FirBasicCompletionContext,
) : FirContextCompletionContributorBase<FirNameReferencePositionContext>(basicContext) {
private val typeNamesProvider = TypeNamesProvider(indexHelper)
protected open val insertionStrategy: CallableInsertionStrategy = CallableInsertionStrategy.AsCall
protected open fun KtAnalysisSession.getInsertionStrategy(symbol: KtCallableSymbol): CallableInsertionStrategy = when (symbol) {
is KtFunctionLikeSymbol -> CallableInsertionStrategy.AsCall
else -> CallableInsertionStrategy.AsIdentifier
}
protected open fun KtAnalysisSession.getInsertionStrategyForExtensionFunction(
symbol: KtCallableSymbol,
applicabilityResult: KtExtensionApplicabilityResult
): CallableInsertionStrategy? = when (applicabilityResult) {
KtExtensionApplicabilityResult.ApplicableAsExtensionCallable -> getInsertionStrategy(symbol)
KtExtensionApplicabilityResult.ApplicableAsFunctionalVariableCall -> CallableInsertionStrategy.AsCall
KtExtensionApplicabilityResult.NonApplicable -> null
}
protected fun KtAnalysisSession.getOptions(symbol: KtCallableSymbol): CallableInsertionOptions =
CallableInsertionOptions(detectImportStrategy(symbol), getInsertionStrategy(symbol))
private fun KtAnalysisSession.getExtensionOptions(
symbol: KtCallableSymbol,
applicability: KtExtensionApplicabilityResult
): CallableInsertionOptions? =
getInsertionStrategyForExtensionFunction(symbol, applicability)?.let { CallableInsertionOptions(detectImportStrategy(symbol), it) }
protected open fun KtAnalysisSession.filter(symbol: KtCallableSymbol): Boolean = true
@@ -73,19 +98,21 @@ internal open class FirCallableCompletionContributor(
val availableNonExtensions = collectNonExtensions(implicitScopes, visibilityChecker)
val extensionsWhichCanBeCalled = collectSuitableExtensions(implicitScopes, extensionChecker, visibilityChecker)
availableNonExtensions.forEach { addCallableSymbolToCompletion(expectedType, it, insertionStrategy = insertionStrategy) }
extensionsWhichCanBeCalled.forEach { addCallableSymbolToCompletion(expectedType, it, insertionStrategy = insertionStrategy) }
availableNonExtensions.forEach { addCallableSymbolToCompletion(expectedType, it, getOptions(it)) }
extensionsWhichCanBeCalled.forEach { (symbol, applicable) ->
getExtensionOptions(symbol, applicable)?.let { addCallableSymbolToCompletion(expectedType, symbol, it) }
}
if (shouldCompleteTopLevelCallablesFromIndex) {
val topLevelCallables = indexHelper.getTopLevelCallables(scopeNameFilter)
topLevelCallables.asSequence()
.map { it.getSymbol() as KtCallableSymbol }
.filter { with(visibilityChecker) { isVisible(it) } }
.forEach { addCallableSymbolToCompletion(expectedType, it, insertionStrategy = insertionStrategy) }
.forEach { addCallableSymbolToCompletion(expectedType, it, getOptions(it)) }
}
collectTopLevelExtensionsFromIndices(implicitReceiversTypes, extensionChecker, visibilityChecker)
.forEach { addCallableSymbolToCompletion(expectedType, it, insertionStrategy = insertionStrategy) }
.forEach { addCallableSymbolToCompletion(expectedType, it, getOptions(it)) }
}
protected open fun KtAnalysisSession.collectDotCompletion(
@@ -118,7 +145,7 @@ internal open class FirCallableCompletionContributor(
.filter { with(visibilityChecker) { isVisible(it) } }
.filter { filter(it) }
.forEach { callable ->
addCallableSymbolToCompletion(expectedType, callable, insertionStrategy = insertionStrategy)
addCallableSymbolToCompletion(expectedType, callable, getOptions(callable))
}
}
@@ -136,16 +163,15 @@ internal open class FirCallableCompletionContributor(
val extensionNonMembers = collectSuitableExtensions(implicitScopes, extensionChecker, visibilityChecker)
nonExtensionMembers
.filter { filter(it) }
.forEach { addCallableSymbolToCompletion(expectedType, it, insertionStrategy = insertionStrategy) }
.forEach { addCallableSymbolToCompletion(expectedType, it, getOptions(it)) }
extensionNonMembers
.filter { filter(it) }
.forEach { addCallableSymbolToCompletion(expectedType, it, insertionStrategy = insertionStrategy) }
extensionNonMembers.forEach { (symbol, applicability) ->
getExtensionOptions(symbol, applicability)?.let { addCallableSymbolToCompletion(expectedType, symbol, it) }
}
collectTopLevelExtensionsFromIndices(listOf(typeOfPossibleReceiver), extensionChecker, visibilityChecker)
.filter { filter(it) }
.forEach { addCallableSymbolToCompletion(expectedType, it, insertionStrategy = insertionStrategy) }
.forEach { addCallableSymbolToCompletion(expectedType, it, getOptions(it)) }
}
private fun KtAnalysisSession.collectTopLevelExtensionsFromIndices(
@@ -160,7 +186,7 @@ internal open class FirCallableCompletionContributor(
.map { it.getSymbol() as KtCallableSymbol }
.filter { filter(it) }
.filter { with(visibilityChecker) { isVisible(it) } }
.filter { with(extensionChecker) { isApplicable(it) } }
.filter { with(extensionChecker) { isApplicable(it).isApplicable } }
}
private fun KtAnalysisSession.collectNonExtensions(scope: KtScope, visibilityChecker: CompletionVisibilityChecker) =
@@ -173,12 +199,17 @@ internal open class FirCallableCompletionContributor(
scope: KtCompositeScope,
hasSuitableExtensionReceiver: ExtensionApplicabilityChecker,
visibilityChecker: CompletionVisibilityChecker,
): Sequence<KtCallableSymbol> =
): Sequence<Pair<KtCallableSymbol, KtExtensionApplicabilityResult>> =
scope.getCallableSymbols(scopeNameFilter)
.filter { it.isExtension }
.filter { it.isExtension || it is KtVariableLikeSymbol && (it.annotatedType.type as? KtFunctionalType)?.hasReceiver == true }
.filter { with(visibilityChecker) { isVisible(it) } }
.filter { filter(it) }
.filter { with(hasSuitableExtensionReceiver) { isApplicable(it) } }
.mapNotNull { callable ->
val applicabilityResult = with(hasSuitableExtensionReceiver) { isApplicable(callable) }
if (applicabilityResult.isApplicable) {
callable to applicabilityResult
} else null
}
private fun KtAnalysisSession.findAllNamesOfTypes(implicitReceiversTypes: List<KtType>) =
implicitReceiversTypes.flatMapTo(hashSetOf()) { with(typeNamesProvider) { findAllNames(it) } }
@@ -187,7 +218,18 @@ internal open class FirCallableCompletionContributor(
internal class FirCallableReferenceCompletionContributor(
basicContext: FirBasicCompletionContext
) : FirCallableCompletionContributor(basicContext) {
override val insertionStrategy: CallableInsertionStrategy = CallableInsertionStrategy.AsIdentifier
override fun KtAnalysisSession.getInsertionStrategy(symbol: KtCallableSymbol): CallableInsertionStrategy =
CallableInsertionStrategy.AsIdentifier
override fun KtAnalysisSession.getInsertionStrategyForExtensionFunction(
symbol: KtCallableSymbol,
applicabilityResult: KtExtensionApplicabilityResult
): CallableInsertionStrategy? = when (applicabilityResult) {
KtExtensionApplicabilityResult.ApplicableAsExtensionCallable -> CallableInsertionStrategy.AsIdentifier
KtExtensionApplicabilityResult.ApplicableAsFunctionalVariableCall -> null
KtExtensionApplicabilityResult.NonApplicable -> null
}
override fun KtAnalysisSession.collectDotCompletion(
implicitScopes: KtCompositeScope,
@@ -203,7 +245,7 @@ internal class FirCallableReferenceCompletionContributor(
.getCallableSymbols(scopeNameFilter)
.filter { with(visibilityChecker) { isVisible(it) } }
.forEach { symbol ->
addCallableSymbolToCompletion(expectedType = null, symbol, insertionStrategy = insertionStrategy)
addCallableSymbolToCompletion(expectedType = null, symbol, getOptions(symbol))
}
}
@@ -217,13 +259,27 @@ internal class FirCallableReferenceCompletionContributor(
internal class FirInfixCallableCompletionContributor(
basicContext: FirBasicCompletionContext
) : FirCallableCompletionContributor(basicContext) {
override val insertionStrategy: CallableInsertionStrategy = CallableInsertionStrategy.AsIdentifierCustom {
insertSymbolAndInvokeCompletion(" ")
override fun KtAnalysisSession.getInsertionStrategy(symbol: KtCallableSymbol): CallableInsertionStrategy =
insertionStrategy
override fun KtAnalysisSession.getInsertionStrategyForExtensionFunction(
symbol: KtCallableSymbol,
applicabilityResult: KtExtensionApplicabilityResult
): CallableInsertionStrategy? = when (applicabilityResult) {
KtExtensionApplicabilityResult.ApplicableAsExtensionCallable -> getInsertionStrategy(symbol)
KtExtensionApplicabilityResult.ApplicableAsFunctionalVariableCall -> null
KtExtensionApplicabilityResult.NonApplicable -> null
}
override fun KtAnalysisSession.filter(symbol: KtCallableSymbol): Boolean {
return symbol is KtFunctionSymbol && symbol.isInfix
}
companion object {
private val insertionStrategy = CallableInsertionStrategy.AsIdentifierCustom {
insertSymbolAndInvokeCompletion(" ")
}
}
}
private class TypeNamesProvider(private val indexHelper: HLIndexHelper) {
@@ -12,8 +12,9 @@ import com.intellij.openapi.project.Project
import org.jetbrains.kotlin.idea.completion.LookupElementSink
import org.jetbrains.kotlin.idea.completion.context.FirBasicCompletionContext
import org.jetbrains.kotlin.idea.completion.context.FirRawPositionCompletionContext
import org.jetbrains.kotlin.idea.completion.lookups.*
import org.jetbrains.kotlin.idea.completion.lookups.CallableInsertionOptions
import org.jetbrains.kotlin.idea.completion.lookups.ImportStrategy
import org.jetbrains.kotlin.idea.completion.lookups.CallableInsertionStrategy
import org.jetbrains.kotlin.idea.completion.lookups.detectImportStrategy
import org.jetbrains.kotlin.idea.completion.lookups.factories.KotlinFirLookupElementFactory
import org.jetbrains.kotlin.idea.completion.weighers.Weighers
@@ -71,12 +72,11 @@ internal abstract class FirCompletionContributorBase<C : FirRawPositionCompletio
protected fun KtAnalysisSession.addCallableSymbolToCompletion(
expectedType: KtType?,
symbol: KtCallableSymbol,
importingStrategy: ImportStrategy = detectImportStrategy(symbol),
insertionStrategy: CallableInsertionStrategy,
options: CallableInsertionOptions,
) {
if (symbol !is KtNamedSymbol) return
val lookup = with(lookupElementFactory) {
createCallableLookupElement(symbol, importingStrategy, insertionStrategy)
createCallableLookupElement(symbol, options)
}
applyWeighers(lookup, symbol, expectedType)
sink.addElement(lookup)
@@ -9,6 +9,7 @@ import org.jetbrains.kotlin.idea.completion.checkers.CompletionVisibilityChecker
import org.jetbrains.kotlin.idea.completion.context.FirBasicCompletionContext
import org.jetbrains.kotlin.idea.completion.context.FirImportDirectivePositionContext
import org.jetbrains.kotlin.idea.completion.contributors.helpers.getStaticScope
import org.jetbrains.kotlin.idea.completion.lookups.CallableInsertionOptions
import org.jetbrains.kotlin.idea.completion.lookups.ImportStrategy
import org.jetbrains.kotlin.idea.completion.lookups.CallableInsertionStrategy
import org.jetbrains.kotlin.idea.frontend.api.KtAnalysisSession
@@ -31,8 +32,7 @@ internal class FirImportDirectivePackageMembersCompletionContributor(
addCallableSymbolToCompletion(
expectedType = null,
it,
importingStrategy = ImportStrategy.DoNothing,
insertionStrategy = CallableInsertionStrategy.AsIdentifier
CallableInsertionOptions(ImportStrategy.DoNothing, CallableInsertionStrategy.AsIdentifier)
)
}
}
@@ -0,0 +1,29 @@
/*
* Copyright 2010-2021 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.completion.lookups
import org.jetbrains.kotlin.idea.frontend.api.KtAnalysisSession
import org.jetbrains.kotlin.idea.frontend.api.symbols.KtCallableSymbol
import org.jetbrains.kotlin.idea.frontend.api.symbols.KtFunctionSymbol
internal data class CallableInsertionOptions(
val importingStrategy: ImportStrategy,
val insertionStrategy: CallableInsertionStrategy,
) {
fun withImportingStrategy(newImportStrategy: ImportStrategy): CallableInsertionOptions =
copy(importingStrategy = newImportStrategy)
fun withInsertionStrategy(newInsertionStrategy: CallableInsertionStrategy): CallableInsertionOptions =
copy(insertionStrategy = newInsertionStrategy)
}
internal fun KtAnalysisSession.detectCallableOptions(symbol: KtCallableSymbol): CallableInsertionOptions = CallableInsertionOptions(
importingStrategy = detectImportStrategy(symbol),
insertionStrategy = when (symbol) {
is KtFunctionSymbol -> CallableInsertionStrategy.AsCall
else -> CallableInsertionStrategy.AsIdentifier
}
)
@@ -7,12 +7,26 @@ package org.jetbrains.kotlin.idea.completion.lookups
import org.jetbrains.kotlin.idea.frontend.api.KtAnalysisSession
import org.jetbrains.kotlin.idea.frontend.api.components.KtTypeRendererOptions
import org.jetbrains.kotlin.idea.frontend.api.symbols.KtCallableSymbol
import org.jetbrains.kotlin.idea.frontend.api.symbols.KtFunctionSymbol
import org.jetbrains.kotlin.idea.frontend.api.symbols.KtValueParameterSymbol
import org.jetbrains.kotlin.idea.frontend.api.symbols.KtVariableLikeSymbol
internal object CompletionShortNamesRenderer {
fun KtAnalysisSession.renderFunctionParameters(function: KtFunctionSymbol): String =
function.valueParameters.joinToString(", ", "(", ")") { renderFunctionParameter(it) }
fun KtAnalysisSession.renderFunctionParameters(function: KtFunctionSymbol): String {
val receiver = renderReceiver(function)
val parameters = function.valueParameters.joinToString(", ", "(", ")") { renderFunctionParameter(it) }
return receiver + parameters
}
fun KtAnalysisSession.renderVariable(function: KtVariableLikeSymbol): String {
return renderReceiver(function)
}
private fun KtAnalysisSession.renderReceiver(symbol: KtCallableSymbol): String {
val receiverType = symbol.receiverType?.type ?: return ""
return receiverType.render(TYPE_RENDERING_OPTIONS) + "."
}
private fun KtAnalysisSession.renderFunctionParameter(param: KtValueParameterSymbol): String =
"${if (param.isVararg) "vararg " else ""}${param.name.asString()}: ${param.annotatedType.type.render(TYPE_RENDERING_OPTIONS)}"
@@ -5,6 +5,7 @@
package org.jetbrains.kotlin.idea.completion.lookups
abstract class KotlinCallableLookupObject: KotlinLookupObject {
abstract val renderedReceiverType: String?
internal abstract class KotlinCallableLookupObject : KotlinLookupObject {
abstract val renderedDeclaration: String
abstract val options: CallableInsertionOptions
}
@@ -31,4 +31,9 @@ internal object TailTextProvider {
val singleParam = symbol.valueParameters.singleOrNull()
return singleParam != null && !singleParam.hasDefaultValue && singleParam.annotatedType.type is KtFunctionalType
}
fun KtAnalysisSession.insertLambdaBraces(symbol: KtFunctionalType): Boolean {
val singleParam = symbol.parameterTypes.singleOrNull()
return singleParam != null && singleParam is KtFunctionalType
}
}
@@ -32,23 +32,22 @@ import org.jetbrains.kotlin.idea.completion.lookups.TailTextProvider.insertLambd
import org.jetbrains.kotlin.idea.completion.lookups.CompletionShortNamesRenderer.renderFunctionParameters
import org.jetbrains.kotlin.idea.completion.lookups.CompletionShortNamesRenderer.TYPE_RENDERING_OPTIONS
import org.jetbrains.kotlin.idea.core.withRootPrefixIfNeeded
import org.jetbrains.kotlin.idea.frontend.api.components.KtDeclarationRendererOptions
internal class FunctionLookupElementFactory {
fun KtAnalysisSession.createLookup(
symbol: KtFunctionSymbol,
importStrategy: ImportStrategy,
insertionStrategy: CallableInsertionStrategy
options: CallableInsertionOptions,
): LookupElementBuilder {
val lookupObject = FunctionLookupObject(
val lookupObject = FunctionCallLookupObject(
symbol.name,
importStrategy = importStrategy,
options,
renderFunctionParameters(symbol),
inputValueArguments = symbol.valueParameters.isNotEmpty(),
insertEmptyLambda = insertLambdaBraces(symbol),
renderedFunctionParameters = renderFunctionParameters(symbol),
renderedReceiverType = symbol.receiverType?.type?.render(TYPE_RENDERING_OPTIONS)
)
val insertionHandler = when (insertionStrategy) {
val insertionHandler = when (val insertionStrategy = options.insertionStrategy) {
CallableInsertionStrategy.AsCall -> FunctionInsertionHandler
CallableInsertionStrategy.AsIdentifier -> QuotedNamesAwareInsertionHandler()
is CallableInsertionStrategy.AsIdentifierCustom -> object : QuotedNamesAwareInsertionHandler() {
@@ -61,28 +60,24 @@ internal class FunctionLookupElementFactory {
return LookupElementBuilder.create(lookupObject, symbol.name.asString())
.withTailText(getTailText(symbol), true)
.withTypeText(symbol.annotatedType.type.render(CompletionShortNamesRenderer.TYPE_RENDERING_OPTIONS))
.withTypeText(symbol.annotatedType.type.render(TYPE_RENDERING_OPTIONS))
.withInsertHandler(insertionHandler)
.let { withSymbolInfo(symbol, it) }
}
}
/**
* Simplest lookup object so two lookup elements for the same function will clash.
*/
private data class FunctionLookupObject(
internal data class FunctionCallLookupObject(
override val shortName: Name,
val importStrategy: ImportStrategy,
override val options: CallableInsertionOptions,
override val renderedDeclaration: String,
val inputValueArguments: Boolean,
val insertEmptyLambda: Boolean,
// for distinction between different overloads
private val renderedFunctionParameters: String,
override val renderedReceiverType: String?
) : KotlinCallableLookupObject()
private object FunctionInsertionHandler : QuotedNamesAwareInsertionHandler() {
private fun addArguments(context: InsertionContext, offsetElement: PsiElement, lookupObject: FunctionLookupObject) {
internal object FunctionInsertionHandler : QuotedNamesAwareInsertionHandler() {
private fun addArguments(context: InsertionContext, offsetElement: PsiElement, lookupObject: FunctionCallLookupObject) {
val completionChar = context.completionChar
if (completionChar == '(') { //TODO: more correct behavior related to braces type
context.setAddCompletionChar(false)
@@ -152,7 +147,7 @@ private object FunctionInsertionHandler : QuotedNamesAwareInsertionHandler() {
}
}
private fun shouldPlaceCaretInBrackets(completionChar: Char, lookupObject: FunctionLookupObject): Boolean {
private fun shouldPlaceCaretInBrackets(completionChar: Char, lookupObject: FunctionCallLookupObject): Boolean {
if (completionChar == ',' || completionChar == '.' || completionChar == '=') return false
if (completionChar == '(') return true
return lookupObject.inputValueArguments || lookupObject.insertEmptyLambda
@@ -163,14 +158,14 @@ private object FunctionInsertionHandler : QuotedNamesAwareInsertionHandler() {
override fun handleInsert(context: InsertionContext, item: LookupElement) {
val targetFile = context.file as? KtFile ?: return
val lookupObject = item.`object` as FunctionLookupObject
val lookupObject = item.`object` as FunctionCallLookupObject
super.handleInsert(context, item)
val startOffset = context.startOffset
val element = context.file.findElementAt(startOffset) ?: return
val importStrategy = lookupObject.importStrategy
val importStrategy = lookupObject.options.importingStrategy
if (importStrategy is ImportStrategy.InsertFqNameAndShorten) {
context.document.replaceString(
context.startOffset,
@@ -7,8 +7,9 @@ package org.jetbrains.kotlin.idea.completion.lookups.factories
import com.intellij.codeInsight.lookup.LookupElement
import com.intellij.codeInsight.lookup.LookupElementBuilder
import org.jetbrains.kotlin.idea.completion.lookups.*
import org.jetbrains.kotlin.idea.completion.lookups.CallableInsertionOptions
import org.jetbrains.kotlin.idea.completion.lookups.ImportStrategy
import org.jetbrains.kotlin.idea.completion.lookups.CallableInsertionStrategy
import org.jetbrains.kotlin.idea.completion.lookups.detectImportStrategy
import org.jetbrains.kotlin.idea.frontend.api.KtAnalysisSession
import org.jetbrains.kotlin.idea.frontend.api.symbols.*
@@ -26,8 +27,7 @@ internal class KotlinFirLookupElementFactory {
return when (symbol) {
is KtCallableSymbol -> createCallableLookupElement(
symbol,
importingStrategy = detectImportStrategy(symbol),
CallableInsertionStrategy.AsCall
detectCallableOptions(symbol),
)
is KtClassLikeSymbol -> with(classLookupElementFactory) { createLookup(symbol) }
is KtTypeParameterSymbol -> with(typeParameterLookupElementFactory) { createLookup(symbol) }
@@ -37,12 +37,11 @@ internal class KotlinFirLookupElementFactory {
fun KtAnalysisSession.createCallableLookupElement(
symbol: KtCallableSymbol,
importingStrategy: ImportStrategy,
insertionStrategy: CallableInsertionStrategy,
options: CallableInsertionOptions,
): LookupElementBuilder {
return when (symbol) {
is KtFunctionSymbol -> with(functionLookupElementFactory) { createLookup(symbol, importingStrategy, insertionStrategy) }
is KtVariableLikeSymbol -> with(variableLookupElementFactory) { createLookup(symbol, importingStrategy) }
is KtFunctionSymbol -> with(functionLookupElementFactory) { createLookup(symbol, options) }
is KtVariableLikeSymbol -> with(variableLookupElementFactory) { createLookup(symbol, options) }
else -> throw IllegalArgumentException("Cannot create a lookup element for $symbol")
}
}
@@ -11,16 +11,18 @@ import com.intellij.codeInsight.lookup.LookupElement
import com.intellij.codeInsight.lookup.LookupElementBuilder
import com.intellij.openapi.util.TextRange
import org.jetbrains.kotlin.idea.completion.lookups.*
import org.jetbrains.kotlin.idea.completion.lookups.CompletionShortNamesRenderer.renderVariable
import org.jetbrains.kotlin.idea.completion.lookups.ImportStrategy
import org.jetbrains.kotlin.idea.completion.lookups.TailTextProvider.getTailText
import org.jetbrains.kotlin.idea.completion.lookups.TailTextProvider.insertLambdaBraces
import org.jetbrains.kotlin.idea.completion.lookups.addCallableImportIfRequired
import org.jetbrains.kotlin.idea.completion.lookups.detectImportStrategy
import org.jetbrains.kotlin.idea.completion.lookups.shortenReferencesForFirCompletion
import org.jetbrains.kotlin.idea.core.withRootPrefixIfNeeded
import org.jetbrains.kotlin.idea.frontend.api.KtAnalysisSession
import org.jetbrains.kotlin.idea.frontend.api.components.KtTypeRendererOptions
import org.jetbrains.kotlin.idea.frontend.api.symbols.KtSyntheticJavaPropertySymbol
import org.jetbrains.kotlin.idea.frontend.api.symbols.KtVariableLikeSymbol
import org.jetbrains.kotlin.idea.frontend.api.types.KtFunctionalType
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.renderer.render
@@ -28,20 +30,42 @@ import org.jetbrains.kotlin.renderer.render
internal class VariableLookupElementFactory {
fun KtAnalysisSession.createLookup(
symbol: KtVariableLikeSymbol,
importStrategy: ImportStrategy = detectImportStrategy(symbol)
options: CallableInsertionOptions,
): LookupElementBuilder {
val lookupObject = VariableLookupObject(
symbol.name,
importStrategy = importStrategy,
renderedReceiverType = symbol.receiverType?.type?.render(CompletionShortNamesRenderer.TYPE_RENDERING_OPTIONS),
)
val rendered = renderVariable(symbol)
val builder = when (options.insertionStrategy) {
CallableInsertionStrategy.AsCall -> {
val functionalType = symbol.annotatedType.type as KtFunctionalType
val lookupObject = FunctionCallLookupObject(
symbol.name,
options,
rendered,
inputValueArguments = functionalType.parameterTypes.isNotEmpty(),
insertEmptyLambda = insertLambdaBraces(functionalType),
)
return LookupElementBuilder.create(lookupObject, symbol.name.asString())
.withTypeText(symbol.annotatedType.type.render(KtTypeRendererOptions.SHORT_NAMES))
.withTailText(getTailText(symbol), true)
.markIfSyntheticJavaProperty(symbol)
.withInsertHandler(VariableInsertionHandler)
.let { withSymbolInfo(symbol, it) }
val tailText = functionalType.parameterTypes.joinToString(prefix = "(", postfix = ")") {
it.render(CompletionShortNamesRenderer.TYPE_RENDERING_OPTIONS)
}
val typeText = functionalType.returnType.render(CompletionShortNamesRenderer.TYPE_RENDERING_OPTIONS)
LookupElementBuilder.create(lookupObject, symbol.name.asString())
.withTailText(tailText, true)
.withTypeText(typeText)
.withInsertHandler(FunctionInsertionHandler)
}
else -> {
val lookupObject = VariableLookupObject(symbol.name, options, rendered)
LookupElementBuilder.create(lookupObject, symbol.name.asString())
.withTypeText(symbol.annotatedType.type.render(KtTypeRendererOptions.SHORT_NAMES))
.withTailText(getTailText(symbol), true)
.markIfSyntheticJavaProperty(symbol)
.withInsertHandler(VariableInsertionHandler)
}
}
return withSymbolInfo(symbol, builder)
}
private fun LookupElementBuilder.markIfSyntheticJavaProperty(symbol: KtVariableLikeSymbol): LookupElementBuilder = when (symbol) {
@@ -63,8 +87,8 @@ internal class VariableLookupElementFactory {
*/
private data class VariableLookupObject(
override val shortName: Name,
val importStrategy: ImportStrategy,
override val renderedReceiverType: String?,
override val options: CallableInsertionOptions,
override val renderedDeclaration: String,
) : KotlinCallableLookupObject()
@@ -73,7 +97,7 @@ private object VariableInsertionHandler : InsertHandler<LookupElement> {
val targetFile = context.file as? KtFile ?: return
val lookupObject = item.`object` as VariableLookupObject
when (val importStrategy = lookupObject.importStrategy) {
when (val importStrategy = lookupObject.options.importingStrategy) {
is ImportStrategy.AddImport -> {
addCallableImportIfRequired(targetFile, importStrategy.nameToImport)
}
@@ -16,7 +16,13 @@ abstract class KtCompletionCandidateChecker : KtAnalysisSessionComponent() {
originalFile: KtFile,
nameExpression: KtSimpleNameExpression,
possibleExplicitReceiver: KtExpression?,
): Boolean
): KtExtensionApplicabilityResult
}
enum class KtExtensionApplicabilityResult(val isApplicable: Boolean) {
ApplicableAsExtensionCallable(isApplicable = true),
ApplicableAsFunctionalVariableCall(isApplicable = true),
NonApplicable(isApplicable = false),
}
interface KtCompletionCandidateCheckerMixIn : KtAnalysisSessionMixIn {
@@ -24,7 +30,7 @@ interface KtCompletionCandidateCheckerMixIn : KtAnalysisSessionMixIn {
originalPsiFile: KtFile,
psiFakeCompletionExpression: KtSimpleNameExpression,
psiReceiverExpression: KtExpression?,
): Boolean =
): KtExtensionApplicabilityResult =
analysisSession.completionCandidateChecker.checkExtensionFitsCandidate(
this,
originalPsiFile,
@@ -42,6 +42,7 @@ abstract class KtFunctionalType : KtNonErrorClassType() {
abstract val isSuspend: Boolean
abstract val arity: Int
abstract val receiverType: KtType?
abstract val hasReceiver: Boolean
abstract val parameterTypes: List<KtType>
abstract val returnType: KtType
}
@@ -8,6 +8,8 @@ package org.jetbrains.kotlin.idea.frontend.api.fir.components
import org.jetbrains.kotlin.fir.declarations.*
import org.jetbrains.kotlin.fir.expressions.FirExpression
import org.jetbrains.kotlin.fir.resolve.calls.ImplicitReceiverValue
import org.jetbrains.kotlin.fir.resolve.inference.receiverType
import org.jetbrains.kotlin.fir.types.coneType
import org.jetbrains.kotlin.idea.fir.low.level.api.api.LowLevelFirApiFacadeForResolveOnAir.getTowerContextProvider
import org.jetbrains.kotlin.idea.fir.low.level.api.api.getOrBuildFirFile
import org.jetbrains.kotlin.idea.fir.low.level.api.api.getOrBuildFirOfType
@@ -17,19 +19,15 @@ import org.jetbrains.kotlin.idea.fir.low.level.api.resolver.SingleCandidateResol
import org.jetbrains.kotlin.idea.fir.low.level.api.util.getElementTextInContext
import org.jetbrains.kotlin.idea.frontend.api.tokens.ValidityToken
import org.jetbrains.kotlin.idea.frontend.api.components.KtCompletionCandidateChecker
import org.jetbrains.kotlin.idea.frontend.api.components.KtExtensionApplicabilityResult
import org.jetbrains.kotlin.idea.frontend.api.fir.KtFirAnalysisSession
import org.jetbrains.kotlin.idea.frontend.api.fir.symbols.KtFirFunctionSymbol
import org.jetbrains.kotlin.idea.frontend.api.fir.symbols.KtFirKotlinPropertySymbol
import org.jetbrains.kotlin.idea.frontend.api.fir.symbols.KtFirSymbol
import org.jetbrains.kotlin.idea.frontend.api.fir.symbols.KtFirValueParameterSymbol
import org.jetbrains.kotlin.idea.frontend.api.fir.utils.firRef
import org.jetbrains.kotlin.idea.frontend.api.fir.utils.weakRef
import org.jetbrains.kotlin.idea.frontend.api.symbols.KtCallableSymbol
import org.jetbrains.kotlin.idea.frontend.api.withValidityAssertion
import org.jetbrains.kotlin.psi.KtExpression
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.psi.KtSimpleNameExpression
import org.jetbrains.kotlin.utils.addToStdlib.safeAs
internal class KtFirCompletionCandidateChecker(
analysisSession: KtFirAnalysisSession,
@@ -42,7 +40,7 @@ internal class KtFirCompletionCandidateChecker(
originalFile: KtFile,
nameExpression: KtSimpleNameExpression,
possibleExplicitReceiver: KtExpression?,
): Boolean = withValidityAssertion {
): KtExtensionApplicabilityResult = withValidityAssertion {
require(firSymbolForCandidate is KtFirSymbol<*>)
return firSymbolForCandidate.firRef.withFir(phase = FirResolvePhase.IMPLICIT_TYPES_BODY_RESOLVE) { declaration ->
check(declaration is FirCallableDeclaration<*>)
@@ -55,7 +53,7 @@ internal class KtFirCompletionCandidateChecker(
originalFile: KtFile,
nameExpression: KtSimpleNameExpression,
possibleExplicitReceiver: KtExpression?,
): Boolean {
): KtExtensionApplicabilityResult {
val file = originalFile.getOrBuildFirFile(firResolveState)
val explicitReceiverExpression = possibleExplicitReceiver?.getOrBuildFirOfType<FirExpression>(firResolveState)
val resolver = SingleCandidateResolver(firResolveState.rootModuleSession, file)
@@ -68,11 +66,17 @@ internal class KtFirCompletionCandidateChecker(
explicitReceiver = explicitReceiverExpression
)
resolver.resolveSingleCandidate(resolutionParameters)?.let {
// not null if resolved and completed successfully
return true
return when {
candidateSymbol is FirVariable<*> && candidateSymbol.returnTypeRef.coneType.receiverType(rootModuleSession) != null -> {
KtExtensionApplicabilityResult.ApplicableAsFunctionalVariableCall
}
else -> {
KtExtensionApplicabilityResult.ApplicableAsExtensionCallable
}
}
}
}
return false
return KtExtensionApplicabilityResult.NonApplicable
}
private fun getImplicitReceivers(
@@ -6,6 +6,7 @@
package org.jetbrains.kotlin.idea.frontend.api.fir.types
import org.jetbrains.kotlin.fir.resolve.inference.isSuspendFunctionType
import org.jetbrains.kotlin.fir.resolve.inference.receiverType
import org.jetbrains.kotlin.fir.types.*
import org.jetbrains.kotlin.fir.types.impl.ConeClassLikeTypeImpl
import org.jetbrains.kotlin.idea.frontend.api.*
@@ -80,6 +81,11 @@ internal class KtFirFunctionalType(
else null
}
override val hasReceiver: Boolean
get() = withValidityAssertion {
coneType.receiverType(firBuilder.rootSession) != null
}
override val parameterTypes: List<KtType> by cached {
val parameterTypeArgs = if (coneType.isExtensionFunctionType) typeArguments.subList(1, typeArguments.lastIndex)
else typeArguments.subList(0, typeArguments.lastIndex)