From 81c6f38f792e66a024a8cf294e6694c53dbf59d9 Mon Sep 17 00:00:00 2001 From: Ilya Kirillov Date: Thu, 6 May 2021 15:31:06 +0200 Subject: [PATCH] FIR IDE: move context creation out of KotlinFirCompletionContributor --- .../KotlinFirCompletionContributor.kt | 127 ++++++++++-------- .../context/FirPositionCompletionContext.kt | 67 +++++++++ 2 files changed, 140 insertions(+), 54 deletions(-) create mode 100644 idea/idea-fir/src/org/jetbrains/kotlin/idea/completion/context/FirPositionCompletionContext.kt diff --git a/idea/idea-fir/src/org/jetbrains/kotlin/idea/completion/KotlinFirCompletionContributor.kt b/idea/idea-fir/src/org/jetbrains/kotlin/idea/completion/KotlinFirCompletionContributor.kt index 3817c737b9a..229fc889c4f 100644 --- a/idea/idea-fir/src/org/jetbrains/kotlin/idea/completion/KotlinFirCompletionContributor.kt +++ b/idea/idea-fir/src/org/jetbrains/kotlin/idea/completion/KotlinFirCompletionContributor.kt @@ -12,13 +12,14 @@ import com.intellij.patterns.PsiJavaPatterns import com.intellij.util.ProcessingContext import org.jetbrains.kotlin.idea.caches.project.getModuleInfo import org.jetbrains.kotlin.idea.completion.context.FirBasicCompletionContext +import org.jetbrains.kotlin.idea.completion.context.FirNameReferencePositionContext +import org.jetbrains.kotlin.idea.completion.context.FirPositionCompletionContextDetector +import org.jetbrains.kotlin.idea.completion.context.FirUnknownPositionContext 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.InvalidWayOfUsingAnalysisSession import org.jetbrains.kotlin.idea.frontend.api.KtAnalysisSession import org.jetbrains.kotlin.idea.frontend.api.components.KtScopeContext -import org.jetbrains.kotlin.idea.frontend.api.analyseInFakeAnalysisSession 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.scopes.KtScopeNameFilter @@ -27,11 +28,9 @@ import org.jetbrains.kotlin.idea.frontend.api.symbols.markers.KtNamedSymbol import org.jetbrains.kotlin.idea.frontend.api.symbols.markers.KtSymbolWithVisibility import org.jetbrains.kotlin.idea.frontend.api.types.KtClassType import org.jetbrains.kotlin.idea.frontend.api.types.KtType -import org.jetbrains.kotlin.idea.references.mainReference import org.jetbrains.kotlin.lexer.KtTokens import org.jetbrains.kotlin.platform.jvm.isJvm import org.jetbrains.kotlin.psi.* -import org.jetbrains.kotlin.psi.psiUtil.getReceiverExpression class KotlinFirCompletionContributor : CompletionContributor() { init { @@ -49,7 +48,39 @@ private object KotlinFirCompletionProvider : CompletionProvider with( + KotlinWithNameReferenceCompletionProvider(resultSet.prefixMatcher, indexHelper) + ) { + addCompletions(basicContext, positionContext) + } + is FirUnknownPositionContext -> { + // TODO + } + } + } + } + + + private fun recordOriginalFile(basicCompletionContext: FirBasicCompletionContext) { + val originalFile = basicCompletionContext.originalKtFile + val fakeFile = basicCompletionContext.fakeKtFile + fakeFile.originalKtFile = originalFile } private fun createResultSet(parameters: CompletionParameters, result: CompletionResultSet): CompletionResultSet = @@ -103,7 +134,7 @@ internal fun interface CompletionVisibilityChecker { * * TODO refactor it, try to split into several classes, or decompose it into several classes. */ -private class KotlinCommonCompletionProvider( +private class KotlinWithNameReferenceCompletionProvider( private val prefixMatcher: PrefixMatcher, private val indexHelper: IndexHelper ) { @@ -133,63 +164,51 @@ private class KotlinCommonCompletionProvider( with(Weighers) { applyWeighsToLookupElement(lookupElement, symbol, expectedType) } } - private fun recordOriginalFile(basicCompletionContext: FirBasicCompletionContext) { - val originalFile = basicCompletionContext.originalKtFile - val fakeFile = basicCompletionContext.fakeKtFile - fakeFile.originalKtFile = originalFile - } + fun KtAnalysisSession.addCompletions( + basicContext: FirBasicCompletionContext, + positionContext: FirNameReferencePositionContext + ) = with(positionContext) { + val fileSymbol = basicContext.originalKtFile.getFileSymbol() + val expectedType = nameExpression.getExpectedType() - @OptIn(InvalidWayOfUsingAnalysisSession::class) - fun addCompletions(parameters: CompletionParameters, result: CompletionResultSet) { - val basicContext = FirBasicCompletionContext.createFromParameters(parameters, result) ?: return - recordOriginalFile(basicContext) + val scopesContext = basicContext.originalKtFile.getScopeContextForPosition(nameExpression) - FirKeywordCompletion.completeKeywords( - result, - parameters, - parameters.position, - prefixMatcher, - basicContext.project, - isJvmModule = basicContext.targetPlatform.isJvm() - ) + val extensionChecker = ExtensionApplicabilityChecker { + it.checkExtensionIsSuitable(basicContext.originalKtFile, nameExpression, explicitReceiver) + } - val reference = (parameters.position.parent as? KtSimpleNameExpression)?.mainReference ?: return - val nameExpression = reference.expression.takeIf { it !is KtLabelReferenceExpression } ?: return + val visibilityChecker = CompletionVisibilityChecker { + basicContext.parameters.invocationCount > 1 || isVisible( + it, + fileSymbol, + positionContext.explicitReceiver, + positionContext.position + ) + } - val explicitReceiver = nameExpression.getReceiverExpression() - - analyseInFakeAnalysisSession(basicContext.originalKtFile, nameExpression) { - val fileSymbol = basicContext.originalKtFile.getFileSymbol() - val expectedType = nameExpression.getExpectedType() - - val scopesContext = basicContext.originalKtFile.getScopeContextForPosition(nameExpression) - - val extensionChecker = ExtensionApplicabilityChecker { - it.checkExtensionIsSuitable(basicContext.originalKtFile, nameExpression, explicitReceiver) + when { + nameExpression.parent is KtUserType -> collectTypesCompletion( + basicContext.result, + scopesContext.scopes, + expectedType, + visibilityChecker + ) + explicitReceiver != null -> { + collectDotCompletion( + basicContext.result, + scopesContext.scopes, + explicitReceiver, + expectedType, + extensionChecker, + visibilityChecker, + ) } - val visibilityChecker = CompletionVisibilityChecker { - parameters.invocationCount > 1 || isVisible(it, fileSymbol, explicitReceiver, parameters.position) - } - - when { - nameExpression.parent is KtUserType -> collectTypesCompletion(result, scopesContext.scopes, expectedType, visibilityChecker) - explicitReceiver != null -> { - collectDotCompletion( - result, - scopesContext.scopes, - explicitReceiver, - expectedType, - extensionChecker, - visibilityChecker, - ) - } - - else -> collectDefaultCompletion(result, scopesContext, expectedType, extensionChecker, visibilityChecker) - } + else -> collectDefaultCompletion(basicContext.result, scopesContext, expectedType, extensionChecker, visibilityChecker) } } + private fun KtAnalysisSession.collectTypesCompletion( result: CompletionResultSet, implicitScopes: KtScope, diff --git a/idea/idea-fir/src/org/jetbrains/kotlin/idea/completion/context/FirPositionCompletionContext.kt b/idea/idea-fir/src/org/jetbrains/kotlin/idea/completion/context/FirPositionCompletionContext.kt new file mode 100644 index 00000000000..96624dfee10 --- /dev/null +++ b/idea/idea-fir/src/org/jetbrains/kotlin/idea/completion/context/FirPositionCompletionContext.kt @@ -0,0 +1,67 @@ +/* + * 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.context + +import com.intellij.psi.PsiElement +import org.jetbrains.kotlin.idea.frontend.api.KtAnalysisSession +import org.jetbrains.kotlin.idea.frontend.api.analyseInFakeAnalysisSession +import org.jetbrains.kotlin.idea.references.KtSimpleNameReference +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.psiUtil.getReceiverExpression +import kotlin.contracts.ExperimentalContracts +import kotlin.contracts.InvocationKind +import kotlin.contracts.contract + +internal sealed class FirPositionCompletionContext { + abstract val position: PsiElement +} + +internal class FirNameReferencePositionContext( + override val position: PsiElement, + val reference: KtSimpleNameReference, + val nameExpression: KtSimpleNameExpression, + val explicitReceiver: KtExpression? +) : FirPositionCompletionContext() + +internal class FirUnknownPositionContext( + override val position: PsiElement +) : FirPositionCompletionContext() + +internal object FirPositionCompletionContextDetector { + fun detect(basicContext: FirBasicCompletionContext): FirPositionCompletionContext { + val position = basicContext.parameters.position + + return when (val reference = (position.parent as? KtSimpleNameExpression)?.mainReference) { + null -> FirUnknownPositionContext(position) + else -> { + val nameExpression = reference.expression.takeIf { it !is KtLabelReferenceExpression } + ?: return FirUnknownPositionContext(position) + val explicitReceiver = nameExpression.getReceiverExpression() + FirNameReferencePositionContext(position, reference, nameExpression, explicitReceiver) + } + } + } + + inline fun analyseInContext( + basicContext: FirBasicCompletionContext, + positionContext: FirPositionCompletionContext, + action: KtAnalysisSession.() -> Unit + ) { + return when (positionContext) { + is FirNameReferencePositionContext -> analyseInFakeAnalysisSession( + basicContext.originalKtFile, + positionContext.nameExpression, + action + ) + is FirUnknownPositionContext -> { + // TODO + } + } + } +} \ No newline at end of file