K2: Implement partially constrained lambda analysis (PCLA)

It's expected to partially mimic the behavior of what
previously was called builder inference, but with more clear contracts
(documentation is in progress, though)

See a lot of fixed issues in the later commits with test data,
especially [red-to-green]

^KT-59791 In Progress
This commit is contained in:
Denis.Zharkov
2023-08-21 17:51:54 +02:00
committed by Space Team
parent 9ae5287df8
commit 276f5b26d8
44 changed files with 1356 additions and 1086 deletions
+7
View File
@@ -0,0 +1,7 @@
<component name="ProjectDictionaryState">
<dictionary name="Denis.Zharkov">
<words>
<w>pcla</w>
</words>
</dictionary>
</component>
+1
View File
@@ -22,6 +22,7 @@ subprojects {
tasks.withType<org.jetbrains.kotlin.gradle.dsl.KotlinCompile<*>>().configureEach {
kotlinOptions {
freeCompilerArgs += "-opt-in=org.jetbrains.kotlin.fir.symbols.SymbolInternals"
freeCompilerArgs += "-opt-in=org.jetbrains.kotlin.types.model.K2Only"
}
}
}
@@ -316,13 +316,13 @@ private fun mapInapplicableCandidateError(
rootCause.expectedContextReceiverType.removeTypeVariableTypes(typeContext)
)
is StubBuilderInferenceReceiver -> {
val typeParameterSymbol = rootCause.typeParameterSymbol
is TypeVariableAsExplicitReceiver -> {
val typeParameter = rootCause.typeParameter
@OptIn(SymbolInternals::class)
FirErrors.BUILDER_INFERENCE_STUB_RECEIVER.createOn(
qualifiedAccessSource ?: source,
typeParameterSymbol.name,
(typeParameterSymbol.containingDeclarationSymbol.fir as FirMemberDeclaration).nameOrSpecialName
rootCause.explicitReceiver.source,
typeParameter.symbol.name,
(typeParameter.symbol.containingDeclarationSymbol.fir as FirMemberDeclaration).nameOrSpecialName
)
}
@@ -46,6 +46,8 @@ class ConeTypeVariableTypeConstructor(
fun recordInfoAboutTypeVariableUsagesAsInvariantOrContravariantParameter() {
isContainedInInvariantOrContravariantPositions = true
}
override fun toString(): String = "${this::class.simpleName}($debugName)"
}
// ----------------------------------- Stub types -----------------------------------
@@ -125,6 +125,13 @@ private fun ConeKotlinType.scope(
is ConeDefinitelyNotNullType -> original.scope(useSiteSession, scopeSession, requiredMembersPhase)
is ConeIntegerConstantOperatorType -> scopeSession.getOrBuildScopeForIntegerConstantOperatorType(useSiteSession, this)
is ConeIntegerLiteralConstantType -> error("ILT should not be in receiver position")
// See testData/diagnostics/tests/inference/builderInference/memberScopeOfCapturedTypeForPostponedCall.kt
is ConeCapturedType -> {
val supertypes =
constructor.supertypes?.takeIf { it.isNotEmpty() }
?: listOf(useSiteSession.builtinTypes.anyType.type)
useSiteSession.typeContext.intersectTypes(supertypes).scope(useSiteSession, scopeSession, requiredMembersPhase)
}
else -> null
}
@@ -9,7 +9,6 @@ import org.jetbrains.kotlin.KtFakeSourceElementKind
import org.jetbrains.kotlin.fakeElement
import org.jetbrains.kotlin.fir.FirSession
import org.jetbrains.kotlin.fir.declarations.FirResolvePhase
import org.jetbrains.kotlin.fir.diagnostics.ConeIntermediateDiagnostic
import org.jetbrains.kotlin.fir.expressions.FirCheckNotNullCall
import org.jetbrains.kotlin.fir.expressions.FirExpression
import org.jetbrains.kotlin.fir.expressions.FirSmartCastExpression
@@ -18,7 +17,6 @@ import org.jetbrains.kotlin.fir.expressions.builder.buildInaccessibleReceiverExp
import org.jetbrains.kotlin.fir.expressions.builder.buildSmartCastExpression
import org.jetbrains.kotlin.fir.expressions.builder.buildThisReceiverExpression
import org.jetbrains.kotlin.fir.references.builder.buildImplicitThisReference
import org.jetbrains.kotlin.fir.renderWithType
import org.jetbrains.kotlin.fir.resolve.ScopeSession
import org.jetbrains.kotlin.fir.resolve.scope
import org.jetbrains.kotlin.fir.resolve.smartcastScope
@@ -28,8 +26,10 @@ import org.jetbrains.kotlin.fir.symbols.FirBasedSymbol
import org.jetbrains.kotlin.fir.symbols.impl.FirCallableSymbol
import org.jetbrains.kotlin.fir.symbols.impl.FirClassSymbol
import org.jetbrains.kotlin.fir.symbols.impl.FirScriptSymbol
import org.jetbrains.kotlin.fir.types.*
import org.jetbrains.kotlin.fir.types.ConeKotlinType
import org.jetbrains.kotlin.fir.types.builder.buildResolvedTypeRef
import org.jetbrains.kotlin.fir.types.constructType
import org.jetbrains.kotlin.fir.types.resolvedType
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.types.SmartcastStability
@@ -89,6 +89,7 @@ sealed class ImplicitReceiverValue<S : FirBasedSymbol<*>>(
abstract val isContextReceiver: Boolean
// Type before smart cast
val originalType: ConeKotlinType = type
var implicitScope: FirTypeScope? =
@@ -145,19 +146,6 @@ sealed class ImplicitReceiverValue<S : FirBasedSymbol<*>>(
@RequiresOptIn
annotation class ImplicitReceiverInternals
@Deprecated(level = DeprecationLevel.ERROR, message = "Builder inference should not modify implicit receivers. KT-54708")
fun updateTypeInBuilderInference(type: ConeKotlinType) {
this.type = type
originalReceiverExpression = receiverExpression(boundSymbol, type, contextReceiverNumber, inaccessibleReceiver)
_receiverExpression = null
implicitScope = type.scope(
useSiteSession = useSiteSession,
scopeSession = scopeSession,
callableCopyTypeCalculator = CallableCopyTypeCalculator.DoNothing,
requiredMembersPhase = FirResolvePhase.STATUS,
)
}
/*
* Should be called only in ImplicitReceiverStack
*/
@@ -14,10 +14,7 @@ import org.jetbrains.kotlin.fir.resolve.createSubstitutionForSupertype
import org.jetbrains.kotlin.fir.resolve.fullyExpandedType
import org.jetbrains.kotlin.fir.resolve.providers.FirSymbolProvider
import org.jetbrains.kotlin.fir.resolve.providers.symbolProvider
import org.jetbrains.kotlin.fir.resolve.substitution.ConeSubstitutor
import org.jetbrains.kotlin.fir.resolve.substitution.ConeSubstitutorByMap
import org.jetbrains.kotlin.fir.resolve.substitution.NoSubstitutor
import org.jetbrains.kotlin.fir.resolve.substitution.createTypeSubstitutorByTypeConstructor
import org.jetbrains.kotlin.fir.resolve.substitution.*
import org.jetbrains.kotlin.fir.symbols.ConeClassLikeLookupTag
import org.jetbrains.kotlin.fir.symbols.ConeTypeParameterLookupTag
import org.jetbrains.kotlin.fir.symbols.impl.FirAnonymousObjectSymbol
@@ -205,6 +202,10 @@ interface ConeInferenceContext : TypeSystemInferenceExtensionContext, ConeTypeCo
return this.intersectedTypes.any { it.containsInternal(predicate) }
}
if (this is ConeCapturedType) {
return this.constructor.projection.type?.containsInternal(predicate) == true
}
repeat(argumentsCount()) { index ->
val argument = getArgument(index)
if (!argument.isStarProjection() && argument.getType().containsInternal(predicate)) return true
@@ -358,6 +359,15 @@ interface ConeInferenceContext : TypeSystemInferenceExtensionContext, ConeTypeCo
return ConeSubstitutor.Empty
}
override fun createSubstitutionFromSubtypingStubTypesToTypeVariables(): TypeSubstitutorMarker {
return object : AbstractConeSubstitutor(this) {
override fun substituteType(type: ConeKotlinType): ConeKotlinType? {
return ((type as? ConeStubTypeForTypeVariableInSubtyping)
?.constructor?.variable?.defaultType)?.withNullability(type.nullability, this@ConeInferenceContext)
}
}
}
override fun TypeSubstitutorMarker.safeSubstitute(type: KotlinTypeMarker): KotlinTypeMarker {
if (this === NoSubstitutor) return type
require(this is ConeSubstitutor)
@@ -26,8 +26,8 @@ import org.jetbrains.kotlin.fir.resolve.calls.tower.FirTowerResolver
import org.jetbrains.kotlin.fir.resolve.calls.tower.TowerGroup
import org.jetbrains.kotlin.fir.resolve.calls.tower.TowerResolveManager
import org.jetbrains.kotlin.fir.resolve.diagnostics.*
import org.jetbrains.kotlin.fir.resolve.inference.FirBuilderInferenceSession
import org.jetbrains.kotlin.fir.resolve.inference.ResolvedCallableReferenceAtom
import org.jetbrains.kotlin.fir.resolve.inference.csBuilder
import org.jetbrains.kotlin.fir.resolve.inference.inferenceComponents
import org.jetbrains.kotlin.fir.resolve.substitution.ConeSubstitutor
import org.jetbrains.kotlin.fir.resolve.transformers.body.resolve.FirAbstractBodyResolveTransformer
@@ -398,10 +398,11 @@ class FirCallResolver(
}
fun resolveCallableReference(
constraintSystemBuilder: ConstraintSystemBuilder,
containingCallCandidate: Candidate,
resolvedCallableReferenceAtom: ResolvedCallableReferenceAtom,
hasSyntheticOuterCall: Boolean,
): Pair<CandidateApplicability, Boolean> {
): Pair<CandidateApplicability, Boolean> = components.context.inferenceSession.runCallableReferenceResolution(containingCallCandidate) {
val constraintSystemBuilder = containingCallCandidate.csBuilder
val callableReferenceAccess = resolvedCallableReferenceAtom.reference
val calleeReference = callableReferenceAccess.calleeReference
val lhs = resolvedCallableReferenceAtom.lhs
@@ -450,7 +451,7 @@ class FirCallResolver(
calleeReference.source
)
resolvedCallableReferenceAtom.resultingReference = errorReference
return applicability to false
return@runCallableReferenceResolution applicability to false
}
reducedCandidates.size > 1 -> {
if (resolvedCallableReferenceAtom.hasBeenPostponed) {
@@ -460,10 +461,10 @@ class FirCallResolver(
calleeReference.source
)
resolvedCallableReferenceAtom.resultingReference = errorReference
return applicability to false
return@runCallableReferenceResolution applicability to false
}
resolvedCallableReferenceAtom.hasBeenPostponed = true
return applicability to true
return@runCallableReferenceResolution applicability to true
}
}
@@ -486,7 +487,7 @@ class FirCallResolver(
resolvedCallableReferenceAtom.resultingReference = reference
resolvedCallableReferenceAtom.resultingTypeForCallableReference = chosenCandidate.resultingTypeForCallableReference
return applicability to true
return@runCallableReferenceResolution applicability to true
}
fun resolveDelegatingConstructorCall(
@@ -859,7 +860,7 @@ class FirCallResolver(
* can be important in builder inference mode, and it will never work if we skip completion here.
* See inferenceFromLambdaReturnStatement.kt test.
*/
if (components.context.inferenceSession !is FirBuilderInferenceSession &&
if (!candidate.usedOuterCs &&
createResolvedReferenceWithoutCandidateForLocalVariables &&
explicitReceiver?.resolvedType !is ConeIntegerLiteralType &&
coneSymbol is FirVariableSymbol &&
@@ -39,26 +39,6 @@ class FirOverloadByLambdaReturnTypeResolver(
): Set<Candidate> where T : FirStatement, T : FirResolvable {
if (bestCandidates.size <= 1) return bestCandidates
/*
* Inference session may look into candidate of call, and for that it uses callee reference.
* So we need replace reference with proper candidate before calling inference session
*/
val shouldRunCompletion = if (inferenceSession != FirInferenceSession.DEFAULT) {
var shouldRunCompletion = true
val originalReference = qualifiedAccess.calleeReference
for (candidate in bestCandidates) {
qualifiedAccess.replaceCalleeReference(FirNamedReferenceWithCandidate(null, candidate.callInfo.name, candidate))
shouldRunCompletion = shouldRunCompletion && inferenceSession.shouldRunCompletion(qualifiedAccess)
if (!shouldRunCompletion) break
}
qualifiedAccess.replaceCalleeReference(originalReference)
shouldRunCompletion
} else {
true
}
if (!shouldRunCompletion) return bestCandidates
return reduceCandidatesImpl(
qualifiedAccess,
bestCandidates,
@@ -137,7 +117,7 @@ class FirOverloadByLambdaReturnTypeResolver(
firstCandidate.system,
firstAtom,
firstCandidate,
ConstraintSystemCompletionMode.FULL,
forOverloadByLambdaReturnType = true,
)
while (iterator.hasNext()) {
val (candidate, atom) = iterator.next()
@@ -147,7 +127,6 @@ class FirOverloadByLambdaReturnTypeResolver(
atom,
candidate,
results,
ConstraintSystemCompletionMode.FULL,
)
}
@@ -7,43 +7,54 @@ package org.jetbrains.kotlin.fir.resolve
import org.jetbrains.kotlin.fir.declarations.FirTowerDataContext
import org.jetbrains.kotlin.fir.expressions.FirCallableReferenceAccess
import org.jetbrains.kotlin.fir.resolve.inference.FirInferenceSession
import org.jetbrains.kotlin.fir.symbols.impl.FirAnonymousFunctionSymbol
class FirSpecialTowerDataContexts {
private val towerDataContextForAnonymousFunctions: MutableMap<FirAnonymousFunctionSymbol, FirTowerDataContext> = mutableMapOf()
private val towerDataContextForCallableReferences: MutableMap<FirCallableReferenceAccess, FirTowerDataContext> = mutableMapOf()
private val contextForAnonymousFunctions: MutableMap<FirAnonymousFunctionSymbol, PostponedAtomsResolutionContext> = mutableMapOf()
private val contextForCallableReferences: MutableMap<FirCallableReferenceAccess, PostponedAtomsResolutionContext> = mutableMapOf()
fun getAnonymousFunctionContext(symbol: FirAnonymousFunctionSymbol): FirTowerDataContext? {
return towerDataContextForAnonymousFunctions[symbol]
fun getAnonymousFunctionContext(symbol: FirAnonymousFunctionSymbol): PostponedAtomsResolutionContext? {
return contextForAnonymousFunctions[symbol]
}
fun getCallableReferenceContext(access: FirCallableReferenceAccess): FirTowerDataContext? {
return towerDataContextForCallableReferences[access]
fun getCallableReferenceContext(access: FirCallableReferenceAccess): PostponedAtomsResolutionContext? {
return contextForCallableReferences[access]
}
fun storeAnonymousFunctionContext(symbol: FirAnonymousFunctionSymbol, context: FirTowerDataContext) {
towerDataContextForAnonymousFunctions[symbol] = context
fun storeAnonymousFunctionContext(
symbol: FirAnonymousFunctionSymbol,
context: FirTowerDataContext,
inferenceSession: FirInferenceSession,
) {
contextForAnonymousFunctions[symbol] = Pair(context, inferenceSession)
}
fun dropAnonymousFunctionContext(symbol: FirAnonymousFunctionSymbol) {
towerDataContextForAnonymousFunctions.remove(symbol)
contextForAnonymousFunctions.remove(symbol)
}
fun storeCallableReferenceContext(access: FirCallableReferenceAccess, context: FirTowerDataContext) {
towerDataContextForCallableReferences[access] = context
fun storeCallableReferenceContext(
access: FirCallableReferenceAccess,
context: FirTowerDataContext,
inferenceSession: FirInferenceSession
) {
contextForCallableReferences[access] = Pair(context, inferenceSession)
}
fun dropCallableReferenceContext(access: FirCallableReferenceAccess) {
towerDataContextForCallableReferences.remove(access)
contextForCallableReferences.remove(access)
}
fun putAll(contexts: FirSpecialTowerDataContexts) {
towerDataContextForCallableReferences.putAll(contexts.towerDataContextForCallableReferences)
towerDataContextForAnonymousFunctions.putAll(contexts.towerDataContextForAnonymousFunctions)
contextForCallableReferences.putAll(contexts.contextForCallableReferences)
contextForAnonymousFunctions.putAll(contexts.contextForAnonymousFunctions)
}
fun clear() {
towerDataContextForAnonymousFunctions.clear()
towerDataContextForCallableReferences.clear()
contextForAnonymousFunctions.clear()
contextForCallableReferences.clear()
}
}
typealias PostponedAtomsResolutionContext = Pair<FirTowerDataContext, FirInferenceSession>
@@ -28,7 +28,11 @@ sealed class ResolutionMode(
}
data object ContextIndependent : ResolutionMode(forceFullCompletion = true)
data object ReceiverResolution : ResolutionMode(forceFullCompletion = true)
sealed class ReceiverResolution(val forCallableReference: Boolean) : ResolutionMode(forceFullCompletion = true) {
data object ForCallableReference : ReceiverResolution(forCallableReference = true)
companion object : ReceiverResolution(forCallableReference = false)
}
class WithExpectedType(
val expectedTypeRef: FirResolvedTypeRef,
@@ -34,6 +34,7 @@ import org.jetbrains.kotlin.resolve.calls.inference.model.ConstraintPosition
import org.jetbrains.kotlin.resolve.calls.inference.model.SimpleConstraintSystemConstraintPosition
import org.jetbrains.kotlin.types.AbstractTypeChecker
import org.jetbrains.kotlin.types.model.TypeSystemCommonSuperTypesContext
import org.jetbrains.kotlin.types.model.safeSubstitute
import org.jetbrains.kotlin.types.model.typeConstructor
val SAM_LOOKUP_NAME = Name.special("<SAM-CONSTRUCTOR>")
@@ -190,7 +191,7 @@ fun Candidate.resolveSubCallArgument(
* placeholder type with value 0, but argument contains type with proper literal value
*/
val type: ConeKotlinType = context.returnTypeCalculator.tryCalculateReturnType(candidate.symbol.fir as FirCallableDeclaration).type
val argumentType = candidate.substitutor.substituteOrSelf(type)
val argumentType = candidate.substitutor.substituteOrSelf(type).prepareTypeForPartiallyCompletedPCLACall(candidate, csBuilder)
resolvePlainArgumentType(
csBuilder,
argument,
@@ -204,6 +205,26 @@ fun Candidate.resolveSubCallArgument(
)
}
// For PCLA/Delegate inference partially completed calls, there might be a situation when
// - There are already fixed variables (because we need them for lambda analysis)
// - They are used in return types
//
// In this case, we substitute them explicitly because otherwise
// TypeCheckerStateForConstraintInjector.fixedTypeVariable throws an exception.
//
// Note that this is not relevant outside PCLA context because
// - For partial completion, we avoid fixing TVs that are used inside return types
// - For FULL completion, we would run completion results writing, so there would be no candidates and type variables inside return types.
//
// See singleBranchConditionLastStatementInLambda.kt and assignmentUsingIncompletePCLACall.kt tests
private fun ConeKotlinType.prepareTypeForPartiallyCompletedPCLACall(
candidate: Candidate, outerCSBuilder: ConstraintSystemBuilder
): ConeKotlinType {
if (!candidate.system.usesOuterCs) return this
return outerCSBuilder.buildCurrentSubstitutor().safeSubstitute(candidate.callInfo.session.typeContext, this) as ConeKotlinType
}
fun Candidate.resolvePlainExpressionArgument(
csBuilder: ConstraintSystemBuilder,
argument: FirExpression,
@@ -24,6 +24,7 @@ sealed class CallKind(vararg resolutionSequence: ResolutionStage) {
ConstraintSystemForks,
CheckIncompatibleTypeVariableUpperBounds,
TypeParameterAsCallable,
TypeVariablesInExplicitReceivers,
)
object SyntheticSelect : CallKind(
@@ -59,6 +60,7 @@ sealed class CallKind(vararg resolutionSequence: ResolutionStage) {
ConstraintSystemForks,
CheckIncompatibleTypeVariableUpperBounds,
TypeParameterAsCallable,
TypeVariablesInExplicitReceivers,
)
object DelegatingConstructorCall : CallKind(
@@ -94,6 +96,7 @@ sealed class CallKind(vararg resolutionSequence: ResolutionStage) {
CheckIncompatibleTypeVariableUpperBounds,
ProcessDynamicExtensionAnnotation,
LowerPriorityIfDynamic,
TypeVariablesInExplicitReceivers,
)
object SyntheticIdForCallableReferencesResolution : CallKind(
@@ -7,12 +7,12 @@ package org.jetbrains.kotlin.fir.resolve.calls
import org.jetbrains.kotlin.KtFakeSourceElementKind
import org.jetbrains.kotlin.fakeElement
import org.jetbrains.kotlin.fir.declarations.FirAnonymousFunction
import org.jetbrains.kotlin.fir.declarations.FirValueParameter
import org.jetbrains.kotlin.fir.expressions.FirExpression
import org.jetbrains.kotlin.fir.expressions.FirSmartCastExpression
import org.jetbrains.kotlin.fir.expressions.FirThisReceiverExpression
import org.jetbrains.kotlin.fir.expressions.*
import org.jetbrains.kotlin.fir.expressions.builder.buildThisReceiverExpressionCopy
import org.jetbrains.kotlin.fir.expressions.impl.FirExpressionStub
import org.jetbrains.kotlin.fir.resolve.inference.FirInferenceSession
import org.jetbrains.kotlin.fir.resolve.inference.InferenceComponents
import org.jetbrains.kotlin.fir.resolve.inference.PostponedResolvedAtom
import org.jetbrains.kotlin.fir.resolve.substitution.ConeSubstitutor
@@ -28,6 +28,7 @@ import org.jetbrains.kotlin.resolve.calls.tasks.ExplicitReceiverKind
import org.jetbrains.kotlin.resolve.calls.tower.CandidateApplicability
import org.jetbrains.kotlin.resolve.calls.tower.isSuccess
import org.jetbrains.kotlin.util.CodeFragmentAdjustment
import org.jetbrains.kotlin.utils.addToStdlib.runUnless
class Candidate(
symbol: FirBasedSymbol<*>,
@@ -47,6 +48,7 @@ class Candidate(
val isFromCompanionObjectTypeScope: Boolean = false,
// It's only true if we're in the member scope of smart cast receiver and this particular candidate came from original type
val isFromOriginalTypeInPresenceOfSmartCast: Boolean = false,
inferenceSession: FirInferenceSession,
) : AbstractCandidate() {
override var symbol: FirBasedSymbol<*> = symbol
@@ -66,10 +68,21 @@ class Candidate(
this.symbol = symbol
}
val usedOuterCs: Boolean get() = system.usesOuterCs
private var systemInitialized: Boolean = false
val system: NewConstraintSystemImpl by lazy(LazyThreadSafetyMode.NONE) {
val system = constraintSystemFactory.createConstraintSystem()
system.setBaseSystem(baseSystem)
val baseCSFromInferenceSession =
runUnless(baseSystem.usesOuterCs) { inferenceSession.baseConstraintStorageForCandidate(this) }
if (baseCSFromInferenceSession != null) {
system.setBaseSystem(baseCSFromInferenceSession)
system.addOtherSystem(baseSystem)
} else {
system.setBaseSystem(baseSystem)
}
systemInitialized = true
system
}
@@ -102,6 +115,9 @@ class Candidate(
var functionTypesOfSamConversions: HashMap<FirExpression, ConeKotlinType>? = null
lateinit var typeArgumentMapping: TypeArgumentMapping
val postponedAtoms = mutableListOf<PostponedResolvedAtom>()
val postponedPCLACalls = mutableListOf<FirStatement>()
val lambdasAnalyzedWithPCLA = mutableListOf<FirAnonymousFunction>()
val onCompletionResultsWritingCallbacks = mutableListOf<(ConeSubstitutor) -> Unit>()
var currentApplicability = CandidateApplicability.RESOLVED
private set
@@ -35,7 +35,6 @@ class CandidateFactory private constructor(
companion object {
private fun buildBaseSystem(context: ResolutionContext, callInfo: CallInfo): ConstraintStorage {
val system = context.inferenceComponents.createConstraintSystem()
system.addOuterSystem(context.bodyResolveContext.outerConstraintStorage)
callInfo.arguments.forEach {
system.addSubsystemFromExpression(it)
}
@@ -79,6 +78,7 @@ class CandidateFactory private constructor(
ExplicitReceiverKind.NO_EXPLICIT_RECEIVER, ExplicitReceiverKind.BOTH_RECEIVERS -> false
},
isFromOriginalTypeInPresenceOfSmartCast,
context.bodyResolveContext.inferenceSession,
)
// The counterpart in FE 1.0 checks if the given descriptor is VariableDescriptor yet not PropertyDescriptor.
@@ -165,6 +165,7 @@ class CandidateFactory private constructor(
baseSystem,
callInfo,
originScope = null,
inferenceSession = context.bodyResolveContext.inferenceSession,
)
}
@@ -194,25 +195,47 @@ class CandidateFactory private constructor(
}
}
fun PostponedArgumentsAnalyzerContext.addSubsystemFromExpression(statement: FirStatement): Boolean {
fun processConstraintStorageFromExpression(statement: FirStatement, processor: (ConstraintStorage) -> Unit): Boolean {
return when (statement) {
is FirQualifiedAccessExpression,
is FirWhenExpression,
is FirTryExpression,
is FirCheckNotNullCall,
is FirElvisExpression -> {
is FirElvisExpression,
-> {
val candidate = (statement as FirResolvable).candidate() ?: return false
addOtherSystem(candidate.system.asReadOnlyStorage())
processor(candidate.system.asReadOnlyStorage())
true
}
is FirSafeCallExpression -> addSubsystemFromExpression(statement.selector)
is FirWrappedArgumentExpression -> addSubsystemFromExpression(statement.expression)
is FirBlock -> statement.returnExpressions().any { addSubsystemFromExpression(it) }
is FirSafeCallExpression -> processConstraintStorageFromExpression(statement.selector, processor)
is FirWrappedArgumentExpression -> processConstraintStorageFromExpression(statement.expression, processor)
is FirBlock -> {
var wasAny = false
// Might be `.any {` call, but we should process all the items
statement.returnExpressions().forEach {
if (processConstraintStorageFromExpression(it, processor)) {
wasAny = true
}
}
wasAny
}
else -> false
}
}
fun PostponedArgumentsAnalyzerContext.addSubsystemFromExpression(statement: FirStatement): Boolean {
return processConstraintStorageFromExpression(statement) {
// If a call inside a lambda uses outer CS,
// it's already integrated into inference session via FirPCLAInferenceSession.processPartiallyResolvedCall
if (!it.usesOuterCs) {
addOtherSystem(it)
}
}
}
internal fun FirResolvable.candidate(): Candidate? {
return when (val callee = this.calleeReference) {
is FirNamedReferenceWithCandidate -> return callee.candidate
@@ -123,21 +123,6 @@ object CheckExtensionReceiver : ResolutionStage() {
candidate.chosenExtensionReceiver = receiver.expression
val checkBuilderInferenceRestriction =
!context.session.languageVersionSettings
.supportsFeature(LanguageFeature.NoBuilderInferenceWithoutAnnotationRestriction)
if (checkBuilderInferenceRestriction) {
val resolvedType = receiver.expression.resolvedType
if (resolvedType is ConeStubTypeForChainInference) {
val typeVariable = resolvedType.constructor.variable
sink.yieldDiagnostic(
StubBuilderInferenceReceiver(
(typeVariable.typeConstructor.originalTypeParameter as ConeTypeParameterLookupTag).typeParameterSymbol
)
)
}
}
sink.yieldIfNeed()
}
@@ -264,6 +249,39 @@ object CheckContextReceivers : ResolutionStage() {
}
}
object TypeVariablesInExplicitReceivers : ResolutionStage() {
override suspend fun check(candidate: Candidate, callInfo: CallInfo, sink: CheckerSink, context: ResolutionContext) {
if (callInfo.callSite.isAnyOfDelegateOperators()) return
val explicitReceiver = callInfo.explicitReceiver ?: return checkOtherCases(candidate)
val typeVariableType = explicitReceiver.resolvedType.obtainTypeVariable() ?: return checkOtherCases(candidate)
val typeParameter =
(typeVariableType.typeConstructor.originalTypeParameter as? ConeTypeParameterLookupTag)?.typeParameterSymbol?.fir
?: return checkOtherCases(candidate)
sink.reportDiagnostic(TypeVariableAsExplicitReceiver(explicitReceiver, typeParameter))
}
private fun checkOtherCases(candidate: Candidate) {
require(candidate.chosenExtensionReceiverExpression()?.resolvedType?.obtainTypeVariable() == null) {
"Found TV in extension receiver of $candidate"
}
require(candidate.dispatchReceiverExpression()?.resolvedType?.obtainTypeVariable() == null) {
"Found TV in dispatch receiver of $candidate"
}
}
private fun ConeKotlinType.obtainTypeVariable(): ConeTypeVariableType? = when (this) {
is ConeFlexibleType -> lowerBound.obtainTypeVariable()
is ConeTypeVariableType -> this
is ConeDefinitelyNotNullType -> original.obtainTypeVariable()
is ConeIntersectionType -> intersectedTypes.firstNotNullOfOrNull { it.obtainTypeVariable() }
else -> null
}
}
private fun Candidate.findClosestMatchingReceivers(
expectedType: ConeKotlinType,
receiverGroups: List<List<FirExpression>>,
@@ -569,7 +587,7 @@ internal object EagerResolveOfCallableReferences : CheckerStage() {
if (atom is ResolvedCallableReferenceAtom) {
val (applicability, success) =
context.bodyResolveComponents.callResolver.resolveCallableReference(
candidate.csBuilder, atom, hasSyntheticOuterCall = candidate.callInfo.name == ACCEPT_SPECIFIC_TYPE.callableName
candidate, atom, hasSyntheticOuterCall = candidate.callInfo.name == ACCEPT_SPECIFIC_TYPE.callableName
)
if (!success) {
// If the resolution was unsuccessful, we ensure that an error will be reported for the callable reference
@@ -20,6 +20,7 @@ import org.jetbrains.kotlin.fir.references.symbol
import org.jetbrains.kotlin.fir.references.toResolvedPropertySymbol
import org.jetbrains.kotlin.fir.resolve.*
import org.jetbrains.kotlin.fir.resolve.calls.ImplicitReceiverValue
import org.jetbrains.kotlin.fir.resolve.calls.candidate
import org.jetbrains.kotlin.fir.resolve.dfa.cfg.*
import org.jetbrains.kotlin.fir.resolve.substitution.ConeSubstitutor
import org.jetbrains.kotlin.fir.resolve.substitution.ConeSubstitutorByMap
@@ -30,7 +31,10 @@ import org.jetbrains.kotlin.fir.scopes.getFunctions
import org.jetbrains.kotlin.fir.scopes.impl.toConeType
import org.jetbrains.kotlin.fir.scopes.unsubstitutedScope
import org.jetbrains.kotlin.fir.symbols.FirBasedSymbol
import org.jetbrains.kotlin.fir.symbols.impl.*
import org.jetbrains.kotlin.fir.symbols.impl.FirClassSymbol
import org.jetbrains.kotlin.fir.symbols.impl.FirPropertySymbol
import org.jetbrains.kotlin.fir.symbols.impl.FirRegularClassSymbol
import org.jetbrains.kotlin.fir.symbols.impl.FirTypeParameterSymbol
import org.jetbrains.kotlin.fir.types.*
import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.name.StandardClassIds
@@ -926,6 +930,11 @@ abstract class FirDataFlowAnalyzer(
private fun FirStatement.orderedArguments(callee: FirFunction): Array<out FirExpression?>? {
fun FirQualifiedAccessExpression.firstReceiver(): FirExpression? {
val candidate = candidate()
// Processing case with a candidate might be necessary for PCLA, because even top-level calls might be not fully completed
if (candidate != null) {
return candidate.chosenExtensionReceiverExpression() ?: candidate.dispatchReceiverExpression()
}
return extensionReceiver ?: dispatchReceiver
}
@@ -937,7 +946,8 @@ abstract class FirDataFlowAnalyzer(
return when (this) {
is FirFunctionCall -> {
val argumentToParameter = resolvedArgumentMapping ?: return null
// Processing case with a candidate might be necessary for PCLA, because even top-level calls might be not fully completed
val argumentToParameter = resolvedArgumentMapping ?: candidate()?.argumentMapping ?: return null
val parameterToArgument = argumentToParameter.entries.associate { it.value to it.key.unwrapArgument() }
Array(callee.valueParameters.size + 1) { i ->
if (i > 0) parameterToArgument[callee.valueParameters[i - 1]] else receiver
@@ -1155,7 +1165,11 @@ abstract class FirDataFlowAnalyzer(
}
private fun exitBooleanNot(flow: MutableFlow, expression: FirFunctionCall) {
val argumentVariable = variableStorage.get(flow, expression.dispatchReceiver!!) ?: return
val argumentVariable = variableStorage.get(
flow,
// Processing case with a candidate might be necessary for PCLA, because even top-level calls might be not fully completed
expression.candidate()?.dispatchReceiverExpression() ?: expression.dispatchReceiver!!
) ?: return
val expressionVariable = variableStorage.createSynthetic(expression)
// Alternatively: (expression == true => argument == false) && (expression == false => argument == true)
// Which implementation is faster and/or consumes less memory is an open question.
@@ -23,10 +23,7 @@ import org.jetbrains.kotlin.fir.symbols.impl.FirTypeParameterSymbol
import org.jetbrains.kotlin.fir.types.ConeErrorType
import org.jetbrains.kotlin.fir.types.ConeKotlinType
import org.jetbrains.kotlin.fir.types.ConeTypeVariable
import org.jetbrains.kotlin.resolve.calls.inference.components.ConstraintSystemCompletionContext
import org.jetbrains.kotlin.resolve.calls.inference.components.ConstraintSystemCompletionMode
import org.jetbrains.kotlin.resolve.calls.inference.components.TypeVariableDependencyInformationProvider
import org.jetbrains.kotlin.resolve.calls.inference.components.TypeVariableDirectionCalculator
import org.jetbrains.kotlin.resolve.calls.inference.components.*
import org.jetbrains.kotlin.resolve.calls.inference.model.NewConstraintSystemImpl
import org.jetbrains.kotlin.resolve.calls.inference.model.NotEnoughInformationForTypeParameter
import org.jetbrains.kotlin.resolve.calls.inference.model.VariableWithConstraints
@@ -81,9 +78,9 @@ class ConstraintSystemCompleter(components: BodyResolveComponents, private val c
if (analyzeArgumentWithFixedParameterTypes(languageVersionSettings, postponedArguments, analyze))
continue
val isThereAnyReadyForFixationVariable = variableFixationFinder.findFirstVariableForFixation(
this,
getOrderedAllTypeVariables(collectVariablesFromContext, topLevelAtoms),
val isThereAnyReadyForFixationVariable = findFirstVariableForFixation(
collectVariablesFromContext,
topLevelAtoms,
postponedArguments,
completionMode,
topLevelType
@@ -113,7 +110,7 @@ class ConstraintSystemCompleter(components: BodyResolveComponents, private val c
if (wasBuiltNewExpectedTypeForSomeArgument)
continue
if (completionMode == ConstraintSystemCompletionMode.FULL) {
if (completionMode.allLambdasShouldBeAnalyzed) {
// Stage 3: fix variables for parameter types of all postponed arguments
for (argument in postponedArguments) {
val variableWasFixed = postponedArgumentsInputTypesResolver.fixNextReadyVariableForParameterTypeIfNeeded(
@@ -152,21 +149,33 @@ class ConstraintSystemCompleter(components: BodyResolveComponents, private val c
continue
// Stage 7: try to complete call with the builder inference if there are uninferred type variables
val allTypeVariables = getOrderedAllTypeVariables(collectVariablesFromContext, topLevelAtoms)
val areThereAppearedProperConstraintsForSomeVariable = tryToCompleteWithBuilderInference(
completionMode, topLevelType, postponedArguments, allTypeVariables, analyze
val areThereAppearedProperConstraintsForSomeVariable = tryToCompleteWithPCLA(
completionMode, postponedArguments, analyze,
)
if (areThereAppearedProperConstraintsForSomeVariable)
continue
if (completionMode == ConstraintSystemCompletionMode.PCLA_POSTPONED_CALL) {
// Complete all lambdas, maybe with fixing type variables used as top-level input types.
// It's necessary because we need to process all data-flow info before going to the next statement.
if (analyzeRemainingNotAnalyzedPostponedArgument(postponedArguments, analyze))
continue
}
// Stage 8: report "not enough information" for uninferred type variables
reportNotEnoughTypeInformation(
completionMode, topLevelAtoms, topLevelType, allTypeVariables, postponedArguments
completionMode, topLevelAtoms, topLevelType, postponedArguments
)
if (completionMode == ConstraintSystemCompletionMode.PCLA_POSTPONED_CALL) {
reportNotEnoughInformationForTypeVariablesRequiredForInputTypesOfLambdas(
postponedArguments, topLevelType, dependencyProvider, topLevelAtoms
)
}
// Stage 9: force analysis of remaining not analyzed postponed arguments and rerun stages if there are
if (completionMode == ConstraintSystemCompletionMode.FULL) {
if (completionMode.allLambdasShouldBeAnalyzed) {
if (analyzeRemainingNotAnalyzedPostponedArgument(postponedArguments, analyze))
continue
}
@@ -175,20 +184,61 @@ class ConstraintSystemCompleter(components: BodyResolveComponents, private val c
}
}
private fun ConstraintSystemCompletionContext.reportNotEnoughInformationForTypeVariablesRequiredForInputTypesOfLambdas(
postponedArguments: List<PostponedResolvedAtom>,
topLevelType: ConeKotlinType,
dependencyProvider: TypeVariableDependencyInformationProvider,
topLevelAtoms: List<FirStatement>,
) {
for (argument in postponedArguments) {
val variableForFixation = postponedArgumentsInputTypesResolver.findNextVariableForReportingNotInferredInputType(
this,
argument,
postponedArguments,
topLevelType,
dependencyProvider,
) ?: continue
assert(!variableForFixation.isReady) {
"At this stage there should be no remaining variables with proper constraints from input types"
}
val variableWithConstraints = notFixedTypeVariables.getValue(variableForFixation.variable)
processVariableWhenNotEnoughInformation(variableWithConstraints, topLevelAtoms)
}
}
private fun ConstraintSystemCompletionContext.findFirstVariableForFixation(
collectVariablesFromContext: Boolean,
topLevelAtoms: List<FirStatement>,
postponedArguments: List<PostponedResolvedAtom>,
completionMode: ConstraintSystemCompletionMode,
topLevelType: ConeKotlinType,
): VariableFixationFinder.VariableForFixation? {
return variableFixationFinder.findFirstVariableForFixation(
this,
getOrderedAllTypeVariables(
collectVariablesFromContext,
topLevelAtoms
),
postponedArguments,
completionMode,
topLevelType
)
}
/**
* General documentation for builder inference algorithm is located at `/docs/fir/builder_inference.md`
*
* This function checks if any of the postponed arguments are suitable for builder inference, and performs it for all eligible lambda arguments
* @return true if we got new proper constraints after builder inference
*/
private fun ConstraintSystemCompletionContext.tryToCompleteWithBuilderInference(
private fun ConstraintSystemCompletionContext.tryToCompleteWithPCLA(
completionMode: ConstraintSystemCompletionMode,
topLevelType: ConeKotlinType,
postponedArguments: List<PostponedResolvedAtom>,
allTypeVariables: List<TypeConstructorMarker>,
analyze: (PostponedResolvedAtom) -> Unit,
): Boolean {
if (completionMode != ConstraintSystemCompletionMode.FULL) return false
if (!completionMode.allLambdasShouldBeAnalyzed) return false
// If we use the builder inference anyway (if the annotation is presented), then we are already analysed builder inference lambdas
if (!languageVersionSettings.supportsFeature(LanguageFeature.UseBuilderInferenceOnlyIfNeeded)) return false
@@ -198,56 +248,17 @@ class ConstraintSystemCompleter(components: BodyResolveComponents, private val c
fun ResolvedLambdaAtom.notFixedInputTypeVariables(): List<TypeVariableTypeConstructorMarker> =
inputTypes.flatMap { it.extractTypeVariables() }.filter { it !in fixedTypeVariables }
val checkForDangerousBuilderInference =
!languageVersionSettings.supportsFeature(LanguageFeature.NoBuilderInferenceWithoutAnnotationRestriction)
// Let's call builder lambda (BL) a lambda that has non-zero not fixed input type variables in
// type arguments of it's input types
// ex: MutableList<T>.() -> Unit
// During type inference of call-site such lambda will be considered BL, if
// T not fixed yet
// Given we have two or more builder lambdas among postponed arguments, it could result in incorrect type inference due to
// incorrect constraint propagation into common system
// See KT-53740
// Constraint propagation into common system happens at
// org.jetbrains.kotlin.resolve.calls.components.PostponedArgumentsAnalyzer.applyResultsOfAnalyzedLambdaToCandidateSystem
val dangerousBuilderInferenceWithoutAnnotation =
lambdaArguments.size >= 2 && lambdaArguments.count { it.notFixedInputTypeVariables().isNotEmpty() } >= 2
// We assume useBuilderInferenceWithoutAnnotation = true for FIR
val builder = getBuilder()
var anyAnalyzed = false
for (argument in lambdaArguments) {
val reallyHasBuilderInferenceAnnotation = argument.isCorrespondingParameterAnnotatedWithBuilderInference
val notFixedInputTypeVariables = argument.notFixedInputTypeVariables()
if (notFixedInputTypeVariables.isEmpty()) continue
// we have dangerous inference situation
// if lambda annotated with BuilderInference it's probably safe, due to type shape
// otherwise report multi-lambda builder inference restriction diagnostic
if (checkForDangerousBuilderInference && dangerousBuilderInferenceWithoutAnnotation && !reallyHasBuilderInferenceAnnotation) {
for (variable in notFixedInputTypeVariables) {
addError(AnonymousFunctionBasedMultiLambdaBuilderInferenceRestriction(argument.atom, variable.typeParameter!!))
}
}
for (variable in notFixedInputTypeVariables) {
builder.markPostponedVariable(notFixedTypeVariables.getValue(variable).typeVariable)
}
analyze(argument)
anyAnalyzed = true
}
val variableForFixation = variableFixationFinder.findFirstVariableForFixation(
this, allTypeVariables, postponedArguments, completionMode, topLevelType
)
// continue completion (rerun stages) only if ready for fixation variables with proper constraints have appeared
// (after analysing a lambda with the builder inference)
// otherwise we don't continue and report "not enough type information" error
return variableForFixation?.hasProperConstraint == true
return anyAnalyzed
}
private fun transformToAtomWithNewFunctionExpectedType(
@@ -277,16 +288,12 @@ class ConstraintSystemCompleter(components: BodyResolveComponents, private val c
collectVariablesFromContext: Boolean,
postponedArguments: List<PostponedResolvedAtom>,
): Boolean {
val variableForFixation = variableFixationFinder.findFirstVariableForFixation(
this,
getOrderedAllTypeVariables(collectVariablesFromContext, topLevelAtoms),
postponedArguments,
completionMode,
topLevelType
val variableForFixation = findFirstVariableForFixation(
collectVariablesFromContext, topLevelAtoms, postponedArguments, completionMode, topLevelType
) ?: return false
val variableWithConstraints = notFixedTypeVariables.getValue(variableForFixation.variable)
if (!variableForFixation.hasProperConstraint) return false
if (!variableForFixation.isReady) return false
fixVariable(this, variableWithConstraints)
@@ -297,19 +304,17 @@ class ConstraintSystemCompleter(components: BodyResolveComponents, private val c
completionMode: ConstraintSystemCompletionMode,
topLevelAtoms: List<FirStatement>,
topLevelType: ConeKotlinType,
allTypeVariables: List<TypeConstructorMarker>,
postponedArguments: List<PostponedResolvedAtom>,
) {
while (true) {
val variableForFixation = variableFixationFinder.findFirstVariableForFixation(
this, allTypeVariables, postponedArguments, completionMode, topLevelType,
) ?: break
assert(!variableForFixation.hasProperConstraint) {
val variableForFixation =
findFirstVariableForFixation(false, topLevelAtoms, postponedArguments, completionMode, topLevelType)
?: break
assert(!variableForFixation.isReady) {
"At this stage there should be no remaining variables with proper constraints"
}
if (completionMode == ConstraintSystemCompletionMode.PARTIAL) break
if (completionMode == ConstraintSystemCompletionMode.PARTIAL || completionMode == ConstraintSystemCompletionMode.PCLA_POSTPONED_CALL) break
val variableWithConstraints = notFixedTypeVariables.getValue(variableForFixation.variable)
processVariableWhenNotEnoughInformation(variableWithConstraints, topLevelAtoms)
@@ -399,30 +404,9 @@ class ConstraintSystemCompleter(components: BodyResolveComponents, private val c
topLevelAtom.collectAllTypeVariables()
}
checkNotFixedTypeVariablesCountConsistency(result)
return result.toList()
}
private fun ConstraintSystemCompletionContext.checkNotFixedTypeVariablesCountConsistency(
result: LinkedHashSet<TypeConstructorMarker>,
) {
val notFixedTypeVariablesToUse =
when (outerSystemVariablesPrefixSize) {
0 -> notFixedTypeVariables.keys
else -> notFixedTypeVariables.keys.toMutableSet().apply {
removeAll(allTypeVariables.keys.take(outerSystemVariablesPrefixSize).toSet())
}
}
require(result.size == notFixedTypeVariablesToUse.size) {
val notFoundTypeVariables = notFixedTypeVariablesToUse.toMutableSet().apply {
removeAll(result)
}
"Not all type variables found: $notFoundTypeVariables"
}
}
private fun fixVariable(
c: ConstraintSystemCompletionContext,
variableWithConstraints: VariableWithConstraints,
@@ -581,13 +565,21 @@ private fun FirResolvable.processCandidateIfApplicable(
val candidate = (calleeReference as? FirNamedReferenceWithCandidate)?.candidate ?: return
processor(candidate)
val visited = mutableSetOf<FirStatement>()
for (atom in candidate.postponedAtoms) {
if (atom !is ResolvedLambdaAtom || !atom.analyzed) continue
atom.returnStatements.forEach {
visited += it
it.processAllContainingCallCandidates(processBlocks, processor)
}
}
for (call in candidate.postponedPCLACalls) {
if (!visited.add(call)) continue
call.processAllContainingCallCandidates(processBlocks, processor)
}
}
val Candidate.csBuilder: NewConstraintSystemImpl get() = system.getBuilder()
@@ -1,430 +0,0 @@
/*
* 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.fir.resolve.inference
import org.jetbrains.kotlin.config.LanguageFeature
import org.jetbrains.kotlin.fir.FirElement
import org.jetbrains.kotlin.fir.FirSession
import org.jetbrains.kotlin.fir.declarations.FirAnonymousFunction
import org.jetbrains.kotlin.fir.declarations.FirDeclaration
import org.jetbrains.kotlin.fir.declarations.hasAnnotation
import org.jetbrains.kotlin.fir.expressions.*
import org.jetbrains.kotlin.fir.languageVersionSettings
import org.jetbrains.kotlin.fir.resolve.calls.Candidate
import org.jetbrains.kotlin.fir.resolve.calls.ImplicitExtensionReceiverValue
import org.jetbrains.kotlin.fir.resolve.calls.ResolutionContext
import org.jetbrains.kotlin.fir.resolve.calls.candidate
import org.jetbrains.kotlin.fir.resolve.inference.model.ConeBuilderInferenceSubstitutionConstraintPosition
import org.jetbrains.kotlin.fir.resolve.substitution.AbstractConeSubstitutor
import org.jetbrains.kotlin.fir.resolve.substitution.ChainedSubstitutor
import org.jetbrains.kotlin.fir.resolve.substitution.ConeSubstitutor
import org.jetbrains.kotlin.fir.resolve.substitution.replaceStubsAndTypeVariablesToErrors
import org.jetbrains.kotlin.fir.types.*
import org.jetbrains.kotlin.fir.visitors.FirDefaultTransformer
import org.jetbrains.kotlin.fir.visitors.transformSingle
import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.resolve.calls.inference.ConstraintSystemBuilder
import org.jetbrains.kotlin.resolve.calls.inference.buildAbstractResultingSubstitutor
import org.jetbrains.kotlin.resolve.calls.inference.components.ConstraintSystemCompletionMode
import org.jetbrains.kotlin.resolve.calls.inference.model.*
import org.jetbrains.kotlin.resolve.calls.inference.registerTypeVariableIfNotPresent
import org.jetbrains.kotlin.resolve.descriptorUtil.BUILDER_INFERENCE_ANNOTATION_FQ_NAME
import org.jetbrains.kotlin.types.model.TypeConstructorMarker
import org.jetbrains.kotlin.types.model.TypeSubstitutorMarker
import org.jetbrains.kotlin.types.model.safeSubstitute
/**
* General documentation for builder inference algorithm is located at `/docs/fir/builder_inference.md`
*/
class FirBuilderInferenceSession(
private val lambda: FirAnonymousFunction,
resolutionContext: ResolutionContext,
private val stubsForPostponedVariables: Map<ConeTypeVariable, ConeStubType>,
) : FirInferenceSessionForChainedResolve(resolutionContext) {
private val session = resolutionContext.session
private val commonCalls: MutableList<Pair<FirStatement, Candidate>> = mutableListOf()
private var lambdaImplicitReceivers: MutableList<ImplicitExtensionReceiverValue> = mutableListOf()
override fun <T> shouldRunCompletion(call: T): Boolean where T : FirResolvable, T : FirStatement {
val candidate = call.candidate
val system = candidate.system
if (system.hasContradiction) return true
if (!candidate.isSuitableForBuilderInference()) return true
val storage = system.getBuilder().currentStorage()
if (call.hasPostponed()) return true
return storage.notFixedTypeVariables.keys.all {
val variable = storage.allTypeVariables[it]
val isPostponed = variable != null && variable in storage.postponedTypeVariables
isPostponed || components.callCompleter.completer.variableFixationFinder.isTypeVariableHasProperConstraint(system, it)
}
}
private fun Candidate.isSuitableForBuilderInference(): Boolean {
val extensionReceiver = chosenExtensionReceiver
val dispatchReceiver = dispatchReceiver
return when {
extensionReceiver == null && dispatchReceiver == null -> false
dispatchReceiver?.resolvedType?.containsStubType() == true -> true
extensionReceiver?.resolvedType?.containsStubType() == true -> symbol.fir.hasBuilderInferenceAnnotation(session)
else -> false
}
}
private fun ConeKotlinType.containsStubType(): Boolean {
return this.contains {
it is ConeStubTypeForChainInference
}
}
private fun FirStatement.hasPostponed(): Boolean {
var result = false
processAllContainingCallCandidates(processBlocks = false) {
result = result || it.hasPostponed()
}
return result
}
private fun Candidate.hasPostponed(): Boolean {
return postponedAtoms.any { !it.analyzed }
}
fun addLambdaImplicitReceiver(receiver: ImplicitExtensionReceiverValue) {
lambdaImplicitReceivers += receiver
}
override fun <T> addCompletedCall(call: T, candidate: Candidate) where T : FirResolvable, T : FirStatement {
if (skipCall(call)) return
commonCalls += call to candidate
}
@Suppress("UNUSED_PARAMETER")
private fun <T> skipCall(call: T): Boolean where T : FirResolvable, T : FirStatement {
// TODO: what is FIR analog?
// if (descriptor is FakeCallableDescriptorForObject) return true
// if (!DescriptorUtils.isObject(descriptor) && isInLHSOfDoubleColonExpression(callInfo)) return true
return false
}
override fun inferPostponedVariables(
lambda: ResolvedLambdaAtom,
constraintSystemBuilder: ConstraintSystemBuilder,
completionMode: ConstraintSystemCompletionMode,
candidate: Candidate
): Map<ConeTypeVariableTypeConstructor, ConeKotlinType>? {
val (commonSystem, effectivelyEmptyConstraintSystem) = buildCommonSystem(constraintSystemBuilder.currentStorage())
val resultingSubstitutor by lazy { getResultingSubstitutor(commonSystem) }
if (effectivelyEmptyConstraintSystem) {
updateCalls(resultingSubstitutor)
return null
}
val context = commonSystem.asConstraintSystemCompleterContext()
components.callCompleter.completer.complete(
context,
completionMode,
partiallyResolvedCalls.map { it.first as FirStatement },
components.session.builtinTypes.unitType.type, resolutionContext,
collectVariablesFromContext = true
) {
error("Shouldn't be called in complete constraint system mode")
}
if (completionMode == ConstraintSystemCompletionMode.FULL) {
constraintSystemBuilder.substituteFixedVariables(resultingSubstitutor)
}
if (!session.languageVersionSettings.supportsFeature(LanguageFeature.NoAdditionalErrorsInK1DiagnosticReporter)) {
for (error in commonSystem.errors) {
candidate.system.addError(error)
}
}
updateCalls(resultingSubstitutor)
@Suppress("UNCHECKED_CAST")
return commonSystem.fixedTypeVariables as Map<ConeTypeVariableTypeConstructor, ConeKotlinType>
}
private fun buildCommonSystem(initialStorage: ConstraintStorage): Pair<NewConstraintSystemImpl, Boolean> {
// TODO(KT-64034): Missing handling of parent builder inference sessions
// - See [org.jetbrains.kotlin.resolve.calls.inference.BuilderInferenceSession.initializeCommonSystem]
val commonSystem = components.session.inferenceComponents.createConstraintSystem()
val nonFixedToVariablesSubstitutor = createNonFixedTypeToVariableSubstitutor()
var effectivelyEmptyCommonSystem =
!integrateConstraints(commonSystem, initialStorage, nonFixedToVariablesSubstitutor, false)
for ((_, candidate) in commonCalls) {
val hasConstraints =
integrateConstraints(commonSystem, candidate.system.asReadOnlyStorage(), nonFixedToVariablesSubstitutor, false)
if (hasConstraints) effectivelyEmptyCommonSystem = false
}
for ((_, candidate) in partiallyResolvedCalls) {
val hasConstraints =
integrateConstraints(commonSystem, candidate.system.asReadOnlyStorage(), nonFixedToVariablesSubstitutor, true)
if (hasConstraints) effectivelyEmptyCommonSystem = false
}
return commonSystem to effectivelyEmptyCommonSystem
}
private fun createNonFixedTypeToVariableSubstitutor(): ConeSubstitutor {
val typeContext = components.session.typeContext
val bindings = mutableMapOf<TypeConstructorMarker, ConeKotlinType>()
for ((variable, nonFixedType) in stubsForPostponedVariables) {
bindings[nonFixedType.constructor] = variable.defaultType
}
return typeContext.typeSubstitutorByTypeConstructor(bindings)
}
private fun integrateConstraints(
commonSystem: NewConstraintSystemImpl,
storage: ConstraintStorage,
nonFixedToVariablesSubstitutor: ConeSubstitutor,
shouldIntegrateAllConstraints: Boolean
): Boolean {
storage.notFixedTypeVariables.values.forEach { commonSystem.registerTypeVariableIfNotPresent(it.typeVariable) }
/*
* storage can contain the following substitutions:
* TypeVariable(A) -> ProperType
* TypeVariable(B) -> Special-Non-Fixed-Type
*
* while substitutor from parameter map non-fixed types to the original type variable
* */
val callSubstitutor =
storage.buildAbstractResultingSubstitutor(commonSystem, transformTypeVariablesToErrorTypes = false) as ConeSubstitutor
var introducedConstraint = false
for (initialConstraint in storage.initialConstraints) {
if (initialConstraint.position is BuilderInferencePosition) continue
if (integrateConstraintToSystem(
commonSystem, initialConstraint, callSubstitutor, nonFixedToVariablesSubstitutor
)
) {
introducedConstraint = true
}
}
if (shouldIntegrateAllConstraints) {
for ((variableConstructor, type) in storage.fixedTypeVariables) {
val typeVariable = storage.allTypeVariables.getValue(variableConstructor)
commonSystem.registerTypeVariableIfNotPresent(typeVariable)
commonSystem.addEqualityConstraint((typeVariable as ConeTypeVariable).defaultType, type, BuilderInferencePosition)
introducedConstraint = true
}
}
return introducedConstraint
}
private fun extractCommonCapturedTypes(lower: ConeKotlinType, upper: ConeKotlinType): List<ConeCapturedType> {
val extractedCapturedTypes = mutableSetOf<ConeCapturedType>().also { extractCapturedTypesTo(lower, it) }
return extractedCapturedTypes.filter { capturedType ->
upper.contains { it is ConeCapturedType && it.constructor === capturedType.constructor }
}
}
private fun extractCapturedTypesTo(type: ConeKotlinType, to: MutableSet<ConeCapturedType>) {
if (type is ConeCapturedType) {
to.add(type)
}
for (typeArgument in type.typeArguments) {
if (typeArgument !is ConeKotlinTypeProjection) continue
extractCapturedTypesTo(typeArgument.type, to)
}
}
private fun getResultingSubstitutor(commonSystem: NewConstraintSystemImpl): ConeSubstitutor {
val nonFixedToVariablesSubstitutor = createNonFixedTypeToVariableSubstitutor()
val commonSystemSubstitutor = commonSystem.buildCurrentSubstitutor() as ConeSubstitutor
return ChainedSubstitutor(nonFixedToVariablesSubstitutor, commonSystemSubstitutor)
.replaceStubsAndTypeVariablesToErrors(resolutionContext.typeContext, stubsForPostponedVariables.values.map { it.constructor })
}
private fun updateCalls(substitutor: ConeSubstitutor) {
val stubTypeSubstitutor = FirStubTypeTransformer(substitutor)
lambda.transformSingle(stubTypeSubstitutor, null)
// TODO: Builder inference should not modify implicit receivers. KT-54708
for (receiver in lambdaImplicitReceivers) {
@Suppress("DEPRECATION_ERROR")
receiver.updateTypeInBuilderInference(substitutor.substituteOrSelf(receiver.type))
}
val completionResultsWriter = components.callCompleter.createCompletionResultsWriter(substitutor)
for ((call, _) in partiallyResolvedCalls) {
call.transformSingle(completionResultsWriter, null)
}
}
private fun integrateConstraintToSystem(
commonSystem: NewConstraintSystemImpl,
initialConstraint: InitialConstraint,
callSubstitutor: ConeSubstitutor,
nonFixedToVariablesSubstitutor: ConeSubstitutor,
): Boolean {
val substitutedConstraintWith =
initialConstraint.substitute(callSubstitutor).substituteNonFixedToVariables(nonFixedToVariablesSubstitutor)
val substitutedA = substitutedConstraintWith.a
val substitutedB = substitutedConstraintWith.b
if (commonSystem.isProperType(substitutedA) && (substitutedA == substitutedB || commonSystem.isProperType(substitutedB))) return false
val position = substitutedConstraintWith.position
when (initialConstraint.constraintKind) {
ConstraintKind.LOWER -> error("LOWER constraint shouldn't be used, please use UPPER")
ConstraintKind.UPPER -> commonSystem.addSubtypeConstraint(substitutedA, substitutedB, position)
ConstraintKind.EQUALITY ->
with(commonSystem) {
addSubtypeConstraint(substitutedA, substitutedB, position)
addSubtypeConstraint(substitutedB, substitutedA, position)
}
}
return true
}
private fun InitialConstraint.substitute(substitutor: TypeSubstitutorMarker): InitialConstraint {
val substitutedA = substitutor.safeSubstitute(resolutionContext.typeContext, this.a)
val substitutedB = substitutor.safeSubstitute(resolutionContext.typeContext, this.b)
if (substitutedA == a && substitutedB == b) return this
// TODO(KT-64033): Missing check for ForbidInferringPostponedTypeVariableIntoDeclaredUpperBound language feature
return InitialConstraint(
substitutedA,
substitutedB,
this.constraintKind,
ConeBuilderInferenceSubstitutionConstraintPosition(this)
)
}
/**
* This function substitutes stub types in constraint with the corresponding type variable
* Please make sure to pass correct {Stub(Tv) => Tv} substitutor
*
* E.g.:
* ```
* nonFixedToVariablesSubstitutor = {Stub(Tv) => Tv, Stub(Ov) => Ov}
* constraint: A<Stub(Tv)> <: B<Stub(Ov)> -> A<Tv> <: B<Ov>
* ```
*
* The main reason for this function's existence is the custom handling of captured types.
* See KT-64027
*
* @return Constraint, substituted according to the [nonFixedToVariablesSubstitutor]
*/
private fun InitialConstraint.substituteNonFixedToVariables(
nonFixedToVariablesSubstitutor: ConeSubstitutor
): InitialConstraint {
require(constraintKind != ConstraintKind.LOWER)
// TODO(KT-63996, KT-64027): This function assumes types passed in lower, upper order which isn't true for equality constraints
val commonCapTypes = extractCommonCapturedTypes(lower = a as ConeKotlinType, upper = b as ConeKotlinType)
// TODO(KT-64027): This logic tries to work-around the problem with type substitution consistency in captured types
// In order to preserve consistency we collect captured types from both a and b and substitute them collectively
// E.g:
// substitutor = { B => W }
// a = C<CapturedType(out B)_0>, b = D<CapturedType(out B)_0>
// commonCapTypes = [CapturedType(out B)_0]
// capTypesSubstitutor = { CapturedTypeConstructor_0 => CapturedType(out W)_1 }
// substitutedA = C<CapturedType(out W)_1>
// substitutedB = D<CapturedType(out W)_1>
val substitutedCommonCapType = commonCapTypes.associate {
it.constructor to nonFixedToVariablesSubstitutor.substituteOrSelf(it)
}
val capTypesSubstitutor = object : AbstractConeSubstitutor(resolutionContext.typeContext) {
override fun substituteType(type: ConeKotlinType): ConeKotlinType? {
if (type !is ConeCapturedType) return null
return substitutedCommonCapType[type.constructor]
}
}
val substitutedA = nonFixedToVariablesSubstitutor.safeSubstitute(
resolutionContext.typeContext,
capTypesSubstitutor.safeSubstitute(resolutionContext.typeContext, this.a)
)
val substitutedB = nonFixedToVariablesSubstitutor.safeSubstitute(
resolutionContext.typeContext,
capTypesSubstitutor.safeSubstitute(resolutionContext.typeContext, this.b)
)
return InitialConstraint(
substitutedA,
substitutedB,
constraintKind,
position
)
}
}
class FirStubTypeTransformer(private val substitutor: ConeSubstitutor) : FirDefaultTransformer<Nothing?>() {
override fun <E : FirElement> transformElement(element: E, data: Nothing?): E {
// All resolvable nodes should be implemented separately to cover substitution of receivers in the candidate
if (element is FirResolvable) {
element.candidate()?.let { processCandidate(it) }
}
// Since FirExpressions don't have typeRefs, they need to be updated separately.
// FirAnonymousFunctionExpression doesn't support replacing the type
// since it delegates the getter to the underlying FirAnonymousFunction.
if (element is FirExpression && element !is FirAnonymousFunctionExpression) {
// TODO Check why some expressions have unresolved type in builder inference session KT-61835
@OptIn(UnresolvedExpressionTypeAccess::class)
element.coneTypeOrNull
?.let(substitutor::substituteOrNull)
?.let { element.replaceConeTypeOrNull(it) }
}
@Suppress("UNCHECKED_CAST")
return element.transformChildren(this, data = null) as E
}
override fun transformTypeOperatorCall(typeOperatorCall: FirTypeOperatorCall, data: Nothing?): FirStatement {
if (typeOperatorCall.argument.resolvedType is ConeStubType) {
typeOperatorCall.replaceArgFromStubType(true)
}
return super.transformTypeOperatorCall(typeOperatorCall, data)
}
override fun transformResolvedTypeRef(resolvedTypeRef: FirResolvedTypeRef, data: Nothing?): FirTypeRef =
substitutor.substituteOrNull(resolvedTypeRef.type)?.let {
resolvedTypeRef.withReplacedConeType(it)
} ?: resolvedTypeRef
/*
* We should manually update all receivers in the all not completed candidates, because not all calls with candidates
* contained in partiallyResolvedCalls and candidate stores not receiver values, which are updated, (TODO: remove this comment after removal of updating values)
* and receivers of candidates are not direct FIR children of calls, so they won't be visited during regular transformChildren
*/
private fun processCandidate(candidate: Candidate) {
candidate.dispatchReceiver = candidate.dispatchReceiver?.transform(this, data = null)
candidate.chosenExtensionReceiver = candidate.chosenExtensionReceiver?.transform(this, data = null)
candidate.contextReceiverArguments = candidate.contextReceiverArguments?.map { it.transform(this, data = null) }
}
}
private val BUILDER_INFERENCE_ANNOTATION_CLASS_ID: ClassId = ClassId.topLevel(BUILDER_INFERENCE_ANNOTATION_FQ_NAME)
fun FirDeclaration.hasBuilderInferenceAnnotation(session: FirSession): Boolean =
hasAnnotation(BUILDER_INFERENCE_ANNOTATION_CLASS_ID, session)
@@ -40,9 +40,8 @@ import org.jetbrains.kotlin.resolve.calls.inference.addEqualityConstraintIfCompa
import org.jetbrains.kotlin.resolve.calls.inference.addSubtypeConstraintIfCompatible
import org.jetbrains.kotlin.resolve.calls.inference.buildAbstractResultingSubstitutor
import org.jetbrains.kotlin.resolve.calls.inference.components.ConstraintSystemCompletionMode
import org.jetbrains.kotlin.resolve.calls.inference.model.ConstraintStorage
import org.jetbrains.kotlin.types.TypeApproximatorConfiguration
import org.jetbrains.kotlin.types.model.StubTypeMarker
import org.jetbrains.kotlin.types.model.TypeVariableMarker
import org.jetbrains.kotlin.types.model.safeSubstitute
import org.jetbrains.kotlin.utils.addToStdlib.runIf
@@ -80,18 +79,11 @@ class FirCallCompleter(
val completionMode = candidate.computeCompletionMode(
session.inferenceComponents, resolutionMode, initialType
).let {
// The difference between `shouldAvoidFullCompletion` and `!shouldRunCompletion` is very subtle:
// we don't run even partial completion for `!inferenceSession.shouldRunCompletion(call)`, while actually
// do that for `shouldAvoidFullCompletion`
//
// As for implementations, `shouldRunCompletion` only works for Builder inference, while `shouldAvoidFullCompletion` is for
// delegate inference where it's assumed to have partially completed intermediate calls.
//
// Ideally, we should get rid of `shouldRunCompletion` once Builder inference is rewritten (see KT-61041 for tracking)
if (it == ConstraintSystemCompletionMode.FULL && inferenceSession.shouldAvoidFullCompletion(call))
ConstraintSystemCompletionMode.PARTIAL
else
it
when {
it == ConstraintSystemCompletionMode.FULL ->
inferenceSession.customCompletionModeInsteadOfFull(call) ?: ConstraintSystemCompletionMode.FULL
else -> it
}
}
val analyzer = createPostponedArgumentsAnalyzer(transformer.resolutionContext)
@@ -101,35 +93,27 @@ class FirCallCompleter(
return when (completionMode) {
ConstraintSystemCompletionMode.FULL -> {
if (inferenceSession.shouldRunCompletion(call)) {
runCompletionForCall(candidate, completionMode, call, initialType, analyzer)
val finalSubstitutor = candidate.system.asReadOnlyStorage()
.buildAbstractResultingSubstitutor(session.typeContext) as ConeSubstitutor
val completedCall = call.transformSingle(
FirCallCompletionResultsWriterTransformer(
session, components.scopeSession, finalSubstitutor,
components.returnTypeCalculator,
session.typeApproximator,
components.dataFlowAnalyzer,
components.integerLiteralAndOperatorApproximationTransformer,
components.samResolver,
components.context,
),
null
)
inferenceSession.addCompletedCall(completedCall, candidate)
completedCall
} else {
inferenceSession.processPartiallyResolvedCall(call, resolutionMode)
call
}
runCompletionForCall(candidate, completionMode, call, initialType, analyzer)
val finalSubstitutor = candidate.system.asReadOnlyStorage()
.buildAbstractResultingSubstitutor(session.typeContext) as ConeSubstitutor
call.transformSingle(
FirCallCompletionResultsWriterTransformer(
session, components.scopeSession, finalSubstitutor,
components.returnTypeCalculator,
session.typeApproximator,
components.dataFlowAnalyzer,
components.integerLiteralAndOperatorApproximationTransformer,
components.samResolver,
components.context,
),
null
)
}
ConstraintSystemCompletionMode.PARTIAL -> {
ConstraintSystemCompletionMode.PARTIAL, ConstraintSystemCompletionMode.PCLA_POSTPONED_CALL -> {
runCompletionForCall(candidate, completionMode, call, initialType, analyzer)
if (inferenceSession is FirDelegatedPropertyInferenceSession) {
inferenceSession.processPartiallyResolvedCall(call, resolutionMode)
}
inferenceSession.processPartiallyResolvedCall(call, resolutionMode, completionMode)
call
}
@@ -183,8 +167,8 @@ class FirCallCompleter(
completionMode: ConstraintSystemCompletionMode,
call: T,
initialType: ConeKotlinType,
analyzer: PostponedArgumentsAnalyzer? = null
) where T : FirResolvable, T : FirStatement {
analyzer: PostponedArgumentsAnalyzer? = null,
) where T : FirStatement {
@Suppress("NAME_SHADOWING")
val analyzer = analyzer ?: createPostponedArgumentsAnalyzer(transformer.resolutionContext)
completer.complete(
@@ -194,7 +178,7 @@ class FirCallCompleter(
initialType,
transformer.resolutionContext
) {
analyzer.analyze(candidate.system, it, candidate, completionMode)
analyzer.analyze(candidate.system, it, candidate)
}
}
@@ -251,8 +235,9 @@ class FirCallCompleter(
contextReceivers: List<ConeKotlinType>,
parameters: List<ConeKotlinType>,
expectedReturnType: ConeKotlinType?,
stubsForPostponedVariables: Map<TypeVariableMarker, StubTypeMarker>,
candidate: Candidate
candidate: Candidate,
withPCLASession: Boolean,
forOverloadByLambdaReturnType: Boolean,
): ReturnArgumentsAnalysisResult {
val lambdaArgument: FirAnonymousFunction = lambdaAtom.atom
val needItParam = lambdaArgument.valueParameters.isEmpty() && parameters.size == 1
@@ -355,29 +340,34 @@ class FirCallCompleter(
} ?: components.noExpectedType
)
val builderInferenceSession = runIf(stubsForPostponedVariables.isNotEmpty()) {
@Suppress("UNCHECKED_CAST")
FirBuilderInferenceSession(
lambdaArgument,
transformer.resolutionContext,
stubsForPostponedVariables as Map<ConeTypeVariable, ConeStubType>
)
}
var additionalConstraints: ConstraintStorage? = null
transformer.context.withAnonymousFunctionTowerDataContext(lambdaArgument.symbol) {
if (builderInferenceSession != null) {
transformer.context.withInferenceSession(builderInferenceSession) {
val pclaInferenceSession =
runIf(withPCLASession) {
candidate.lambdasAnalyzedWithPCLA += lambdaArgument
FirPCLAInferenceSession(candidate, session.inferenceComponents, transformer.context.returnTypeCalculator)
}
if (pclaInferenceSession != null) {
transformer.context.withInferenceSession(pclaInferenceSession) {
lambdaArgument.transformSingle(transformer, ResolutionMode.LambdaResolution(expectedReturnTypeRef))
applyResultsToMainCandidate()
}
} else {
lambdaArgument.transformSingle(transformer, ResolutionMode.LambdaResolution(expectedReturnTypeRef))
additionalConstraints =
transformer.context.inferenceSession.runLambdaCompletion(candidate, forOverloadByLambdaReturnType) {
lambdaArgument.transformSingle(transformer, ResolutionMode.LambdaResolution(expectedReturnTypeRef))
}
}
}
transformer.context.dropContextForAnonymousFunction(lambdaArgument)
val returnArguments = components.dataFlowAnalyzer.returnExpressionsOfAnonymousFunction(lambdaArgument).map { it.expression }
return ReturnArgumentsAnalysisResult(returnArguments, builderInferenceSession)
return ReturnArgumentsAnalysisResult(returnArguments, additionalConstraints)
}
}
@@ -385,7 +375,7 @@ class FirCallCompleter(
// We only run lambda completion from ConstraintSystemCompletionContext.analyzeRemainingNotAnalyzedPostponedArgument when they are
// left uninferred.
// Currently, we use stub types for builder inference, so CANNOT_INFER_PARAMETER_TYPE is the only possible result here.
if (this is ConeTypeVariableType) {
if (isTypeVariableThatShouldBeFixedBeforeLambdaAnalysis(isReceiver = valueParameter == null)) {
val diagnostic = valueParameter?.let(::ConeCannotInferValueParameterType) ?: ConeCannotInferReceiverParameterType()
return ConeErrorType(diagnostic)
}
@@ -394,6 +384,20 @@ class FirCallCompleter(
this, TypeApproximatorConfiguration.FinalApproximationAfterResolutionAndInference
) ?: this
}
private fun ConeKotlinType.isTypeVariableThatShouldBeFixedBeforeLambdaAnalysis(isReceiver: Boolean): Boolean {
if (this !is ConeTypeVariableType) return false
// Outside PCLA, all type variables for parameter types should be fixed before lambda analysis
// Inside PCLA, we force fixing receivers before lambda analysis
if (inferenceSession !is FirPCLAInferenceSession || isReceiver) return true
// For type variables not based on type parameters (created for lambda parameters with no expected type)
// we force them to be fixed before lambda analysis
if (typeConstructor.originalTypeParameter == null) return true
return false
}
}
private fun Candidate.isFunctionForExpectTypeFromCastFeature(): Boolean {
@@ -5,55 +5,72 @@
package org.jetbrains.kotlin.fir.resolve.inference
import org.jetbrains.kotlin.fir.declarations.FirProperty
import org.jetbrains.kotlin.fir.expressions.FirFunctionCall
import org.jetbrains.kotlin.fir.expressions.FirFunctionCallOrigin
import org.jetbrains.kotlin.fir.expressions.FirResolvable
import org.jetbrains.kotlin.fir.expressions.FirStatement
import org.jetbrains.kotlin.fir.FirElement
import org.jetbrains.kotlin.fir.expressions.*
import org.jetbrains.kotlin.fir.render
import org.jetbrains.kotlin.fir.resolve.BodyResolveComponents
import org.jetbrains.kotlin.fir.resolve.ResolutionMode
import org.jetbrains.kotlin.fir.resolve.calls.Candidate
import org.jetbrains.kotlin.fir.resolve.calls.InferenceError
import org.jetbrains.kotlin.fir.resolve.calls.ResolutionContext
import org.jetbrains.kotlin.fir.resolve.calls.candidate
import org.jetbrains.kotlin.fir.resolve.initialTypeOfCandidate
import org.jetbrains.kotlin.fir.resolve.substitution.ConeSubstitutor
import org.jetbrains.kotlin.fir.resolve.transformers.FirCallCompletionResultsWriterTransformer
import org.jetbrains.kotlin.fir.types.ConeClassLikeType
import org.jetbrains.kotlin.fir.types.ConeKotlinType
import org.jetbrains.kotlin.fir.types.ConeTypeVariableTypeConstructor
import org.jetbrains.kotlin.fir.types.typeContext
import org.jetbrains.kotlin.resolve.calls.inference.ConstraintSystemBuilder
import org.jetbrains.kotlin.fir.visitors.transformSingle
import org.jetbrains.kotlin.resolve.calls.inference.buildAbstractResultingSubstitutor
import org.jetbrains.kotlin.resolve.calls.inference.components.ConstraintSystemCompletionMode
import org.jetbrains.kotlin.resolve.calls.inference.model.ConstraintStorage
import org.jetbrains.kotlin.util.OperatorNameConventions
import org.jetbrains.kotlin.utils.addIfNotNull
class FirDelegatedPropertyInferenceSession(
val property: FirProperty,
resolutionContext: ResolutionContext,
private val postponedArgumentsAnalyzer: PostponedArgumentsAnalyzer,
) : FirInferenceSessionForChainedResolve(resolutionContext) {
private val resolutionContext: ResolutionContext,
private val callCompleter: FirCallCompleter,
private val delegateExpression: FirExpression,
) : FirInferenceSession() {
private val partiallyResolvedCalls: MutableList<Pair<FirResolvable, Candidate>> = mutableListOf()
private val components: BodyResolveComponents
get() = resolutionContext.bodyResolveComponents
private val FirResolvable.candidate: Candidate
get() = candidate()!!
private val nonTrivialParentSession: FirInferenceSession? =
resolutionContext.bodyResolveContext.inferenceSession.takeIf { it !== DEFAULT }
private val delegateCandidate = (delegateExpression as? FirResolvable)?.candidate()
private val parentConstraintSystem =
delegateCandidate?.system
?: (resolutionContext.bodyResolveContext.inferenceSession as? FirPCLAInferenceSession)?.currentCommonSystem
?: components.session.inferenceComponents.createConstraintSystem()
private val currentConstraintSystem =
prepareSharedBaseSystem(parentConstraintSystem, components.session.inferenceComponents)
private var currentConstraintSystem = components.session.inferenceComponents.createConstraintSystem()
val currentConstraintStorage: ConstraintStorage get() = currentConstraintSystem.currentStorage()
private val unitType: ConeClassLikeType = components.session.builtinTypes.unitType.type
// TODO after PCLA (KT-59107):
// Outer system seems to be a property of a concrete call resolution and probably should be applied to concrete call resolution
override fun <R> onCandidatesResolution(call: FirFunctionCall, candidatesResolutionCallback: () -> R): R {
return if (!call.isAnyOfDelegateOperators())
candidatesResolutionCallback()
else
resolutionContext.bodyResolveContext.withOuterConstraintStorage(
currentConstraintSystem.currentStorage(),
candidatesResolutionCallback
)
private var wasCompletionRun = false
override fun customCompletionModeInsteadOfFull(call: FirResolvable): ConstraintSystemCompletionMode? = when {
call.isAnyOfDelegateOperators() && !wasCompletionRun -> ConstraintSystemCompletionMode.PARTIAL
else -> null
}
override fun <T> shouldAvoidFullCompletion(call: T): Boolean where T : FirResolvable, T : FirStatement =
call.isAnyOfDelegateOperators()
override fun <T> processPartiallyResolvedCall(
call: T,
resolutionMode: ResolutionMode,
completionMode: ConstraintSystemCompletionMode,
) where T : FirResolvable, T : FirStatement {
if (wasCompletionRun || !call.isAnyOfDelegateOperators()) return
override fun <T> processPartiallyResolvedCall(call: T, resolutionMode: ResolutionMode) where T : FirResolvable, T : FirStatement {
if (resolutionMode != ResolutionMode.ContextDependent.Delegate && !call.isAnyOfDelegateOperators()) return
requireCallIsDelegateOperator(call)
val candidate = call.candidate
@@ -64,38 +81,84 @@ class FirDelegatedPropertyInferenceSession(
val candidateSystem = candidate.system
partiallyResolvedCalls.add(call to candidate)
currentConstraintSystem = candidateSystem
currentConstraintSystem.addOtherSystem(candidateSystem.currentStorage())
}
private fun <T> requireCallIsDelegateOperator(call: T) where T : FirResolvable, T : FirStatement {
require(call.isAnyOfDelegateOperators()) {
"Unexpected ${call.render()} call"
}
}
private fun <T> T.isProvideDelegate() where T : FirResolvable, T : FirStatement =
isAnyOfDelegateOperators() && (this as FirFunctionCall).calleeReference.name == OperatorNameConventions.PROVIDE_DELEGATE
isAnyOfDelegateOperators() && (this as FirResolvable).candidate()?.callInfo?.name == OperatorNameConventions.PROVIDE_DELEGATE
private fun <T> T.isAnyOfDelegateOperators(): Boolean where T : FirResolvable {
if (this !is FirFunctionCall || origin != FirFunctionCallOrigin.Operator) return false
val name = calleeReference.name
return name == OperatorNameConventions.PROVIDE_DELEGATE || name == OperatorNameConventions.GET_VALUE || name == OperatorNameConventions.SET_VALUE
override fun baseConstraintStorageForCandidate(candidate: Candidate): ConstraintStorage? {
if (wasCompletionRun || !candidate.callInfo.callSite.isAnyOfDelegateOperators()) return null
return currentConstraintStorage
}
override fun inferPostponedVariables(
lambda: ResolvedLambdaAtom,
constraintSystemBuilder: ConstraintSystemBuilder,
completionMode: ConstraintSystemCompletionMode,
candidate: Candidate
): Map<ConeTypeVariableTypeConstructor, ConeKotlinType>? = null
fun completeSessionOrPostponeIfNonRoot(onCompletionResultsWriting: (ConeSubstitutor) -> Unit) {
check(!wasCompletionRun)
wasCompletionRun = true
fun completeCandidates(): List<FirResolvable> {
val commonSystem = currentConstraintSystem.apply { prepareForGlobalCompletion() }
parentConstraintSystem.addOtherSystem(currentConstraintStorage)
val notCompletedCalls = partiallyResolvedCalls.mapNotNull { partiallyResolvedCall ->
partiallyResolvedCall.first.takeIf { resolvable ->
resolvable.candidate() != null
(nonTrivialParentSession as? FirPCLAInferenceSession)?.apply {
if (delegateCandidate != null) {
callCompleter.runCompletionForCall(
delegateCandidate,
ConstraintSystemCompletionMode.PCLA_POSTPONED_CALL,
delegateExpression,
components.initialTypeOfCandidate(delegateCandidate)
)
}
integrateChildSession(
buildList {
addIfNotNull(delegateExpression)
partiallyResolvedCalls.mapTo(this) { it.first as FirStatement }
},
parentConstraintSystem.currentStorage(),
onCompletionResultsWriting,
)
return
}
val completedCalls = completeCandidatesForRootSession()
val finalSubstitutor = parentConstraintSystem.asReadOnlyStorage()
.buildAbstractResultingSubstitutor(components.session.typeContext) as ConeSubstitutor
val callCompletionResultsWriter = callCompleter.createCompletionResultsWriter(
finalSubstitutor,
// TODO: Get rid of the mode
mode = FirCallCompletionResultsWriterTransformer.Mode.DelegatedPropertyCompletion
)
completedCalls.forEach {
it.transformSingle(callCompletionResultsWriter, null)
}
onCompletionResultsWriting(finalSubstitutor)
}
private fun completeCandidatesForRootSession(): List<FirResolvable> {
val parentSystem = parentConstraintSystem.apply { prepareForGlobalCompletion() }
val notCompletedCalls =
buildList {
addIfNotNull(delegateExpression as? FirResolvable)
partiallyResolvedCalls.mapNotNullTo(this) { partiallyResolvedCall ->
partiallyResolvedCall.first.takeIf { resolvable ->
resolvable.candidate() != null
}
}
}
resolutionContext.bodyResolveContext.withInferenceSession(DEFAULT) {
@Suppress("UNCHECKED_CAST")
components.callCompleter.completer.complete(
commonSystem.asConstraintSystemCompleterContext(),
parentSystem.asConstraintSystemCompleterContext(),
ConstraintSystemCompletionMode.FULL,
notCompletedCalls as List<FirStatement>,
unitType, resolutionContext
@@ -110,29 +173,31 @@ class FirDelegatedPropertyInferenceSession(
}
found
}.candidate
postponedArgumentsAnalyzer.analyze(
commonSystem,
callCompleter.createPostponedArgumentsAnalyzer(resolutionContext).analyze(
parentSystem,
lambdaAtom,
containingCandidateForLambda,
ConstraintSystemCompletionMode.FULL,
)
}
}
for ((_, candidate) in partiallyResolvedCalls) {
for (error in commonSystem.errors) {
for (candidate in notCompletedCalls.mapNotNull { it.candidate() }) {
for (error in parentSystem.errors) {
candidate.addDiagnostic(InferenceError(error))
}
}
return notCompletedCalls
}
fun createFinalSubstitutor(): ConeSubstitutor =
currentConstraintSystem.asReadOnlyStorage()
.buildAbstractResultingSubstitutor(components.session.typeContext) as ConeSubstitutor
override fun <T> addCompletedCall(call: T, candidate: Candidate) where T : FirResolvable, T : FirStatement {}
override fun <T> shouldRunCompletion(call: T): Boolean where T : FirResolvable, T : FirStatement = true
}
fun FirElement.isAnyOfDelegateOperators(): Boolean {
if (this is FirPropertyAccessExpression) {
val originalCall = this.candidate()?.callInfo?.callSite as? FirFunctionCall ?: return false
return originalCall.isAnyOfDelegateOperators()
}
if (this !is FirFunctionCall || origin != FirFunctionCallOrigin.Operator) return false
val name = calleeReference.name
return name == OperatorNameConventions.PROVIDE_DELEGATE || name == OperatorNameConventions.GET_VALUE || name == OperatorNameConventions.SET_VALUE
}
@@ -5,52 +5,57 @@
package org.jetbrains.kotlin.fir.resolve.inference
import org.jetbrains.kotlin.fir.expressions.FirFunctionCall
import org.jetbrains.kotlin.fir.FirElement
import org.jetbrains.kotlin.fir.expressions.FirResolvable
import org.jetbrains.kotlin.fir.expressions.FirStatement
import org.jetbrains.kotlin.fir.resolve.ResolutionMode
import org.jetbrains.kotlin.fir.resolve.calls.Candidate
import org.jetbrains.kotlin.fir.types.ConeKotlinType
import org.jetbrains.kotlin.fir.types.ConeTypeVariableTypeConstructor
import org.jetbrains.kotlin.resolve.calls.inference.ConstraintSystemBuilder
import org.jetbrains.kotlin.resolve.calls.inference.components.ConstraintSystemCompletionMode
import org.jetbrains.kotlin.resolve.calls.inference.model.ConstraintStorage
import org.jetbrains.kotlin.resolve.calls.inference.model.NewConstraintSystemImpl
abstract class FirInferenceSession {
companion object {
val DEFAULT: FirInferenceSession = object : FirStubInferenceSession() {}
val DEFAULT: FirInferenceSession = object : FirInferenceSession() {
override fun <T> processPartiallyResolvedCall(
call: T,
resolutionMode: ResolutionMode,
completionMode: ConstraintSystemCompletionMode,
) where T : FirResolvable, T : FirStatement {
// Do nothing
}
}
@JvmStatic
protected fun prepareSharedBaseSystem(
outerSystem: NewConstraintSystemImpl,
components: InferenceComponents,
): NewConstraintSystemImpl {
return components.createConstraintSystem().apply {
addOuterSystem(outerSystem.currentStorage())
}
}
}
abstract fun <T> shouldRunCompletion(call: T): Boolean where T : FirResolvable, T : FirStatement
open fun runLambdaCompletion(candidate: Candidate, forOverloadByLambdaReturnType: Boolean, block: () -> Unit): ConstraintStorage? {
block()
return null
}
/**
* In some cases (like getValue/setValue resolution of property delegation convention), it might be necessary to postpone full completion
* even with the ContextIndependent mode (which is used for delegated accessors bodies)
*/
open fun <T> shouldAvoidFullCompletion(call: T): Boolean where T : FirResolvable, T : FirStatement = false
open fun <T> runCallableReferenceResolution(candidate: Candidate, block: () -> T): T = block()
abstract fun <T> processPartiallyResolvedCall(call: T, resolutionMode: ResolutionMode) where T : FirResolvable, T : FirStatement
abstract fun <T> addCompletedCall(call: T, candidate: Candidate) where T : FirResolvable, T : FirStatement
open fun customCompletionModeInsteadOfFull(
call: FirResolvable,
): ConstraintSystemCompletionMode? = null
abstract fun inferPostponedVariables(
lambda: ResolvedLambdaAtom,
constraintSystemBuilder: ConstraintSystemBuilder,
completionMode: ConstraintSystemCompletionMode,
candidate: Candidate
): Map<ConeTypeVariableTypeConstructor, ConeKotlinType>?
abstract fun <T> processPartiallyResolvedCall(
call: T,
resolutionMode: ResolutionMode,
completionMode: ConstraintSystemCompletionMode
) where T : FirResolvable, T : FirStatement
open fun <R> onCandidatesResolution(call: FirFunctionCall, candidatesResolutionCallback: () -> R) = candidatesResolutionCallback()
}
abstract class FirStubInferenceSession : FirInferenceSession() {
override fun <T> shouldRunCompletion(call: T): Boolean where T : FirResolvable, T : FirStatement = true
override fun <T> processPartiallyResolvedCall(call: T, resolutionMode: ResolutionMode) where T : FirResolvable, T : FirStatement {}
override fun <T> addCompletedCall(call: T, candidate: Candidate) where T : FirResolvable, T : FirStatement {}
override fun inferPostponedVariables(
lambda: ResolvedLambdaAtom,
constraintSystemBuilder: ConstraintSystemBuilder,
completionMode: ConstraintSystemCompletionMode,
candidate: Candidate
): Map<ConeTypeVariableTypeConstructor, ConeKotlinType>? = null
open fun baseConstraintStorageForCandidate(candidate: Candidate): ConstraintStorage? = null
open fun addSubtypeConstraintIfCompatible(lowerType: ConeKotlinType, upperType: ConeKotlinType, element: FirElement) {}
}
@@ -1,34 +0,0 @@
/*
* 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.fir.resolve.inference
import org.jetbrains.kotlin.fir.expressions.FirResolvable
import org.jetbrains.kotlin.fir.expressions.FirStatement
import org.jetbrains.kotlin.fir.resolve.BodyResolveComponents
import org.jetbrains.kotlin.fir.resolve.ResolutionMode
import org.jetbrains.kotlin.fir.resolve.calls.Candidate
import org.jetbrains.kotlin.fir.resolve.calls.ResolutionContext
import org.jetbrains.kotlin.fir.resolve.calls.candidate
abstract class FirInferenceSessionForChainedResolve(
protected val resolutionContext: ResolutionContext
) : FirInferenceSession() {
protected val partiallyResolvedCalls: MutableList<Pair<FirResolvable, Candidate>> = mutableListOf()
protected val components: BodyResolveComponents
get() = resolutionContext.bodyResolveComponents
override fun <T> addCompletedCall(call: T, candidate: Candidate) where T : FirResolvable, T : FirStatement {
// do nothing
}
override fun <T> processPartiallyResolvedCall(call: T, resolutionMode: ResolutionMode) where T : FirResolvable, T : FirStatement {
partiallyResolvedCalls += call to call.candidate
}
protected val FirResolvable.candidate: Candidate
get() = candidate()!!
}
@@ -0,0 +1,312 @@
/*
* Copyright 2010-2023 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.fir.resolve.inference
import org.jetbrains.kotlin.fir.FirElement
import org.jetbrains.kotlin.fir.expressions.*
import org.jetbrains.kotlin.fir.resolve.ResolutionMode
import org.jetbrains.kotlin.fir.resolve.calls.CallKind
import org.jetbrains.kotlin.fir.resolve.calls.Candidate
import org.jetbrains.kotlin.fir.resolve.calls.candidate
import org.jetbrains.kotlin.fir.resolve.calls.processConstraintStorageFromExpression
import org.jetbrains.kotlin.fir.resolve.inference.model.ConeExpectedTypeConstraintPosition
import org.jetbrains.kotlin.fir.resolve.inference.model.ConeFixVariableConstraintPosition
import org.jetbrains.kotlin.fir.resolve.substitution.ConeSubstitutor
import org.jetbrains.kotlin.fir.resolve.transformers.ReturnTypeCalculator
import org.jetbrains.kotlin.fir.symbols.impl.FirVariableSymbol
import org.jetbrains.kotlin.fir.types.*
import org.jetbrains.kotlin.fir.visitors.FirDefaultTransformer
import org.jetbrains.kotlin.resolve.calls.inference.addSubtypeConstraintIfCompatible
import org.jetbrains.kotlin.resolve.calls.inference.components.ConstraintSystemCompletionMode
import org.jetbrains.kotlin.resolve.calls.inference.components.TypeVariableDirectionCalculator
import org.jetbrains.kotlin.resolve.calls.inference.model.ConstraintStorage
import org.jetbrains.kotlin.resolve.calls.inference.model.NewConstraintSystemImpl
import org.jetbrains.kotlin.types.model.TypeConstructorMarker
import org.jetbrains.kotlin.types.model.defaultType
class FirPCLAInferenceSession(
private val outerCandidate: Candidate,
private val inferenceComponents: InferenceComponents,
private val returnTypeCalculator: ReturnTypeCalculator,
) : FirInferenceSession() {
var currentCommonSystem = prepareSharedBaseSystem(outerCandidate.system, inferenceComponents)
private set
override fun baseConstraintStorageForCandidate(candidate: Candidate): ConstraintStorage? {
if (candidate.needsToBePostponed()) return currentCommonSystem.currentStorage()
return null
}
override fun customCompletionModeInsteadOfFull(
call: FirResolvable,
): ConstraintSystemCompletionMode? = when {
call.candidate()?.usedOuterCs == true -> ConstraintSystemCompletionMode.PCLA_POSTPONED_CALL
else -> null
}
override fun <T> processPartiallyResolvedCall(
call: T,
resolutionMode: ResolutionMode,
completionMode: ConstraintSystemCompletionMode,
) where T : FirResolvable, T : FirStatement {
if (call is FirExpression) {
call.updateReturnTypeWithCurrentSubstitutor(resolutionMode)
}
val candidate = call.candidate()
if (candidate?.usedOuterCs != true) return
// Integrating back would happen at FirDelegatedPropertyInferenceSession.completeSessionOrPostponeIfNonRoot
// after all other delegation-related calls are being analyzed
if (resolutionMode == ResolutionMode.ContextDependent.Delegate) return
currentCommonSystem.replaceContentWith(candidate.system.currentStorage())
if (completionMode == ConstraintSystemCompletionMode.PCLA_POSTPONED_CALL) {
outerCandidate.postponedPCLACalls += call
}
}
override fun runLambdaCompletion(candidate: Candidate, forOverloadByLambdaReturnType: Boolean, block: () -> Unit): ConstraintStorage? {
if (forOverloadByLambdaReturnType) {
val constraintAccumulatorForLambda =
inferenceComponents.createConstraintSystem().apply {
setBaseSystem(currentCommonSystem.currentStorage())
}
runWithSpecifiedCurrentCommonSystem(constraintAccumulatorForLambda, block)
return constraintAccumulatorForLambda.currentStorage()
}
runWithSpecifiedCurrentCommonSystem(candidate.system, block)
return null
}
override fun <T> runCallableReferenceResolution(candidate: Candidate, block: () -> T): T {
candidate.system.apply {
// It's necessary because otherwise when we create CS for a child, it would simplify constraints
// (see 3rd constructor of MutableVariableWithConstraints)
// and merging it back might become a problem for transaction logic because the latter literally remembers
// the number of constraints for each variable and then restores it back.
// But since the constraints are simplified in the child, their number might become even fewer, leading to incorrect behavior
// or runtime exceptions.
// See callableReferenceAsArgumentForTransaction.kt test data
notFixedTypeVariables.values.forEach { it.runConstraintsSimplification() }
}
return runWithSpecifiedCurrentCommonSystem(candidate.system, block)
}
private fun <T> runWithSpecifiedCurrentCommonSystem(newSystem: NewConstraintSystemImpl, block: () -> T): T {
val previous = currentCommonSystem
return try {
currentCommonSystem = newSystem
block()
} finally {
currentCommonSystem = previous
}
}
fun applyResultsToMainCandidate() {
outerCandidate.system.replaceContentWith(currentCommonSystem.currentStorage())
}
fun integrateChildSession(
childCalls: Collection<FirStatement>,
childStorage: ConstraintStorage,
onCompletionResultsWriting: (ConeSubstitutor) -> Unit,
) {
outerCandidate.postponedPCLACalls += childCalls
currentCommonSystem.addOtherSystem(childStorage)
outerCandidate.onCompletionResultsWritingCallbacks += onCompletionResultsWriting
}
private fun FirExpression.updateReturnTypeWithCurrentSubstitutor(
resolutionMode: ResolutionMode,
) {
val additionalBindings = mutableMapOf<TypeConstructorMarker, ConeKotlinType>()
val system = (this as? FirResolvable)?.candidate()?.system ?: currentCommonSystem
if (resolutionMode is ResolutionMode.ReceiverResolution) {
fixVariablesForMemberScope(resolvedType, system)?.let { additionalBindings += it }
}
val substitutor = system.buildCurrentSubstitutor(additionalBindings) as ConeSubstitutor
val updatedType = substitutor.substituteOrNull(resolvedType)
if (updatedType != null) {
replaceConeTypeOrNull(updatedType)
}
}
fun fixVariablesForMemberScope(
type: ConeKotlinType,
myCs: NewConstraintSystemImpl,
): Pair<ConeTypeVariableTypeConstructor, ConeKotlinType>? {
return when (type) {
is ConeFlexibleType -> fixVariablesForMemberScope(type.lowerBound, myCs)
is ConeDefinitelyNotNullType -> fixVariablesForMemberScope(type.original, myCs)
is ConeTypeVariableType -> fixVariablesForMemberScope(type, myCs)
else -> null
}
}
private fun fixVariablesForMemberScope(
type: ConeTypeVariableType,
myCs: NewConstraintSystemImpl,
): Pair<ConeTypeVariableTypeConstructor, ConeKotlinType>? {
val coneTypeVariableTypeConstructor = type.typeConstructor
require(coneTypeVariableTypeConstructor in myCs.allTypeVariables) {
"$coneTypeVariableTypeConstructor not found"
}
if (coneTypeVariableTypeConstructor in myCs.outerTypeVariables.orEmpty()) return null
val variableWithConstraints = myCs.notFixedTypeVariables[coneTypeVariableTypeConstructor] ?: return null
val c = myCs.getBuilder()
val resultType = c.run {
c.withTypeVariablesThatAreCountedAsProperTypes(c.outerTypeVariables.orEmpty()) {
if (!inferenceComponents.variableFixationFinder.isTypeVariableHasProperConstraint(c, coneTypeVariableTypeConstructor)) {
return@withTypeVariablesThatAreCountedAsProperTypes null
}
inferenceComponents.resultTypeResolver.findResultType(
c,
variableWithConstraints,
TypeVariableDirectionCalculator.ResolveDirection.UNKNOWN
) as ConeKotlinType
}
} ?: return null
val variable = variableWithConstraints.typeVariable
// TODO: Consider using different position (KT-64860)
c.addEqualityConstraint(variable.defaultType(c), resultType, ConeFixVariableConstraintPosition(variable))
return Pair(coneTypeVariableTypeConstructor, resultType)
}
private fun FirExpression.shouldBePostponed(): Boolean {
if (this is FirWrappedArgumentExpression) return expression.shouldBePostponed()
if (this is FirAnonymousFunctionExpression) return true
if (this is FirCallableReferenceAccess) return true
if (this is FirAnonymousObjectExpression) return true
return false
}
private fun Candidate.needsToBePostponed(): Boolean {
if (dispatchReceiver?.isReceiverPostponed() == true) return true
if (givenExtensionReceiverOptions.any { it.isReceiverPostponed() }) return true
if (callInfo.arguments.any { it.shouldBePostponed() }) return true
if (callInfo.callKind == CallKind.VariableAccess) {
val returnType = (symbol as? FirVariableSymbol)?.let(returnTypeCalculator::tryCalculateReturnType)
if (returnType?.type?.containsNotFixedTypeVariables() == true) return true
}
if (callInfo.arguments.any { it.isQualifiedAccessContainingTypeVariables() || it.doesArgumentUseOuterCS() }) return true
if (callInfo.resolutionMode is ResolutionMode.ContextDependent.Delegate) return true
// For assignments like myVarContainingTV = SomeCallWithNonTrivialInference(...)
// We should integrate the call into the PCLA tree, too
if ((callInfo.resolutionMode as? ResolutionMode.WithExpectedType)?.expectedTypeRef?.type?.containsNotFixedTypeVariables() == true) {
return true
}
// Synthetic calls with blocks work like lambdas
if (callInfo.callKind == CallKind.SyntheticSelect) return true
return false
}
private fun FirExpression.doesArgumentUseOuterCS(): Boolean {
var result = false
processConstraintStorageFromExpression(this) {
if (it.usesOuterCs) {
result = true
}
}
return result
}
private fun FirExpression.isReceiverPostponed(): Boolean {
if (resolvedType.containsNotFixedTypeVariables()) return true
if ((this as? FirResolvable)?.candidate()?.usedOuterCs == true) return true
return false
}
private fun FirExpression.isQualifiedAccessContainingTypeVariables(): Boolean {
if (this is FirNamedArgumentExpression) return expression.isQualifiedAccessContainingTypeVariables()
if (!isQualifiedAccessOrSmartCastOnIt()) return false
if (this is FirCallableReferenceAccess) return false
return resolvedType.containsNotFixedTypeVariables() && (this as? FirResolvable)?.candidate() == null
}
private fun FirExpression.isQualifiedAccessOrSmartCastOnIt(): Boolean = when (this) {
is FirQualifiedAccessExpression -> true
is FirSmartCastExpression -> originalExpression.isQualifiedAccessOrSmartCastOnIt()
else -> false
}
private fun ConeKotlinType.containsNotFixedTypeVariables(): Boolean =
contains {
// TODO: Investigate why using `notFixedTypeVariables` instead of `allTypeVariables` leads to failure of the test (KT-64861)
// org.jetbrains.kotlin.test.runners.codegen.FirPsiBlackBoxCodegenTestGenerated.BuilderInference.OneParameter.OneTypeVariable.
// OneTypeInfoOrigin.SourceSinkFeedContexts.testThroughDelegatedLocalVariableYieldCase
it is ConeTypeVariableType && it.typeConstructor in currentCommonSystem.allTypeVariables
}
override fun addSubtypeConstraintIfCompatible(lowerType: ConeKotlinType, upperType: ConeKotlinType, element: FirElement) {
currentCommonSystem.addSubtypeConstraintIfCompatible(lowerType, upperType, ConeExpectedTypeConstraintPosition)
}
}
class FirTypeVariablesAfterPCLATransformer(private val substitutor: ConeSubstitutor) : FirDefaultTransformer<Nothing?>() {
override fun <E : FirElement> transformElement(element: E, data: Nothing?): E {
// All resolvable nodes should be implemented separately to cover substitution of receivers in the candidate
if (element is FirResolvable) {
element.candidate()?.let { processCandidate(it) }
}
// Since FirExpressions don't have typeRefs, they need to be updated separately.
// FirAnonymousFunctionExpression doesn't support replacing the type
// since it delegates the getter to the underlying FirAnonymousFunction.
if (element is FirExpression && element !is FirAnonymousFunctionExpression) {
// TODO Check why some expressions have unresolved type in builder inference session KT-61835
@OptIn(UnresolvedExpressionTypeAccess::class)
element.coneTypeOrNull
?.let(substitutor::substituteOrNull)
?.let { element.replaceConeTypeOrNull(it) }
}
@Suppress("UNCHECKED_CAST")
return element.transformChildren(this, data = null) as E
}
override fun transformResolvedTypeRef(resolvedTypeRef: FirResolvedTypeRef, data: Nothing?): FirTypeRef =
substitutor.substituteOrNull(resolvedTypeRef.type)?.let {
resolvedTypeRef.withReplacedConeType(it)
} ?: resolvedTypeRef
/*
* We should manually update all receivers in the all not completed candidates, because not all calls with candidates
* contained in partiallyResolvedCalls and candidate stores not receiver values, which are updated, (TODO: remove this comment after removal of updating values)
* and receivers of candidates are not direct FIR children of calls, so they won't be visited during regular transformChildren
*/
private fun processCandidate(candidate: Candidate) {
candidate.dispatchReceiver = candidate.dispatchReceiver?.transform(this, data = null)
candidate.chosenExtensionReceiver = candidate.chosenExtensionReceiver?.transform(this, data = null)
candidate.contextReceiverArguments = candidate.contextReceiverArguments?.map { it.transform(this, data = null) }
}
}
@@ -19,13 +19,16 @@ import org.jetbrains.kotlin.fir.resolvedTypeFromPrototype
import org.jetbrains.kotlin.fir.types.*
import org.jetbrains.kotlin.resolve.calls.components.PostponedArgumentsAnalyzerContext
import org.jetbrains.kotlin.resolve.calls.inference.ConstraintSystemBuilder
import org.jetbrains.kotlin.resolve.calls.inference.components.ConstraintSystemCompletionMode
import org.jetbrains.kotlin.resolve.calls.inference.model.BuilderInferencePosition
import org.jetbrains.kotlin.types.model.*
import org.jetbrains.kotlin.resolve.calls.inference.addSubtypeConstraintIfCompatible
import org.jetbrains.kotlin.resolve.calls.inference.model.ConstraintStorage
import org.jetbrains.kotlin.types.model.KotlinTypeMarker
import org.jetbrains.kotlin.types.model.TypeConstructorMarker
import org.jetbrains.kotlin.types.model.freshTypeConstructor
import org.jetbrains.kotlin.types.model.safeSubstitute
data class ReturnArgumentsAnalysisResult(
val returnArguments: Collection<FirExpression>,
val inferenceSession: FirInferenceSession?
val additionalConstraints: ConstraintStorage?,
)
interface LambdaAnalyzer {
@@ -35,8 +38,9 @@ interface LambdaAnalyzer {
contextReceivers: List<ConeKotlinType>,
parameters: List<ConeKotlinType>,
expectedReturnType: ConeKotlinType?, // null means, that return type is not proper i.e. it depends on some type variables
stubsForPostponedVariables: Map<TypeVariableMarker, StubTypeMarker>,
candidate: Candidate
candidate: Candidate,
withPCLASession: Boolean,
forOverloadByLambdaReturnType: Boolean,
): ReturnArgumentsAnalysisResult
}
@@ -44,21 +48,24 @@ class PostponedArgumentsAnalyzer(
private val resolutionContext: ResolutionContext,
private val lambdaAnalyzer: LambdaAnalyzer,
private val components: InferenceComponents,
private val callResolver: FirCallResolver
private val callResolver: FirCallResolver,
) {
fun analyze(
c: PostponedArgumentsAnalyzerContext,
argument: PostponedResolvedAtom,
candidate: Candidate,
completionMode: ConstraintSystemCompletionMode
) {
when (argument) {
is ResolvedLambdaAtom ->
analyzeLambda(c, argument, candidate, completionMode)
analyzeLambda(c, argument, candidate, forOverloadByLambdaReturnType = false)
is LambdaWithTypeVariableAsExpectedTypeAtom ->
analyzeLambda(c, argument.transformToResolvedLambda(c.getBuilder(), resolutionContext), candidate, completionMode)
analyzeLambda(
c,
argument.transformToResolvedLambda(c.getBuilder(), resolutionContext),
candidate, forOverloadByLambdaReturnType = false
)
is ResolvedCallableReferenceAtom -> processCallableReference(argument, candidate)
}
@@ -66,7 +73,7 @@ class PostponedArgumentsAnalyzer(
private fun processCallableReference(atom: ResolvedCallableReferenceAtom, candidate: Candidate) {
if (atom.mightNeedAdditionalResolution) {
callResolver.resolveCallableReference(candidate.csBuilder, atom, hasSyntheticOuterCall = false)
callResolver.resolveCallableReference(candidate, atom, hasSyntheticOuterCall = false)
}
val callableReferenceAccess = atom.reference
@@ -98,17 +105,26 @@ class PostponedArgumentsAnalyzer(
c: PostponedArgumentsAnalyzerContext,
lambda: ResolvedLambdaAtom,
candidate: Candidate,
completionMode: ConstraintSystemCompletionMode,
forOverloadByLambdaReturnType: Boolean,
//diagnosticHolder: KotlinDiagnosticsHolder
): ReturnArgumentsAnalysisResult {
// TODO: replace with `require(!lambda.analyzed)` when KT-54767 will be fixed
if (lambda.analyzed) {
return ReturnArgumentsAnalysisResult(lambda.returnStatements, inferenceSession = null)
return ReturnArgumentsAnalysisResult(lambda.returnStatements, additionalConstraints = null)
}
val additionalBindings: Map<TypeConstructorMarker, KotlinTypeMarker>? =
(resolutionContext.bodyResolveContext.inferenceSession as? FirPCLAInferenceSession)?.let { builderInferenceSession ->
// TODO: Fix variables for context receivers, too (KT-64859)
buildMap {
lambda.receiver
?.let { builderInferenceSession.fixVariablesForMemberScope(it, candidate.system) }
?.let(this::plusAssign)
}
}
val unitType = components.session.builtinTypes.unitType.type
val stubsForPostponedVariables = c.bindingStubsForPostponedVariables()
val currentSubstitutor = c.buildCurrentSubstitutor(stubsForPostponedVariables.mapKeys { it.key.freshTypeConstructor(c) })
val currentSubstitutor = c.buildCurrentSubstitutor(additionalBindings ?: emptyMap()) as ConeSubstitutor
fun substitute(type: ConeKotlinType) = currentSubstitutor.safeSubstitute(c, type) as ConeKotlinType
@@ -126,21 +142,27 @@ class PostponedArgumentsAnalyzer(
else -> null
}
val withPCLASession =
lambda.inputTypes
.any { inputType ->
with(c) { inputType.extractTypeVariables() }.any(c.notFixedTypeVariables::contains)
}
val results = lambdaAnalyzer.analyzeAndGetLambdaReturnArguments(
lambda,
receiver,
contextReceivers,
parameters,
expectedTypeForReturnArguments,
stubsForPostponedVariables,
candidate
candidate,
withPCLASession,
forOverloadByLambdaReturnType,
)
applyResultsOfAnalyzedLambdaToCandidateSystem(
c,
lambda,
candidate,
results,
completionMode,
::substitute
)
return results
@@ -151,10 +173,13 @@ class PostponedArgumentsAnalyzer(
lambda: ResolvedLambdaAtom,
candidate: Candidate,
results: ReturnArgumentsAnalysisResult,
completionMode: ConstraintSystemCompletionMode,
substitute: (ConeKotlinType) -> ConeKotlinType = c.createSubstituteFunctorForLambdaAnalysis()
substitute: (ConeKotlinType) -> ConeKotlinType = c.createSubstituteFunctorForLambdaAnalysis(),
) {
val (returnArguments, inferenceSession) = results
val (returnArguments, additionalConstraintStorage) = results
if (additionalConstraintStorage != null) {
c.addOtherSystem(additionalConstraintStorage)
}
val checkerSink: CheckerSink = CheckerSinkImpl(candidate)
val builder = c.getBuilder()
@@ -169,11 +194,6 @@ class PostponedArgumentsAnalyzer(
// If the lambda returns Unit, the last expression is not returned and should not be constrained.
val isLastExpression = it == lastExpression
// Don't add last call for builder-inference
// Note, that we don't use the same condition "(returnTypeRef.type.isUnitOrFlexibleUnit || lambda.atom.shouldReturnUnit(returnArguments))"
// as in the if below, because "lambda.atom.shouldReturnUnit(returnArguments)" might mean that the last statement is not completed
if (isLastExpression && lambdaExpectedTypeIsUnit && inferenceSession is FirBuilderInferenceSession) return@forEach
// TODO (KT-55837) questionable moment inherited from FE1.0 (the `haveSubsystem` case):
// fun <T> foo(): T
// run {
@@ -181,11 +201,25 @@ class PostponedArgumentsAnalyzer(
// foo() // T = Unit, even though there is no implicit return
// }
// Things get even weirder if T has an upper bound incompatible with Unit.
// Not calling `addSubsystemFromExpression` for builder-inference is crucial
val haveSubsystem = c.addSubsystemFromExpression(it)
if (isLastExpression && !haveSubsystem &&
(lambdaExpectedTypeIsUnit || lambda.atom.shouldReturnUnit(returnArguments))
) return@forEach
val isUnitLambda = lambdaExpectedTypeIsUnit || lambda.atom.shouldReturnUnit(returnArguments)
if (isLastExpression && isUnitLambda) {
// That "if" is necessary because otherwise we would force a lambda return type
// to be inferred from completed last expression.
// See `test1` at testData/diagnostics/tests/inference/coercionToUnit/afterBareReturn.kt
if (haveSubsystem) {
// We don't force it because of the cases like
// buildMap {
// put("a", 1) // While `put` returns V, we should not enforce the latter to be a subtype of Unit
// }
// See KT-63602 for details.
builder.addSubtypeConstraintIfCompatible(
it.resolvedType, returnTypeRef.type,
ConeLambdaArgumentConstraintPosition(lambda.atom)
)
}
return@forEach
}
hasExpressionInReturnArguments = true
if (!builder.hasContradiction) {
@@ -212,25 +246,6 @@ class PostponedArgumentsAnalyzer(
lambda.analyzed = true
lambda.returnStatements = returnArguments
c.resolveForkPointsConstraints()
if (inferenceSession != null) {
val postponedVariables = inferenceSession.inferPostponedVariables(lambda, builder, completionMode, candidate)
if (postponedVariables == null) {
builder.removePostponedVariables()
return
}
for ((constructor, resultType) in postponedVariables) {
val variableWithConstraints = builder.currentStorage().notFixedTypeVariables[constructor] ?: continue
val variable = variableWithConstraints.typeVariable as ConeTypeVariable
builder.unmarkPostponedVariable(variable)
builder.addSubtypeConstraint(resultType, variable.defaultType(c), BuilderInferencePosition)
}
c.removePostponedTypeVariablesFromConstraints(postponedVariables.keys)
}
}
fun PostponedArgumentsAnalyzerContext.createSubstituteFunctorForLambdaAnalysis(): (ConeKotlinType) -> ConeKotlinType {
@@ -9,6 +9,7 @@ import org.jetbrains.kotlin.KtFakeSourceElementKind
import org.jetbrains.kotlin.fakeElement
import org.jetbrains.kotlin.fir.*
import org.jetbrains.kotlin.fir.declarations.*
import org.jetbrains.kotlin.fir.declarations.synthetic.FirSyntheticProperty
import org.jetbrains.kotlin.fir.declarations.utils.isInline
import org.jetbrains.kotlin.fir.diagnostics.ConeDiagnostic
import org.jetbrains.kotlin.fir.diagnostics.ConeSimpleDiagnostic
@@ -22,6 +23,7 @@ import org.jetbrains.kotlin.fir.resolve.*
import org.jetbrains.kotlin.fir.resolve.calls.*
import org.jetbrains.kotlin.fir.resolve.dfa.FirDataFlowAnalyzer
import org.jetbrains.kotlin.fir.resolve.diagnostics.*
import org.jetbrains.kotlin.fir.resolve.inference.FirTypeVariablesAfterPCLATransformer
import org.jetbrains.kotlin.fir.resolve.inference.ResolvedLambdaAtom
import org.jetbrains.kotlin.fir.resolve.substitution.ConeSubstitutor
import org.jetbrains.kotlin.fir.resolve.substitution.substitutorByMap
@@ -32,7 +34,10 @@ import org.jetbrains.kotlin.fir.scopes.impl.FirClassSubstitutionScope
import org.jetbrains.kotlin.fir.scopes.impl.isWrappedIntegerOperator
import org.jetbrains.kotlin.fir.scopes.impl.isWrappedIntegerOperatorForUnsignedType
import org.jetbrains.kotlin.fir.symbols.FirBasedSymbol
import org.jetbrains.kotlin.fir.symbols.impl.*
import org.jetbrains.kotlin.fir.symbols.impl.FirCallableSymbol
import org.jetbrains.kotlin.fir.symbols.impl.FirConstructorSymbol
import org.jetbrains.kotlin.fir.symbols.impl.FirFunctionSymbol
import org.jetbrains.kotlin.fir.symbols.impl.FirNamedFunctionSymbol
import org.jetbrains.kotlin.fir.types.*
import org.jetbrains.kotlin.fir.types.builder.buildErrorTypeRef
import org.jetbrains.kotlin.fir.types.builder.buildStarProjection
@@ -124,7 +129,7 @@ class FirCallCompletionResultsWriterTransformer(
if (mode == Mode.DelegatedPropertyCompletion) {
// Update type for `$delegateField` in `$$delegateField.get/setValue()` calls inside accessors
val typeUpdater = TypeUpdaterForDelegateArguments()
val typeUpdater = TypeUpdaterForPCLAAndDelegateReceivers()
qualifiedAccessExpression.transformExplicitReceiver(typeUpdater, null)
}
@@ -137,6 +142,12 @@ class FirCallCompletionResultsWriterTransformer(
extensionReceiver = extensionReceiver?.transformSingle(integerOperatorApproximator, expectedExtensionReceiverType)
}
if (subCandidate.usedOuterCs) {
val updaterForThisReferences = TypeUpdaterForPCLAAndDelegateReceivers()
dispatchReceiver = dispatchReceiver?.transformSingle(updaterForThisReferences, null)
extensionReceiver = extensionReceiver?.transformSingle(updaterForThisReferences, null)
}
qualifiedAccessExpression.apply {
replaceCalleeReference(calleeReference.toResolvedReference())
replaceDispatchReceiver(dispatchReceiver)
@@ -171,6 +182,21 @@ class FirCallCompletionResultsWriterTransformer(
if (declaration !is FirErrorFunction) {
qualifiedAccessExpression.replaceTypeArguments(typeArguments)
}
for (postponedCall in subCandidate.postponedPCLACalls) {
postponedCall.transformSingle(this, null)
}
for (callback in subCandidate.onCompletionResultsWritingCallbacks) {
callback(finalSubstitutor)
}
// TODO: Be aware of exponent
val firStubTypeTransformer = FirTypeVariablesAfterPCLATransformer(finalSubstitutor)
for (lambda in subCandidate.lambdasAnalyzedWithPCLA) {
lambda.transformSingle(firStubTypeTransformer, null)
}
session.lookupTracker?.recordTypeResolveAsLookup(type, qualifiedAccessExpression.source, context.file.source)
return qualifiedAccessExpression
}
@@ -195,7 +221,7 @@ class FirCallCompletionResultsWriterTransformer(
* TODO: In future, it would be nice to get rid of it and there's actually a way to do it not using substitution overrides (see KT-61618)
*/
private fun Candidate.updateSubstitutedMemberIfReceiverContainsTypeVariable() {
val updatedSymbol = symbol.updateSubstitutedMemberIfReceiverContainsTypeVariable() ?: return
val updatedSymbol = symbol.updateSubstitutedMemberIfReceiverContainsTypeVariable(usedOuterCs) ?: return
val oldSymbol = symbol
@OptIn(Candidate.UpdatingSymbol::class)
@@ -221,9 +247,9 @@ class FirCallCompletionResultsWriterTransformer(
}
}
private fun FirBasedSymbol<*>.updateSubstitutedMemberIfReceiverContainsTypeVariable(): FirBasedSymbol<*>? {
private fun FirBasedSymbol<*>.updateSubstitutedMemberIfReceiverContainsTypeVariable(usedOuterCs: Boolean): FirBasedSymbol<*>? {
// TODO: Add assertion that this function returns not-null only for BI and delegation inference
if (mode != Mode.DelegatedPropertyCompletion) return null
if (mode != Mode.DelegatedPropertyCompletion && !usedOuterCs) return null
val fir = fir
if (fir !is FirCallableDeclaration) return null
@@ -240,6 +266,22 @@ class FirCallCompletionResultsWriterTransformer(
) as? FirClassSubstitutionScope ?: return null
val original = fir.originalForSubstitutionOverride ?: return null
if (fir is FirSyntheticProperty && fir.symbol is FirSimpleSyntheticPropertySymbol && original is FirSyntheticProperty) {
var result: FirBasedSymbol<*>? = null
FirSyntheticPropertiesScope.createIfSyntheticNamesProviderIsDefined(session, updatedDispatchReceiverType, scope)
?.processPropertiesByName(fir.name) {
val newProperty = it.fir as? FirSyntheticProperty ?: return@processPropertiesByName
val originalForNew = newProperty.originalForSubstitutionOverride ?: return@processPropertiesByName
if (originalForNew.getter.delegate == original.getter.delegate) {
check(result == null)
result = it
}
}
return result ?: error("Not found synthetic property: ${fir.renderWithType()}")
}
return findSingleSubstitutedSymbolWithOriginal(original.symbol) { processor ->
when (original) {
is FirSimpleFunction -> scope.processFunctionsByName(original.name, processor)
@@ -519,10 +561,19 @@ class FirCallCompletionResultsWriterTransformer(
}
}
var dispatchReceiver = subCandidate.dispatchReceiverExpression()
var extensionReceiver = subCandidate.chosenExtensionReceiverExpression()
if (subCandidate.usedOuterCs) {
val updaterForThisReferences = TypeUpdaterForPCLAAndDelegateReceivers()
dispatchReceiver = dispatchReceiver?.transformSingle(updaterForThisReferences, null)
extensionReceiver = extensionReceiver?.transformSingle(updaterForThisReferences, null)
}
return callableReferenceAccess.apply {
replaceCalleeReference(resolvedReference)
replaceDispatchReceiver(subCandidate.dispatchReceiverExpression())
replaceExtensionReceiver(subCandidate.chosenExtensionReceiverExpression())
replaceDispatchReceiver(dispatchReceiver)
replaceExtensionReceiver(extensionReceiver)
if (calleeReference.candidate.doesResolutionResultOverrideOtherToPreserveCompatibility()) {
addNonFatalDiagnostic(ConeResolutionResultOverridesOtherToPreserveCompatibility)
}
@@ -533,25 +584,33 @@ class FirCallCompletionResultsWriterTransformer(
return smartCastExpression.transformOriginalExpression(this, data)
}
private inner class TypeUpdaterForDelegateArguments : FirTransformer<Any?>() {
private inner class TypeUpdaterForPCLAAndDelegateReceivers : FirTransformer<Any?>() {
override fun <E : FirElement> transformElement(element: E, data: Any?): E {
return element
}
override fun transformThisReceiverExpression(thisReceiverExpression: FirThisReceiverExpression, data: Any?): FirStatement {
return transformTypeRefForQualifiedAccess(thisReceiverExpression)
}
override fun transformQualifiedAccessExpression(
qualifiedAccessExpression: FirQualifiedAccessExpression,
data: Any?,
): FirStatement {
return transformTypeRefForQualifiedAccess(qualifiedAccessExpression)
}
override fun transformPropertyAccessExpression(propertyAccessExpression: FirPropertyAccessExpression, data: Any?): FirStatement {
return transformQualifiedAccessExpression(propertyAccessExpression, data)
}
private fun transformTypeRefForQualifiedAccess(qualifiedAccessExpression: FirQualifiedAccessExpression): FirQualifiedAccessExpression {
val originalType = qualifiedAccessExpression.resolvedType
val substitutedReceiverType = finallySubstituteOrNull(originalType) ?: return qualifiedAccessExpression
qualifiedAccessExpression.replaceConeTypeOrNull(substitutedReceiverType)
session.lookupTracker?.recordTypeResolveAsLookup(substitutedReceiverType, qualifiedAccessExpression.source, context.file.source)
return qualifiedAccessExpression
}
override fun transformPropertyAccessExpression(propertyAccessExpression: FirPropertyAccessExpression, data: Any?): FirStatement {
return transformQualifiedAccessExpression(propertyAccessExpression, data)
}
}
private fun FirTypeRef.substitute(candidate: Candidate): ConeKotlinType {
@@ -20,12 +20,9 @@ import org.jetbrains.kotlin.fir.resolve.*
import org.jetbrains.kotlin.fir.resolve.calls.ImplicitExtensionReceiverValue
import org.jetbrains.kotlin.fir.resolve.calls.ImplicitReceiverValue
import org.jetbrains.kotlin.fir.resolve.calls.InaccessibleImplicitReceiverValue
import org.jetbrains.kotlin.fir.resolve.calls.ResolutionContext
import org.jetbrains.kotlin.fir.resolve.dfa.DataFlowAnalyzerContext
import org.jetbrains.kotlin.fir.resolve.inference.FirBuilderInferenceSession
import org.jetbrains.kotlin.fir.resolve.inference.FirCallCompleter
import org.jetbrains.kotlin.fir.resolve.inference.FirDelegatedPropertyInferenceSession
import org.jetbrains.kotlin.fir.resolve.inference.FirInferenceSession
import org.jetbrains.kotlin.fir.resolve.inference.FirPCLAInferenceSession
import org.jetbrains.kotlin.fir.resolve.transformers.ReturnTypeCalculator
import org.jetbrains.kotlin.fir.resolve.transformers.withScopeCleanup
import org.jetbrains.kotlin.fir.scopes.FirScope
@@ -40,7 +37,6 @@ import org.jetbrains.kotlin.fir.symbols.impl.FirFunctionSymbol
import org.jetbrains.kotlin.fir.types.*
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.name.SpecialNames.UNDERSCORE_FOR_UNUSED_VAR
import org.jetbrains.kotlin.resolve.calls.inference.model.ConstraintStorage
import org.jetbrains.kotlin.util.PrivateForInline
class BodyResolveContext(
@@ -57,6 +53,7 @@ class BodyResolveContext(
@PrivateForInline
var regularTowerDataContexts = FirRegularTowerDataContexts(regular = FirTowerDataContext())
// TODO: Rename to postponed
@PrivateForInline
val specialTowerDataContexts = FirSpecialTowerDataContexts()
@@ -108,16 +105,6 @@ class BodyResolveContext(
}
}
/**
* CS for an outer type system if it's relevant, for example, in the case of `val x by myGenericDelegateCall()`,
* relevant `getValue` call is expected to be resolved in the context of an outer CS built from `myGenericDelegateCall()` and
* probably using its type variables inside `getValue` candidates resolution.
*
* Note that it's not assumed to modify the storage, but only use it as a base system content for the candidate.
*/
@set:PrivateForInline
var outerConstraintStorage: ConstraintStorage = ConstraintStorage.Empty
val anonymousFunctionsAnalyzedInDependentContext: MutableSet<FirFunctionSymbol<*>> = mutableSetOf()
var containingClassDeclarations: ArrayDeque<FirRegularClass> = ArrayDeque()
@@ -263,7 +250,6 @@ class BodyResolveContext(
holder.scopeSession
)
addReceiver(labelName, receiver, additionalLabelName)
(inferenceSession as? FirBuilderInferenceSession)?.addLambdaImplicitReceiver(receiver)
}
f()
@@ -331,11 +317,11 @@ class BodyResolveContext(
}
@OptIn(PrivateForInline::class)
inline fun <R> withInferenceSession(inferenceSession: FirInferenceSession, block: () -> R): R {
inline fun <R, S : FirInferenceSession> withInferenceSession(inferenceSession: S, block: S.() -> R): R {
val oldSession = this.inferenceSession
this.inferenceSession = inferenceSession
return try {
block()
inferenceSession.block()
} finally {
this.inferenceSession = oldSession
}
@@ -352,10 +338,17 @@ class BodyResolveContext(
}
@PrivateForInline
inline fun <T> withTemporaryRegularContext(newContext: FirTowerDataContext?, f: () -> T): T {
if (newContext == null) return f()
inline fun <T> withTemporaryRegularContext(newContext: PostponedAtomsResolutionContext?, f: () -> T): T {
val (towerDataContext, newInferenceSession) = newContext ?: return f()
return withTowerDataModeCleanup {
withTowerDataContexts(regularTowerDataContexts.replaceAndSetActiveRegularContext(newContext), f)
withTowerDataContexts(regularTowerDataContexts.replaceAndSetActiveRegularContext(towerDataContext)) {
if (newInferenceSession !== this.inferenceSession) {
withInferenceSession(newInferenceSession) { f() }
} else {
f()
}
}
}
}
@@ -382,7 +375,7 @@ class BodyResolveContext(
// to use information from local class inside it.
// However, we should not copy other kinds of inference sessions,
// otherwise we can "inherit" type variables from there provoking inference problems
if (this@BodyResolveContext.inferenceSession is FirBuilderInferenceSession) {
if (this@BodyResolveContext.inferenceSession is FirPCLAInferenceSession) {
inferenceSession = this@BodyResolveContext.inferenceSession
}
}
@@ -756,7 +749,9 @@ class BodyResolveContext(
@OptIn(PrivateForInline::class)
fun storeContextForAnonymousFunction(anonymousFunction: FirAnonymousFunction) {
specialTowerDataContexts.storeAnonymousFunctionContext(anonymousFunction.symbol, towerDataContext)
specialTowerDataContexts.storeAnonymousFunctionContext(
anonymousFunction.symbol, towerDataContext, inferenceSession
)
}
@OptIn(PrivateForInline::class)
@@ -867,38 +862,6 @@ class BodyResolveContext(
}
}
inline fun <T> forPropertyDelegateAccessors(
property: FirProperty,
resolutionContext: ResolutionContext,
callCompleter: FirCallCompleter,
f: FirDelegatedPropertyInferenceSession.() -> T
) {
val inferenceSession = FirDelegatedPropertyInferenceSession(
property,
resolutionContext,
callCompleter.createPostponedArgumentsAnalyzer(resolutionContext)
)
withInferenceSession(inferenceSession) {
inferenceSession.f()
}
}
@OptIn(PrivateForInline::class)
inline fun <T> withOuterConstraintStorage(
storage: ConstraintStorage,
f: () -> T
): T {
val oldStorage = this.outerConstraintStorage
this.outerConstraintStorage = storage
return try {
f()
} finally {
this.outerConstraintStorage = oldStorage
}
}
@OptIn(PrivateForInline::class)
inline fun <T> withConstructor(constructor: FirConstructor, f: () -> T): T =
withContainer(constructor, f)
@@ -956,7 +919,8 @@ class BodyResolveContext(
fun storeCallableReferenceContext(callableReferenceAccess: FirCallableReferenceAccess) {
specialTowerDataContexts.storeCallableReferenceContext(
callableReferenceAccess,
towerDataContext.createSnapshot(keepMutable = false)
towerDataContext.createSnapshot(keepMutable = false),
inferenceSession,
)
}
@@ -38,7 +38,6 @@ import org.jetbrains.kotlin.fir.resolve.inference.ResolvedLambdaAtom
import org.jetbrains.kotlin.fir.resolve.inference.extractLambdaInfoFromFunctionType
import org.jetbrains.kotlin.fir.resolve.substitution.ChainedSubstitutor
import org.jetbrains.kotlin.fir.resolve.substitution.ConeSubstitutor
import org.jetbrains.kotlin.fir.resolve.transformers.FirCallCompletionResultsWriterTransformer
import org.jetbrains.kotlin.fir.resolve.transformers.FirStatusResolver
import org.jetbrains.kotlin.fir.resolve.transformers.contracts.runContractResolveForFunction
import org.jetbrains.kotlin.fir.resolve.transformers.transformVarargTypeToArrayType
@@ -329,89 +328,82 @@ open class FirDeclarationsResolveTransformer(
private fun transformPropertyAccessorsWithDelegate(
property: FirProperty,
delegate: FirExpression,
delegateContainer: FirExpression,
shouldResolveEverything: Boolean,
) {
val isImplicitTypedProperty = property.returnTypeRef is FirImplicitTypeRef
require(delegateContainer is FirWrappedDelegateExpression)
dataFlowAnalyzer.enterDelegateExpression()
context.forPropertyDelegateAccessors(property, resolutionContext, callCompleter) {
dataFlowAnalyzer.enterDelegateExpression()
// Resolve delegate expression, after that, delegate will contain either expr.provideDelegate or expr
// First, resolve delegate expression in dependent context withing existing (possibly Default) inference session
val delegateExpression =
// Resolve delegate expression; after that, delegate will contain either expr.provideDelegate or expr
if (property.isLocal) {
property.transformDelegate(transformer, ResolutionMode.ContextDependent.Delegate)
transformDelegateExpression(delegateContainer)
} else {
context.forPropertyInitializer {
property.transformDelegate(transformer, ResolutionMode.ContextDependent.Delegate)
transformDelegateExpression(delegateContainer)
}
}
// We don't use inference from setValue calls (i.e. don't resolve setters until the delegate inference is completed),
// when property doesn't have explicit type.
// Then, create a root/child inference session based on the resolved delegate expression
context.withInferenceSession(
FirDelegatedPropertyInferenceSession(
resolutionContext,
callCompleter,
delegateExpression,
)
) {
property.replaceDelegate(
getResolvedProvideDelegateIfSuccessful(delegateContainer.provideDelegateCall, delegateExpression)
?: delegateExpression
)
// We don't use inference from setValue calls (i.e., don't resolve setters until the delegate inference is completed)
// when the property doesn't have an explicit type.
// It's necessary because we need to supply the property type as the 3rd argument for `setValue` and there might be uninferred
// variables from `getValue`.
// The same logic was used at K1 (see org.jetbrains.kotlin.resolve.DelegatedPropertyResolver.inferDelegateTypeFromGetSetValueMethods)
val isImplicitTypedProperty = property.returnTypeRef is FirImplicitTypeRef
property.transformAccessors(
if (isImplicitTypedProperty) SetterResolutionMode.SKIP else SetterResolutionMode.FULLY_RESOLVE,
shouldResolveEverything,
)
val completedCalls = completeCandidates()
val finalSubstitutor = createFinalSubstitutor()
finalSubstitutor.substituteOrNull(property.returnTypeRef.coneType)?.let { substitutedType ->
property.replaceReturnTypeRef(property.returnTypeRef.withReplacedConeType(substitutedType))
}
property.getter?.transformTypeWithPropertyType(property.returnTypeRef, forceUpdateForNonImplicitTypes = true)
property.setter?.transformTypeWithPropertyType(property.returnTypeRef, forceUpdateForNonImplicitTypes = true)
property.replaceReturnTypeRef(
property.returnTypeRef.approximateDeclarationType(
session,
property.visibilityForApproximation(),
property.isLocal
completeSessionOrPostponeIfNonRoot { finalSubstitutor ->
finalSubstitutor.substituteOrNull(property.returnTypeRef.coneType)?.let { substitutedType ->
property.replaceReturnTypeRef(property.returnTypeRef.withReplacedConeType(substitutedType))
}
property.getter?.transformTypeWithPropertyType(property.returnTypeRef, forceUpdateForNonImplicitTypes = true)
property.setter?.transformTypeWithPropertyType(property.returnTypeRef, forceUpdateForNonImplicitTypes = true)
property.replaceReturnTypeRef(
property.returnTypeRef.approximateDeclarationType(
session,
property.visibilityForApproximation(),
property.isLocal
)
)
)
val callCompletionResultsWriter = callCompleter.createCompletionResultsWriter(
finalSubstitutor,
mode = FirCallCompletionResultsWriterTransformer.Mode.DelegatedPropertyCompletion
)
completedCalls.forEach {
it.transformSingle(callCompletionResultsWriter, null)
// `isImplicitTypedProperty` means we haven't run setter resolution yet (see its second usage)
if (isImplicitTypedProperty) {
property.resolveSetter(mayResolveSetterBody = true, shouldResolveEverything = shouldResolveEverything)
}
dataFlowAnalyzer.exitDelegateExpression(delegateContainer)
}
}
// `isImplicitTypedProperty` means we haven't run setter resolution yet (see its second usage)
if (isImplicitTypedProperty) {
property.resolveSetter(mayResolveSetterBody = true, shouldResolveEverything = shouldResolveEverything)
}
dataFlowAnalyzer.exitDelegateExpression(delegate)
}
override fun transformPropertyAccessor(propertyAccessor: FirPropertyAccessor, data: ResolutionMode): FirPropertyAccessor {
return propertyAccessor.also {
transformProperty(it.propertySymbol.fir, data)
}
}
override fun transformWrappedDelegateExpression(
wrappedDelegateExpression: FirWrappedDelegateExpression,
data: ResolutionMode,
): FirStatement {
// First, resolve delegate expression in dependent context, and add potentially partially resolved call to inference session
// (that is why we use ResolutionMode.ContextDependent.Delegate instead of plain ContextDependent)
val delegateExpression = wrappedDelegateExpression.expression.transformSingle(transformer, ResolutionMode.ContextDependent.Delegate)
.transformSingle(components.integerLiteralAndOperatorApproximationTransformer, null)
val provideDelegateCall = wrappedDelegateExpression.provideDelegateCall
provideDelegateCall.replaceExplicitReceiver(delegateExpression)
private fun getResolvedProvideDelegateIfSuccessful(
provideDelegateCall: FirFunctionCall,
resolvedDelegateExpression: FirExpression,
): FirFunctionCall? {
provideDelegateCall.replaceExplicitReceiver(resolvedDelegateExpression)
// Resolve call for provideDelegate, without completion
// TODO: this generates some nodes in the control flow graph which we don't want if we
// end up not selecting this option, KT-59684
transformer.expressionsTransformer?.transformFunctionCallInternal(
provideDelegateCall, ResolutionMode.ContextDependent, provideDelegate = true
provideDelegateCall, ResolutionMode.ReceiverResolution, provideDelegate = true
)
// If we got successful candidate for provideDelegate, let's select it
@@ -438,10 +430,19 @@ open class FirDeclarationsResolveTransformer(
return provideDelegateCall
}
// Select delegate expression otherwise
return delegateExpression
return null
}
override fun transformPropertyAccessor(propertyAccessor: FirPropertyAccessor, data: ResolutionMode): FirPropertyAccessor {
return propertyAccessor.also {
transformProperty(it.propertySymbol.fir, data)
}
}
private fun transformDelegateExpression(delegate: FirWrappedDelegateExpression): FirExpression =
delegate.expression.transformSingle(transformer, ResolutionMode.ContextDependent.Delegate)
.transformSingle(components.integerLiteralAndOperatorApproximationTransformer, null)
/**
* For supporting the case when `provideDelegate` has a signature with type variable as a return type, like
* fun <K> K.provideDelegate(receiver: Any?, property: kotlin.reflect.KProperty<*>): K = this
@@ -481,11 +482,6 @@ open class FirDeclarationsResolveTransformer(
val candidateSystem = candidate.system
val candidateStorage = candidateSystem.currentStorage()
val allTypeVariables = candidateStorage.allTypeVariables.keys.toList()
// Subset of type variables obtained from the `provideDelegate` call
val typeVariablesRelatedToProvideDelegate =
allTypeVariables.subList(candidateStorage.outerSystemVariablesPrefixSize, allTypeVariables.size).toSet()
val variableWithConstraints =
candidateSystem.notFixedTypeVariables[typeVariable] ?: error("Not found type variable $typeVariable")
@@ -494,7 +490,7 @@ open class FirDeclarationsResolveTransformer(
// Temporary declare all the "outer" variables as proper (i.e., all inner variables as improper)
// Without that, all variables (both inner and outer ones) would be considered as improper,
// while we want to fix to assume `Delegate<Tv>` as proper because `Tv` belongs to the outer system
candidateSystem.withTypeVariablesThatAreNotCountedAsProperTypes(typeVariablesRelatedToProvideDelegate) {
candidateSystem.withTypeVariablesThatAreCountedAsProperTypes(candidateSystem.outerTypeVariables.orEmpty()) {
// TODO: reconsider the approach here (KT-61781 for tracking)
// Actually, this code might fail with an exception in some rare cases (see KT-61781)
// The problem is that in the issue example, when fixing T type variable, it has two upper bounds: X and Delegate<Y>
@@ -506,7 +502,7 @@ open class FirDeclarationsResolveTransformer(
// it seems like they do something relevant.
resultType = inferenceComponents.resultTypeResolver.findResultTypeOrNull(
candidateSystem, variableWithConstraints, TypeVariableDirectionCalculator.ResolveDirection.UNKNOWN
) as? ConeKotlinType ?: return@withTypeVariablesThatAreNotCountedAsProperTypes
) as? ConeKotlinType ?: return@withTypeVariablesThatAreCountedAsProperTypes
check(!candidateStorage.hasContradiction) { "We only should try fixing variables on successful provideDelegate candidate" }
@@ -31,7 +31,6 @@ import org.jetbrains.kotlin.fir.references.impl.FirSimpleNamedReference
import org.jetbrains.kotlin.fir.resolve.*
import org.jetbrains.kotlin.fir.resolve.calls.*
import org.jetbrains.kotlin.fir.resolve.diagnostics.*
import org.jetbrains.kotlin.fir.resolve.inference.FirStubInferenceSession
import org.jetbrains.kotlin.fir.resolve.substitution.ConeSubstitutor
import org.jetbrains.kotlin.fir.resolve.transformers.replaceLambdaArgumentInvocationKinds
import org.jetbrains.kotlin.fir.scopes.impl.isWrappedIntegerOperator
@@ -91,7 +90,7 @@ open class FirExpressionsResolveTransformer(transformer: FirAbstractBodyResolveT
transformQualifiedAccessExpression(qualifiedAccessExpression, data, isUsedAsReceiver = false, isUsedAsGetClassReceiver = false)
}
fun transformQualifiedAccessExpression(
private fun transformQualifiedAccessExpression(
qualifiedAccessExpression: FirQualifiedAccessExpression,
data: ResolutionMode,
isUsedAsReceiver: Boolean,
@@ -438,9 +437,7 @@ open class FirExpressionsResolveTransformer(transformer: FirAbstractBodyResolveT
functionCall
}
val resultExpression = context.inferenceSession.onCandidatesResolution(withTransformedArguments) {
callResolver.resolveCallAndSelectCandidate(withTransformedArguments, data)
}
val resultExpression = callResolver.resolveCallAndSelectCandidate(withTransformedArguments, data)
val completeInference = callCompleter.completeCall(resultExpression, data)
val result = completeInference.transformToIntegerOperatorCallOrApproximateItIfNeeded(data)
@@ -1035,6 +1032,13 @@ open class FirExpressionsResolveTransformer(transformer: FirAbstractBodyResolveT
),
)
// for cases like
// buildSomething { tVar = "" // Should infer TV from String assignment }
context.inferenceSession.addSubtypeConstraintIfCompatible(
variableAssignment.lValue.resolvedType, variableAssignment.rValue.resolvedType,
variableAssignment,
)
dataFlowAnalyzer.exitVariableAssignment(result)
return result
@@ -1053,10 +1057,10 @@ open class FirExpressionsResolveTransformer(transformer: FirAbstractBodyResolveT
val transformedLHS = when (explicitReceiver) {
is FirPropertyAccessExpression ->
transformQualifiedAccessExpression(
explicitReceiver, ResolutionMode.ContextIndependent, isUsedAsReceiver = true, isUsedAsGetClassReceiver = false
explicitReceiver, ResolutionMode.ReceiverResolution.ForCallableReference, isUsedAsReceiver = true, isUsedAsGetClassReceiver = false
) as FirExpression
else ->
explicitReceiver?.transformSingle(this, ResolutionMode.ContextIndependent)
explicitReceiver?.transformSingle(this, ResolutionMode.ReceiverResolution.ForCallableReference)
}.apply {
if (this is FirResolvedQualifier && callableReferenceAccess.hasQuestionMarkAtLHS) {
replaceIsNullableLHSForCallableReference(true)
@@ -1181,7 +1185,7 @@ open class FirExpressionsResolveTransformer(transformer: FirAbstractBodyResolveT
constExpression.replaceKind(expressionType.toConstKind() as ConstantValueKind<T>)
expressionType
}
data is ResolutionMode.ReceiverResolution -> {
data is ResolutionMode.ReceiverResolution && !data.forCallableReference -> {
require(expressionType is ConeIntegerLiteralConstantTypeImpl)
ConeIntegerConstantOperatorTypeImpl(expressionType.isUnsigned, ConeNullability.NOT_NULL)
}
@@ -1740,4 +1744,4 @@ private fun FirFunctionCall.setArrayAugmentedAssignSource(operation: FirOperatio
this.source = newSource
this.resolvedSymbol = oldCalleeReference.resolvedSymbol
})
}
}
@@ -7,6 +7,7 @@ package org.jetbrains.kotlin.fir.resolve.calls
import org.jetbrains.kotlin.KtSourceElement
import org.jetbrains.kotlin.fir.declarations.FirFunction
import org.jetbrains.kotlin.fir.declarations.FirTypeParameter
import org.jetbrains.kotlin.fir.declarations.FirValueParameter
import org.jetbrains.kotlin.fir.expressions.FirExpression
import org.jetbrains.kotlin.fir.expressions.FirNamedArgumentExpression
@@ -15,7 +16,6 @@ import org.jetbrains.kotlin.fir.symbols.FirBasedSymbol
import org.jetbrains.kotlin.fir.symbols.impl.FirConstructorSymbol
import org.jetbrains.kotlin.fir.symbols.impl.FirNamedFunctionSymbol
import org.jetbrains.kotlin.fir.symbols.impl.FirPropertySymbol
import org.jetbrains.kotlin.fir.symbols.impl.FirTypeParameterSymbol
import org.jetbrains.kotlin.fir.types.ConeKotlinType
import org.jetbrains.kotlin.fir.types.ConeTypeVariable
import org.jetbrains.kotlin.resolve.ForbiddenNamedArgumentsTarget
@@ -158,6 +158,7 @@ object AdaptedCallableReferenceIsUsedWithReflection : ResolutionDiagnostic(RESOL
object TypeParameterAsExpression : ResolutionDiagnostic(INAPPLICABLE)
class StubBuilderInferenceReceiver(
val typeParameterSymbol: FirTypeParameterSymbol
) : ResolutionDiagnostic(RESOLVED_WITH_ERROR)
class TypeVariableAsExplicitReceiver(
val explicitReceiver: FirExpression,
val typeParameter: FirTypeParameter,
) : ResolutionDiagnostic(RESOLVED_WITH_ERROR)
@@ -7,10 +7,13 @@ package org.jetbrains.kotlin.resolve.calls.components
import org.jetbrains.kotlin.resolve.calls.inference.ConstraintSystemBuilder
import org.jetbrains.kotlin.resolve.calls.inference.model.ConstraintStorage
import org.jetbrains.kotlin.resolve.calls.inference.model.VariableWithConstraints
import org.jetbrains.kotlin.types.model.*
interface PostponedArgumentsAnalyzerContext : TypeSystemInferenceExtensionContext {
fun buildCurrentSubstitutor(additionalBindings: Map<TypeConstructorMarker, StubTypeMarker>): TypeSubstitutorMarker
val notFixedTypeVariables: Map<TypeConstructorMarker, VariableWithConstraints>
fun buildCurrentSubstitutor(additionalBindings: Map<TypeConstructorMarker, KotlinTypeMarker>): TypeSubstitutorMarker
fun buildNotFixedVariablesToStubTypesSubstitutor(): TypeSubstitutorMarker
fun bindingStubsForPostponedVariables(): Map<TypeVariableMarker, StubTypeMarker>
@@ -11,21 +11,16 @@ import org.jetbrains.kotlin.resolve.calls.inference.ConstraintSystemBuilder
import org.jetbrains.kotlin.resolve.calls.inference.model.*
import org.jetbrains.kotlin.resolve.calls.model.PostponedAtomWithRevisableExpectedType
import org.jetbrains.kotlin.resolve.calls.model.PostponedResolvedAtomMarker
import org.jetbrains.kotlin.types.model.K2Only
import org.jetbrains.kotlin.types.model.KotlinTypeMarker
import org.jetbrains.kotlin.types.model.TypeConstructorMarker
import org.jetbrains.kotlin.types.model.TypeVariableMarker
abstract class ConstraintSystemCompletionContext : VariableFixationFinder.Context, ResultTypeResolver.Context {
abstract val allTypeVariables: Map<TypeConstructorMarker, TypeVariableMarker>
abstract override val notFixedTypeVariables: Map<TypeConstructorMarker, VariableWithConstraints>
abstract override val fixedTypeVariables: Map<TypeConstructorMarker, KotlinTypeMarker>
abstract override val postponedTypeVariables: List<TypeVariableMarker>
/**
* See [org.jetbrains.kotlin.resolve.calls.inference.model.ConstraintStorage.outerSystemVariablesPrefixSize]
*/
abstract val outerSystemVariablesPrefixSize: Int
abstract fun getBuilder(): ConstraintSystemBuilder
// type can be proper if it not contains not fixed type variables
@@ -69,7 +64,7 @@ abstract class ConstraintSystemCompletionContext : VariableFixationFinder.Contex
completionMode: ConstraintSystemCompletionMode,
analyze: (A) -> Unit
): Boolean {
if (completionMode == ConstraintSystemCompletionMode.FULL) {
if (completionMode.allLambdasShouldBeAnalyzed) {
val argumentWithTypeVariableAsExpectedType = findPostponedArgumentWithRevisableExpectedType(postponedArguments)
if (argumentWithTypeVariableAsExpectedType != null) {
@@ -120,4 +115,11 @@ abstract class ConstraintSystemCompletionContext : VariableFixationFinder.Contex
!it.typeConstructor().isClassTypeConstructor() && !it.typeConstructor().isTypeParameterTypeConstructor()
}
}.map { it.type }
/**
* @see [org.jetbrains.kotlin.resolve.calls.inference.components.VariableFixationFinder.Context.typeVariablesThatAreNotCountedAsProperTypes]
* @see [org.jetbrains.kotlin.fir.resolve.transformers.body.resolve.FirDeclarationsResolveTransformer.fixInnerVariablesForProvideDelegateIfNeeded]
*/
@K2Only
abstract fun <R> withTypeVariablesThatAreCountedAsProperTypes(typeVariables: Set<TypeConstructorMarker>, block: () -> R): R
}
@@ -5,8 +5,9 @@
package org.jetbrains.kotlin.resolve.calls.inference.components
enum class ConstraintSystemCompletionMode {
FULL,
enum class ConstraintSystemCompletionMode(val allLambdasShouldBeAnalyzed: Boolean) {
FULL(true),
PCLA_POSTPONED_CALL(true),
/**
* This mode allows us to infer variables in calls, which have enough type-info to be completed right-away
@@ -18,6 +19,6 @@ enum class ConstraintSystemCompletionMode {
* x.plus(run { x }) // Here, to select plus overload we need to analyze lambda
* ```
*/
PARTIAL,
UNTIL_FIRST_LAMBDA
PARTIAL(false),
UNTIL_FIRST_LAMBDA(false),
}
@@ -9,8 +9,13 @@ import org.jetbrains.kotlin.builtins.functions.FunctionTypeKind
import org.jetbrains.kotlin.builtins.functions.isBasicFunctionOrKFunction
import org.jetbrains.kotlin.config.LanguageFeature
import org.jetbrains.kotlin.config.LanguageVersionSettings
import org.jetbrains.kotlin.resolve.calls.inference.model.*
import org.jetbrains.kotlin.resolve.calls.model.*
import org.jetbrains.kotlin.resolve.calls.inference.model.Constraint
import org.jetbrains.kotlin.resolve.calls.inference.model.ConstraintKind
import org.jetbrains.kotlin.resolve.calls.inference.model.VariableWithConstraints
import org.jetbrains.kotlin.resolve.calls.model.LambdaWithTypeVariableAsExpectedTypeMarker
import org.jetbrains.kotlin.resolve.calls.model.PostponedAtomWithRevisableExpectedType
import org.jetbrains.kotlin.resolve.calls.model.PostponedCallableReferenceMarker
import org.jetbrains.kotlin.resolve.calls.model.PostponedResolvedAtomMarker
import org.jetbrains.kotlin.types.model.*
import org.jetbrains.kotlin.utils.SmartSet
import java.util.*
@@ -531,24 +536,40 @@ class PostponedArgumentInputTypesResolver(
postponedArguments: List<PostponedResolvedAtomMarker>,
topLevelType: KotlinTypeMarker,
dependencyProvider: TypeVariableDependencyInformationProvider,
resolvedAtomProvider: ResolvedAtomProvider
): Boolean = with(c) {
val expectedType = argument.run { (this as? PostponedAtomWithRevisableExpectedType)?.revisedExpectedType ?: expectedType }
resolvedAtomProvider: ResolvedAtomProvider,
): Boolean {
val expectedType = argument.expectedFunctionType(c) ?: return false
if (expectedType != null && expectedType.isFunctionOrKFunctionWithAnySuspendability()) {
val wasFixedSomeVariable = c.fixNextReadyVariableForParameterType(
expectedType,
postponedArguments,
topLevelType,
dependencyProvider,
resolvedAtomProvider
)
return c.fixNextReadyVariableForParameterType(
expectedType,
postponedArguments,
topLevelType,
dependencyProvider,
resolvedAtomProvider,
)
}
if (wasFixedSomeVariable)
return true
}
@K2Only
fun findNextVariableForReportingNotInferredInputType(
c: Context,
argument: PostponedResolvedAtomMarker,
postponedArguments: List<PostponedResolvedAtomMarker>,
topLevelType: KotlinTypeMarker,
dependencyProvider: TypeVariableDependencyInformationProvider,
): VariableFixationFinder.VariableForFixation? {
val expectedType = argument.expectedFunctionType(c) ?: return null
return false
return c.findNextVariableForParameterType(
expectedType,
dependencyProvider,
postponedArguments,
topLevelType,
)
}
private fun PostponedResolvedAtomMarker.expectedFunctionType(c: Context): KotlinTypeMarker? = with(c) {
val expectedType = (this@expectedFunctionType as? PostponedAtomWithRevisableExpectedType)?.revisedExpectedType ?: expectedType
expectedType?.takeIf { it.isFunctionOrKFunctionWithAnySuspendability() }
}
private fun Context.fixNextReadyVariableForParameterType(
@@ -558,17 +579,9 @@ class PostponedArgumentInputTypesResolver(
dependencyProvider: TypeVariableDependencyInformationProvider,
resolvedAtomByTypeVariableProvider: ResolvedAtomProvider,
): Boolean = with(resolutionTypeSystemContext) {
val relatedVariables = type.extractArgumentsForFunctionTypeOrSubtype()
.flatMap { getAllDeeplyRelatedTypeVariables(it, dependencyProvider) }
val variableForFixation = variableFixationFinder.findFirstVariableForFixation(
this@fixNextReadyVariableForParameterType,
relatedVariables,
postponedArguments,
ConstraintSystemCompletionMode.FULL,
topLevelType
)
val variableForFixation = findNextVariableForParameterType(type, dependencyProvider, postponedArguments, topLevelType)
if (variableForFixation == null || !variableForFixation.hasProperConstraint)
if (variableForFixation == null || !variableForFixation.isReady)
return false
val variableWithConstraints = notFixedTypeVariables.getValue(variableForFixation.variable)
@@ -589,6 +602,26 @@ class PostponedArgumentInputTypesResolver(
return true
}
private fun Context.findNextVariableForParameterType(
type: KotlinTypeMarker,
dependencyProvider: TypeVariableDependencyInformationProvider,
postponedArguments: List<PostponedResolvedAtomMarker>,
topLevelType: KotlinTypeMarker,
): VariableFixationFinder.VariableForFixation? {
val outerTypeVariables = outerTypeVariables.orEmpty()
val relatedVariables = type.extractArgumentsForFunctionTypeOrSubtype()
.flatMap { getAllDeeplyRelatedTypeVariables(it, dependencyProvider) }
.filter { it !in outerTypeVariables }
return variableFixationFinder.findFirstVariableForFixation(
this,
relatedVariables,
postponedArguments,
ConstraintSystemCompletionMode.FULL,
topLevelType,
)
}
private fun KotlinTypeMarker?.wrapToTypeWithKind() = this?.let { TypeWithKind(it) }
companion object {
@@ -22,6 +22,8 @@ class ResultTypeResolver(
private val languageVersionSettings: LanguageVersionSettings
) {
interface Context : TypeSystemInferenceExtensionContext {
val notFixedTypeVariables: Map<TypeConstructorMarker, VariableWithConstraints>
val outerSystemVariablesPrefixSize: Int
fun isProperType(type: KotlinTypeMarker): Boolean
fun buildNotFixedVariablesToStubTypesSubstitutor(): TypeSubstitutorMarker
fun isReified(variable: TypeVariableMarker): Boolean
@@ -220,6 +222,10 @@ class ResultTypeResolver(
if (typesWithoutStubs.isNotEmpty()) {
commonSuperType = computeCommonSuperType(typesWithoutStubs)
} else if (outerSystemVariablesPrefixSize > 0) {
// outerSystemVariablesPrefixSize > 0 only for PCLA (K2)
@OptIn(K2Only::class)
commonSuperType = createSubstitutionFromSubtypingStubTypesToTypeVariables().safeSubstitute(commonSuperType)
}
}
@@ -273,6 +279,14 @@ class ResultTypeResolver(
}
if (!atLeastOneProper) return emptyList()
// PCLA slow path
// We only allow using TVs fixation for nested PCLA calls
if (outerSystemVariablesPrefixSize > 0) {
val notFixedToStubTypesSubstitutor = buildNotFixedVariablesToStubTypesSubstitutor()
return lowerConstraintTypes.map { notFixedToStubTypesSubstitutor.safeSubstitute(it) }
}
if (!atLeastOneNonProper) return lowerConstraintTypes
val notFixedToStubTypesSubstitutor = buildNotFixedVariablesToStubTypesSubstitutor()
@@ -336,7 +350,7 @@ class ResultTypeResolver(
}
private fun Context.isProperTypeForFixation(type: KotlinTypeMarker): Boolean =
isProperTypeForFixation(type) { isProperType(it) }
isProperTypeForFixation(type, notFixedTypeVariables.keys) { isProperType(it) }
private fun findResultIfThereIsEqualsConstraint(c: Context, variableWithConstraints: VariableWithConstraints): KotlinTypeMarker? =
with(c) {
@@ -7,15 +7,22 @@ package org.jetbrains.kotlin.resolve.calls.inference.components
import org.jetbrains.kotlin.resolve.calls.inference.model.VariableWithConstraints
import org.jetbrains.kotlin.resolve.calls.model.PostponedResolvedAtomMarker
import org.jetbrains.kotlin.types.model.*
import org.jetbrains.kotlin.types.model.KotlinTypeMarker
import org.jetbrains.kotlin.types.model.TypeConstructorMarker
import org.jetbrains.kotlin.types.model.freshTypeConstructor
import org.jetbrains.kotlin.types.model.typeConstructor
import org.jetbrains.kotlin.utils.SmartSet
class TypeVariableDependencyInformationProvider(
private val notFixedTypeVariables: Map<TypeConstructorMarker, VariableWithConstraints>,
private val postponedKtPrimitives: List<PostponedResolvedAtomMarker>,
private val topLevelType: KotlinTypeMarker?,
private val typeSystemContext: TypeSystemInferenceExtensionContext
private val typeSystemContext: VariableFixationFinder.Context
) {
private val outerTypeVariables: Set<TypeConstructorMarker>? =
typeSystemContext.outerTypeVariables
/*
* Not oriented edges
* TypeVariable(A) has UPPER(Function1<TypeVariable(B), R>) => A and B are related deeply
@@ -41,7 +48,16 @@ class TypeVariableDependencyInformationProvider(
computeRelatedToTopLevelType()
}
fun isVariableRelatedToTopLevelType(variable: TypeConstructorMarker) = relatedToTopLevelType.contains(variable)
fun isVariableRelatedToTopLevelType(variable: TypeConstructorMarker) =
relatedToTopLevelType.contains(variable)
fun isRelatedToOuterTypeVariable(variable: TypeConstructorMarker): Boolean {
val outerTypeVariables = outerTypeVariables ?: return false
val myDependent = getDeeplyDependentVariables(variable) ?: return false
return myDependent.any { it in outerTypeVariables }
}
fun isVariableRelatedToAnyOutputType(variable: TypeConstructorMarker) = relatedToAllOutputTypes.contains(variable)
fun getDeeplyDependentVariables(variable: TypeConstructorMarker) = deepTypeVariableDependencies[variable]
@@ -27,25 +27,40 @@ class VariableFixationFinder(
val fixedTypeVariables: Map<TypeConstructorMarker, KotlinTypeMarker>
val postponedTypeVariables: List<TypeVariableMarker>
val constraintsFromAllForkPoints: MutableList<Pair<IncorporationConstraintPosition, ForkPointData>>
val allTypeVariables: Map<TypeConstructorMarker, TypeVariableMarker>
/**
* If not null, that property means that we should assume temporary
* `allTypeVariables.keys.minus(typeVariablesThatAreNotCountedAsProperTypes)` as proper types when fixating some variables.
* See [org.jetbrains.kotlin.resolve.calls.inference.model.ConstraintStorage.outerSystemVariablesPrefixSize]
*/
val outerSystemVariablesPrefixSize: Int
val outerTypeVariables: Set<TypeConstructorMarker>?
get() =
when {
outerSystemVariablesPrefixSize > 0 -> allTypeVariables.keys.take(outerSystemVariablesPrefixSize).toSet()
else -> null
}
/**
* If not null, that property means that we should assume temporary them all as proper types when fixating some variables.
*
* By default, if that property is null, we assume all `allTypeVariables` as not proper.
*
* Currently, that is only used for `provideDelegate` resolution, see
* [org.jetbrains.kotlin.fir.resolve.transformers.body.resolve.FirDeclarationsResolveTransformer.fixInnerVariablesForProvideDelegateIfNeeded]
*/
val typeVariablesThatAreNotCountedAsProperTypes: Set<TypeConstructorMarker>?
val typeVariablesThatAreCountedAsProperTypes: Set<TypeConstructorMarker>?
fun isReified(variable: TypeVariableMarker): Boolean
}
data class VariableForFixation(
class VariableForFixation(
val variable: TypeConstructorMarker,
val hasProperConstraint: Boolean,
)
private val hasProperConstraint: Boolean,
private val hasDependencyOnOuterTypeVariable: Boolean = false,
) {
val isReady: Boolean get() = hasProperConstraint && !hasDependencyOnOuterTypeVariable
}
fun findFirstVariableForFixation(
c: Context,
@@ -53,11 +68,13 @@ class VariableFixationFinder(
postponedKtPrimitives: List<PostponedResolvedAtomMarker>,
completionMode: ConstraintSystemCompletionMode,
topLevelType: KotlinTypeMarker,
): VariableForFixation? = c.findTypeVariableForFixation(allTypeVariables, postponedKtPrimitives, completionMode, topLevelType)
): VariableForFixation? =
c.findTypeVariableForFixation(allTypeVariables, postponedKtPrimitives, completionMode, topLevelType)
enum class TypeVariableFixationReadiness {
FORBIDDEN,
WITHOUT_PROPER_ARGUMENT_CONSTRAINT, // proper constraint from arguments -- not from upper bound for type parameters
OUTER_TYPE_VARIABLE_DEPENDENCY,
READY_FOR_FIXATION_DECLARED_UPPER_BOUND_WITH_SELF_TYPES,
WITH_COMPLEX_DEPENDENCY, // if type variable T has constraint with non fixed type variable inside (non-top-level): T <: Foo<S>
ALL_CONSTRAINTS_TRIVIAL_OR_NON_PROPER, // proper trivial constraint from arguments, Nothing <: T
@@ -85,6 +102,7 @@ class VariableFixationFinder(
isTypeInferenceForSelfTypesSupported && areAllProperConstraintsSelfTypeBased(variable) ->
TypeVariableFixationReadiness.READY_FOR_FIXATION_DECLARED_UPPER_BOUND_WITH_SELF_TYPES
!variableHasProperArgumentConstraints(variable) -> TypeVariableFixationReadiness.WITHOUT_PROPER_ARGUMENT_CONSTRAINT
dependencyProvider.isRelatedToOuterTypeVariable(variable) -> TypeVariableFixationReadiness.OUTER_TYPE_VARIABLE_DEPENDENCY
hasDependencyToOtherTypeVariables(variable) -> TypeVariableFixationReadiness.WITH_COMPLEX_DEPENDENCY
// TODO: Consider removing this kind of readiness, see KT-63032
allConstraintsTrivialOrNonProper(variable) -> TypeVariableFixationReadiness.ALL_CONSTRAINTS_TRIVIAL_OR_NON_PROPER
@@ -152,7 +170,7 @@ class VariableFixationFinder(
if (allTypeVariables.isEmpty()) return null
val dependencyProvider = TypeVariableDependencyInformationProvider(
notFixedTypeVariables, postponedArguments, topLevelType.takeIf { completionMode == PARTIAL }, this
notFixedTypeVariables, postponedArguments, topLevelType.takeIf { completionMode == PARTIAL }, this,
)
val candidate =
@@ -161,6 +179,8 @@ class VariableFixationFinder(
return when (getTypeVariableReadiness(candidate, dependencyProvider)) {
TypeVariableFixationReadiness.FORBIDDEN -> null
TypeVariableFixationReadiness.WITHOUT_PROPER_ARGUMENT_CONSTRAINT -> VariableForFixation(candidate, false)
TypeVariableFixationReadiness.OUTER_TYPE_VARIABLE_DEPENDENCY ->
VariableForFixation(candidate, hasProperConstraint = true, hasDependencyOnOuterTypeVariable = true)
else -> VariableForFixation(candidate, true)
}
@@ -190,12 +210,13 @@ class VariableFixationFinder(
&& !c.isNullabilityConstraint
private fun Context.isProperType(type: KotlinTypeMarker): Boolean =
isProperTypeForFixation(type) { t -> !t.contains { isNotFixedRelevantVariable(it) } }
isProperTypeForFixation(type, notFixedTypeVariables.keys) { t -> !t.contains { isNotFixedRelevantVariable(it) } }
private fun Context.isNotFixedRelevantVariable(it: KotlinTypeMarker): Boolean {
if (!notFixedTypeVariables.containsKey(it.typeConstructor())) return false
if (typeVariablesThatAreNotCountedAsProperTypes == null) return true
return typeVariablesThatAreNotCountedAsProperTypes!!.contains(it.typeConstructor())
val key = it.typeConstructor()
if (!notFixedTypeVariables.containsKey(key)) return false
if (typeVariablesThatAreCountedAsProperTypes?.contains(key) == true) return false
return true
}
private fun Context.isReified(variable: TypeConstructorMarker): Boolean =
@@ -235,8 +256,25 @@ class VariableFixationFinder(
}
}
inline fun TypeSystemInferenceExtensionContext.isProperTypeForFixation(type: KotlinTypeMarker, isProper: (KotlinTypeMarker) -> Boolean) =
isProper(type) && extractProjectionsForAllCapturedTypes(type).all(isProper)
/**
* Returns `false` for fixed type variables types even if `isProper(type) == true`
* Thus allowing only non-TVs types to be used for fixation on top level.
* While this limitation is important, it doesn't really limit final results because when we have a constraint like T <: E or E <: T
* and we're going to fix T into E, we assume that if E has some other constraints, they are being incorporated to T, so we would choose
* them instead of E itself.
*/
inline fun TypeSystemInferenceExtensionContext.isProperTypeForFixation(
type: KotlinTypeMarker,
notFixedTypeVariables: Set<TypeConstructorMarker>,
isProper: (KotlinTypeMarker) -> Boolean
): Boolean {
// We don't allow fixing T into any top-level TV type, like T := F or T := F & Any
// Even if F is considered as a proper by `isProper` (e.g., it belongs to an outer CS)
// But at the same time, we don't forbid fixing into T := MutableList<F>
if (type.typeConstructor() in notFixedTypeVariables) return false
return isProper(type) && extractProjectionsForAllCapturedTypes(type).all(isProper)
}
fun TypeSystemInferenceExtensionContext.extractProjectionsForAllCapturedTypes(baseType: KotlinTypeMarker): Set<KotlinTypeMarker> {
if (baseType.isFlexible()) {
@@ -49,6 +49,8 @@ interface ConstraintStorage {
val constraintsFromAllForkPoints: List<Pair<IncorporationConstraintPosition, ForkPointData>>
/**
* Outer system for a call means some set of variables defined beside it/its arguments
*
* In case some candidate's CS is built in the context of some outer CS, first [outerSystemVariablesPrefixSize] in the list
* of [allTypeVariables] belong to the outer CS.
*
@@ -62,6 +64,8 @@ interface ConstraintStorage {
*/
val outerSystemVariablesPrefixSize: Int
val usesOuterCs: Boolean
object Empty : ConstraintStorage {
override val allTypeVariables: Map<TypeConstructorMarker, TypeVariableMarker> get() = emptyMap()
override val notFixedTypeVariables: Map<TypeConstructorMarker, VariableWithConstraints> get() = emptyMap()
@@ -77,6 +81,8 @@ interface ConstraintStorage {
override val constraintsFromAllForkPoints: List<Pair<IncorporationConstraintPosition, ForkPointData>> = emptyList()
override val outerSystemVariablesPrefixSize: Int get() = 0
override val usesOuterCs: Boolean get() = false
}
}
@@ -221,6 +221,14 @@ class MutableVariableWithConstraints private constructor(
}
}
fun runConstraintsSimplification() {
val currentState = constraints.toList()
mutableConstraints.apply {
clear()
addAll(currentState)
}
}
private fun isUsefulConstraint(constraint: Constraint, equalityConstraints: Map<Int, List<Constraint>>): Boolean {
if (constraint.kind == ConstraintKind.EQUALITY) return true
return equalityConstraints[constraint.typeHashCode]?.none { it.type == constraint.type } ?: true
@@ -251,4 +259,15 @@ internal class MutableConstraintStorage : ConstraintStorage {
override val constraintsFromAllForkPoints: MutableList<Pair<IncorporationConstraintPosition, ForkPointData>> = SmartList()
override var outerSystemVariablesPrefixSize: Int = 0
override var usesOuterCs: Boolean = false
@AssertionsOnly
internal var outerCS: ConstraintStorage? = null
}
/**
* Annotated member is used only for assertion purposes and does not affect semantics
*/
@RequiresOptIn
annotation class AssertionsOnly
@@ -41,7 +41,7 @@ class NewConstraintSystemImpl(
private val properTypesCache: MutableSet<KotlinTypeMarker> = SmartSet.create()
private val notProperTypesCache: MutableSet<KotlinTypeMarker> = SmartSet.create()
private val intersectionTypesCache: MutableMap<Collection<KotlinTypeMarker>, EmptyIntersectionTypeInfo?> = mutableMapOf()
override var typeVariablesThatAreNotCountedAsProperTypes: Set<TypeConstructorMarker>? = null
override var typeVariablesThatAreCountedAsProperTypes: Set<TypeConstructorMarker>? = null
private var couldBeResolvedWithUnrestrictedBuilderInference: Boolean = false
@@ -51,23 +51,26 @@ class NewConstraintSystemImpl(
* @see [org.jetbrains.kotlin.resolve.calls.inference.components.VariableFixationFinder.Context.typeVariablesThatAreNotCountedAsProperTypes]
* @see [org.jetbrains.kotlin.fir.resolve.transformers.body.resolve.FirDeclarationsResolveTransformer.fixInnerVariablesForProvideDelegateIfNeeded]
*/
fun withTypeVariablesThatAreNotCountedAsProperTypes(typeVariables: Set<TypeConstructorMarker>, block: () -> Unit) {
@K2Only
override fun <R> withTypeVariablesThatAreCountedAsProperTypes(typeVariables: Set<TypeConstructorMarker>, block: () -> R): R {
checkState(State.BUILDING)
// Cleaning cache is necessary because temporarily we change the meaning of what does "proper type" mean
properTypesCache.clear()
notProperTypesCache.clear()
require(typeVariablesThatAreNotCountedAsProperTypes == null) {
require(typeVariablesThatAreCountedAsProperTypes == null) {
"Currently there should be no nested withDisallowingOnlyThisTypeVariablesForProperTypes calls"
}
typeVariablesThatAreNotCountedAsProperTypes = typeVariables
typeVariablesThatAreCountedAsProperTypes = typeVariables
block()
val result = block()
typeVariablesThatAreNotCountedAsProperTypes = null
typeVariablesThatAreCountedAsProperTypes = null
properTypesCache.clear()
notProperTypesCache.clear()
return result
}
private enum class State {
@@ -221,6 +224,7 @@ class NewConstraintSystemImpl(
// ConstraintSystemBuilder
private fun transactionRegisterVariable(variable: TypeVariableMarker) {
if (state != State.TRANSACTION) return
if (variable.freshTypeConstructor() in storage.allTypeVariables) return
typeVariablesTransaction.add(variable)
}
@@ -302,13 +306,25 @@ class NewConstraintSystemImpl(
}
fun addOuterSystem(outerSystem: ConstraintStorage) {
addOtherSystem(outerSystem)
require(!storage.usesOuterCs)
storage.usesOuterCs = true
storage.outerSystemVariablesPrefixSize = outerSystem.allTypeVariables.size
@OptIn(AssertionsOnly::class)
storage.outerCS = outerSystem
addOtherSystem(outerSystem, isAddingOuter = true)
}
fun setBaseSystem(outerSystem: ConstraintStorage) {
addOtherSystem(outerSystem)
storage.outerSystemVariablesPrefixSize = outerSystem.outerSystemVariablesPrefixSize
@K2Only
fun setBaseSystem(baseSystem: ConstraintStorage) {
require(storage.allTypeVariables.isEmpty())
storage.usesOuterCs = baseSystem.usesOuterCs
storage.outerSystemVariablesPrefixSize = baseSystem.outerSystemVariablesPrefixSize
@OptIn(AssertionsOnly::class)
storage.outerCS = (baseSystem as? MutableConstraintStorage)?.outerCS
addOtherSystem(baseSystem)
}
fun prepareForGlobalCompletion() {
@@ -317,6 +333,17 @@ class NewConstraintSystemImpl(
}
override fun addOtherSystem(otherSystem: ConstraintStorage) {
addOtherSystem(otherSystem, isAddingOuter = false)
}
fun replaceContentWith(otherSystem: ConstraintStorage) {
addOtherSystem(otherSystem, isAddingOuter = false, clearNotFixedTypeVariables = true)
}
private fun addOtherSystem(otherSystem: ConstraintStorage, isAddingOuter: Boolean, clearNotFixedTypeVariables: Boolean = false) {
@OptIn(AssertionsOnly::class)
runOuterCSRelatedAssertions(otherSystem, isAddingOuter)
if (otherSystem.allTypeVariables.isNotEmpty()) {
otherSystem.allTypeVariables.forEach {
transactionRegisterVariable(it.value)
@@ -324,16 +351,47 @@ class NewConstraintSystemImpl(
storage.allTypeVariables.putAll(otherSystem.allTypeVariables)
notProperTypesCache.clear()
}
// `clearNotFixedTypeVariables` means that we're mostly replacing the content, thus we need to remove variables that have been fixed
// in `otherSystem` from `this.notFixedTypeVariables`, too
if (clearNotFixedTypeVariables) {
notFixedTypeVariables.clear()
}
for ((variable, constraints) in otherSystem.notFixedTypeVariables) {
notFixedTypeVariables[variable] = MutableVariableWithConstraints(this, constraints)
}
storage.initialConstraints.addAll(otherSystem.initialConstraints)
val currentInitialConstraints = storage.initialConstraints.toSet()
otherSystem.initialConstraints.filterTo(storage.initialConstraints) {
it !in currentInitialConstraints
}
storage.maxTypeDepthFromInitialConstraints =
max(storage.maxTypeDepthFromInitialConstraints, otherSystem.maxTypeDepthFromInitialConstraints)
storage.errors.addAll(otherSystem.errors)
storage.fixedTypeVariables.putAll(otherSystem.fixedTypeVariables)
storage.postponedTypeVariables.addAll(otherSystem.postponedTypeVariables)
storage.constraintsFromAllForkPoints.addAll(otherSystem.constraintsFromAllForkPoints)
}
@AssertionsOnly
private fun runOuterCSRelatedAssertions(otherSystem: ConstraintStorage, isAddingOuter: Boolean) {
if (!otherSystem.usesOuterCs) return
// When integrating a child system back, it's ok that for root CS, `storage.usesOuterCs == false`
if ((otherSystem as? MutableConstraintStorage)?.outerCS === storage) return
require(storage.usesOuterCs)
if (!isAddingOuter) {
require(storage.outerSystemVariablesPrefixSize == otherSystem.outerSystemVariablesPrefixSize) {
"Expected to be ${otherSystem.outerSystemVariablesPrefixSize}, but ${storage.outerSystemVariablesPrefixSize} found"
}
}
}
// ResultTypeResolver.Context, ConstraintSystemBuilder
@@ -357,11 +415,11 @@ class NewConstraintSystemImpl(
it
if (typeToCheck == null) return@contains false
if (typeVariablesThatAreNotCountedAsProperTypes != null) {
return@contains typeVariablesThatAreNotCountedAsProperTypes!!.contains(typeToCheck.typeConstructor())
if (typeVariablesThatAreCountedAsProperTypes?.contains(typeToCheck.typeConstructor()) == true) {
return@contains false
}
storage.allTypeVariables.containsKey(typeToCheck.typeConstructor())
return@contains storage.allTypeVariables.containsKey(typeToCheck.typeConstructor())
}
override fun isTypeVariable(type: KotlinTypeMarker): Boolean {
@@ -689,7 +747,7 @@ class NewConstraintSystemImpl(
return buildCurrentSubstitutor(emptyMap())
}
override fun buildCurrentSubstitutor(additionalBindings: Map<TypeConstructorMarker, StubTypeMarker>): TypeSubstitutorMarker {
override fun buildCurrentSubstitutor(additionalBindings: Map<TypeConstructorMarker, KotlinTypeMarker>): TypeSubstitutorMarker {
checkState(State.BUILDING, State.COMPLETION, State.TRANSACTION)
return storage.buildCurrentSubstitutor(this, additionalBindings)
}
@@ -715,6 +773,8 @@ class NewConstraintSystemImpl(
return storage
}
val usesOuterCs: Boolean get() = storage.usesOuterCs
// PostponedArgumentsAnalyzer.Context
override fun hasUpperOrEqualUnitConstraint(type: KotlinTypeMarker): Boolean {
checkState(State.BUILDING, State.COMPLETION, State.FREEZED)
@@ -10,22 +10,25 @@ import org.jetbrains.kotlin.config.LanguageFeature
import org.jetbrains.kotlin.config.LanguageVersionSettings
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
import org.jetbrains.kotlin.descriptors.synthetic.SyntheticMemberDescriptor
import org.jetbrains.kotlin.resolve.calls.components.candidate.ResolutionCandidate
import org.jetbrains.kotlin.resolve.calls.components.candidate.CallableReferenceResolutionCandidate
import org.jetbrains.kotlin.resolve.calls.components.candidate.ResolutionCandidate
import org.jetbrains.kotlin.resolve.calls.components.candidate.SimpleResolutionCandidate
import org.jetbrains.kotlin.resolve.calls.inference.NewConstraintSystem
import org.jetbrains.kotlin.resolve.calls.inference.addEqualityConstraintIfCompatible
import org.jetbrains.kotlin.resolve.calls.inference.components.*
import org.jetbrains.kotlin.resolve.calls.inference.components.ConstraintSystemCompletionMode
import org.jetbrains.kotlin.resolve.calls.inference.components.KotlinConstraintSystemCompleter
import org.jetbrains.kotlin.resolve.calls.inference.components.NewTypeSubstitutorByConstructorMap
import org.jetbrains.kotlin.resolve.calls.inference.components.TrivialConstraintTypeInferenceOracle
import org.jetbrains.kotlin.resolve.calls.inference.model.ConstraintStorage.Empty.hasContradiction
import org.jetbrains.kotlin.resolve.calls.inference.model.ExpectedTypeConstraintPositionImpl
import org.jetbrains.kotlin.resolve.calls.model.*
import org.jetbrains.kotlin.resolve.calls.tower.CandidateFactory
import org.jetbrains.kotlin.resolve.calls.tower.forceResolution
import org.jetbrains.kotlin.types.error.ErrorUtils
import org.jetbrains.kotlin.types.TypeUtils
import org.jetbrains.kotlin.types.UnwrappedType
import org.jetbrains.kotlin.types.error.ErrorType
import org.jetbrains.kotlin.types.error.ErrorTypeKind
import org.jetbrains.kotlin.types.error.ErrorUtils
import org.jetbrains.kotlin.types.model.safeSubstitute
import org.jetbrains.kotlin.utils.addToStdlib.same
@@ -80,6 +83,7 @@ class KotlinCallCompleter(
candidate.runCompletion(completionMode, diagnosticHolder, resolutionCallbacks)
candidate.asCallResolutionResult(completionMode, diagnosticHolder)
}
ConstraintSystemCompletionMode.PCLA_POSTPONED_CALL -> error("PCLA might be only run for K2")
ConstraintSystemCompletionMode.UNTIL_FIRST_LAMBDA -> throw IllegalStateException("Should not be here")
}
@@ -12,7 +12,10 @@ import org.jetbrains.kotlin.resolve.calls.components.transformToResolvedLambda
import org.jetbrains.kotlin.resolve.calls.inference.model.*
import org.jetbrains.kotlin.resolve.calls.model.*
import org.jetbrains.kotlin.resolve.calls.model.MultiLambdaBuilderInferenceRestriction
import org.jetbrains.kotlin.types.*
import org.jetbrains.kotlin.types.KotlinType
import org.jetbrains.kotlin.types.KotlinTypeFactory
import org.jetbrains.kotlin.types.TypeConstructor
import org.jetbrains.kotlin.types.UnwrappedType
import org.jetbrains.kotlin.types.error.ErrorTypeKind
import org.jetbrains.kotlin.types.error.ErrorUtils
import org.jetbrains.kotlin.types.model.*
@@ -279,7 +282,7 @@ class KotlinConstraintSystemCompleter(
// continue completion (rerun stages) only if ready for fixation variables with proper constraints have appeared
// (after analysing a lambda with the builder inference)
// otherwise we don't continue and report "not enough type information" error
return variableForFixation?.hasProperConstraint == true
return variableForFixation?.isReady == true
}
private fun transformToAtomWithNewFunctionalExpectedType(
@@ -319,7 +322,7 @@ class KotlinConstraintSystemCompleter(
topLevelType
) ?: return false
if (!variableForFixation.hasProperConstraint) return false
if (!variableForFixation.isReady) return false
fixVariable(this, notFixedTypeVariables.getValue(variableForFixation.variable), topLevelAtoms, diagnosticsHolder)
@@ -340,7 +343,7 @@ class KotlinConstraintSystemCompleter(
postponedArguments, completionMode, topLevelType,
) ?: break
assert(!variableForFixation.hasProperConstraint) {
assert(!variableForFixation.isReady) {
"At this stage there should be no remaining variables with proper constraints"
}
@@ -8,7 +8,9 @@ package org.jetbrains.kotlin.types.model
import org.jetbrains.kotlin.builtins.functions.FunctionTypeKind
import org.jetbrains.kotlin.resolve.checkers.EmptyIntersectionTypeChecker
import org.jetbrains.kotlin.resolve.checkers.EmptyIntersectionTypeInfo
import org.jetbrains.kotlin.types.*
import org.jetbrains.kotlin.types.AbstractTypeChecker
import org.jetbrains.kotlin.types.TypeCheckerState
import org.jetbrains.kotlin.types.Variance
import kotlin.contracts.ExperimentalContracts
import kotlin.contracts.contract
@@ -310,6 +312,9 @@ interface TypeSystemInferenceExtensionContext : TypeSystemContext, TypeSystemBui
*/
fun KotlinTypeMarker.convertToNonRaw(): KotlinTypeMarker
@K2Only
fun createSubstitutionFromSubtypingStubTypesToTypeVariables(): TypeSubstitutorMarker
fun createCapturedStarProjectionForSelfType(
typeVariable: TypeVariableTypeConstructorMarker,
typesForRecursiveTypeParameters: List<KotlinTypeMarker>,
@@ -588,3 +593,6 @@ fun requireOrDescribe(condition: Boolean, value: Any?) {
@RequiresOptIn("This kinds of type is obsolete and should not be used until you really need it")
annotation class ObsoleteTypeKind
@RequiresOptIn
annotation class K2Only
@@ -622,6 +622,11 @@ interface ClassicTypeSystemContext : TypeSystemInferenceExtensionContext, TypeSy
errorSupportedOnlyInTypeInference()
}
@K2Only
override fun createSubstitutionFromSubtypingStubTypesToTypeVariables(): TypeSubstitutorMarker {
error("Only for K2")
}
override fun TypeSubstitutorMarker.safeSubstitute(type: KotlinTypeMarker): KotlinTypeMarker {
require(type is UnwrappedType, type::errorMessage)
require(this is TypeSubstitutor, this::errorMessage)