FIR IDE: add expressions to return keyword completion
Also, adds return targets for return with labels while FE1.0 plugin don't
This commit is contained in:
committed by
TeamCityServer
parent
8cfa85df6b
commit
8f399652d7
@@ -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<LookupElement>(element) {
|
||||
override fun handleInsert(context: InsertionContext) {
|
||||
WithTailInsertHandler.SPACE.handleInsert(context, delegate)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
element
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
fun BasicLookupElementFactory.createLookupElementForType(type: KotlinType): LookupElement? {
|
||||
if (type.isError) return null
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// FIR_COMPARISON
|
||||
// COMPILER_ARGUMENTS: -XXLanguage:-NewInference
|
||||
|
||||
val v: Boolean = run {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// FIR_COMPARISON
|
||||
// COMPILER_ARGUMENTS: -XXLanguage:-NewInference
|
||||
|
||||
val v: Boolean = run {
|
||||
|
||||
+1
-2
@@ -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<CompletionParame
|
||||
|
||||
if (positionContext is FirUnknownPositionContext) {
|
||||
// TODO unify when LL API will be able to handle more context for completion
|
||||
keywordContributor.completeKeywordsWithoutAnalysisSessionContext(positionContext.position, expression = null)
|
||||
keywordContributor.completeDefaultKeywordsWithoutResolve(positionContext.position, expression = null)
|
||||
} else {
|
||||
FirPositionCompletionContextDetector.analyseInContext(basicContext, positionContext) {
|
||||
with(keywordContributor) { completeKeywords(positionContext) }
|
||||
|
||||
+35
-12
@@ -5,20 +5,19 @@
|
||||
|
||||
package org.jetbrains.kotlin.idea.completion.contributors
|
||||
|
||||
import com.intellij.codeInsight.lookup.LookupElement
|
||||
import com.intellij.openapi.module.Module
|
||||
import com.intellij.psi.PsiElement
|
||||
import org.jetbrains.kotlin.config.LanguageVersionSettingsImpl
|
||||
import org.jetbrains.kotlin.idea.completion.DefaultCompletionKeywordHandlers
|
||||
import org.jetbrains.kotlin.idea.completion.KeywordCompletion
|
||||
import org.jetbrains.kotlin.idea.completion.*
|
||||
import org.jetbrains.kotlin.idea.completion.context.FirBasicCompletionContext
|
||||
import org.jetbrains.kotlin.idea.completion.context.FirNameReferencePositionContext
|
||||
import org.jetbrains.kotlin.idea.completion.context.FirPositionCompletionContext
|
||||
import org.jetbrains.kotlin.idea.completion.context.FirUnknownPositionContext
|
||||
import org.jetbrains.kotlin.idea.completion.contributors.keywords.ReturnKeywordHandler
|
||||
import org.jetbrains.kotlin.idea.frontend.api.KtAnalysisSession
|
||||
import org.jetbrains.kotlin.platform.jvm.isJvm
|
||||
import org.jetbrains.kotlin.psi.KtExpression
|
||||
import org.jetbrains.kotlin.psi.KtExpressionWithLabel
|
||||
import org.jetbrains.kotlin.psi.KtLabelReferenceExpression
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
|
||||
internal class FirKeywordCompletionContributor(basicContext: FirBasicCompletionContext) : FirCompletionContributorBase(basicContext) {
|
||||
private val keywordCompletion = KeywordCompletion(object : KeywordCompletion.LanguageVersionSettingProvider {
|
||||
@@ -39,17 +38,41 @@ internal class FirKeywordCompletionContributor(basicContext: FirBasicCompletionC
|
||||
}
|
||||
is FirUnknownPositionContext -> 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
+97
@@ -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<KtAnalysisSession>(KtTokens.RETURN_KEYWORD) {
|
||||
@OptIn(ExperimentalStdlibApi::class)
|
||||
override fun KtAnalysisSession.createLookups(
|
||||
parameters: CompletionParameters,
|
||||
expression: KtExpression?,
|
||||
lookup: LookupElement,
|
||||
project: Project
|
||||
): Collection<LookupElement> {
|
||||
if (expression == null) return emptyList()
|
||||
val result = mutableListOf<LookupElement>()
|
||||
|
||||
for (parent in expression.parentsWithSelf.filterIsInstance<KtDeclarationWithBody>()) {
|
||||
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<LookupElement>, 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<String> = 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
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -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()
|
||||
}
|
||||
|
||||
+61
-47
@@ -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<CompletionKeywordHandler>) {
|
||||
constructor(vararg handlers: CompletionKeywordHandler): this(handlers.toList())
|
||||
class CompletionKeywordHandlers<CONTEXT>(handlers: List<CompletionKeywordHandler<CONTEXT>>) {
|
||||
constructor(vararg handlers: CompletionKeywordHandler<CONTEXT>) : this(handlers.toList())
|
||||
|
||||
private val handlerByKeyword = handlers.associateBy { it.keyword.value }
|
||||
|
||||
fun getHandlerForKeyword(keyword: String): CompletionKeywordHandler? =
|
||||
fun getHandlerForKeyword(keyword: String): CompletionKeywordHandler<CONTEXT>? =
|
||||
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<CompletionKeywordHandler.NO_CONTEXT>(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<CompletionKeywordHandler.NO_CONTEXT>(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<CompletionKeywordHandler.NO_CONTEXT>(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<CompletionKeywordHandler.NO_CONTEXT>(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<CompletionKeywordHandler.NO_CONTEXT>(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<CompletionKeywordHandler.NO_CONTEXT>.createLookups(
|
||||
parameters: CompletionParameters,
|
||||
expression: KtExpression?,
|
||||
lookup: LookupElement,
|
||||
project: Project
|
||||
): Collection<LookupElement> = 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<CONTEXT>(
|
||||
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<LookupElement>
|
||||
|
||||
companion object {
|
||||
inline operator fun invoke(
|
||||
inline operator fun <CONTEXT> invoke(
|
||||
keyword: KtKeywordToken,
|
||||
crossinline create: (
|
||||
crossinline create: CONTEXT.(
|
||||
parameters: CompletionParameters,
|
||||
expression: KtExpression?,
|
||||
lookup: LookupElement,
|
||||
project: Project
|
||||
) -> Collection<LookupElement>
|
||||
) = object : CompletionKeywordHandler(keyword) {
|
||||
override fun createLookups(
|
||||
) = object : CompletionKeywordHandler<CONTEXT>(keyword) {
|
||||
override fun CONTEXT.createLookups(
|
||||
parameters: CompletionParameters,
|
||||
expression: KtExpression?,
|
||||
lookup: LookupElement,
|
||||
|
||||
+21
@@ -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<LookupElement>(element) {
|
||||
override fun handleInsert(context: InsertionContext) {
|
||||
WithTailInsertHandler.SPACE.handleInsert(context, delegate)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
element
|
||||
}
|
||||
}
|
||||
|
||||
fun Name?.labelNameToTail(): String = if (this != null) "@" + render() else ""
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user