[FIR-IDE] Changes after review

Move usage of SingleCandidateResolver into dedicated component from KtFirAnalysisSession.
Add validity assertion, use concurrent map for context cache, provide original PSI when building context.
Update names, make other minor changes.
This commit is contained in:
Pavel Kirpichenkov
2020-08-21 19:02:46 +03:00
parent ec072798b1
commit dc538d420e
7 changed files with 193 additions and 90 deletions
@@ -8,6 +8,7 @@ package org.jetbrains.kotlin.idea.completion
import com.intellij.codeInsight.completion.* import com.intellij.codeInsight.completion.*
import com.intellij.patterns.PlatformPatterns import com.intellij.patterns.PlatformPatterns
import com.intellij.patterns.PsiJavaPatterns import com.intellij.patterns.PsiJavaPatterns
import com.intellij.psi.PsiElement
import com.intellij.util.ProcessingContext import com.intellij.util.ProcessingContext
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
@@ -79,12 +80,19 @@ private object KotlinAvailableScopesCompletionProvider {
val explicitReceiver = nameExpression.getQualifiedExpressionForSelector()?.receiverExpression val explicitReceiver = nameExpression.getQualifiedExpressionForSelector()?.receiverExpression
with(getAnalysisSessionFor(originalFile).createContextDependentCopy()) { with(getAnalysisSessionFor(originalFile).createContextDependentCopy()) {
val (implicitScopes, implicitReceivers) = originalFile.getScopeContextForPosition(nameExpression) val (implicitScopes, _) = originalFile.getScopeContextForPosition(nameExpression)
when { when {
nameExpression.parent is KtUserType -> collectTypesCompletion(result, implicitScopes) nameExpression.parent is KtUserType -> collectTypesCompletion(result, implicitScopes)
explicitReceiver != null -> collectDotCompletion(result, implicitScopes, explicitReceiver) explicitReceiver != null -> collectDotCompletion(
else -> collectDefaultCompletion(result, implicitScopes, implicitReceivers) result,
implicitScopes,
explicitReceiver,
nameExpression,
originalFile,
parameters.originalPosition
)
else -> collectDefaultCompletion(result, implicitScopes, nameExpression, originalFile, parameters.originalPosition)
} }
} }
} }
@@ -97,7 +105,10 @@ private object KotlinAvailableScopesCompletionProvider {
private fun KtAnalysisSession.collectDotCompletion( private fun KtAnalysisSession.collectDotCompletion(
result: CompletionResultSet, result: CompletionResultSet,
implicitScopes: KtCompositeScope, implicitScopes: KtCompositeScope,
explicitReceiver: KtExpression explicitReceiver: KtExpression,
nameExpression: KtSimpleNameExpression,
originalFile: KtFile,
originalPosition: PsiElement?,
) { ) {
val typeOfPossibleReceiver = explicitReceiver.getKtType() val typeOfPossibleReceiver = explicitReceiver.getKtType()
val possibleReceiverScope = typeOfPossibleReceiver.getTypeScope() ?: return val possibleReceiverScope = typeOfPossibleReceiver.getTypeScope() ?: return
@@ -108,16 +119,25 @@ private object KotlinAvailableScopesCompletionProvider {
val extensionNonMembers = implicitScopes val extensionNonMembers = implicitScopes
.getCallableSymbols() .getCallableSymbols()
.filter { it.isExtension && it.canBeCalledWith(listOf(typeOfPossibleReceiver)) } .filter {
it.isExtension && it.checkExtensionIsSuitable(
originalFile,
originalPosition,
nameExpression,
explicitReceiver
)
}
nonExtensionMembers.forEach { result.addSymbolToCompletion(it) } nonExtensionMembers.forEach { result.addSymbolToCompletion(it) }
extensionNonMembers.forEach { result.addSymbolToCompletion(it) } extensionNonMembers.forEach { result.addSymbolToCompletion(it) }
} }
private fun collectDefaultCompletion( private fun KtAnalysisSession.collectDefaultCompletion(
result: CompletionResultSet, result: CompletionResultSet,
implicitScopes: KtCompositeScope, implicitScopes: KtCompositeScope,
implicitReceivers: List<KtType> nameExpression: KtSimpleNameExpression,
originalFile: KtFile,
originalPosition: PsiElement?,
) { ) {
val availableNonExtensions = implicitScopes val availableNonExtensions = implicitScopes
.getCallableSymbols() .getCallableSymbols()
@@ -125,7 +145,14 @@ private object KotlinAvailableScopesCompletionProvider {
val extensionsWhichCanBeCalled = implicitScopes val extensionsWhichCanBeCalled = implicitScopes
.getCallableSymbols() .getCallableSymbols()
.filter { it.isExtension && it.canBeCalledWith(implicitReceivers) } .filter {
it.isExtension && it.checkExtensionIsSuitable(
originalFile,
originalPosition,
nameExpression,
null
)
}
availableNonExtensions.forEach { result.addSymbolToCompletion(it) } availableNonExtensions.forEach { result.addSymbolToCompletion(it) }
extensionsWhichCanBeCalled.forEach { result.addSymbolToCompletion(it) } extensionsWhichCanBeCalled.forEach { result.addSymbolToCompletion(it) }
@@ -133,10 +160,3 @@ private object KotlinAvailableScopesCompletionProvider {
collectTypesCompletion(result, implicitScopes) collectTypesCompletion(result, implicitScopes)
} }
} }
private fun KtCallableSymbol.canBeCalledWith(implicitReceivers: List<KtType>): Boolean {
val requiredReceiverType = (this as? KtPossibleExtensionSymbol)?.receiverType
?: error("Extension receiver type should be present on $this")
return implicitReceivers.any { it.isSubTypeOf(requiredReceiverType) }
}
@@ -40,6 +40,7 @@ abstract class KtAnalysisSession(override val token: ValidityToken) : ValidityTo
protected abstract val containingDeclarationProvider: KtSymbolContainingDeclarationProvider protected abstract val containingDeclarationProvider: KtSymbolContainingDeclarationProvider
protected abstract val symbolProvider: KtSymbolProvider protected abstract val symbolProvider: KtSymbolProvider
protected abstract val callResolver: KtCallResolver protected abstract val callResolver: KtCallResolver
protected abstract val completionCandidateChecker: KtCompletionCandidateChecker
/// TODO: get rid of /// TODO: get rid of
@@ -119,10 +120,16 @@ abstract class KtAnalysisSession(override val token: ValidityToken) : ValidityTo
return resolveToSymbols().singleOrNull() return resolveToSymbols().singleOrNull()
} }
abstract fun resolveAndCheckReceivers( fun KtCallableSymbol.checkExtensionIsSuitable(
firSymbolForCandidate: KtCallableSymbol, originalPsiFile: KtFile,
originalFile: KtFile, originalPosition: PsiElement?,
nameExpression: KtSimpleNameExpression, psiFakeCompletionExpression: KtSimpleNameExpression,
possibleReceiver: KtExpression?, psiReceiverExpression: KtExpression?,
): Boolean ): Boolean = completionCandidateChecker.checkExtensionFitsCandidate(
this,
originalPsiFile,
originalPosition,
psiFakeCompletionExpression,
psiReceiverExpression
)
} }
@@ -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 com.intellij.psi.PsiElement
import org.jetbrains.kotlin.idea.frontend.api.symbols.KtCallableSymbol
import org.jetbrains.kotlin.psi.KtExpression
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.psi.KtSimpleNameExpression
abstract class KtCompletionCandidateChecker : KtAnalysisSessionComponent() {
abstract fun checkExtensionFitsCandidate(
firSymbolForCandidate: KtCallableSymbol,
originalFile: KtFile,
originalPosition: PsiElement?,
nameExpression: KtSimpleNameExpression,
possibleExplicitReceiver: KtExpression?,
): Boolean
}
@@ -49,14 +49,14 @@ class SingleCandidateResolver(
resolutionParameters: ResolutionParameters resolutionParameters: ResolutionParameters
): FirFunctionCall? { ): FirFunctionCall? {
val partProvider = createCandidatePartsProvider(resolutionParameters) val infoProvider = createCandidateInfoProvider(resolutionParameters)
if (partProvider.shouldFailBeforeResolve()) if (infoProvider.shouldFailBeforeResolve())
return null return null
val callInfo = partProvider.callInfo() val callInfo = infoProvider.callInfo()
val explicitReceiverKind = partProvider.explicitReceiverKind() val explicitReceiverKind = infoProvider.explicitReceiverKind()
val dispatchReceiverValue = partProvider.dispatchReceiverValue() val dispatchReceiverValue = infoProvider.dispatchReceiverValue()
val implicitExtensionReceiverValue = partProvider.implicitExtensionReceiverValue() val implicitExtensionReceiverValue = infoProvider.implicitExtensionReceiverValue()
val candidate = CandidateFactory(bodyResolveComponents, callInfo).createCandidate( val candidate = CandidateFactory(bodyResolveComponents, callInfo).createCandidate(
resolutionParameters.callableSymbol, resolutionParameters.callableSymbol,
@@ -72,7 +72,7 @@ class SingleCandidateResolver(
return null return null
} }
private fun createCandidatePartsProvider(resolutionParameters: ResolutionParameters): CandidateInfoProvider { private fun createCandidateInfoProvider(resolutionParameters: ResolutionParameters): CandidateInfoProvider {
return when (resolutionParameters.singleCandidateResolutionMode) { return when (resolutionParameters.singleCandidateResolutionMode) {
SingleCandidateResolutionMode.CHECK_EXTENSION_FOR_COMPLETION -> CheckExtensionForCompletionCandidateInfoProvider( SingleCandidateResolutionMode.CHECK_EXTENSION_FOR_COMPLETION -> CheckExtensionForCompletionCandidateInfoProvider(
resolutionParameters, resolutionParameters,
@@ -35,7 +35,7 @@ abstract class AbstractCandidateInfoProvider(
protected val firSession: FirSession, protected val firSession: FirSession,
) : CandidateInfoProvider { ) : CandidateInfoProvider {
override fun callInfo(): CallInfo = with(resolutionParameters) { override fun callInfo(): CallInfo = with(resolutionParameters) {
return CallInfo( CallInfo(
callKind = callKind(), callKind = callKind(),
name = callableSymbol.callableId.callableName, name = callableSymbol.callableId.callableName,
explicitReceiver = explicitReceiver, explicitReceiver = explicitReceiver,
@@ -81,7 +81,7 @@ class CheckExtensionForCompletionCandidateInfoProvider(
// Passing them through can lead to false positives. // Passing them through can lead to false positives.
override fun shouldFailBeforeResolve(): Boolean = with(resolutionParameters) { override fun shouldFailBeforeResolve(): Boolean = with(resolutionParameters) {
val callHasExtensionReceiver = explicitReceiverKind() == ExplicitReceiverKind.EXTENSION_RECEIVER val callHasExtensionReceiver = explicitReceiverKind() == ExplicitReceiverKind.EXTENSION_RECEIVER
|| implicitExtensionReceiverValue() != null || implicitExtensionReceiverValue() != null
val candidateHasExtensionReceiver = callableSymbol.fir.receiverTypeRef != null val candidateHasExtensionReceiver = callableSymbol.fir.receiverTypeRef != null
callHasExtensionReceiver != candidateHasExtensionReceiver callHasExtensionReceiver != candidateHasExtensionReceiver
} }
@@ -5,9 +5,6 @@
package org.jetbrains.kotlin.idea.frontend.api.fir package org.jetbrains.kotlin.idea.frontend.api.fir
import com.jetbrains.rd.util.getOrCreate
import org.jetbrains.kotlin.fir.declarations.*
import org.jetbrains.kotlin.fir.expressions.*
import org.jetbrains.kotlin.fir.resolve.firSymbolProvider import org.jetbrains.kotlin.fir.resolve.firSymbolProvider
import org.jetbrains.kotlin.idea.fir.low.level.api.FirModuleResolveState 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.fir.low.level.api.LowLevelFirApiFacade
@@ -20,20 +17,10 @@ import org.jetbrains.kotlin.idea.frontend.api.components.KtDiagnosticProvider
import org.jetbrains.kotlin.idea.frontend.api.components.KtSmartCastProvider import org.jetbrains.kotlin.idea.frontend.api.components.KtSmartCastProvider
import org.jetbrains.kotlin.idea.frontend.api.components.KtTypeProvider import org.jetbrains.kotlin.idea.frontend.api.components.KtTypeProvider
import org.jetbrains.kotlin.idea.frontend.api.fir.components.* import org.jetbrains.kotlin.idea.frontend.api.fir.components.*
import org.jetbrains.kotlin.idea.fir.*
import org.jetbrains.kotlin.idea.fir.low.level.api.resolver.SingleCandidateResolver
import org.jetbrains.kotlin.idea.fir.low.level.api.resolver.SingleCandidateResolutionMode
import org.jetbrains.kotlin.idea.fir.low.level.api.resolver.ResolutionParameters
import org.jetbrains.kotlin.idea.frontend.api.fir.symbols.KtFirFunctionSymbol
import org.jetbrains.kotlin.idea.frontend.api.fir.symbols.KtFirSymbolProvider import org.jetbrains.kotlin.idea.frontend.api.fir.symbols.KtFirSymbolProvider
import org.jetbrains.kotlin.idea.frontend.api.fir.utils.threadLocal import org.jetbrains.kotlin.idea.frontend.api.fir.utils.threadLocal
import org.jetbrains.kotlin.idea.frontend.api.symbols.KtSymbolProvider import org.jetbrains.kotlin.idea.frontend.api.symbols.KtSymbolProvider
import org.jetbrains.kotlin.psi.KtElement import org.jetbrains.kotlin.psi.KtElement
import org.jetbrains.kotlin.idea.frontend.api.symbols.*
import org.jetbrains.kotlin.idea.util.getElementTextInContext
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.getNonStrictParentOfType
import org.jetbrains.kotlin.utils.addToStdlib.safeAs
internal class KtFirAnalysisSession internal class KtFirAnalysisSession
private constructor( private constructor(
@@ -57,6 +44,7 @@ private constructor(
override val scopeProvider by threadLocal { KtFirScopeProvider(this, firSymbolBuilder, project, firResolveState, token) } override val scopeProvider by threadLocal { KtFirScopeProvider(this, firSymbolBuilder, project, firResolveState, token) }
override val symbolProvider: KtSymbolProvider = override val symbolProvider: KtSymbolProvider =
KtFirSymbolProvider(this, firResolveState.firIdeLibrariesSession.firSymbolProvider, firResolveState, firSymbolBuilder, token) KtFirSymbolProvider(this, firResolveState.firIdeLibrariesSession.firSymbolProvider, firResolveState, firSymbolBuilder, token)
override val completionCandidateChecker: KtCompletionCandidateChecker = KtFirCompletionCandidateChecker(this, token)
override fun createContextDependentCopy(): KtAnalysisSession { override fun createContextDependentCopy(): KtAnalysisSession {
@@ -71,53 +59,6 @@ private constructor(
) )
} }
private val completionContextCache = HashMap<Pair<FirFile, KtNamedFunction>, LowLevelFirApiFacade.FirCompletionContext>()
// Temporary kludge to check single candidate resolver in tests
override fun resolveAndCheckReceivers(
firSymbolForCandidate: KtCallableSymbol,
originalFile: KtFile,
nameExpression: KtSimpleNameExpression,
possibleReceiver: KtExpression?,
): Boolean {
firSymbolForCandidate.safeAs<KtFirFunctionSymbol>()?.firRef?.withFir { firFunction ->
val file = originalFile.getOrBuildFirOfType<FirFile>(firResolveState)
val explicitReceiverExpression = possibleReceiver?.getOrBuildFirOfType<FirExpression>(firResolveState)
val resolver = SingleCandidateResolver(firResolveState.firIdeSourcesSession, file)
val enclosingFunction = nameExpression.getNonStrictParentOfType<KtNamedFunction>()
?: error("Cannot find enclosing function for ${nameExpression.getElementTextInContext()}")
val completionContext = completionContextCache.getOrCreate(file to enclosingFunction) {
LowLevelFirApiFacade.buildCompletionContextForFunction(
file,
enclosingFunction,
enclosingFunction,
state = firResolveState
)
}
val towerDataContext = completionContext.getTowerDataContext(nameExpression)
val implicitReceivers = sequence {
yield(null)
yieldAll(towerDataContext.implicitReceiverStack)
}
for (implicitReceiverValue in implicitReceivers) {
val resolutionParameters = ResolutionParameters(
singleCandidateResolutionMode = SingleCandidateResolutionMode.CHECK_EXTENSION_FOR_COMPLETION,
callableSymbol = firFunction.symbol,
implicitReceiver = implicitReceiverValue,
explicitReceiver = explicitReceiverExpression
)
resolver.resolveSingleCandidate(resolutionParameters)?.let {
// not null if resolved and completed successfully
return true
}
}
}
return false
}
companion object { companion object {
@Deprecated("Please use org.jetbrains.kotlin.idea.frontend.api.KtAnalysisSessionProviderKt.analyze") @Deprecated("Please use org.jetbrains.kotlin.idea.frontend.api.KtAnalysisSessionProviderKt.analyze")
internal fun createForElement(element: KtElement): KtFirAnalysisSession { internal fun createForElement(element: KtElement): KtFirAnalysisSession {
@@ -0,0 +1,113 @@
/*
* 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 com.intellij.psi.PsiElement
import com.jetbrains.rd.util.getOrCreate
import org.jetbrains.kotlin.fir.declarations.*
import org.jetbrains.kotlin.fir.expressions.FirExpression
import org.jetbrains.kotlin.fir.resolve.calls.ImplicitReceiverValue
import org.jetbrains.kotlin.idea.fir.getOrBuildFirOfType
import org.jetbrains.kotlin.idea.fir.low.level.api.LowLevelFirApiFacade
import org.jetbrains.kotlin.idea.fir.low.level.api.resolver.ResolutionParameters
import org.jetbrains.kotlin.idea.fir.low.level.api.resolver.SingleCandidateResolutionMode
import org.jetbrains.kotlin.idea.fir.low.level.api.resolver.SingleCandidateResolver
import org.jetbrains.kotlin.idea.frontend.api.ValidityToken
import org.jetbrains.kotlin.idea.frontend.api.components.KtCompletionCandidateChecker
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.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.utils.addToStdlib.safeAs
import java.util.concurrent.ConcurrentHashMap
internal class KtFirCompletionCandidateChecker(
override val analysisSession: KtFirAnalysisSession,
override val token: ValidityToken,
) : KtCompletionCandidateChecker(), KtFirAnalysisSessionComponent {
private val completionContextCache = ConcurrentHashMap<Pair<FirFile, KtNamedFunction>, LowLevelFirApiFacade.FirCompletionContext>()
override fun checkExtensionFitsCandidate(
firSymbolForCandidate: KtCallableSymbol,
originalFile: KtFile,
originalPosition: PsiElement?,
nameExpression: KtSimpleNameExpression,
possibleExplicitReceiver: KtExpression?,
): Boolean = withValidityAssertion {
val originalEnclosingFunction = originalPosition?.getNonStrictParentOfType<KtNamedFunction>()
?: error("Cannot find enclosing function for completion in provided position (or position is absent)")
val functionFits = firSymbolForCandidate.withResolvedFirOfType<KtFirFunctionSymbol, FirSimpleFunction, Boolean> { firFunction ->
checkExtension(firFunction, originalFile, originalEnclosingFunction, nameExpression, possibleExplicitReceiver)
}
val propertyFits = firSymbolForCandidate.withResolvedFirOfType<KtFirPropertySymbol, FirProperty, Boolean> { firProperty ->
checkExtension(firProperty, originalFile, originalEnclosingFunction, nameExpression, possibleExplicitReceiver)
}
functionFits ?: propertyFits ?: false
}
private inline fun <reified T : KtFirSymbol<F>, F : FirDeclaration, R> KtCallableSymbol.withResolvedFirOfType(
noinline action: (F) -> R,
): R? = this.safeAs<T>()?.firRef?.withFir(FirResolvePhase.BODY_RESOLVE, action)
private fun checkExtension(
candidateSymbol: FirCallableDeclaration<*>,
originalFile: KtFile,
originalEnclosingFunction: KtNamedFunction,
nameExpression: KtSimpleNameExpression,
possibleExplicitReceiver: KtExpression?,
): Boolean {
val file = originalFile.getOrBuildFirOfType<FirFile>(firResolveState)
val explicitReceiverExpression = possibleExplicitReceiver?.getOrBuildFirOfType<FirExpression>(firResolveState)
val resolver = SingleCandidateResolver(firResolveState.firIdeSourcesSession, file)
val implicitReceivers = getImplicitReceivers(file, nameExpression, originalEnclosingFunction)
for (implicitReceiverValue in implicitReceivers) {
val resolutionParameters = ResolutionParameters(
singleCandidateResolutionMode = SingleCandidateResolutionMode.CHECK_EXTENSION_FOR_COMPLETION,
callableSymbol = candidateSymbol.symbol,
implicitReceiver = implicitReceiverValue,
explicitReceiver = explicitReceiverExpression
)
resolver.resolveSingleCandidate(resolutionParameters)?.let {
// not null if resolved and completed successfully
return true
}
}
return false
}
private fun getImplicitReceivers(
file: FirFile,
fakeNameExpression: KtElement,
originalEnclosingFunction: KtNamedFunction,
): Sequence<ImplicitReceiverValue<*>?> {
val fakeEnclosingFunction = fakeNameExpression.getNonStrictParentOfType<KtNamedFunction>()
?: error("Cannot find enclosing function for ${fakeNameExpression.getElementTextInContext()}")
val completionContext = completionContextCache.getOrCreate(file to fakeEnclosingFunction) {
LowLevelFirApiFacade.buildCompletionContextForFunction(
file,
fakeEnclosingFunction,
originalEnclosingFunction,
state = firResolveState
)
}
val towerDataContext = completionContext.getTowerDataContext(fakeNameExpression)
return sequence {
yield(null) // otherwise explicit receiver won't be checked when there are no implicit receivers in completion position
yieldAll(towerDataContext.implicitReceiverStack)
}
}
}