diff --git a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/calls/CallKind.kt b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/calls/CallKind.kt index 51fa103985d..e7530886079 100644 --- a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/calls/CallKind.kt +++ b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/calls/CallKind.kt @@ -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 = 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().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() +} \ No newline at end of file diff --git a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/calls/CandidateFactory.kt b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/calls/CandidateFactory.kt index efd033acb22..60f2f896db1 100644 --- a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/calls/CandidateFactory.kt +++ b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/calls/CandidateFactory.kt @@ -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, diff --git a/idea/idea-frontend-fir/idea-fir-low-level-api/src/org/jetbrains/kotlin/idea/fir/low/level/api/resolver/SingleCandidateResolver.kt b/idea/idea-frontend-fir/idea-fir-low-level-api/src/org/jetbrains/kotlin/idea/fir/low/level/api/resolver/SingleCandidateResolver.kt new file mode 100644 index 00000000000..028a99e2e61 --- /dev/null +++ b/idea/idea-frontend-fir/idea-fir-low-level-api/src/org/jetbrains/kotlin/idea/fir/low/level/api/resolver/SingleCandidateResolver.kt @@ -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 = 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 +} diff --git a/idea/idea-frontend-fir/idea-fir-low-level-api/src/org/jetbrains/kotlin/idea/fir/low/level/api/resolver/candidateInfoProviders.kt b/idea/idea-frontend-fir/idea-fir-low-level-api/src/org/jetbrains/kotlin/idea/fir/low/level/api/resolver/candidateInfoProviders.kt new file mode 100644 index 00000000000..3bd706c408a --- /dev/null +++ b/idea/idea-frontend-fir/idea-fir-low-level-api/src/org/jetbrains/kotlin/idea/fir/low/level/api/resolver/candidateInfoProviders.kt @@ -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 + } +} \ No newline at end of file