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:
Ilya Kirillov
2021-05-06 18:46:19 +02:00
committed by TeamCityServer
parent 8cfa85df6b
commit 8f399652d7
9 changed files with 218 additions and 81 deletions
@@ -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 {
@@ -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) }
@@ -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
)
}
@@ -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
}
}
@@ -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()
}
@@ -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,
@@ -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 ""