[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:
@@ -5,8 +5,8 @@
|
||||
|
||||
package org.jetbrains.kotlin.fir.resolve.calls
|
||||
|
||||
enum class CallKind(vararg resolutionSequence: ResolutionStage) {
|
||||
VariableAccess(
|
||||
sealed class CallKind(vararg resolutionSequence: ResolutionStage) {
|
||||
object VariableAccess : CallKind(
|
||||
CheckVisibility,
|
||||
DiscriminateSynthetics,
|
||||
CheckExplicitReceiverConsistency,
|
||||
@@ -16,15 +16,17 @@ enum class CallKind(vararg resolutionSequence: ResolutionStage) {
|
||||
CheckReceivers.Extension,
|
||||
CheckLowPriorityInOverloadResolution,
|
||||
PostponedVariablesInitializerResolutionStage
|
||||
),
|
||||
SyntheticSelect(
|
||||
)
|
||||
|
||||
object SyntheticSelect : CallKind(
|
||||
MapArguments,
|
||||
NoTypeArguments,
|
||||
CreateFreshTypeVariableSubstitutorStage,
|
||||
CheckArguments,
|
||||
EagerResolveOfCallableReferences
|
||||
),
|
||||
Function(
|
||||
)
|
||||
|
||||
object Function : CallKind(
|
||||
CheckVisibility,
|
||||
DiscriminateSynthetics,
|
||||
MapArguments,
|
||||
@@ -37,8 +39,9 @@ enum class CallKind(vararg resolutionSequence: ResolutionStage) {
|
||||
EagerResolveOfCallableReferences,
|
||||
CheckLowPriorityInOverloadResolution,
|
||||
PostponedVariablesInitializerResolutionStage
|
||||
),
|
||||
DelegatingConstructorCall(
|
||||
)
|
||||
|
||||
object DelegatingConstructorCall : CallKind(
|
||||
CheckVisibility,
|
||||
MapArguments,
|
||||
CheckExplicitReceiverConsistency,
|
||||
@@ -48,8 +51,9 @@ enum class CallKind(vararg resolutionSequence: ResolutionStage) {
|
||||
CheckReceivers.Extension,
|
||||
CheckArguments,
|
||||
EagerResolveOfCallableReferences
|
||||
),
|
||||
CallableReference(
|
||||
)
|
||||
|
||||
object CallableReference : CallKind(
|
||||
CheckVisibility,
|
||||
DiscriminateSynthetics,
|
||||
NoTypeArguments,
|
||||
@@ -57,14 +61,54 @@ enum class CallKind(vararg resolutionSequence: ResolutionStage) {
|
||||
CheckReceivers.Dispatch,
|
||||
CheckReceivers.Extension,
|
||||
CheckCallableReferenceExpectedType
|
||||
),
|
||||
SyntheticIdForCallableReferencesResolution(
|
||||
)
|
||||
|
||||
object SyntheticIdForCallableReferencesResolution : CallKind(
|
||||
MapArguments,
|
||||
NoTypeArguments,
|
||||
CreateFreshTypeVariableSubstitutorStage,
|
||||
CheckArguments,
|
||||
EagerResolveOfCallableReferences
|
||||
);
|
||||
)
|
||||
|
||||
internal class CustomForIde(vararg resolutionSequence: ResolutionStage) : CallKind(*resolutionSequence)
|
||||
|
||||
val resolutionSequence: List<ResolutionStage> = resolutionSequence.toList()
|
||||
}
|
||||
|
||||
class ResolutionSequenceBuilder(
|
||||
var checkVisibility: Boolean = false,
|
||||
var discriminateSynthetics: Boolean = false,
|
||||
var checkExplicitReceiverConsistency: Boolean = false,
|
||||
var checkDispatchReceiver: Boolean = false,
|
||||
var checkExtensionReceiver: Boolean = false,
|
||||
var checkArguments: Boolean = false,
|
||||
var checkLowPriorityInOverloadResolution: Boolean = false,
|
||||
var initializePostponedVariables: Boolean = false,
|
||||
var mapTypeArguments: Boolean = false,
|
||||
var resolveCallableReferenceArguments: Boolean = false,
|
||||
var checkCallableReferenceExpectedType: Boolean = false,
|
||||
) {
|
||||
fun build(): CallKind {
|
||||
val stages = mutableListOf<ResolutionStage>().apply {
|
||||
if (checkVisibility) add(CheckVisibility)
|
||||
if (discriminateSynthetics) add(DiscriminateSynthetics)
|
||||
if (checkArguments) add(MapArguments)
|
||||
if (checkExplicitReceiverConsistency) add(CheckExplicitReceiverConsistency)
|
||||
if (mapTypeArguments) add(MapTypeArguments) else add(NoTypeArguments)
|
||||
if (checkArguments || checkDispatchReceiver || checkExtensionReceiver) add(CreateFreshTypeVariableSubstitutorStage)
|
||||
if (checkDispatchReceiver) add(CheckReceivers.Dispatch)
|
||||
if (checkExtensionReceiver) add(CheckReceivers.Extension)
|
||||
if (checkArguments) add(CheckArguments)
|
||||
if (resolveCallableReferenceArguments) add(EagerResolveOfCallableReferences)
|
||||
if (checkLowPriorityInOverloadResolution) add(CheckLowPriorityInOverloadResolution)
|
||||
if (initializePostponedVariables) add(PostponedVariablesInitializerResolutionStage)
|
||||
if (checkCallableReferenceExpectedType) add(CheckCallableReferenceExpectedType)
|
||||
}.toTypedArray()
|
||||
return CallKind.CustomForIde(*stages)
|
||||
}
|
||||
}
|
||||
|
||||
fun buildCallKindWithCustomResolutionSequence(init: ResolutionSequenceBuilder.() -> Unit): CallKind {
|
||||
return ResolutionSequenceBuilder().apply(init).build()
|
||||
}
|
||||
@@ -65,12 +65,13 @@ class CandidateFactory private constructor(
|
||||
|
||||
fun createErrorCandidate(diagnostic: ConeDiagnostic): Candidate {
|
||||
val symbol: AbstractFirBasedSymbol<*> = when (callInfo.callKind) {
|
||||
CallKind.VariableAccess -> createErrorPropertySymbol(diagnostic)
|
||||
CallKind.Function,
|
||||
CallKind.DelegatingConstructorCall,
|
||||
CallKind.CallableReference -> createErrorFunctionSymbol(diagnostic)
|
||||
CallKind.SyntheticSelect -> throw IllegalStateException()
|
||||
CallKind.SyntheticIdForCallableReferencesResolution -> throw IllegalStateException()
|
||||
is CallKind.VariableAccess -> createErrorPropertySymbol(diagnostic)
|
||||
is CallKind.Function,
|
||||
is CallKind.DelegatingConstructorCall,
|
||||
is CallKind.CallableReference -> createErrorFunctionSymbol(diagnostic)
|
||||
is CallKind.SyntheticSelect -> throw IllegalStateException()
|
||||
is CallKind.SyntheticIdForCallableReferencesResolution -> throw IllegalStateException()
|
||||
is CallKind.CustomForIde -> throw IllegalStateException()
|
||||
}
|
||||
return Candidate(
|
||||
symbol,
|
||||
|
||||
+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