FIR IDE: add type arguments and -> symbol to when conditions completion
This commit is contained in:
committed by
TeamCityServer
parent
fa2f2c34cb
commit
2e57361457
+12
-5
@@ -5,14 +5,21 @@
|
||||
|
||||
package org.jetbrains.kotlin.idea.completion
|
||||
|
||||
import org.jetbrains.kotlin.idea.frontend.api.analyse
|
||||
import org.jetbrains.kotlin.idea.frontend.api.symbols.KtClassKind
|
||||
import org.jetbrains.kotlin.idea.frontend.api.symbols.KtClassOrObjectSymbol
|
||||
import org.jetbrains.kotlin.idea.frontend.api.symbols.KtFunctionSymbol
|
||||
import org.jetbrains.kotlin.idea.references.mainReference
|
||||
import com.intellij.codeInsight.completion.CompletionInitializationContext
|
||||
import com.intellij.codeInsight.completion.CompletionUtilCore
|
||||
import com.intellij.psi.util.parentOfType
|
||||
import org.jetbrains.kotlin.psi.KtNameReferenceExpression
|
||||
import org.jetbrains.kotlin.psi.KtWhenEntry
|
||||
|
||||
class FirCompletionDummyIdentifierProviderService : CompletionDummyIdentifierProviderService() {
|
||||
override fun handleDefaultCase(context: CompletionInitializationContext): String? {
|
||||
val elementAtOffset = context.file.findElementAt(context.startOffset) ?: return null
|
||||
return when {
|
||||
elementAtOffset.parentOfType<KtWhenEntry>() != null -> CompletionUtilCore.DUMMY_IDENTIFIER_TRIMMED
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
override fun allTargetsAreFunctionsOrClasses(nameReferenceExpression: KtNameReferenceExpression): Boolean {
|
||||
return true
|
||||
// TODO fir cannot handle invalid code and handles listOf< as binary expression
|
||||
|
||||
+73
-15
@@ -24,10 +24,15 @@ import org.jetbrains.kotlin.idea.frontend.api.symbols.*
|
||||
import org.jetbrains.kotlin.lexer.KtTokens
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
import org.jetbrains.kotlin.idea.completion.contributors.helpers.FirClassifierProvider.getAvailableClassifiersCurrentScope
|
||||
import org.jetbrains.kotlin.idea.completion.contributors.helpers.addSymbolToCompletion
|
||||
import org.jetbrains.kotlin.idea.completion.contributors.helpers.addTypeArguments
|
||||
import org.jetbrains.kotlin.idea.completion.contributors.helpers.createStarTypeArgumentsList
|
||||
import org.jetbrains.kotlin.idea.completion.lookups.KotlinLookupObject
|
||||
import org.jetbrains.kotlin.idea.completion.lookups.shortenReferencesForFirCompletion
|
||||
import org.jetbrains.kotlin.idea.frontend.api.symbols.markers.KtNamedSymbol
|
||||
import org.jetbrains.kotlin.idea.frontend.api.symbols.markers.KtSymbolWithTypeParameters
|
||||
import org.jetbrains.kotlin.idea.frontend.api.types.*
|
||||
import org.jetbrains.kotlin.miniStdLib.letIf
|
||||
import org.jetbrains.kotlin.name.ClassId
|
||||
import org.jetbrains.kotlin.name.FqName
|
||||
import org.jetbrains.kotlin.name.Name
|
||||
@@ -44,15 +49,22 @@ internal class FirWhenWithSubjectConditionContributor(
|
||||
val subjectType = subject.getKtType()
|
||||
val classSymbol = getClassSymbol(subjectType)
|
||||
val visibilityChecker = CompletionVisibilityChecker.create(basicContext, positionContext)
|
||||
val isSingleCondition = whenCondition.isSingleConditionInEntry()
|
||||
when {
|
||||
classSymbol?.classKind == KtClassKind.ENUM_CLASS -> {
|
||||
completeEnumEntries(classSymbol, allConditionsExceptCurrent, visibilityChecker)
|
||||
completeEnumEntries(classSymbol, allConditionsExceptCurrent, visibilityChecker, isSingleCondition)
|
||||
}
|
||||
classSymbol?.modality == Modality.SEALED -> {
|
||||
completeSubClassesOfSealedClass(classSymbol, allConditionsExceptCurrent, whenCondition, visibilityChecker)
|
||||
completeSubClassesOfSealedClass(
|
||||
classSymbol,
|
||||
allConditionsExceptCurrent,
|
||||
whenCondition,
|
||||
visibilityChecker,
|
||||
isSingleCondition
|
||||
)
|
||||
}
|
||||
else -> {
|
||||
completeAllTypes(whenCondition, visibilityChecker)
|
||||
completeAllTypes(whenCondition, visibilityChecker, isSingleCondition)
|
||||
}
|
||||
}
|
||||
addNullIfWhenExpressionCanReturnNull(subjectType)
|
||||
@@ -75,6 +87,7 @@ internal class FirWhenWithSubjectConditionContributor(
|
||||
private fun KtAnalysisSession.completeAllTypes(
|
||||
whenCondition: KtWhenCondition,
|
||||
visibilityChecker: CompletionVisibilityChecker,
|
||||
isSingleCondition: Boolean,
|
||||
) {
|
||||
getAvailableClassifiersCurrentScope(originalKtFile, whenCondition, scopeNameFilter, indexHelper, visibilityChecker)
|
||||
.forEach { classifier ->
|
||||
@@ -84,7 +97,8 @@ internal class FirWhenWithSubjectConditionContributor(
|
||||
classifier.name.asString(),
|
||||
classifier,
|
||||
(classifier as? KtNamedClassOrObjectSymbol)?.classIdIfNonLocal?.asSingleFqName(),
|
||||
isPrefixNeeded(classifier)
|
||||
isPrefixNeeded(classifier),
|
||||
isSingleCondition,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -104,6 +118,7 @@ internal class FirWhenWithSubjectConditionContributor(
|
||||
conditions: List<KtWhenCondition>,
|
||||
whenCondition: KtWhenCondition,
|
||||
visibilityChecker: CompletionVisibilityChecker,
|
||||
isSingleCondition: Boolean,
|
||||
) {
|
||||
require(classSymbol.modality == Modality.SEALED)
|
||||
val handledCasesClassIds = getHandledClassIds(conditions)
|
||||
@@ -115,11 +130,17 @@ internal class FirWhenWithSubjectConditionContributor(
|
||||
.filter { with(visibilityChecker) { isVisible(it as KtClassifierSymbol) } }
|
||||
.forEach { inheritor ->
|
||||
val classId = inheritor.classIdIfNonLocal ?: return@forEach
|
||||
addLookupElement(classId.relativeClassName.asString(), inheritor, classId.asSingleFqName(), isPrefixNeeded(inheritor))
|
||||
addLookupElement(
|
||||
classId.relativeClassName.asString(),
|
||||
inheritor,
|
||||
classId.asSingleFqName(),
|
||||
isPrefixNeeded(inheritor),
|
||||
isSingleCondition
|
||||
)
|
||||
}
|
||||
|
||||
if (allInheritors.any { it.modality == Modality.ABSTRACT }) {
|
||||
completeAllTypes(whenCondition, visibilityChecker)
|
||||
completeAllTypes(whenCondition, visibilityChecker, isSingleCondition)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -162,7 +183,8 @@ internal class FirWhenWithSubjectConditionContributor(
|
||||
private fun KtAnalysisSession.completeEnumEntries(
|
||||
classSymbol: KtNamedClassOrObjectSymbol,
|
||||
conditions: List<KtWhenCondition>,
|
||||
visibilityChecker: CompletionVisibilityChecker
|
||||
visibilityChecker: CompletionVisibilityChecker,
|
||||
isSingleCondition: Boolean,
|
||||
) {
|
||||
require(classSymbol.classKind == KtClassKind.ENUM_CLASS)
|
||||
val handledCasesNames = conditions.mapNotNullTo(hashSetOf()) { condition ->
|
||||
@@ -179,18 +201,33 @@ internal class FirWhenWithSubjectConditionContributor(
|
||||
"${classSymbol.name.asString()}.${entry.name.asString()}",
|
||||
entry,
|
||||
entry.callableIdIfNonLocal?.asSingleFqName(),
|
||||
isPrefixNeeded = false
|
||||
isPrefixNeeded = false,
|
||||
isSingleCondition,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun KtAnalysisSession.addLookupElement(lookupString: String, symbol: KtNamedSymbol, fqName: FqName?, isPrefixNeeded: Boolean) {
|
||||
val lookupObject = WhenConditionLookupObject(symbol.name, fqName, isPrefixNeeded)
|
||||
private fun KtWhenCondition.isSingleConditionInEntry(): Boolean {
|
||||
val entry = parent as KtWhenEntry
|
||||
return entry.conditions.size == 1
|
||||
}
|
||||
|
||||
private fun KtAnalysisSession.addLookupElement(
|
||||
lookupString: String,
|
||||
symbol: KtNamedSymbol,
|
||||
fqName: FqName?,
|
||||
isPrefixNeeded: Boolean,
|
||||
isSingleCondition: Boolean
|
||||
) {
|
||||
val typeArgumentsCount = (symbol as? KtSymbolWithTypeParameters)?.typeParameters?.size ?: 0
|
||||
val lookupObject = WhenConditionLookupObject(symbol.name, fqName, isPrefixNeeded, isSingleCondition, typeArgumentsCount)
|
||||
|
||||
LookupElementBuilder.create(lookupObject, getIsPrefix(isPrefixNeeded) + lookupString)
|
||||
.withIcon(getIconFor(symbol))
|
||||
.withPsiElement(symbol.psi)
|
||||
.withInsertHandler(WhenConditionInsertionHandler)
|
||||
.withTailText(createStarTypeArgumentsList(typeArgumentsCount), /*grayed*/true)
|
||||
.letIf(isSingleCondition) { it.appendTailText(" -> ", /*grayed*/true) }
|
||||
.let(sink::addElement)
|
||||
}
|
||||
}
|
||||
@@ -199,20 +236,41 @@ private data class WhenConditionLookupObject(
|
||||
override val shortName: Name,
|
||||
val fqName: FqName?,
|
||||
val needIsPrefix: Boolean,
|
||||
val isSingleCondition: Boolean,
|
||||
val typeArgumentsCount: Int,
|
||||
) : KotlinLookupObject
|
||||
|
||||
|
||||
private object WhenConditionInsertionHandler : InsertionHandlerBase<WhenConditionLookupObject>(WhenConditionLookupObject::class) {
|
||||
override fun handleInsert(context: InsertionContext, item: LookupElement, ktFile: KtFile, lookupObject: WhenConditionLookupObject) {
|
||||
context.insertName(lookupObject, ktFile)
|
||||
context.addTypeArguments(lookupObject.typeArgumentsCount)
|
||||
context.addArrow(lookupObject)
|
||||
}
|
||||
|
||||
private fun InsertionContext.addArrow(
|
||||
lookupObject: WhenConditionLookupObject
|
||||
) {
|
||||
if (lookupObject.isSingleCondition && completionChar != ',') {
|
||||
addSymbolToCompletion(" -> ")
|
||||
commitDocument()
|
||||
}
|
||||
}
|
||||
|
||||
private fun InsertionContext.insertName(
|
||||
lookupObject: WhenConditionLookupObject,
|
||||
ktFile: KtFile
|
||||
) {
|
||||
if (lookupObject.fqName != null) {
|
||||
val fqName = lookupObject.fqName
|
||||
context.document.replaceString(
|
||||
context.startOffset,
|
||||
context.tailOffset,
|
||||
document.replaceString(
|
||||
startOffset,
|
||||
tailOffset,
|
||||
getIsPrefix(lookupObject.needIsPrefix) + fqName.render()
|
||||
)
|
||||
context.commitDocument()
|
||||
commitDocument()
|
||||
|
||||
shortenReferencesForFirCompletion(ktFile, TextRange(context.startOffset, context.tailOffset))
|
||||
shortenReferencesForFirCompletion(ktFile, TextRange(startOffset, tailOffset))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+22
-5
@@ -9,9 +9,10 @@ import com.intellij.codeInsight.completion.CodeCompletionHandlerBase
|
||||
import com.intellij.codeInsight.completion.CompletionType
|
||||
import com.intellij.codeInsight.completion.InsertionContext
|
||||
import com.intellij.openapi.application.ApplicationManager
|
||||
import com.intellij.psi.PsiDocumentManager
|
||||
|
||||
internal fun InsertionContext.addSymbolAndInvokeCompletion(symbol: String) {
|
||||
addSymbolToCompletion(this, symbol)
|
||||
this.addSymbolToCompletion(symbol)
|
||||
invokeCompletion(this)
|
||||
}
|
||||
|
||||
@@ -24,8 +25,24 @@ private fun invokeCompletion(context: InsertionContext) {
|
||||
}
|
||||
}
|
||||
|
||||
private fun addSymbolToCompletion(context: InsertionContext, symbol: String) {
|
||||
context.document.insertString(context.tailOffset, symbol)
|
||||
context.commitDocument()
|
||||
context.editor.caretModel.moveToOffset(context.tailOffset)
|
||||
internal fun InsertionContext.addSymbolToCompletion(symbol: String) {
|
||||
PsiDocumentManager.getInstance(project).doPostponedOperationsAndUnblockDocument(document)
|
||||
document.insertString(tailOffset, symbol)
|
||||
commitDocument()
|
||||
editor.caretModel.moveToOffset(tailOffset)
|
||||
}
|
||||
|
||||
internal fun InsertionContext.addTypeArguments(typeArgumentsCount: Int) {
|
||||
when {
|
||||
typeArgumentsCount == 0 -> {
|
||||
return
|
||||
}
|
||||
typeArgumentsCount < 0 -> {
|
||||
error("Count of type arguments should be non-negative, but was $typeArgumentsCount")
|
||||
}
|
||||
else -> {
|
||||
commitDocument()
|
||||
addSymbolToCompletion(createStarTypeArgumentsList(typeArgumentsCount))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
/*
|
||||
* 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.helpers
|
||||
|
||||
internal fun createStarTypeArgumentsList(typeArgumentsCount: Int): String =
|
||||
if (typeArgumentsCount > 0) {
|
||||
List(typeArgumentsCount) { "*" }.joinToString(prefix = "<", postfix = ">")
|
||||
} else {
|
||||
""
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
class NON_SEALED
|
||||
class AAAA<E, S>: NON_SEALED()
|
||||
object BBBB: NON_SEALED()
|
||||
class CCCC<E>: NON_SEALED()
|
||||
|
||||
fun foo(e: NON_SEALED) {
|
||||
when (e) {
|
||||
<caret>
|
||||
}
|
||||
}
|
||||
|
||||
// EXIST: { lookupString: "is AAAA", tailText: "<*, *> -> " }
|
||||
// EXIST: BBBB
|
||||
// EXIST: { lookupString: "is CCCC", tailText: "<*> -> " }
|
||||
// EXIST: { lookupString: "else -> "}
|
||||
// FIR_COMPARISON
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
sealed class SEALED
|
||||
class AAAA<E, S>: SEALED()
|
||||
object BBBB: SEALED()
|
||||
class CCCC<E>: SEALED()
|
||||
|
||||
fun foo(e: SEALED) {
|
||||
when (e) {
|
||||
<caret>
|
||||
}
|
||||
}
|
||||
|
||||
// EXIST: { lookupString: "is AAAA", tailText: "<*, *> -> " }
|
||||
// EXIST: BBBB
|
||||
// EXIST: { lookupString: "is CCCC", tailText: "<*> -> " }
|
||||
// EXIST: { lookupString: "else -> "}
|
||||
// NOTHING_ELSE
|
||||
// FIR_COMPARISON
|
||||
+10
@@ -3382,6 +3382,11 @@ public class HighLevelJvmBasicCompletionTestGenerated extends AbstractHighLevelJ
|
||||
runTest("idea/idea-completion/testData/../../idea-fir/testData/completion/basic/common/when/nonSealedClassInWhen.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("nonSealedClassWithTypeArgs.kt")
|
||||
public void testNonSealedClassWithTypeArgs() throws Exception {
|
||||
runTest("idea/idea-completion/testData/../../idea-fir/testData/completion/basic/common/when/nonSealedClassWithTypeArgs.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("nullableEnumInWhen.kt")
|
||||
public void testNullableEnumInWhen() throws Exception {
|
||||
runTest("idea/idea-completion/testData/../../idea-fir/testData/completion/basic/common/when/nullableEnumInWhen.kt");
|
||||
@@ -3407,6 +3412,11 @@ public class HighLevelJvmBasicCompletionTestGenerated extends AbstractHighLevelJ
|
||||
runTest("idea/idea-completion/testData/../../idea-fir/testData/completion/basic/common/when/sealedClassWithAbstractInheritorsInWhen.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("sealedClassWithTypeArgs.kt")
|
||||
public void testSealedClassWithTypeArgs() throws Exception {
|
||||
runTest("idea/idea-completion/testData/../../idea-fir/testData/completion/basic/common/when/sealedClassWithTypeArgs.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("sealedInterfaceInWhen.kt")
|
||||
public void testSealedInterfaceInWhen() throws Exception {
|
||||
runTest("idea/idea-completion/testData/../../idea-fir/testData/completion/basic/common/when/sealedInterfaceInWhen.kt");
|
||||
|
||||
+3
@@ -65,10 +65,13 @@ abstract class CompletionDummyIdentifierProviderService {
|
||||
?: specialInTypeArgsDummyIdentifier(tokenBefore)
|
||||
?: specialInArgumentListDummyIdentifier(tokenBefore)
|
||||
?: isInTypeParametersList(tokenBefore)
|
||||
?: handleDefaultCase(context)
|
||||
?: DEFAULT_DUMMY_IDENTIFIER
|
||||
}
|
||||
}
|
||||
|
||||
protected open fun handleDefaultCase(context: CompletionInitializationContext): String? = null
|
||||
|
||||
private fun isInTypeParametersList(tokenBefore: PsiElement?): String? {
|
||||
if (tokenBefore == null) return null
|
||||
if (tokenBefore.parents.any { it is KtTypeParameterList }) {
|
||||
|
||||
Reference in New Issue
Block a user