diff --git a/idea/idea-completion/testData/basic/common/extensionFunctionTypeValues/ImplicitReceiver.kt b/idea/idea-completion/testData/basic/common/extensionFunctionTypeValues/ImplicitReceiver.kt index 2705b6ec510..8cf3e8847c3 100644 --- a/idea/idea-completion/testData/basic/common/extensionFunctionTypeValues/ImplicitReceiver.kt +++ b/idea/idea-completion/testData/basic/common/extensionFunctionTypeValues/ImplicitReceiver.kt @@ -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" } diff --git a/idea/idea-completion/testData/basic/common/extensionFunctionTypeValues/SmartCast.kt b/idea/idea-completion/testData/basic/common/extensionFunctionTypeValues/SmartCast.kt index 3fae464de8d..02ae94159a6 100644 --- a/idea/idea-completion/testData/basic/common/extensionFunctionTypeValues/SmartCast.kt +++ b/idea/idea-completion/testData/basic/common/extensionFunctionTypeValues/SmartCast.kt @@ -1,3 +1,4 @@ +// FIR_COMPARISON interface I interface J diff --git a/idea/idea-completion/testData/basic/multifile/CallableReferenceNotImported/CallableReferenceNotImported.kt b/idea/idea-completion/testData/basic/multifile/CallableReferenceNotImported/CallableReferenceNotImported.kt index b1507f8026f..9853a70378f 100644 --- a/idea/idea-completion/testData/basic/multifile/CallableReferenceNotImported/CallableReferenceNotImported.kt +++ b/idea/idea-completion/testData/basic/multifile/CallableReferenceNotImported/CallableReferenceNotImported.kt @@ -1,3 +1,4 @@ +// FIR_COMPARISON fun String.foo() { val v = ::xxx_ } diff --git a/idea/idea-completion/testData/handlers/basic/ExtensionFunctionTypeVariable1.kt b/idea/idea-completion/testData/handlers/basic/ExtensionFunctionTypeVariable1.kt index 5ca3c21efa4..d921034d658 100644 --- a/idea/idea-completion/testData/handlers/basic/ExtensionFunctionTypeVariable1.kt +++ b/idea/idea-completion/testData/handlers/basic/ExtensionFunctionTypeVariable1.kt @@ -1,3 +1,4 @@ +// FIR_COMPARISON fun test(i: Int, foo: Int.() -> Char) { i. } diff --git a/idea/idea-completion/testData/handlers/basic/ExtensionFunctionTypeVariable1.kt.after b/idea/idea-completion/testData/handlers/basic/ExtensionFunctionTypeVariable1.kt.after index 21e1231d840..17b46baae66 100644 --- a/idea/idea-completion/testData/handlers/basic/ExtensionFunctionTypeVariable1.kt.after +++ b/idea/idea-completion/testData/handlers/basic/ExtensionFunctionTypeVariable1.kt.after @@ -1,3 +1,4 @@ +// FIR_COMPARISON fun test(i: Int, foo: Int.() -> Char) { i.foo() } diff --git a/idea/idea-completion/testData/handlers/basic/ExtensionFunctionTypeVariable2.kt b/idea/idea-completion/testData/handlers/basic/ExtensionFunctionTypeVariable2.kt index 5e1e9e76116..e32760e142f 100644 --- a/idea/idea-completion/testData/handlers/basic/ExtensionFunctionTypeVariable2.kt +++ b/idea/idea-completion/testData/handlers/basic/ExtensionFunctionTypeVariable2.kt @@ -1,3 +1,4 @@ +// FIR_COMPARISON fun test(i: Int, foo: Int.(String) -> Char) { i. } diff --git a/idea/idea-completion/testData/handlers/basic/ExtensionFunctionTypeVariable2.kt.after b/idea/idea-completion/testData/handlers/basic/ExtensionFunctionTypeVariable2.kt.after index aeee01bdcbb..b511bdccf98 100644 --- a/idea/idea-completion/testData/handlers/basic/ExtensionFunctionTypeVariable2.kt.after +++ b/idea/idea-completion/testData/handlers/basic/ExtensionFunctionTypeVariable2.kt.after @@ -1,3 +1,4 @@ +// FIR_COMPARISON fun test(i: Int, foo: Int.(String) -> Char) { i.foo() } diff --git a/idea/idea-fir/src/org/jetbrains/kotlin/idea/completion/checkers/ExtensionApplicabilityChecker.kt b/idea/idea-fir/src/org/jetbrains/kotlin/idea/completion/checkers/ExtensionApplicabilityChecker.kt index 670271dbe08..2827a4fcf2b 100644 --- a/idea/idea-fir/src/org/jetbrains/kotlin/idea/completion/checkers/ExtensionApplicabilityChecker.kt +++ b/idea/idea-fir/src/org/jetbrains/kotlin/idea/completion/checkers/ExtensionApplicabilityChecker.kt @@ -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 } \ No newline at end of file diff --git a/idea/idea-fir/src/org/jetbrains/kotlin/idea/completion/contributors/FirCallableCompletionContributor.kt b/idea/idea-fir/src/org/jetbrains/kotlin/idea/completion/contributors/FirCallableCompletionContributor.kt index 6bd0b8f604d..66802843f5f 100644 --- a/idea/idea-fir/src/org/jetbrains/kotlin/idea/completion/contributors/FirCallableCompletionContributor.kt +++ b/idea/idea-fir/src/org/jetbrains/kotlin/idea/completion/contributors/FirCallableCompletionContributor.kt @@ -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(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 = + ): Sequence> = 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) = 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) { diff --git a/idea/idea-fir/src/org/jetbrains/kotlin/idea/completion/contributors/FirCompletionContributorBase.kt b/idea/idea-fir/src/org/jetbrains/kotlin/idea/completion/contributors/FirCompletionContributorBase.kt index eeb64d771d7..bcde5c5dde5 100644 --- a/idea/idea-fir/src/org/jetbrains/kotlin/idea/completion/contributors/FirCompletionContributorBase.kt +++ b/idea/idea-fir/src/org/jetbrains/kotlin/idea/completion/contributors/FirCompletionContributorBase.kt @@ -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 CallableInsertionStrategy.AsCall + else -> CallableInsertionStrategy.AsIdentifier + } +) diff --git a/idea/idea-fir/src/org/jetbrains/kotlin/idea/completion/lookups/CompletionShortNamesRenderer.kt b/idea/idea-fir/src/org/jetbrains/kotlin/idea/completion/lookups/CompletionShortNamesRenderer.kt index 781e94bd418..72461ce021e 100644 --- a/idea/idea-fir/src/org/jetbrains/kotlin/idea/completion/lookups/CompletionShortNamesRenderer.kt +++ b/idea/idea-fir/src/org/jetbrains/kotlin/idea/completion/lookups/CompletionShortNamesRenderer.kt @@ -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)}" diff --git a/idea/idea-fir/src/org/jetbrains/kotlin/idea/completion/lookups/KotlinCallableLookupObject.kt b/idea/idea-fir/src/org/jetbrains/kotlin/idea/completion/lookups/KotlinCallableLookupObject.kt index 678aab3760a..c56986f991c 100644 --- a/idea/idea-fir/src/org/jetbrains/kotlin/idea/completion/lookups/KotlinCallableLookupObject.kt +++ b/idea/idea-fir/src/org/jetbrains/kotlin/idea/completion/lookups/KotlinCallableLookupObject.kt @@ -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 } \ No newline at end of file diff --git a/idea/idea-fir/src/org/jetbrains/kotlin/idea/completion/lookups/TailTextProvider.kt b/idea/idea-fir/src/org/jetbrains/kotlin/idea/completion/lookups/TailTextProvider.kt index 7fe87aa8167..ec338001fbd 100644 --- a/idea/idea-fir/src/org/jetbrains/kotlin/idea/completion/lookups/TailTextProvider.kt +++ b/idea/idea-fir/src/org/jetbrains/kotlin/idea/completion/lookups/TailTextProvider.kt @@ -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 + } } \ No newline at end of file diff --git a/idea/idea-fir/src/org/jetbrains/kotlin/idea/completion/lookups/factories/FunctionLookupElementFactory.kt b/idea/idea-fir/src/org/jetbrains/kotlin/idea/completion/lookups/factories/FunctionLookupElementFactory.kt index 1b10eccac9a..52200591681 100644 --- a/idea/idea-fir/src/org/jetbrains/kotlin/idea/completion/lookups/factories/FunctionLookupElementFactory.kt +++ b/idea/idea-fir/src/org/jetbrains/kotlin/idea/completion/lookups/factories/FunctionLookupElementFactory.kt @@ -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, diff --git a/idea/idea-fir/src/org/jetbrains/kotlin/idea/completion/lookups/factories/KotlinFirLookupElementFactory.kt b/idea/idea-fir/src/org/jetbrains/kotlin/idea/completion/lookups/factories/KotlinFirLookupElementFactory.kt index 426127c2e65..08c81b41fa5 100644 --- a/idea/idea-fir/src/org/jetbrains/kotlin/idea/completion/lookups/factories/KotlinFirLookupElementFactory.kt +++ b/idea/idea-fir/src/org/jetbrains/kotlin/idea/completion/lookups/factories/KotlinFirLookupElementFactory.kt @@ -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") } } diff --git a/idea/idea-fir/src/org/jetbrains/kotlin/idea/completion/lookups/factories/VariableLookupElementFactory.kt b/idea/idea-fir/src/org/jetbrains/kotlin/idea/completion/lookups/factories/VariableLookupElementFactory.kt index 95668605aa4..34c82c766d7 100644 --- a/idea/idea-fir/src/org/jetbrains/kotlin/idea/completion/lookups/factories/VariableLookupElementFactory.kt +++ b/idea/idea-fir/src/org/jetbrains/kotlin/idea/completion/lookups/factories/VariableLookupElementFactory.kt @@ -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 { 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) } diff --git a/idea/idea-frontend-api/src/org/jetbrains/kotlin/idea/frontend/api/components/KtCompletionCandidateChecker.kt b/idea/idea-frontend-api/src/org/jetbrains/kotlin/idea/frontend/api/components/KtCompletionCandidateChecker.kt index 2d9d6ab0c6f..bebc350d485 100644 --- a/idea/idea-frontend-api/src/org/jetbrains/kotlin/idea/frontend/api/components/KtCompletionCandidateChecker.kt +++ b/idea/idea-frontend-api/src/org/jetbrains/kotlin/idea/frontend/api/components/KtCompletionCandidateChecker.kt @@ -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, diff --git a/idea/idea-frontend-api/src/org/jetbrains/kotlin/idea/frontend/api/types/KtType.kt b/idea/idea-frontend-api/src/org/jetbrains/kotlin/idea/frontend/api/types/KtType.kt index 495d03b351d..4c95c56b35a 100644 --- a/idea/idea-frontend-api/src/org/jetbrains/kotlin/idea/frontend/api/types/KtType.kt +++ b/idea/idea-frontend-api/src/org/jetbrains/kotlin/idea/frontend/api/types/KtType.kt @@ -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 abstract val returnType: KtType } diff --git a/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/components/KtFirCompletionCandidateChecker.kt b/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/components/KtFirCompletionCandidateChecker.kt index cddb89c931c..028dbdf196c 100644 --- a/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/components/KtFirCompletionCandidateChecker.kt +++ b/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/components/KtFirCompletionCandidateChecker.kt @@ -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(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( diff --git a/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/types/FirKtType.kt b/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/types/FirKtType.kt index 093f024b540..e8b15404861 100644 --- a/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/types/FirKtType.kt +++ b/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/types/FirKtType.kt @@ -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 by cached { val parameterTypeArgs = if (coneType.isExtensionFunctionType) typeArguments.subList(1, typeArguments.lastIndex) else typeArguments.subList(0, typeArguments.lastIndex)