[FIR-IDE] Introduce resolver for a single candidate
The purpose of this resolver is to reuse parts of tower resolver in IDE tasks. For now it can run resolution stages to check suitability of extension receiver.
This commit is contained in:
+118
@@ -0,0 +1,118 @@
|
||||
/*
|
||||
* 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.fir.low.level.api.resolver
|
||||
|
||||
import org.jetbrains.kotlin.fir.FirSession
|
||||
import org.jetbrains.kotlin.fir.declarations.FirFile
|
||||
import org.jetbrains.kotlin.fir.declarations.FirResolvePhase
|
||||
import org.jetbrains.kotlin.fir.expressions.*
|
||||
import org.jetbrains.kotlin.fir.expressions.builder.buildFunctionCall
|
||||
import org.jetbrains.kotlin.fir.resolve.ScopeSession
|
||||
import org.jetbrains.kotlin.fir.resolve.calls.*
|
||||
import org.jetbrains.kotlin.fir.resolve.inference.FirCallCompleter
|
||||
import org.jetbrains.kotlin.fir.resolve.transformers.body.resolve.FirAbstractBodyResolveTransformer
|
||||
import org.jetbrains.kotlin.fir.resolve.transformers.body.resolve.FirBodyResolveTransformer
|
||||
import org.jetbrains.kotlin.fir.symbols.impl.FirCallableSymbol
|
||||
import org.jetbrains.kotlin.fir.types.FirTypeProjection
|
||||
import org.jetbrains.kotlin.fir.types.FirTypeRef
|
||||
|
||||
class SingleCandidateResolver(
|
||||
private val firSession: FirSession,
|
||||
private val firFile: FirFile,
|
||||
) {
|
||||
private val scopeSession = ScopeSession()
|
||||
|
||||
// TODO This transformer is not intended for actual transformations and created here only to simplify access to body resolve components
|
||||
private val stubBodyResolveTransformer = object : FirBodyResolveTransformer(
|
||||
session = firSession,
|
||||
phase = FirResolvePhase.BODY_RESOLVE,
|
||||
implicitTypeOnly = false,
|
||||
scopeSession = scopeSession,
|
||||
) {}
|
||||
private val bodyResolveComponents =
|
||||
FirAbstractBodyResolveTransformer.BodyResolveTransformerComponents(
|
||||
firSession,
|
||||
scopeSession,
|
||||
stubBodyResolveTransformer,
|
||||
stubBodyResolveTransformer.context,
|
||||
)
|
||||
private val firCallCompleter = FirCallCompleter(
|
||||
stubBodyResolveTransformer,
|
||||
bodyResolveComponents,
|
||||
)
|
||||
private val resolutionStageRunner = ResolutionStageRunner(bodyResolveComponents.inferenceComponents)
|
||||
|
||||
fun resolveSingleCandidate(
|
||||
resolutionParameters: ResolutionParameters
|
||||
): FirFunctionCall? {
|
||||
|
||||
val partProvider = createCandidatePartsProvider(resolutionParameters)
|
||||
if (partProvider.shouldFailBeforeResolve())
|
||||
return null
|
||||
|
||||
val callInfo = partProvider.callInfo()
|
||||
val explicitReceiverKind = partProvider.explicitReceiverKind()
|
||||
val dispatchReceiverValue = partProvider.dispatchReceiverValue()
|
||||
val implicitExtensionReceiverValue = partProvider.implicitExtensionReceiverValue()
|
||||
|
||||
val candidate = CandidateFactory(bodyResolveComponents, callInfo).createCandidate(
|
||||
resolutionParameters.callableSymbol,
|
||||
explicitReceiverKind = explicitReceiverKind,
|
||||
dispatchReceiverValue = dispatchReceiverValue,
|
||||
implicitExtensionReceiverValue = implicitExtensionReceiverValue,
|
||||
)
|
||||
|
||||
val applicability = resolutionStageRunner.processCandidate(candidate, stopOnFirstError = true)
|
||||
if (applicability >= CandidateApplicability.SYNTHETIC_RESOLVED) {
|
||||
return completeResolvedCandidate(candidate, resolutionParameters)
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
private fun createCandidatePartsProvider(resolutionParameters: ResolutionParameters): CandidateInfoProvider {
|
||||
return when (resolutionParameters.singleCandidateResolutionMode) {
|
||||
SingleCandidateResolutionMode.CHECK_EXTENSION_FOR_COMPLETION -> CheckExtensionForCompletionCandidateInfoProvider(
|
||||
resolutionParameters,
|
||||
firFile,
|
||||
firSession
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun completeResolvedCandidate(candidate: Candidate, resolutionParameters: ResolutionParameters): FirFunctionCall? {
|
||||
val fakeCall = buildFunctionCall {
|
||||
calleeReference = FirNamedReferenceWithCandidate(
|
||||
source = null,
|
||||
name = resolutionParameters.callableSymbol.callableId.callableName,
|
||||
candidate = candidate
|
||||
)
|
||||
}
|
||||
val completionResult = firCallCompleter.completeCall(fakeCall, resolutionParameters.expectedType)
|
||||
return if (completionResult.callCompleted) {
|
||||
completionResult.result
|
||||
} else null
|
||||
}
|
||||
}
|
||||
|
||||
class ResolutionParameters(
|
||||
val singleCandidateResolutionMode: SingleCandidateResolutionMode,
|
||||
val callableSymbol: FirCallableSymbol<*>,
|
||||
val implicitReceiver: ImplicitReceiverValue<*>? = null,
|
||||
val expectedType: FirTypeRef? = null,
|
||||
val explicitReceiver: FirExpression? = null,
|
||||
val argumentList: FirArgumentList = FirEmptyArgumentList,
|
||||
val typeArgumentList: List<FirTypeProjection> = emptyList(),
|
||||
)
|
||||
|
||||
enum class SingleCandidateResolutionMode {
|
||||
/**
|
||||
* Run resolution stages necessary to type check extension receiver (explicit/implicit) for candidate function.
|
||||
* Candidate is expected to be taken from context scope.
|
||||
* Arguments and type arguments are not expected and not checked.
|
||||
* Explicit receiver can be passed and will always be interpreted as extension receiver.
|
||||
*/
|
||||
CHECK_EXTENSION_FOR_COMPLETION
|
||||
}
|
||||
+88
@@ -0,0 +1,88 @@
|
||||
/*
|
||||
* 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.fir.low.level.api.resolver
|
||||
|
||||
import org.jetbrains.kotlin.fir.FirSession
|
||||
import org.jetbrains.kotlin.fir.declarations.FirFile
|
||||
import org.jetbrains.kotlin.fir.resolve.calls.*
|
||||
import org.jetbrains.kotlin.resolve.calls.tasks.ExplicitReceiverKind
|
||||
|
||||
/**
|
||||
* A supplier of information for resolving a call against a single provided candidate.
|
||||
* Implementors of this interface form a candidate from provided resolution parameters to fit requested resolution mode.
|
||||
* This includes creating artificial CallInfo, combining receivers and generating CallKind with specific resolution sequence.
|
||||
*/
|
||||
interface CandidateInfoProvider {
|
||||
fun callInfo(): CallInfo
|
||||
|
||||
fun callKind(): CallKind
|
||||
|
||||
fun explicitReceiverKind(): ExplicitReceiverKind
|
||||
|
||||
fun dispatchReceiverValue(): ReceiverValue?
|
||||
|
||||
fun implicitExtensionReceiverValue(): ImplicitReceiverValue<*>?
|
||||
|
||||
fun shouldFailBeforeResolve(): Boolean
|
||||
}
|
||||
|
||||
abstract class AbstractCandidateInfoProvider(
|
||||
protected val resolutionParameters: ResolutionParameters,
|
||||
protected val firFile: FirFile,
|
||||
protected val firSession: FirSession,
|
||||
) : CandidateInfoProvider {
|
||||
override fun callInfo(): CallInfo = with(resolutionParameters) {
|
||||
return CallInfo(
|
||||
callKind = callKind(),
|
||||
name = callableSymbol.callableId.callableName,
|
||||
explicitReceiver = explicitReceiver,
|
||||
argumentList = argumentList,
|
||||
typeArguments = typeArgumentList,
|
||||
containingDeclarations = emptyList(), // TODO - maybe we should pass declarations from context here (no visible differences atm)
|
||||
containingFile = firFile,
|
||||
isPotentialQualifierPart = false,
|
||||
session = firSession,
|
||||
)
|
||||
}
|
||||
|
||||
override fun shouldFailBeforeResolve(): Boolean = false
|
||||
}
|
||||
|
||||
/**
|
||||
* Provider for CHECK_EXTENSION_FOR_COMPLETION mode.
|
||||
*/
|
||||
class CheckExtensionForCompletionCandidateInfoProvider(
|
||||
resolutionParameters: ResolutionParameters,
|
||||
firFile: FirFile,
|
||||
firSession: FirSession,
|
||||
) : AbstractCandidateInfoProvider(resolutionParameters, firFile, firSession) {
|
||||
|
||||
override fun callKind(): CallKind = buildCallKindWithCustomResolutionSequence {
|
||||
checkExtensionReceiver = true
|
||||
}
|
||||
|
||||
override fun explicitReceiverKind(): ExplicitReceiverKind =
|
||||
if (resolutionParameters.explicitReceiver == null)
|
||||
ExplicitReceiverKind.NO_EXPLICIT_RECEIVER
|
||||
else ExplicitReceiverKind.EXTENSION_RECEIVER
|
||||
|
||||
// Right now it's impossible to reason about dispatch receiver when candidate comes from arbitrary scope with no other information.
|
||||
// So dispatch receiver is not passed from provider and later not checked during the resolution sequence.
|
||||
override fun dispatchReceiverValue(): ReceiverValue? = null
|
||||
|
||||
override fun implicitExtensionReceiverValue(): ImplicitReceiverValue<*>? = with(resolutionParameters) {
|
||||
if (explicitReceiver == null) implicitReceiver else null
|
||||
}
|
||||
|
||||
// Candidates with inconsistent extension receivers are skipped in tower resolver before resolution stages.
|
||||
// Passing them through can lead to false positives.
|
||||
override fun shouldFailBeforeResolve(): Boolean = with(resolutionParameters) {
|
||||
val callHasExtensionReceiver = explicitReceiverKind() == ExplicitReceiverKind.EXTENSION_RECEIVER
|
||||
|| implicitExtensionReceiverValue() != null
|
||||
val candidateHasExtensionReceiver = callableSymbol.fir.receiverTypeRef != null
|
||||
callHasExtensionReceiver != candidateHasExtensionReceiver
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user