FIR IDE: separate callable and type completion

This commit is contained in:
Ilya Kirillov
2021-05-15 19:03:04 +02:00
committed by TeamCityServer
parent e7288b3293
commit 6c9831f7d4
8 changed files with 126 additions and 87 deletions
@@ -11,27 +11,21 @@ import com.intellij.patterns.PsiJavaPatterns
import com.intellij.util.ProcessingContext
import org.jetbrains.kotlin.idea.caches.project.getModuleInfo
import org.jetbrains.kotlin.idea.completion.checkers.CompletionVisibilityChecker
import org.jetbrains.kotlin.idea.completion.checkers.ExtensionApplicabilityChecker
import org.jetbrains.kotlin.idea.completion.context.*
import org.jetbrains.kotlin.idea.completion.context.FirBasicCompletionContext
import org.jetbrains.kotlin.idea.completion.context.FirNameReferencePositionContext
import org.jetbrains.kotlin.idea.completion.context.FirExpressionNameReferencePositionContext
import org.jetbrains.kotlin.idea.completion.context.FirPositionCompletionContextDetector
import org.jetbrains.kotlin.idea.completion.context.FirTypeNameReferencePositionContext
import org.jetbrains.kotlin.idea.completion.context.FirUnknownPositionContext
import org.jetbrains.kotlin.idea.completion.contributors.FirCallableCompletionContributor
import org.jetbrains.kotlin.idea.completion.contributors.FirClassifierCompletionContributor
import org.jetbrains.kotlin.idea.completion.contributors.FirContextCompletionContributorBase
import org.jetbrains.kotlin.idea.completion.contributors.FirKeywordCompletionContributor
import org.jetbrains.kotlin.idea.completion.contributors.complete
import org.jetbrains.kotlin.idea.completion.weighers.Weighers
import org.jetbrains.kotlin.idea.fir.low.level.api.IndexHelper
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.components.KtScopeContext
import org.jetbrains.kotlin.idea.frontend.api.scopes.KtCompositeScope
import org.jetbrains.kotlin.idea.frontend.api.scopes.KtScope
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.KtType
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.psi.*
class KotlinFirCompletionContributor : CompletionContributor() {
init {
@@ -51,39 +45,35 @@ private object KotlinFirCompletionProvider : CompletionProvider<CompletionParame
val basicContext = FirBasicCompletionContext.createFromParameters(parameters, resultSet) ?: return
recordOriginalFile(basicContext)
val positionContext = FirPositionCompletionContextDetector.detect(basicContext)
val keywordContributor = FirKeywordCompletionContributor(basicContext)
FirPositionCompletionContextDetector.analyseInContext(basicContext, positionContext) {
with(keywordContributor) { completeKeywords(positionContext) }
when (positionContext) {
is FirNameReferencePositionContext -> {
val visibilityChecker = CompletionVisibilityChecker {
parameters.invocationCount > 1 || isVisible(
it,
basicContext.originalKtFile.getFileSymbol(),
positionContext.explicitReceiver,
positionContext.position
)
}
complete(basicContext, positionContext, indexHelper)
}
}
with(FirCallableCompletionContributor(basicContext, indexHelper)) {
complete(positionContext, visibilityChecker)
}
private fun KtAnalysisSession.complete(
basicContext: FirBasicCompletionContext,
positionContext: FirRawPositionCompletionContext,
indexHelper: IndexHelper,
) {
val keywordContributor = FirKeywordCompletionContributor(basicContext)
val callableContributor = FirCallableCompletionContributor(basicContext, indexHelper)
val classifierContributor = FirClassifierCompletionContributor(basicContext, indexHelper)
if (positionContext.explicitReceiver == null) {
with(FirClassifierCompletionContributor(basicContext, indexHelper)) {
collectTypesCompletion(
basicContext.originalKtFile.getScopeContextForPosition(positionContext.nameExpression).scopes,
visibilityChecker
)
}
}
}
is FirUnknownPositionContext -> {
}
when (positionContext) {
is FirExpressionNameReferencePositionContext -> {
complete(keywordContributor, positionContext)
complete(callableContributor, positionContext)
complete(classifierContributor, positionContext)
}
is FirTypeNameReferencePositionContext -> {
complete(keywordContributor, positionContext)
complete(classifierContributor, positionContext)
}
is FirUnknownPositionContext -> {
complete(keywordContributor, positionContext)
}
}
}
@@ -5,6 +5,8 @@
package org.jetbrains.kotlin.idea.completion.checkers
import org.jetbrains.kotlin.idea.completion.context.FirBasicCompletionContext
import org.jetbrains.kotlin.idea.completion.context.FirNameReferenceRawPositionContext
import org.jetbrains.kotlin.idea.frontend.api.KtAnalysisSession
import org.jetbrains.kotlin.idea.frontend.api.symbols.KtCallableSymbol
import org.jetbrains.kotlin.idea.frontend.api.symbols.KtClassifierSymbol
@@ -20,4 +22,18 @@ internal fun interface CompletionVisibilityChecker {
fun KtAnalysisSession.isVisible(symbol: KtClassifierSymbol): Boolean {
return symbol !is KtSymbolWithVisibility || isVisible(symbol as KtSymbolWithVisibility)
}
companion object {
fun create(
basicContext: FirBasicCompletionContext,
positionContext: FirNameReferenceRawPositionContext
): CompletionVisibilityChecker = CompletionVisibilityChecker {
basicContext.parameters.invocationCount > 1 || isVisible(
it,
basicContext.originalKtFile.getFileSymbol(),
positionContext.explicitReceiver,
positionContext.position
)
}
}
}
@@ -14,37 +14,57 @@ import org.jetbrains.kotlin.idea.references.mainReference
import org.jetbrains.kotlin.psi.KtExpression
import org.jetbrains.kotlin.psi.KtLabelReferenceExpression
import org.jetbrains.kotlin.psi.KtSimpleNameExpression
import org.jetbrains.kotlin.psi.KtUserType
import org.jetbrains.kotlin.psi.psiUtil.getReceiverExpression
internal sealed class FirPositionCompletionContext {
internal sealed class FirRawPositionCompletionContext {
abstract val position: PsiElement
}
internal class FirNameReferencePositionContext(
internal sealed class FirNameReferencePositionContext : FirRawPositionCompletionContext() {
abstract val reference: KtSimpleNameReference
abstract val nameExpression: KtSimpleNameExpression
abstract val explicitReceiver: KtExpression?
}
internal class FirTypeNameReferencePositionContext(
override val position: PsiElement,
val reference: KtSimpleNameReference,
val nameExpression: KtSimpleNameExpression,
val explicitReceiver: KtExpression?
) : FirPositionCompletionContext()
override val reference: KtSimpleNameReference,
override val nameExpression: KtSimpleNameExpression,
override val explicitReceiver: KtExpression?
) : FirNameReferencePositionContext()
internal class FirExpressionNameReferencePositionContext(
override val position: PsiElement,
override val reference: KtSimpleNameReference,
override val nameExpression: KtSimpleNameExpression,
override val explicitReceiver: KtExpression?
) : FirNameReferencePositionContext()
internal class FirUnknownPositionContext(
override val position: PsiElement
) : FirPositionCompletionContext()
) : FirRawPositionCompletionContext()
internal object FirPositionCompletionContextDetector {
fun detect(basicContext: FirBasicCompletionContext): FirPositionCompletionContext {
fun detect(basicContext: FirBasicCompletionContext): FirRawPositionCompletionContext {
val position = basicContext.parameters.position
val reference = (position.parent as? KtSimpleNameExpression)?.mainReference
?: return FirUnknownPositionContext(position)
val nameExpression = reference.expression.takeIf { it !is KtLabelReferenceExpression }
?: return FirUnknownPositionContext(position)
val explicitReceiver = nameExpression.getReceiverExpression()
return FirNameReferencePositionContext(position, reference, nameExpression, explicitReceiver)
return if (nameExpression.parent is KtUserType) {
FirTypeNameReferencePositionContext(position, reference, nameExpression, explicitReceiver)
} else {
FirExpressionNameReferencePositionContext(position, reference, nameExpression, explicitReceiver)
}
}
inline fun analyseInContext(
basicContext: FirBasicCompletionContext,
positionContext: FirPositionCompletionContext,
positionContext: FirRawPositionCompletionContext,
action: KtAnalysisSession.() -> Unit
) {
return when (positionContext) {
@@ -19,41 +19,42 @@ import org.jetbrains.kotlin.idea.frontend.api.symbols.KtClassOrObjectSymbol
import org.jetbrains.kotlin.idea.frontend.api.types.KtClassType
import org.jetbrains.kotlin.idea.frontend.api.types.KtType
import org.jetbrains.kotlin.psi.KtExpression
import org.jetbrains.kotlin.psi.KtUserType
internal class FirCallableCompletionContributor(
basicContext: FirBasicCompletionContext,
indexHelper: IndexHelper
) : FirContextCompletionContributorBase(basicContext, indexHelper) {
) : FirContextCompletionContributorBase<FirNameReferencePositionContext>(basicContext, indexHelper) {
private val typeNamesProvider = TypeNamesProvider(indexHelper)
private val shouldCompleteTopLevelCallablesFromIndex: Boolean
get() = prefixMatcher.prefix.isNotEmpty()
fun KtAnalysisSession.complete(positionContext: FirNameReferencePositionContext, visibilityChecker: CompletionVisibilityChecker) =
with(positionContext) {
val expectedType = nameExpression.getExpectedType()
val scopesContext = originalKtFile.getScopeContextForPosition(nameExpression)
override fun KtAnalysisSession.complete(positionContext: FirNameReferencePositionContext): Unit = with(positionContext) {
val visibilityChecker = CompletionVisibilityChecker.create(basicContext, positionContext)
val expectedType = nameExpression.getExpectedType()
val scopesContext = originalKtFile.getScopeContextForPosition(nameExpression)
val extensionChecker = ExtensionApplicabilityChecker {
it.checkExtensionIsSuitable(originalKtFile, nameExpression, explicitReceiver)
}
when {
explicitReceiver != null -> {
collectDotCompletion(
scopesContext.scopes,
explicitReceiver,
expectedType,
extensionChecker,
visibilityChecker,
)
}
else -> completeWithoutReceiver(scopesContext, expectedType, extensionChecker, visibilityChecker)
}
val extensionChecker = ExtensionApplicabilityChecker {
it.checkExtensionIsSuitable(originalKtFile, nameExpression, explicitReceiver)
}
val receiver = explicitReceiver
when {
receiver != null -> {
collectDotCompletion(
scopesContext.scopes,
receiver,
expectedType,
extensionChecker,
visibilityChecker,
)
}
else -> completeWithoutReceiver(scopesContext, expectedType, extensionChecker, visibilityChecker)
}
}
private fun KtAnalysisSession.completeWithoutReceiver(
implicitScopesContext: KtScopeContext,
@@ -7,6 +7,7 @@ package org.jetbrains.kotlin.idea.completion.contributors
import org.jetbrains.kotlin.idea.completion.checkers.CompletionVisibilityChecker
import org.jetbrains.kotlin.idea.completion.context.FirBasicCompletionContext
import org.jetbrains.kotlin.idea.completion.context.FirNameReferenceRawPositionContext
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.KtScope
@@ -17,12 +18,11 @@ import org.jetbrains.kotlin.psi.KtEnumEntry
internal class FirClassifierCompletionContributor(
basicContext: FirBasicCompletionContext,
indexHelper: IndexHelper
) : FirContextCompletionContributorBase(basicContext, indexHelper) {
) : FirContextCompletionContributorBase<FirNameReferenceRawPositionContext>(basicContext, indexHelper) {
fun KtAnalysisSession.collectTypesCompletion(
implicitScopes: KtScope,
visibilityChecker: CompletionVisibilityChecker,
) {
override fun KtAnalysisSession.complete(positionContext: FirNameReferenceRawPositionContext) {
val implicitScopes = originalKtFile.getScopeContextForPosition(positionContext.nameExpression).scopes
val visibilityChecker = CompletionVisibilityChecker.create(basicContext, positionContext)
val classesFromScopes = implicitScopes
.getClassifierSymbols(scopeNameFilter)
.filter { with(visibilityChecker) { isVisible(it) } }
@@ -12,8 +12,8 @@ import com.intellij.codeInsight.lookup.LookupElement
import com.intellij.openapi.project.Project
import org.jetbrains.kotlin.idea.completion.KotlinFirLookupElementFactory
import org.jetbrains.kotlin.idea.completion.context.FirBasicCompletionContext
import org.jetbrains.kotlin.idea.completion.context.FirRawPositionCompletionContext
import org.jetbrains.kotlin.idea.completion.weighers.Weighers
import org.jetbrains.kotlin.idea.completion.weighers.Weighers.applyWeighsToLookupElement
import org.jetbrains.kotlin.idea.frontend.api.KtAnalysisSession
import org.jetbrains.kotlin.idea.frontend.api.symbols.KtSymbol
import org.jetbrains.kotlin.idea.frontend.api.symbols.markers.KtNamedSymbol
@@ -21,7 +21,7 @@ import org.jetbrains.kotlin.idea.frontend.api.types.KtType
import org.jetbrains.kotlin.platform.TargetPlatform
import org.jetbrains.kotlin.psi.KtFile
internal abstract class FirCompletionContributorBase(protected val basicContext: FirBasicCompletionContext) {
internal abstract class FirCompletionContributorBase<C : FirRawPositionCompletionContext>(protected val basicContext: FirBasicCompletionContext) {
protected val prefixMatcher: PrefixMatcher get() = basicContext.prefixMatcher
protected val parameters: CompletionParameters get() = basicContext.parameters
protected val result: CompletionResultSet get() = basicContext.result
@@ -31,6 +31,8 @@ internal abstract class FirCompletionContributorBase(protected val basicContext:
protected val targetPlatform: TargetPlatform get() = basicContext.targetPlatform
protected val lookupElementFactory: KotlinFirLookupElementFactory get() = basicContext.lookupElementFactory
abstract fun KtAnalysisSession.complete(positionContext: C)
protected fun KtAnalysisSession.addSymbolToCompletion(expectedType: KtType?, symbol: KtSymbol) {
if (symbol !is KtNamedSymbol) return
with(lookupElementFactory) {
@@ -47,4 +49,13 @@ internal abstract class FirCompletionContributorBase(protected val basicContext:
): LookupElement = lookupElement.apply {
with(Weighers) { applyWeighsToLookupElement(lookupElement, symbol, expectedType) }
}
}
internal fun <C : FirRawPositionCompletionContext> KtAnalysisSession.complete(
contextContributor: FirCompletionContributorBase<C>,
positionContext: C,
) {
with(contextContributor) {
complete(positionContext)
}
}
@@ -6,13 +6,16 @@
package org.jetbrains.kotlin.idea.completion.contributors
import org.jetbrains.kotlin.idea.completion.context.FirBasicCompletionContext
import org.jetbrains.kotlin.idea.completion.context.FirNameReferenceRawPositionContext
import org.jetbrains.kotlin.idea.completion.context.FirRawPositionCompletionContext
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
internal abstract class FirContextCompletionContributorBase(
internal abstract class FirContextCompletionContributorBase<C : FirNameReferenceRawPositionContext>(
basicContext: FirBasicCompletionContext,
protected val indexHelper: IndexHelper
) : FirCompletionContributorBase(basicContext) {
) : FirCompletionContributorBase<C>(basicContext) {
protected val scopeNameFilter: KtScopeNameFilter =
{ name -> !name.isSpecial && prefixMatcher.prefixMatches(name.identifier) }
@@ -8,15 +8,13 @@ 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.*
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.FirRawPositionCompletionContext
import org.jetbrains.kotlin.idea.completion.context.FirUnknownPositionContext
import org.jetbrains.kotlin.idea.completion.contributors.keywords.OverrideKeywordHandler
import org.jetbrains.kotlin.idea.completion.contributors.keywords.ReturnKeywordHandler
import org.jetbrains.kotlin.idea.completion.contributors.keywords.ReturnKeywordHandler.createLookups
import org.jetbrains.kotlin.idea.completion.contributors.keywords.ThisKeywordHandler
import org.jetbrains.kotlin.idea.completion.keywords.CompletionKeywordHandlerProvider
import org.jetbrains.kotlin.idea.completion.keywords.CompletionKeywordHandlers
@@ -27,7 +25,8 @@ import org.jetbrains.kotlin.idea.project.languageVersionSettings
import org.jetbrains.kotlin.platform.jvm.isJvm
import org.jetbrains.kotlin.psi.*
internal class FirKeywordCompletionContributor(basicContext: FirBasicCompletionContext) : FirCompletionContributorBase(basicContext) {
internal class FirKeywordCompletionContributor(basicContext: FirBasicCompletionContext) :
FirCompletionContributorBase<FirRawPositionCompletionContext>(basicContext) {
private val keywordCompletion = KeywordCompletion(object : KeywordCompletion.LanguageVersionSettingProvider {
override fun getLanguageVersionSetting(element: PsiElement) = element.languageVersionSettings
override fun getLanguageVersionSetting(module: Module) = module.languageVersionSettings
@@ -35,9 +34,7 @@ internal class FirKeywordCompletionContributor(basicContext: FirBasicCompletionC
private val resolveDependentCompletionKeywordHandlers = ResolveDependentCompletionKeywordHandlerProvider(basicContext)
fun KtAnalysisSession.completeKeywords(
positionContext: FirPositionCompletionContext
) {
override fun KtAnalysisSession.complete(positionContext: FirRawPositionCompletionContext) {
val expression = when (positionContext) {
is FirNameReferencePositionContext -> {
val reference = positionContext.reference
@@ -51,6 +48,7 @@ internal class FirKeywordCompletionContributor(basicContext: FirBasicCompletionC
completeWithResolve(expression ?: positionContext.position, expression)
}
fun KtAnalysisSession.completeWithResolve(position: PsiElement, expression: KtExpression?) {
complete(position) { lookupElement, keyword ->
val lookups = DefaultCompletionKeywordHandlerProvider.getHandlerForKeyword(keyword)