FIR IDE: Correctly handle properties in KtFirCompletionCandidateChecker

- Introduce `EnclosingDeclarationContext` to remove code duplication
This commit is contained in:
Roman Golyshev
2020-09-07 19:52:38 +03:00
parent 6fd871d0ba
commit ae8933dbd2
3 changed files with 92 additions and 63 deletions
@@ -20,12 +20,16 @@ import org.jetbrains.kotlin.idea.frontend.api.fir.KtFirAnalysisSession
import org.jetbrains.kotlin.idea.frontend.api.fir.symbols.KtFirFunctionSymbol
import org.jetbrains.kotlin.idea.frontend.api.fir.symbols.KtFirPropertySymbol
import org.jetbrains.kotlin.idea.frontend.api.fir.symbols.KtFirSymbol
import org.jetbrains.kotlin.idea.frontend.api.fir.utils.EnclosingDeclarationContext
import org.jetbrains.kotlin.idea.frontend.api.fir.utils.buildCompletionContext
import org.jetbrains.kotlin.idea.frontend.api.fir.utils.fakeEnclosingDeclaration
import org.jetbrains.kotlin.idea.frontend.api.fir.utils.weakRef
import org.jetbrains.kotlin.idea.frontend.api.symbols.KtCallableSymbol
import org.jetbrains.kotlin.idea.frontend.api.withValidityAssertion
import org.jetbrains.kotlin.idea.util.getElementTextInContext
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.getNonStrictParentOfType
import org.jetbrains.kotlin.psi.KtCallableDeclaration
import org.jetbrains.kotlin.psi.KtExpression
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.psi.KtSimpleNameExpression
import org.jetbrains.kotlin.utils.addToStdlib.safeAs
internal class KtFirCompletionCandidateChecker(
@@ -34,7 +38,7 @@ internal class KtFirCompletionCandidateChecker(
) : KtCompletionCandidateChecker(), KtFirAnalysisSessionComponent {
override val analysisSession: KtFirAnalysisSession by weakRef(analysisSession)
private val completionContextCache = HashMap<Pair<FirFile, KtNamedFunction>, LowLevelFirApiFacade.FirCompletionContext>()
private val completionContextCache = HashMap<Pair<FirFile, KtCallableDeclaration>, LowLevelFirApiFacade.FirCompletionContext>()
override fun checkExtensionFitsCandidate(
firSymbolForCandidate: KtCallableSymbol,
@@ -86,17 +90,10 @@ internal class KtFirCompletionCandidateChecker(
firFile: FirFile,
fakeNameExpression: KtSimpleNameExpression
): Sequence<ImplicitReceiverValue<*>?> {
val fakeEnclosingFunction = fakeNameExpression.getNonStrictParentOfType<KtNamedFunction>()
?: error("Cannot find enclosing function for ${fakeNameExpression.getElementTextInContext()}")
val originalEnclosingFunction = originalFile.findFunctionDeclarationAt(fakeEnclosingFunction.textOffset)
?: error("Cannot find enclosing function for completion in provided position (or position is absent)")
val completionContext = completionContextCache.getOrCreate(firFile to fakeEnclosingFunction) {
LowLevelFirApiFacade.buildCompletionContextForFunction(
firFile,
fakeEnclosingFunction,
originalEnclosingFunction,
state = firResolveState
)
val enclosingContext = EnclosingDeclarationContext.detect(originalFile, fakeNameExpression)
val completionContext = completionContextCache.getOrCreate(firFile to enclosingContext.fakeEnclosingDeclaration) {
enclosingContext.buildCompletionContext(firFile, firResolveState)
}
val towerDataContext = completionContext.getTowerDataContext(fakeNameExpression)
@@ -106,9 +103,4 @@ internal class KtFirCompletionCandidateChecker(
yieldAll(towerDataContext.implicitReceiverStack)
}
}
private fun KtFile.findFunctionDeclarationAt(offset: Int): KtNamedFunction? =
findElementAt(offset)
?.getNonStrictParentOfType<KtNamedFunction>()
?.takeIf { it.textOffset == offset }
}
@@ -6,7 +6,6 @@
package org.jetbrains.kotlin.idea.frontend.api.fir.components
import com.intellij.openapi.project.Project
import com.intellij.psi.util.parentsOfType
import org.jetbrains.kotlin.fir.declarations.FirFile
import org.jetbrains.kotlin.fir.declarations.FirResolvePhase
import org.jetbrains.kotlin.fir.resolve.ScopeSession
@@ -26,26 +25,18 @@ import org.jetbrains.kotlin.idea.frontend.api.components.KtScopeProvider
import org.jetbrains.kotlin.idea.frontend.api.fir.KtFirAnalysisSession
import org.jetbrains.kotlin.idea.frontend.api.fir.KtSymbolByFirBuilder
import org.jetbrains.kotlin.idea.frontend.api.fir.scopes.*
import org.jetbrains.kotlin.idea.frontend.api.fir.scopes.KtFirDeclaredMemberScope
import org.jetbrains.kotlin.idea.frontend.api.fir.scopes.KtFirDelegatingScope
import org.jetbrains.kotlin.idea.frontend.api.fir.scopes.KtFirMemberScope
import org.jetbrains.kotlin.idea.frontend.api.fir.scopes.KtFirNonStarImportingScope
import org.jetbrains.kotlin.idea.frontend.api.fir.scopes.KtFirPackageScope
import org.jetbrains.kotlin.idea.frontend.api.fir.scopes.KtFirStarImportingScope
import org.jetbrains.kotlin.idea.frontend.api.fir.symbols.KtFirClassOrObjectSymbol
import org.jetbrains.kotlin.idea.frontend.api.fir.types.KtFirType
import org.jetbrains.kotlin.idea.frontend.api.fir.utils.EnclosingDeclarationContext
import org.jetbrains.kotlin.idea.frontend.api.fir.utils.buildCompletionContext
import org.jetbrains.kotlin.idea.frontend.api.fir.utils.weakRef
import org.jetbrains.kotlin.idea.frontend.api.scopes.*
import org.jetbrains.kotlin.idea.frontend.api.symbols.KtClassOrObjectSymbol
import org.jetbrains.kotlin.idea.frontend.api.symbols.KtPackageSymbol
import org.jetbrains.kotlin.idea.frontend.api.types.KtType
import org.jetbrains.kotlin.idea.frontend.api.withValidityAssertion
import org.jetbrains.kotlin.idea.util.getElementTextInContext
import org.jetbrains.kotlin.psi.KtElement
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.psi.KtNamedFunction
import org.jetbrains.kotlin.psi.KtProperty
import org.jetbrains.kotlin.psi.psiUtil.getNonStrictParentOfType
import java.util.*
internal class KtFirScopeProvider(
@@ -138,41 +129,11 @@ internal class KtFirScopeProvider(
positionInFakeFile: KtElement
): LowLevelFirApiFacade.FirCompletionContext {
val originalFirFile = originalFile.getOrBuildFirOfType<FirFile>(firResolveState)
val fakeEnclosingFunction = positionInFakeFile.getNonStrictParentOfType<KtNamedFunction>()
val declarationContext = EnclosingDeclarationContext.detect(originalFile, positionInFakeFile)
if (fakeEnclosingFunction != null) {
val originalEnclosingFunction = originalFile.findDeclarationOfTypeAt<KtNamedFunction>(fakeEnclosingFunction.textOffset)
?: error("Cannot find original function matching to ${fakeEnclosingFunction.getElementTextInContext()} in $originalFile")
return LowLevelFirApiFacade.buildCompletionContextForFunction(
originalFirFile,
fakeEnclosingFunction,
originalEnclosingFunction,
state = firResolveState
)
}
val fakeEnclosingProperty = positionInFakeFile.parentsOfType<KtProperty>().firstOrNull { !it.isLocal }
if (fakeEnclosingProperty != null) {
val originalEnclosingProperty = originalFile.findDeclarationOfTypeAt<KtProperty>(fakeEnclosingProperty.textOffset)
?: error("Cannot find original property matching to ${fakeEnclosingProperty.getElementTextInContext()} in $originalFile")
return LowLevelFirApiFacade.buildCompletionContextForProperty(
originalFirFile,
fakeEnclosingProperty,
originalEnclosingProperty,
state = firResolveState
)
}
error("Cannot find enclosing declaration for ${positionInFakeFile.getElementTextInContext()}")
return declarationContext.buildCompletionContext(originalFirFile, firResolveState)
}
private inline fun<reified T: KtElement> KtFile.findDeclarationOfTypeAt(offset: Int): T? =
findElementAt(offset)
?.getNonStrictParentOfType<T>()
?.takeIf { it.textOffset == offset }
private fun convertToKtScope(firScope: FirScope): KtScope {
firScopeStorage.register(firScope)
return when (firScope) {
@@ -0,0 +1,76 @@
/*
* 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.utils
import com.intellij.psi.util.parentsOfType
import org.jetbrains.kotlin.fir.declarations.FirFile
import org.jetbrains.kotlin.idea.fir.low.level.api.FirModuleResolveState
import org.jetbrains.kotlin.idea.fir.low.level.api.LowLevelFirApiFacade
import org.jetbrains.kotlin.idea.util.getElementTextInContext
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.getNonStrictParentOfType
internal sealed class EnclosingDeclarationContext {
companion object {
fun detect(originalFile: KtFile, positionInFakeFile: KtElement): EnclosingDeclarationContext {
val fakeFunction = positionInFakeFile.getNonStrictParentOfType<KtNamedFunction>()
if (fakeFunction != null) {
val originalFunction = originalFile.findDeclarationOfTypeAt<KtNamedFunction>(fakeFunction.textOffset)
?: error("Cannot find original function matching to ${fakeFunction.getElementTextInContext()} in $originalFile")
return FunctionContext(fakeFunction, originalFunction)
}
val fakeProperty = positionInFakeFile.parentsOfType<KtProperty>().firstOrNull { !it.isLocal }
if (fakeProperty != null) {
val originalProperty = originalFile.findDeclarationOfTypeAt<KtProperty>(fakeProperty.textOffset)
?: error("Cannot find original property matching to ${fakeProperty.getElementTextInContext()} in $originalFile")
return PropertyContext(fakeProperty, originalProperty)
}
error("Cannot find enclosing declaration for ${positionInFakeFile.getElementTextInContext()}")
}
}
}
internal class FunctionContext(
val fakeEnclosingFunction: KtNamedFunction,
val originalEnclosingFunction: KtNamedFunction
) : EnclosingDeclarationContext()
internal class PropertyContext(
val fakeEnclosingProperty: KtProperty,
val originalEnclosingProperty: KtProperty
) : EnclosingDeclarationContext()
internal val EnclosingDeclarationContext.fakeEnclosingDeclaration: KtCallableDeclaration
get() = when (this) {
is FunctionContext -> fakeEnclosingFunction
is PropertyContext -> fakeEnclosingProperty
}
internal fun EnclosingDeclarationContext.buildCompletionContext(originalFirFile: FirFile, firResolveState: FirModuleResolveState) =
when (this) {
is FunctionContext -> LowLevelFirApiFacade.buildCompletionContextForFunction(
originalFirFile,
fakeEnclosingFunction,
originalEnclosingFunction,
state = firResolveState
)
is PropertyContext -> LowLevelFirApiFacade.buildCompletionContextForProperty(
originalFirFile,
fakeEnclosingProperty,
originalEnclosingProperty,
state = firResolveState
)
}
private inline fun <reified T : KtElement> KtFile.findDeclarationOfTypeAt(offset: Int): T? =
findElementAt(offset)
?.getNonStrictParentOfType<T>()
?.takeIf { it.textOffset == offset }