diff --git a/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/CompletionUtils.kt b/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/CompletionUtils.kt index 7399c147308..88f6efbe75e 100644 --- a/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/CompletionUtils.kt +++ b/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/CompletionUtils.kt @@ -305,25 +305,6 @@ private fun KtDeclarationWithBody.returnType(bindingContext: BindingContext): Ko } -private fun createKeywordElementWithSpace( - keyword: String, - tail: String = "", - addSpaceAfter: Boolean = false, - lookupObject: KeywordLookupObject = KeywordLookupObject() -): LookupElement { - val element = createKeywordElement(keyword, tail, lookupObject) - return if (addSpaceAfter) { - object : LookupElementDecorator(element) { - override fun handleInsert(context: InsertionContext) { - WithTailInsertHandler.SPACE.handleInsert(context, delegate) - } - } - } else { - element - } -} - - fun BasicLookupElementFactory.createLookupElementForType(type: KotlinType): LookupElement? { if (type.isError) return null diff --git a/idea/idea-completion/testData/handlers/basic/AddLabelToReturn.kt b/idea/idea-completion/testData/handlers/basic/AddLabelToReturn.kt index 9c20c32c14c..eba8f05fde0 100644 --- a/idea/idea-completion/testData/handlers/basic/AddLabelToReturn.kt +++ b/idea/idea-completion/testData/handlers/basic/AddLabelToReturn.kt @@ -1,3 +1,4 @@ +// FIR_COMPARISON // COMPILER_ARGUMENTS: -XXLanguage:-NewInference val v: Boolean = run { diff --git a/idea/idea-completion/testData/handlers/basic/AddLabelToReturn.kt.after b/idea/idea-completion/testData/handlers/basic/AddLabelToReturn.kt.after index 7511b864e13..9ce60d23e1c 100644 --- a/idea/idea-completion/testData/handlers/basic/AddLabelToReturn.kt.after +++ b/idea/idea-completion/testData/handlers/basic/AddLabelToReturn.kt.after @@ -1,3 +1,4 @@ +// FIR_COMPARISON // COMPILER_ARGUMENTS: -XXLanguage:-NewInference val v: Boolean = run { diff --git a/idea/idea-fir/src/org/jetbrains/kotlin/idea/completion/KotlinFirCompletionContributor.kt b/idea/idea-fir/src/org/jetbrains/kotlin/idea/completion/KotlinFirCompletionContributor.kt index 76eb86dcd58..95dc70cdd11 100644 --- a/idea/idea-fir/src/org/jetbrains/kotlin/idea/completion/KotlinFirCompletionContributor.kt +++ b/idea/idea-fir/src/org/jetbrains/kotlin/idea/completion/KotlinFirCompletionContributor.kt @@ -31,7 +31,6 @@ import org.jetbrains.kotlin.idea.frontend.api.symbols.markers.KtSymbolWithVisibi import org.jetbrains.kotlin.idea.frontend.api.types.KtClassType import org.jetbrains.kotlin.idea.frontend.api.types.KtType import org.jetbrains.kotlin.lexer.KtTokens -import org.jetbrains.kotlin.platform.jvm.isJvm import org.jetbrains.kotlin.psi.* class KotlinFirCompletionContributor : CompletionContributor() { @@ -58,7 +57,7 @@ private object KotlinFirCompletionProvider : CompletionProvider null } - completeKeywordsWithoutAnalysisSessionContext(expression ?: positionContext.position, expression) + completeWithResolve(expression ?: positionContext.position, expression) } - fun completeKeywordsWithoutAnalysisSessionContext(position: PsiElement, expression: KtExpression?) { - keywordCompletion.complete(position, prefixMatcher, targetPlatform.isJvm()) { lookupElement -> - val keyword = lookupElement.lookupString - val completionKeywordHandler = DefaultCompletionKeywordHandlers.defaultHandlers.getHandlerForKeyword(keyword) - val lookups = completionKeywordHandler + fun KtAnalysisSession.completeWithResolve(position: PsiElement, expression: KtExpression?) { + complete(position, expression) { lookupElement, keyword -> + val lookups = DefaultCompletionKeywordHandlers.defaultHandlers.getHandlerForKeyword(keyword) + ?.createLookups(parameters, expression, lookupElement, project) + ?: ResolveDependentCompletionKeywordHandlers.handlers.getHandlerForKeyword(keyword)?.run { + createLookups(parameters, expression, lookupElement, project) + } + ?: listOf(lookupElement) + result.addAllElements(lookups) + } + } + + fun completeDefaultKeywordsWithoutResolve(position: PsiElement, expression: KtExpression?) { + complete(position, expression) { lookupElement, keyword -> + val lookups = DefaultCompletionKeywordHandlers.defaultHandlers.getHandlerForKeyword(keyword) ?.createLookups(parameters, expression, lookupElement, project) ?: listOf(lookupElement) result.addAllElements(lookups) } } -} \ No newline at end of file + + private inline fun complete(position: PsiElement, expression: KtExpression?, crossinline complete: (LookupElement, String) -> Unit) { + keywordCompletion.complete(position, prefixMatcher, targetPlatform.isJvm()) { lookupElement -> + val keyword = lookupElement.lookupString + complete(lookupElement, keyword) + } + } +} + +private object ResolveDependentCompletionKeywordHandlers { + val handlers = CompletionKeywordHandlers( + ReturnKeywordHandler + ) +} + diff --git a/idea/idea-fir/src/org/jetbrains/kotlin/idea/completion/contributors/keywords/ReturnKeywordHandler.kt b/idea/idea-fir/src/org/jetbrains/kotlin/idea/completion/contributors/keywords/ReturnKeywordHandler.kt new file mode 100644 index 00000000000..9b313931f9d --- /dev/null +++ b/idea/idea-fir/src/org/jetbrains/kotlin/idea/completion/contributors/keywords/ReturnKeywordHandler.kt @@ -0,0 +1,97 @@ +/* + * 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.contributors.keywords + +import com.intellij.codeInsight.completion.CompletionParameters +import com.intellij.codeInsight.lookup.LookupElement +import com.intellij.openapi.project.Project +import org.jetbrains.kotlin.idea.completion.CompletionKeywordHandler +import org.jetbrains.kotlin.idea.completion.createKeywordElement +import org.jetbrains.kotlin.idea.completion.createKeywordElementWithSpace +import org.jetbrains.kotlin.idea.completion.labelNameToTail +import org.jetbrains.kotlin.idea.frontend.api.KtAnalysisSession +import org.jetbrains.kotlin.idea.frontend.api.symbols.KtFunctionSymbol +import org.jetbrains.kotlin.idea.frontend.api.types.KtType +import org.jetbrains.kotlin.idea.references.mainReference +import org.jetbrains.kotlin.idea.util.findLabelAndCall +import org.jetbrains.kotlin.lexer.KtTokens +import org.jetbrains.kotlin.name.Name +import org.jetbrains.kotlin.name.StandardClassIds +import org.jetbrains.kotlin.psi.* +import org.jetbrains.kotlin.psi.psiUtil.parentsWithSelf + +internal object ReturnKeywordHandler: CompletionKeywordHandler(KtTokens.RETURN_KEYWORD) { + @OptIn(ExperimentalStdlibApi::class) + override fun KtAnalysisSession.createLookups( + parameters: CompletionParameters, + expression: KtExpression?, + lookup: LookupElement, + project: Project + ): Collection { + if (expression == null) return emptyList() + val result = mutableListOf() + + for (parent in expression.parentsWithSelf.filterIsInstance()) { + val returnType = parent.getReturnKtType() + if (parent is KtFunctionLiteral) { + val (label, call) = parent.findLabelAndCall() + if (label != null) { + addAllReturnVariants(result, returnType, label) + } + + // check if the current function literal is inlined and stop processing outer declarations if it's not + if (!isInlineFunctionCall(call)) { + break + } + } else { + if (parent.hasBlockBody()) { + addAllReturnVariants(result, returnType, label = null) + } + break + } + } + + return result + } + + private fun KtAnalysisSession.addAllReturnVariants(result: MutableList, returnType: KtType, label: Name?) { + val isUnit = returnType.isUnit + result.add(createKeywordElementWithSpace("return", tail = label?.labelNameToTail().orEmpty(), addSpaceAfter = !isUnit)) + getExpressionsToReturnByType(returnType).mapTo(result) { returnTarget -> + createKeywordElement("return", tail = "${label.labelNameToTail()} $returnTarget") + } + } + + @OptIn(ExperimentalStdlibApi::class) + private fun KtAnalysisSession.getExpressionsToReturnByType(returnType: KtType): List = buildList { + if (returnType.canBeNull) { + add("null") + } + + fun emptyListShouldBeSuggested(): Boolean = + returnType.isClassTypeWithClassId(StandardClassIds.Collection) + || returnType.isClassTypeWithClassId(StandardClassIds.List) + || returnType.isClassTypeWithClassId(StandardClassIds.Iterable) + + when { + returnType.isClassTypeWithClassId(StandardClassIds.Boolean) -> { + add("true") + add("false") + } + emptyListShouldBeSuggested() -> { + add("emptyList()") + } + returnType.isClassTypeWithClassId(StandardClassIds.Set) -> { + add("emptySet()") + } + } + } + + private fun KtAnalysisSession.isInlineFunctionCall(call: KtCallExpression?): Boolean { + val callee = call?.calleeExpression as? KtReferenceExpression ?: return false + return (callee.mainReference.resolveToSymbol() as? KtFunctionSymbol)?.isInline == true + } +} \ No newline at end of file diff --git a/idea/idea-frontend-api/src/org/jetbrains/kotlin/idea/frontend/api/components/KtReferenceResolveMixIn.kt b/idea/idea-frontend-api/src/org/jetbrains/kotlin/idea/frontend/api/components/KtReferenceResolveMixIn.kt index 84e8fc1d116..384c7846fe5 100644 --- a/idea/idea-frontend-api/src/org/jetbrains/kotlin/idea/frontend/api/components/KtReferenceResolveMixIn.kt +++ b/idea/idea-frontend-api/src/org/jetbrains/kotlin/idea/frontend/api/components/KtReferenceResolveMixIn.kt @@ -17,7 +17,7 @@ interface KtReferenceResolveMixIn : KtAnalysisSessionMixIn { return analysisSession.resolveToSymbols() } - fun KtSimpleReference<*>.resolveToSymbol(): KtSymbol? { + fun KtReference.resolveToSymbol(): KtSymbol? { check(this is KtSymbolBasedReference) { "To get reference symbol the one should be KtSymbolBasedReference but was ${this::class}" } return resolveToSymbols().singleOrNull() } diff --git a/idea/idea-frontend-independent/src/org/jetbrains/kotlin/idea/completion/CompletionKeywordHandlers.kt b/idea/idea-frontend-independent/src/org/jetbrains/kotlin/idea/completion/CompletionKeywordHandlers.kt index c72c7bda8ab..1cc43d55c1e 100644 --- a/idea/idea-frontend-independent/src/org/jetbrains/kotlin/idea/completion/CompletionKeywordHandlers.kt +++ b/idea/idea-frontend-independent/src/org/jetbrains/kotlin/idea/completion/CompletionKeywordHandlers.kt @@ -14,66 +14,70 @@ import org.jetbrains.kotlin.lexer.KtTokens import org.jetbrains.kotlin.psi.KtExpression import org.jetbrains.kotlin.psi.psiUtil.prevLeaf -class CompletionKeywordHandlers(handlers: List) { - constructor(vararg handlers: CompletionKeywordHandler): this(handlers.toList()) +class CompletionKeywordHandlers(handlers: List>) { + constructor(vararg handlers: CompletionKeywordHandler) : this(handlers.toList()) private val handlerByKeyword = handlers.associateBy { it.keyword.value } - fun getHandlerForKeyword(keyword: String): CompletionKeywordHandler? = + fun getHandlerForKeyword(keyword: String): CompletionKeywordHandler? = handlerByKeyword[keyword] } @OptIn(ExperimentalStdlibApi::class) object DefaultCompletionKeywordHandlers { - private val BREAK_HANDLER = CompletionKeywordHandler(KtTokens.BREAK_KEYWORD) { _, expression, _, _ -> - if (expression != null) { - breakOrContinueExpressionItems(expression, KtTokens.BREAK_KEYWORD.value) - } else emptyList() - } + private val BREAK_HANDLER = + CompletionKeywordHandler(KtTokens.BREAK_KEYWORD) { _, expression, _, _ -> + if (expression != null) { + breakOrContinueExpressionItems(expression, KtTokens.BREAK_KEYWORD.value) + } else emptyList() + } - private val CONTINUE_HANDLER = CompletionKeywordHandler(KtTokens.CONTINUE_KEYWORD) { _, expression, _, _ -> - if (expression != null) { - breakOrContinueExpressionItems(expression, KtTokens.CONTINUE_KEYWORD.value) - } else emptyList() - } + private val CONTINUE_HANDLER = + CompletionKeywordHandler(KtTokens.CONTINUE_KEYWORD) { _, expression, _, _ -> + if (expression != null) { + breakOrContinueExpressionItems(expression, KtTokens.CONTINUE_KEYWORD.value) + } else emptyList() + } - private val CONTRACT_HANDLER = CompletionKeywordHandler(KtTokens.CONTRACT_KEYWORD) { _, _, _, _ -> + private val CONTRACT_HANDLER = CompletionKeywordHandler(KtTokens.CONTRACT_KEYWORD) { _, _, _, _ -> emptyList() } - private val GETTER_HANDLER = CompletionKeywordHandler(KtTokens.GET_KEYWORD) { parameters, _, lookupElement, project -> - buildList { - add(lookupElement) - if (!parameters.isUseSiteAnnotationTarget) { - add(createKeywordConstructLookupElement(project, KtTokens.GET_KEYWORD.value, "val v:Int get()=caret")) - add( - createKeywordConstructLookupElement( - project, - KtTokens.GET_KEYWORD.value, - "val v:Int get(){caret}", - trimSpacesAroundCaret = true + private val GETTER_HANDLER = + CompletionKeywordHandler(KtTokens.GET_KEYWORD) { parameters, _, lookupElement, project -> + buildList { + add(lookupElement) + if (!parameters.isUseSiteAnnotationTarget) { + add(createKeywordConstructLookupElement(project, KtTokens.GET_KEYWORD.value, "val v:Int get()=caret")) + add( + createKeywordConstructLookupElement( + project, + KtTokens.GET_KEYWORD.value, + "val v:Int get(){caret}", + trimSpacesAroundCaret = true + ) ) - ) + } } } - } - private val SETTER_HANDLER = CompletionKeywordHandler(KtTokens.SET_KEYWORD) { parameters, _, lookupElement, project -> - buildList { - add(lookupElement) - if (!parameters.isUseSiteAnnotationTarget) { - add(createKeywordConstructLookupElement(project, KtTokens.SET_KEYWORD.value, "var v:Int set(value)=caret")) - add( - createKeywordConstructLookupElement( - project, - KtTokens.SET_KEYWORD.value, - "var v:Int set(value){caret}", - trimSpacesAroundCaret = true + private val SETTER_HANDLER = + CompletionKeywordHandler(KtTokens.SET_KEYWORD) { parameters, _, lookupElement, project -> + buildList { + add(lookupElement) + if (!parameters.isUseSiteAnnotationTarget) { + add(createKeywordConstructLookupElement(project, KtTokens.SET_KEYWORD.value, "var v:Int set(value)=caret")) + add( + createKeywordConstructLookupElement( + project, + KtTokens.SET_KEYWORD.value, + "var v:Int set(value){caret}", + trimSpacesAroundCaret = true + ) ) - ) + } } } - } val defaultHandlers = CompletionKeywordHandlers( BREAK_HANDLER, CONTINUE_HANDLER, @@ -82,13 +86,23 @@ object DefaultCompletionKeywordHandlers { ) } +fun CompletionKeywordHandler.createLookups( + parameters: CompletionParameters, + expression: KtExpression?, + lookup: LookupElement, + project: Project +): Collection = with(CompletionKeywordHandler.NO_CONTEXT) { createLookups(parameters, expression, lookup, project) } + private val CompletionParameters.isUseSiteAnnotationTarget get() = position.prevLeaf()?.node?.elementType == KtTokens.AT -abstract class CompletionKeywordHandler( - val keyword: KtKeywordToken, + +abstract class CompletionKeywordHandler( + val keyword: KtKeywordToken ) { - abstract fun createLookups( + object NO_CONTEXT + + abstract fun CONTEXT.createLookups( parameters: CompletionParameters, expression: KtExpression?, lookup: LookupElement, @@ -96,16 +110,16 @@ abstract class CompletionKeywordHandler( ): Collection companion object { - inline operator fun invoke( + inline operator fun invoke( keyword: KtKeywordToken, - crossinline create: ( + crossinline create: CONTEXT.( parameters: CompletionParameters, expression: KtExpression?, lookup: LookupElement, project: Project ) -> Collection - ) = object : CompletionKeywordHandler(keyword) { - override fun createLookups( + ) = object : CompletionKeywordHandler(keyword) { + override fun CONTEXT.createLookups( parameters: CompletionParameters, expression: KtExpression?, lookup: LookupElement, diff --git a/idea/idea-frontend-independent/src/org/jetbrains/kotlin/idea/completion/completionUtilsNoResolve.kt b/idea/idea-frontend-independent/src/org/jetbrains/kotlin/idea/completion/completionUtilsNoResolve.kt index ac4ed052987..5f6652978ca 100644 --- a/idea/idea-frontend-independent/src/org/jetbrains/kotlin/idea/completion/completionUtilsNoResolve.kt +++ b/idea/idea-frontend-independent/src/org/jetbrains/kotlin/idea/completion/completionUtilsNoResolve.kt @@ -5,8 +5,11 @@ package org.jetbrains.kotlin.idea.completion +import com.intellij.codeInsight.completion.InsertionContext import com.intellij.codeInsight.lookup.LookupElement import com.intellij.codeInsight.lookup.LookupElementBuilder +import com.intellij.codeInsight.lookup.LookupElementDecorator +import org.jetbrains.kotlin.idea.completion.handlers.WithTailInsertHandler import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.psi.KtDeclarationWithBody import org.jetbrains.kotlin.psi.KtElement @@ -53,6 +56,24 @@ fun breakOrContinueExpressionItems(position: KtElement, breakOrContinue: String) return result } +fun createKeywordElementWithSpace( + keyword: String, + tail: String = "", + addSpaceAfter: Boolean = false, + lookupObject: KeywordLookupObject = KeywordLookupObject() +): LookupElement { + val element = createKeywordElement(keyword, tail, lookupObject) + return if (addSpaceAfter) { + object : LookupElementDecorator(element) { + override fun handleInsert(context: InsertionContext) { + WithTailInsertHandler.SPACE.handleInsert(context, delegate) + } + } + } else { + element + } +} + fun Name?.labelNameToTail(): String = if (this != null) "@" + render() else ""