FIR IDE: implement completion for variables with functional types with receivers
This commit is contained in:
committed by
teamcityserver
parent
167917cf07
commit
915c8b7996
-1
@@ -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
@@ -1,3 +1,4 @@
|
||||
// FIR_COMPARISON
|
||||
interface I
|
||||
interface J
|
||||
|
||||
|
||||
+1
@@ -1,3 +1,4 @@
|
||||
// FIR_COMPARISON
|
||||
fun String.foo() {
|
||||
val v = ::xxx_<caret>
|
||||
}
|
||||
|
||||
+1
@@ -1,3 +1,4 @@
|
||||
// FIR_COMPARISON
|
||||
fun test(i: Int, foo: Int.() -> Char) {
|
||||
i.<caret>
|
||||
}
|
||||
|
||||
+1
@@ -1,3 +1,4 @@
|
||||
// FIR_COMPARISON
|
||||
fun test(i: Int, foo: Int.() -> Char) {
|
||||
i.foo()<caret>
|
||||
}
|
||||
|
||||
+1
@@ -1,3 +1,4 @@
|
||||
// FIR_COMPARISON
|
||||
fun test(i: Int, foo: Int.(String) -> Char) {
|
||||
i.<caret>
|
||||
}
|
||||
|
||||
+1
@@ -1,3 +1,4 @@
|
||||
// FIR_COMPARISON
|
||||
fun test(i: Int, foo: Int.(String) -> Char) {
|
||||
i.foo(<caret>)
|
||||
}
|
||||
|
||||
+2
-1
@@ -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
|
||||
}
|
||||
+76
-20
@@ -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) {
|
||||
|
||||
+4
-4
@@ -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)
|
||||
|
||||
+2
-2
@@ -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)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
+29
@@ -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
|
||||
}
|
||||
)
|
||||
+16
-2
@@ -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)}"
|
||||
|
||||
+3
-2
@@ -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
|
||||
}
|
||||
}
|
||||
+16
-21
@@ -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,
|
||||
|
||||
+6
-7
@@ -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")
|
||||
}
|
||||
}
|
||||
|
||||
+40
-16
@@ -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)
|
||||
}
|
||||
|
||||
+8
-2
@@ -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
|
||||
}
|
||||
|
||||
+14
-10
@@ -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 +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)
|
||||
|
||||
Reference in New Issue
Block a user