FIR IDE: implement completion in sealed when
This commit is contained in:
committed by
TeamCityServer
parent
4bdf46e0a4
commit
9af0885e82
@@ -0,0 +1,29 @@
|
|||||||
|
/*
|
||||||
|
* 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
|
||||||
|
|
||||||
|
import com.intellij.codeInsight.completion.InsertHandler
|
||||||
|
import com.intellij.codeInsight.completion.InsertionContext
|
||||||
|
import com.intellij.codeInsight.lookup.LookupElement
|
||||||
|
import org.jetbrains.kotlin.idea.completion.lookups.KotlinLookupObject
|
||||||
|
import org.jetbrains.kotlin.psi.KtFile
|
||||||
|
import kotlin.reflect.KClass
|
||||||
|
|
||||||
|
internal abstract class InsertionHandlerBase<LO : KotlinLookupObject>(
|
||||||
|
private val lookupObjectClass: KClass<LO>
|
||||||
|
) : InsertHandler<LookupElement> {
|
||||||
|
|
||||||
|
final override fun handleInsert(context: InsertionContext, item: LookupElement) {
|
||||||
|
val ktFile = context.file as? KtFile ?: return
|
||||||
|
val lookupObject = item.`object`
|
||||||
|
check(lookupObjectClass.isInstance(lookupObject))
|
||||||
|
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
handleInsert(context, item, ktFile, lookupObject as LO)
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract fun handleInsert(context: InsertionContext, item: LookupElement, ktFile: KtFile, lookupObject: LO)
|
||||||
|
}
|
||||||
+5
-1
@@ -27,7 +27,6 @@ import org.jetbrains.kotlin.idea.fir.low.level.api.util.originalKtFile
|
|||||||
import org.jetbrains.kotlin.idea.frontend.api.KtAnalysisSession
|
import org.jetbrains.kotlin.idea.frontend.api.KtAnalysisSession
|
||||||
import org.jetbrains.kotlin.lexer.KtTokens
|
import org.jetbrains.kotlin.lexer.KtTokens
|
||||||
import org.jetbrains.kotlin.psi.KtFile
|
import org.jetbrains.kotlin.psi.KtFile
|
||||||
import java.io.File
|
|
||||||
|
|
||||||
class KotlinFirCompletionContributor : CompletionContributor() {
|
class KotlinFirCompletionContributor : CompletionContributor() {
|
||||||
init {
|
init {
|
||||||
@@ -71,6 +70,7 @@ private object KotlinFirCompletionProvider : CompletionProvider<CompletionParame
|
|||||||
val importDirectivePackageMembersCompletionContributor = FirImportDirectivePackageMembersCompletionContributor(basicContext)
|
val importDirectivePackageMembersCompletionContributor = FirImportDirectivePackageMembersCompletionContributor(basicContext)
|
||||||
val typeParameterConstraintNameInWhereClauseContributor = FirTypeParameterConstraintNameInWhereClauseCompletionContributor(basicContext)
|
val typeParameterConstraintNameInWhereClauseContributor = FirTypeParameterConstraintNameInWhereClauseCompletionContributor(basicContext)
|
||||||
val classifierNameContributor = FirSameAsFileClassifierNameCompletionContributor(basicContext)
|
val classifierNameContributor = FirSameAsFileClassifierNameCompletionContributor(basicContext)
|
||||||
|
val whenWithSubjecConditionContributor = FirWhenWithSubjectConditionContributor(basicContext)
|
||||||
|
|
||||||
when (positionContext) {
|
when (positionContext) {
|
||||||
is FirExpressionNameReferencePositionContext -> {
|
is FirExpressionNameReferencePositionContext -> {
|
||||||
@@ -117,6 +117,10 @@ private object KotlinFirCompletionProvider : CompletionProvider<CompletionParame
|
|||||||
complete(classifierNameContributor, positionContext)
|
complete(classifierNameContributor, positionContext)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
is FirWithSubjectEntryPositionContext -> {
|
||||||
|
complete(whenWithSubjecConditionContributor, positionContext)
|
||||||
|
}
|
||||||
|
|
||||||
is FirIncorrectPositionContext -> {
|
is FirIncorrectPositionContext -> {
|
||||||
// do nothing, completion is not suposed to be called here
|
// do nothing, completion is not suposed to be called here
|
||||||
}
|
}
|
||||||
|
|||||||
+22
@@ -88,6 +88,14 @@ internal class FirExpressionNameReferencePositionContext(
|
|||||||
) : FirNameReferencePositionContext()
|
) : FirNameReferencePositionContext()
|
||||||
|
|
||||||
|
|
||||||
|
internal class FirWithSubjectEntryPositionContext(
|
||||||
|
override val position: PsiElement,
|
||||||
|
override val reference: KtSimpleNameReference,
|
||||||
|
override val nameExpression: KtSimpleNameExpression,
|
||||||
|
override val explicitReceiver: KtExpression?,
|
||||||
|
val whenCondition: KtWhenCondition,
|
||||||
|
) : FirNameReferencePositionContext()
|
||||||
|
|
||||||
internal class FirUnknownPositionContext(
|
internal class FirUnknownPositionContext(
|
||||||
override val position: PsiElement
|
override val position: PsiElement
|
||||||
) : FirRawPositionCompletionContext()
|
) : FirRawPositionCompletionContext()
|
||||||
@@ -122,6 +130,14 @@ internal object FirPositionCompletionContextDetector {
|
|||||||
parent is KtUserType -> {
|
parent is KtUserType -> {
|
||||||
detectForTypeContext(parent, position, reference, nameExpression, explicitReceiver)
|
detectForTypeContext(parent, position, reference, nameExpression, explicitReceiver)
|
||||||
}
|
}
|
||||||
|
parent is KtWhenCondition && parent.isConditionOnWhenWithSubject() -> {
|
||||||
|
FirWithSubjectEntryPositionContext(
|
||||||
|
position,
|
||||||
|
reference,
|
||||||
|
nameExpression,
|
||||||
|
explicitReceiver, parent
|
||||||
|
)
|
||||||
|
}
|
||||||
nameExpression.isReferenceExpressionInImportDirective() -> {
|
nameExpression.isReferenceExpressionInImportDirective() -> {
|
||||||
FirImportDirectivePositionContext(
|
FirImportDirectivePositionContext(
|
||||||
position,
|
position,
|
||||||
@@ -149,6 +165,12 @@ internal object FirPositionCompletionContextDetector {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun KtWhenCondition.isConditionOnWhenWithSubject(): Boolean {
|
||||||
|
val whenEntry = (parent as? KtWhenEntry) ?: return false
|
||||||
|
val whenExpression = whenEntry.parent as? KtWhenExpression ?: return false
|
||||||
|
return whenExpression.subjectExpression != null
|
||||||
|
}
|
||||||
|
|
||||||
private fun KtExpression.isReferenceExpressionInImportDirective() = when (val parent = parent) {
|
private fun KtExpression.isReferenceExpressionInImportDirective() = when (val parent = parent) {
|
||||||
is KtImportDirective -> parent.importedReference == this
|
is KtImportDirective -> parent.importedReference == this
|
||||||
is KtDotQualifiedExpression -> {
|
is KtDotQualifiedExpression -> {
|
||||||
|
|||||||
+8
-13
@@ -12,8 +12,8 @@ import org.jetbrains.kotlin.idea.completion.contributors.helpers.getStaticScope
|
|||||||
import org.jetbrains.kotlin.idea.frontend.api.KtAnalysisSession
|
import org.jetbrains.kotlin.idea.frontend.api.KtAnalysisSession
|
||||||
import org.jetbrains.kotlin.idea.frontend.api.symbols.*
|
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.KtClassType
|
||||||
import org.jetbrains.kotlin.psi.KtEnumEntry
|
|
||||||
import org.jetbrains.kotlin.psi.KtExpression
|
import org.jetbrains.kotlin.psi.KtExpression
|
||||||
|
import org.jetbrains.kotlin.idea.completion.contributors.helpers.FirClassifierProvider.getAvailableClassifiersCurrentScope
|
||||||
|
|
||||||
internal open class FirClassifierCompletionContributor(
|
internal open class FirClassifierCompletionContributor(
|
||||||
basicContext: FirBasicCompletionContext,
|
basicContext: FirBasicCompletionContext,
|
||||||
@@ -51,19 +51,14 @@ internal open class FirClassifierCompletionContributor(
|
|||||||
positionContext: FirNameReferencePositionContext,
|
positionContext: FirNameReferencePositionContext,
|
||||||
visibilityChecker: CompletionVisibilityChecker
|
visibilityChecker: CompletionVisibilityChecker
|
||||||
) {
|
) {
|
||||||
val implicitScopes = originalKtFile.getScopeContextForPosition(positionContext.nameExpression).scopes
|
getAvailableClassifiersCurrentScope(
|
||||||
val classesFromScopes = implicitScopes
|
originalKtFile,
|
||||||
.getClassifierSymbols(scopeNameFilter)
|
positionContext.nameExpression,
|
||||||
|
scopeNameFilter,
|
||||||
|
indexHelper,
|
||||||
|
visibilityChecker
|
||||||
|
)
|
||||||
.filter { filterClassifiers(it) }
|
.filter { filterClassifiers(it) }
|
||||||
.filter { with(visibilityChecker) { isVisible(it) } }
|
|
||||||
|
|
||||||
classesFromScopes.forEach { addClassifierSymbolToCompletion(it, insertFqName = true) }
|
|
||||||
|
|
||||||
val kotlinClassesFromIndices = indexHelper.getKotlinClasses(scopeNameFilter, psiFilter = { it !is KtEnumEntry })
|
|
||||||
kotlinClassesFromIndices.asSequence()
|
|
||||||
.map { it.getSymbol() as KtClassifierSymbol }
|
|
||||||
.filter { filterClassifiers(it) }
|
|
||||||
.filter { with(visibilityChecker) { isVisible(it) } }
|
|
||||||
.forEach { addClassifierSymbolToCompletion(it, insertFqName = true) }
|
.forEach { addClassifierSymbolToCompletion(it, insertFqName = true) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+230
@@ -0,0 +1,230 @@
|
|||||||
|
/*
|
||||||
|
* 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
|
||||||
|
|
||||||
|
import com.intellij.codeInsight.completion.InsertionContext
|
||||||
|
import com.intellij.codeInsight.lookup.LookupElement
|
||||||
|
import com.intellij.codeInsight.lookup.LookupElementBuilder
|
||||||
|
import com.intellij.openapi.util.TextRange
|
||||||
|
import com.intellij.psi.util.parentOfType
|
||||||
|
import gnu.trove.THashSet
|
||||||
|
import gnu.trove.TObjectHashingStrategy
|
||||||
|
import org.jetbrains.kotlin.descriptors.Modality
|
||||||
|
import org.jetbrains.kotlin.idea.completion.InsertionHandlerBase
|
||||||
|
import org.jetbrains.kotlin.idea.completion.KotlinFirIconProvider.getIconFor
|
||||||
|
import org.jetbrains.kotlin.idea.completion.checkers.CompletionVisibilityChecker
|
||||||
|
import org.jetbrains.kotlin.idea.completion.context.FirBasicCompletionContext
|
||||||
|
import org.jetbrains.kotlin.idea.completion.context.FirWithSubjectEntryPositionContext
|
||||||
|
import org.jetbrains.kotlin.idea.completion.createKeywordElement
|
||||||
|
import org.jetbrains.kotlin.idea.frontend.api.KtAnalysisSession
|
||||||
|
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.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.types.*
|
||||||
|
import org.jetbrains.kotlin.name.ClassId
|
||||||
|
import org.jetbrains.kotlin.name.FqName
|
||||||
|
import org.jetbrains.kotlin.name.Name
|
||||||
|
import org.jetbrains.kotlin.renderer.render
|
||||||
|
|
||||||
|
internal class FirWhenWithSubjectConditionContributor(
|
||||||
|
basicContext: FirBasicCompletionContext,
|
||||||
|
) : FirContextCompletionContributorBase<FirWithSubjectEntryPositionContext>(basicContext) {
|
||||||
|
override fun KtAnalysisSession.complete(positionContext: FirWithSubjectEntryPositionContext) {
|
||||||
|
val whenCondition = positionContext.whenCondition
|
||||||
|
val whenExpression = whenCondition.parentOfType<KtWhenExpression>() ?: return
|
||||||
|
val subject = whenExpression.subjectExpression ?: return
|
||||||
|
val allConditionsExceptCurrent = whenExpression.entries.flatMap { entry -> entry.conditions.filter { it != whenCondition } }
|
||||||
|
val subjectType = subject.getKtType()
|
||||||
|
val classSymbol = getClassSymbol(subjectType)
|
||||||
|
val visibilityChecker = CompletionVisibilityChecker.create(basicContext, positionContext)
|
||||||
|
when {
|
||||||
|
classSymbol?.classKind == KtClassKind.ENUM_CLASS -> {
|
||||||
|
completeEnumEntries(classSymbol, allConditionsExceptCurrent, visibilityChecker)
|
||||||
|
}
|
||||||
|
classSymbol?.modality == Modality.SEALED -> {
|
||||||
|
completeSubClassesOfSealedClass(classSymbol, allConditionsExceptCurrent, whenCondition, visibilityChecker)
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
completeAllTypes(whenCondition, visibilityChecker)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
addNullIfWhenExpressionCanReturnNull(subjectType)
|
||||||
|
addElseBranchIfSingleConditionInEntry(whenCondition)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun KtAnalysisSession.getClassSymbol(subjectType: KtType): KtNamedClassOrObjectSymbol? {
|
||||||
|
val classType = subjectType as? KtClassType
|
||||||
|
return classType?.classSymbol as? KtNamedClassOrObjectSymbol
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private fun KtAnalysisSession.addNullIfWhenExpressionCanReturnNull(type: KtType?) {
|
||||||
|
if (type?.canBeNull == true) {
|
||||||
|
val lookupElement = createKeywordElement(keyword = KtTokens.NULL_KEYWORD.value)
|
||||||
|
result.addElement(lookupElement)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun KtAnalysisSession.completeAllTypes(
|
||||||
|
whenCondition: KtWhenCondition,
|
||||||
|
visibilityChecker: CompletionVisibilityChecker,
|
||||||
|
) {
|
||||||
|
getAvailableClassifiersCurrentScope(originalKtFile, whenCondition, scopeNameFilter, indexHelper, visibilityChecker)
|
||||||
|
.forEach { classifier ->
|
||||||
|
if (classifier !is KtNamedSymbol) return@forEach
|
||||||
|
|
||||||
|
addLookupElement(
|
||||||
|
classifier.name.asString(),
|
||||||
|
classifier,
|
||||||
|
(classifier as? KtNamedClassOrObjectSymbol)?.classIdIfNonLocal?.asSingleFqName(),
|
||||||
|
isPrefixNeeded(classifier)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun KtAnalysisSession.isPrefixNeeded(classifier: KtClassifierSymbol): Boolean {
|
||||||
|
return when (classifier) {
|
||||||
|
is KtAnonymousObjectSymbol -> return false
|
||||||
|
is KtNamedClassOrObjectSymbol -> !classifier.classKind.isObject
|
||||||
|
is KtTypeAliasSymbol -> (classifier.expandedType as? KtClassType)?.classSymbol?.let { isPrefixNeeded(it) } == true
|
||||||
|
is KtTypeParameterSymbol -> true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalStdlibApi::class)
|
||||||
|
private fun KtAnalysisSession.completeSubClassesOfSealedClass(
|
||||||
|
classSymbol: KtNamedClassOrObjectSymbol,
|
||||||
|
conditions: List<KtWhenCondition>,
|
||||||
|
whenCondition: KtWhenCondition,
|
||||||
|
visibilityChecker: CompletionVisibilityChecker,
|
||||||
|
) {
|
||||||
|
require(classSymbol.modality == Modality.SEALED)
|
||||||
|
val handledCasesClassIds = getHandledClassIds(conditions)
|
||||||
|
val allInheritors = getAllSealedInheritors(classSymbol)
|
||||||
|
|
||||||
|
allInheritors
|
||||||
|
.asSequence()
|
||||||
|
.filter { it.classIdIfNonLocal !in handledCasesClassIds }
|
||||||
|
.filter { with(visibilityChecker) { isVisible(it as KtClassifierSymbol) } }
|
||||||
|
.forEach { inheritor ->
|
||||||
|
val classId = inheritor.classIdIfNonLocal ?: return@forEach
|
||||||
|
addLookupElement(classId.relativeClassName.asString(), inheritor, classId.asSingleFqName(), isPrefixNeeded(inheritor))
|
||||||
|
}
|
||||||
|
|
||||||
|
if (allInheritors.any { it.modality == Modality.ABSTRACT }) {
|
||||||
|
completeAllTypes(whenCondition, visibilityChecker)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun KtAnalysisSession.getHandledClassIds(conditions: List<KtWhenCondition>): Set<ClassId> =
|
||||||
|
conditions.mapNotNullTo(hashSetOf()) { condition ->
|
||||||
|
val reference = when (condition) {
|
||||||
|
is KtWhenConditionWithExpression -> condition.expression?.reference()
|
||||||
|
is KtWhenConditionIsPattern -> (condition.typeReference?.typeElement as? KtUserType)?.referenceExpression?.reference()
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
val resolvesTo = reference?.resolveToSymbol() as? KtNamedClassOrObjectSymbol
|
||||||
|
resolvesTo?.classIdIfNonLocal
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun KtAnalysisSession.getAllSealedInheritors(classSymbol: KtNamedClassOrObjectSymbol): Collection<KtNamedClassOrObjectSymbol> {
|
||||||
|
fun KtAnalysisSession.getAllSealedInheritorsTo(
|
||||||
|
classSymbol: KtNamedClassOrObjectSymbol,
|
||||||
|
destination: MutableSet<KtNamedClassOrObjectSymbol>
|
||||||
|
) {
|
||||||
|
classSymbol.getSealedClassInheritors().forEach { inheritor ->
|
||||||
|
destination += inheritor
|
||||||
|
if (inheritor.modality == Modality.SEALED) {
|
||||||
|
getAllSealedInheritorsTo(inheritor, destination)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return THashSet(KtNamedClassOrObjectSymbolTObjectHashingStrategy)
|
||||||
|
.apply { getAllSealedInheritorsTo(classSymbol, this) }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun addElseBranchIfSingleConditionInEntry(whenCondition: KtWhenCondition) {
|
||||||
|
val whenEntry = whenCondition.parent as? KtWhenEntry ?: return
|
||||||
|
if (whenEntry.conditions.size > 1) return
|
||||||
|
val lookupElement = createKeywordElement(keyword = KtTokens.ELSE_KEYWORD.value, tail = " -> ")
|
||||||
|
result.addElement(lookupElement)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private fun KtAnalysisSession.completeEnumEntries(
|
||||||
|
classSymbol: KtNamedClassOrObjectSymbol,
|
||||||
|
conditions: List<KtWhenCondition>,
|
||||||
|
visibilityChecker: CompletionVisibilityChecker
|
||||||
|
) {
|
||||||
|
require(classSymbol.classKind == KtClassKind.ENUM_CLASS)
|
||||||
|
val handledCasesNames = conditions.mapNotNullTo(hashSetOf()) { condition ->
|
||||||
|
val conditionWithExpression = condition as? KtWhenConditionWithExpression
|
||||||
|
val resolvesTo = conditionWithExpression?.expression?.reference()?.resolveToSymbol() as? KtEnumEntrySymbol
|
||||||
|
resolvesTo?.name
|
||||||
|
}
|
||||||
|
val allEnumEntrySymbols = classSymbol.getEnumEntries()
|
||||||
|
allEnumEntrySymbols
|
||||||
|
.filter { it.name !in handledCasesNames }
|
||||||
|
.filter { with(visibilityChecker) { isVisible(it) } }
|
||||||
|
.forEach { entry ->
|
||||||
|
addLookupElement(
|
||||||
|
"${classSymbol.name.asString()}.${entry.name.asString()}",
|
||||||
|
entry,
|
||||||
|
entry.callableIdIfNonLocal?.asSingleFqName(),
|
||||||
|
isPrefixNeeded = false
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun KtAnalysisSession.addLookupElement(lookupString: String, symbol: KtNamedSymbol, fqName: FqName?, isPrefixNeeded: Boolean) {
|
||||||
|
val lookupObject = WhenConditionLookupObject(symbol.name, fqName, isPrefixNeeded)
|
||||||
|
|
||||||
|
LookupElementBuilder.create(lookupObject, getIsPrefix(isPrefixNeeded) + lookupString)
|
||||||
|
.withIcon(getIconFor(symbol))
|
||||||
|
.withPsiElement(symbol.psi)
|
||||||
|
.withInsertHandler(WhenConditionInsertionHandler)
|
||||||
|
.let(result::addElement)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private data class WhenConditionLookupObject(
|
||||||
|
override val shortName: Name,
|
||||||
|
val fqName: FqName?,
|
||||||
|
val needIsPrefix: Boolean,
|
||||||
|
) : KotlinLookupObject
|
||||||
|
|
||||||
|
private object WhenConditionInsertionHandler : InsertionHandlerBase<WhenConditionLookupObject>(WhenConditionLookupObject::class) {
|
||||||
|
override fun handleInsert(context: InsertionContext, item: LookupElement, ktFile: KtFile, lookupObject: WhenConditionLookupObject) {
|
||||||
|
if (lookupObject.fqName != null) {
|
||||||
|
val fqName = lookupObject.fqName
|
||||||
|
context.document.replaceString(
|
||||||
|
context.startOffset,
|
||||||
|
context.tailOffset,
|
||||||
|
getIsPrefix(lookupObject.needIsPrefix) + fqName.render()
|
||||||
|
)
|
||||||
|
context.commitDocument()
|
||||||
|
|
||||||
|
shortenReferencesForFirCompletion(ktFile, TextRange(context.startOffset, context.tailOffset))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getIsPrefix(prefixNeeded: Boolean): String {
|
||||||
|
return if (prefixNeeded) "is " else ""
|
||||||
|
}
|
||||||
|
|
||||||
|
private object KtNamedClassOrObjectSymbolTObjectHashingStrategy : TObjectHashingStrategy<KtNamedClassOrObjectSymbol> {
|
||||||
|
override fun equals(p0: KtNamedClassOrObjectSymbol, p1: KtNamedClassOrObjectSymbol): Boolean =
|
||||||
|
p0.classIdIfNonLocal == p1.classIdIfNonLocal
|
||||||
|
|
||||||
|
override fun computeHashCode(p0: KtNamedClassOrObjectSymbol): Int =
|
||||||
|
p0.classIdIfNonLocal.hashCode()
|
||||||
|
}
|
||||||
|
|||||||
+37
@@ -0,0 +1,37 @@
|
|||||||
|
/*
|
||||||
|
* 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
|
||||||
|
|
||||||
|
import org.jetbrains.kotlin.idea.completion.checkers.CompletionVisibilityChecker
|
||||||
|
import org.jetbrains.kotlin.idea.fir.low.level.api.IndexHelper
|
||||||
|
import org.jetbrains.kotlin.idea.frontend.api.KtAnalysisSession
|
||||||
|
import org.jetbrains.kotlin.idea.frontend.api.scopes.KtScopeNameFilter
|
||||||
|
import org.jetbrains.kotlin.idea.frontend.api.symbols.KtClassifierSymbol
|
||||||
|
import org.jetbrains.kotlin.psi.KtElement
|
||||||
|
import org.jetbrains.kotlin.psi.KtEnumEntry
|
||||||
|
import org.jetbrains.kotlin.psi.KtFile
|
||||||
|
|
||||||
|
internal object FirClassifierProvider {
|
||||||
|
fun KtAnalysisSession.getAvailableClassifiersCurrentScope(
|
||||||
|
originalKtFile: KtFile,
|
||||||
|
position: KtElement,
|
||||||
|
scopeNameFilter: KtScopeNameFilter,
|
||||||
|
indexHelper: IndexHelper,
|
||||||
|
visibilityChecker: CompletionVisibilityChecker
|
||||||
|
): Sequence<KtClassifierSymbol> = sequence {
|
||||||
|
yieldAll(
|
||||||
|
originalKtFile.getScopeContextForPosition(position).scopes
|
||||||
|
.getClassifierSymbols(scopeNameFilter)
|
||||||
|
.filter { with(visibilityChecker) { isVisible(it) } }
|
||||||
|
)
|
||||||
|
|
||||||
|
yieldAll(
|
||||||
|
indexHelper.getKotlinClasses(scopeNameFilter, psiFilter = { it !is KtEnumEntry }).asSequence()
|
||||||
|
.map { it.getSymbol() as KtClassifierSymbol }
|
||||||
|
.filter { with(visibilityChecker) { isVisible(it) } }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
+5
-1
@@ -42,7 +42,8 @@ abstract class KtAnalysisSession(final override val token: ValidityToken) : Vali
|
|||||||
KtReferenceShortenerMixIn,
|
KtReferenceShortenerMixIn,
|
||||||
KtSymbolDeclarationRendererMixIn,
|
KtSymbolDeclarationRendererMixIn,
|
||||||
KtVisibilityCheckerMixIn,
|
KtVisibilityCheckerMixIn,
|
||||||
KtMemberSymbolProviderMixin
|
KtMemberSymbolProviderMixin,
|
||||||
|
KtInheritorsProviderMixIn
|
||||||
{
|
{
|
||||||
|
|
||||||
override val analysisSession: KtAnalysisSession get() = this
|
override val analysisSession: KtAnalysisSession get() = this
|
||||||
@@ -99,4 +100,7 @@ abstract class KtAnalysisSession(final override val token: ValidityToken) : Vali
|
|||||||
|
|
||||||
internal val overrideInfoProvider: KtOverrideInfoProvider get() = overrideInfoProviderImpl
|
internal val overrideInfoProvider: KtOverrideInfoProvider get() = overrideInfoProviderImpl
|
||||||
protected abstract val overrideInfoProviderImpl: KtOverrideInfoProvider
|
protected abstract val overrideInfoProviderImpl: KtOverrideInfoProvider
|
||||||
|
|
||||||
|
internal val inheritorsProvider: KtInheritorsProvider get() = inheritorsProviderImpl
|
||||||
|
protected abstract val inheritorsProviderImpl: KtInheritorsProvider
|
||||||
}
|
}
|
||||||
+22
@@ -0,0 +1,22 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2020 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.frontend.api.components
|
||||||
|
|
||||||
|
import org.jetbrains.kotlin.idea.frontend.api.symbols.KtEnumEntrySymbol
|
||||||
|
import org.jetbrains.kotlin.idea.frontend.api.symbols.KtNamedClassOrObjectSymbol
|
||||||
|
|
||||||
|
abstract class KtInheritorsProvider : KtAnalysisSessionComponent() {
|
||||||
|
abstract fun getInheritorsOfSealedClass(classSymbol: KtNamedClassOrObjectSymbol): List<KtNamedClassOrObjectSymbol>
|
||||||
|
abstract fun getEnumEntries(classSymbol: KtNamedClassOrObjectSymbol): List<KtEnumEntrySymbol>
|
||||||
|
}
|
||||||
|
|
||||||
|
interface KtInheritorsProviderMixIn : KtAnalysisSessionMixIn {
|
||||||
|
fun KtNamedClassOrObjectSymbol.getSealedClassInheritors(): List<KtNamedClassOrObjectSymbol> =
|
||||||
|
analysisSession.inheritorsProvider.getInheritorsOfSealedClass(this)
|
||||||
|
|
||||||
|
fun KtNamedClassOrObjectSymbol.getEnumEntries(): List<KtEnumEntrySymbol> =
|
||||||
|
analysisSession.inheritorsProvider.getEnumEntries(this)
|
||||||
|
}
|
||||||
+1
@@ -34,6 +34,7 @@ class SealedClassInheritorsProviderIdeImpl : SealedClassInheritorsProvider() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun getInheritors(firClass: FirRegularClass): List<ClassId> {
|
private fun getInheritors(firClass: FirRegularClass): List<ClassId> {
|
||||||
|
// TODO fix for non-source classes
|
||||||
val sealedKtClass = firClass.psi as? KtClass ?: return emptyList()
|
val sealedKtClass = firClass.psi as? KtClass ?: return emptyList()
|
||||||
val module = sealedKtClass.module ?: return emptyList()
|
val module = sealedKtClass.module ?: return emptyList()
|
||||||
val containingPackage = firClass.classId.packageFqName
|
val containingPackage = firClass.classId.packageFqName
|
||||||
|
|||||||
+3
@@ -14,6 +14,7 @@ import org.jetbrains.kotlin.fir.resolve.symbolProvider
|
|||||||
import org.jetbrains.kotlin.idea.fir.low.level.api.api.*
|
import org.jetbrains.kotlin.idea.fir.low.level.api.api.*
|
||||||
import org.jetbrains.kotlin.idea.frontend.api.InvalidWayOfUsingAnalysisSession
|
import org.jetbrains.kotlin.idea.frontend.api.InvalidWayOfUsingAnalysisSession
|
||||||
import org.jetbrains.kotlin.idea.frontend.api.KtAnalysisSession
|
import org.jetbrains.kotlin.idea.frontend.api.KtAnalysisSession
|
||||||
|
import org.jetbrains.kotlin.idea.frontend.api.components.KtInheritorsProvider
|
||||||
import org.jetbrains.kotlin.idea.frontend.api.components.KtVisibilityChecker
|
import org.jetbrains.kotlin.idea.frontend.api.components.KtVisibilityChecker
|
||||||
import org.jetbrains.kotlin.idea.frontend.api.components.KtSymbolDeclarationRendererProvider
|
import org.jetbrains.kotlin.idea.frontend.api.components.KtSymbolDeclarationRendererProvider
|
||||||
import org.jetbrains.kotlin.idea.frontend.api.fir.components.*
|
import org.jetbrains.kotlin.idea.frontend.api.fir.components.*
|
||||||
@@ -78,6 +79,8 @@ private constructor(
|
|||||||
|
|
||||||
override val subtypingComponentImpl = KtFirSubtypingComponent(this, token)
|
override val subtypingComponentImpl = KtFirSubtypingComponent(this, token)
|
||||||
|
|
||||||
|
override val inheritorsProviderImpl: KtInheritorsProvider = KtFirInheritorsProvider(this, token)
|
||||||
|
|
||||||
override fun createContextDependentCopy(originalKtFile: KtFile, elementToReanalyze: KtElement): KtAnalysisSession {
|
override fun createContextDependentCopy(originalKtFile: KtFile, elementToReanalyze: KtElement): KtAnalysisSession {
|
||||||
check(mode == AnalysisSessionMode.REGULAR) {
|
check(mode == AnalysisSessionMode.REGULAR) {
|
||||||
"Cannot create context-dependent copy of KtAnalysis session from a context dependent one"
|
"Cannot create context-dependent copy of KtAnalysis session from a context dependent one"
|
||||||
|
|||||||
+45
@@ -0,0 +1,45 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2020 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.frontend.api.fir.components
|
||||||
|
|
||||||
|
import org.jetbrains.kotlin.descriptors.Modality
|
||||||
|
import org.jetbrains.kotlin.fir.declarations.getSealedClassInheritors
|
||||||
|
import org.jetbrains.kotlin.idea.frontend.api.components.KtInheritorsProvider
|
||||||
|
import org.jetbrains.kotlin.idea.frontend.api.fir.KtFirAnalysisSession
|
||||||
|
import org.jetbrains.kotlin.idea.frontend.api.fir.symbols.KtFirNamedClassOrObjectSymbol
|
||||||
|
import org.jetbrains.kotlin.idea.frontend.api.symbols.KtClassKind
|
||||||
|
import org.jetbrains.kotlin.idea.frontend.api.symbols.KtEnumEntrySymbol
|
||||||
|
import org.jetbrains.kotlin.idea.frontend.api.symbols.KtNamedClassOrObjectSymbol
|
||||||
|
import org.jetbrains.kotlin.idea.frontend.api.tokens.ValidityToken
|
||||||
|
import org.jetbrains.kotlin.idea.frontend.api.withValidityAssertion
|
||||||
|
|
||||||
|
internal class KtFirInheritorsProvider(
|
||||||
|
override val analysisSession: KtFirAnalysisSession,
|
||||||
|
override val token: ValidityToken,
|
||||||
|
) : KtInheritorsProvider(), KtFirAnalysisSessionComponent {
|
||||||
|
override fun getInheritorsOfSealedClass(
|
||||||
|
classSymbol: KtNamedClassOrObjectSymbol
|
||||||
|
): List<KtNamedClassOrObjectSymbol> = withValidityAssertion {
|
||||||
|
require(classSymbol.modality == Modality.SEALED)
|
||||||
|
require(classSymbol is KtFirNamedClassOrObjectSymbol)
|
||||||
|
|
||||||
|
val inheritorClassIds = classSymbol.firRef.withFir { fir ->
|
||||||
|
fir.getSealedClassInheritors(analysisSession.rootModuleSession)
|
||||||
|
}
|
||||||
|
|
||||||
|
with(analysisSession) {
|
||||||
|
inheritorClassIds.mapNotNull { it.getCorrespondingToplevelClassOrObjectSymbol() as? KtNamedClassOrObjectSymbol }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getEnumEntries(classSymbol: KtNamedClassOrObjectSymbol): List<KtEnumEntrySymbol> = withValidityAssertion {
|
||||||
|
require(classSymbol.classKind == KtClassKind.ENUM_CLASS)
|
||||||
|
with(analysisSession) {
|
||||||
|
classSymbol.getDeclaredMemberScope().getCallableSymbols().filterIsInstance<KtEnumEntrySymbol>().toList()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user