diff --git a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/inference/FirInferenceSession.kt b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/inference/FirInferenceSession.kt index d44d9772640..f8ae85fb873 100644 --- a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/inference/FirInferenceSession.kt +++ b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/inference/FirInferenceSession.kt @@ -37,6 +37,19 @@ abstract class FirInferenceSession { open fun addSubtypeConstraintIfCompatible(lowerType: ConeKotlinType, upperType: ConeKotlinType, element: FirElement) {} + /** + * For non-trivial inference session (currently PCLA-only), if the type is a type variable that might be fixed, + * fix it and return a fixation result. + * + * Type variable might be fixed if it doesn't belong to an outer CS and have proper constraints. + * + * By semi-fixation we mean that only the relevant EQUALITY constraint is added, + * [org.jetbrains.kotlin.resolve.calls.inference.components.ConstraintSystemCompletionContext.fixVariable] is not expected to be called. + * + * NB: The callee must pay attention that exactly current common CS will be modified. + */ + open fun getAndSemiFixCurrentResultIfTypeVariable(type: ConeKotlinType): ConeKotlinType? = null + companion object { val DEFAULT: FirInferenceSession = object : FirInferenceSession() { override fun processPartiallyResolvedCall( diff --git a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/inference/FirPCLAInferenceSession.kt b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/inference/FirPCLAInferenceSession.kt index c4911209d1d..c9285c0e9f9 100644 --- a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/inference/FirPCLAInferenceSession.kt +++ b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/inference/FirPCLAInferenceSession.kt @@ -135,7 +135,7 @@ class FirPCLAInferenceSession( val system = (this as? FirResolvable)?.candidate()?.system ?: currentCommonSystem if (resolutionMode is ResolutionMode.ReceiverResolution) { - fixVariablesForMemberScope(resolvedType, system)?.let { additionalBindings += it } + fixCurrentResultIfTypeVariableAndReturnBinding(resolvedType, system)?.let { additionalBindings += it } } val substitutor = system.buildCurrentSubstitutor(additionalBindings) as ConeSubstitutor @@ -146,19 +146,22 @@ class FirPCLAInferenceSession( } } - fun fixVariablesForMemberScope( + override fun getAndSemiFixCurrentResultIfTypeVariable(type: ConeKotlinType): ConeKotlinType? = + fixCurrentResultIfTypeVariableAndReturnBinding(type, currentCommonSystem)?.second + + fun fixCurrentResultIfTypeVariableAndReturnBinding( type: ConeKotlinType, myCs: NewConstraintSystemImpl, ): Pair? { return when (type) { - is ConeFlexibleType -> fixVariablesForMemberScope(type.lowerBound, myCs) - is ConeDefinitelyNotNullType -> fixVariablesForMemberScope(type.original, myCs) - is ConeTypeVariableType -> fixVariablesForMemberScope(type, myCs) + is ConeFlexibleType -> fixCurrentResultIfTypeVariableAndReturnBinding(type.lowerBound, myCs) + is ConeDefinitelyNotNullType -> fixCurrentResultIfTypeVariableAndReturnBinding(type.original, myCs) + is ConeTypeVariableType -> fixCurrentResultForNestedTypeVariable(type, myCs) else -> null } } - private fun fixVariablesForMemberScope( + private fun fixCurrentResultForNestedTypeVariable( type: ConeTypeVariableType, myCs: NewConstraintSystemImpl, ): Pair? { diff --git a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/inference/PostponedArgumentsAnalyzer.kt b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/inference/PostponedArgumentsAnalyzer.kt index 3035f94400c..3c9f407d114 100644 --- a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/inference/PostponedArgumentsAnalyzer.kt +++ b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/inference/PostponedArgumentsAnalyzer.kt @@ -120,7 +120,7 @@ class PostponedArgumentsAnalyzer( // TODO: Fix variables for context receivers, too (KT-64859) buildMap { lambda.receiver - ?.let { pclaInferenceSession.fixVariablesForMemberScope(it, candidate.system) } + ?.let { pclaInferenceSession.fixCurrentResultIfTypeVariableAndReturnBinding(it, candidate.system) } ?.let(this::plusAssign) } } diff --git a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/transformers/body/resolve/FirExpressionsResolveTransformer.kt b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/transformers/body/resolve/FirExpressionsResolveTransformer.kt index b242fd9e745..6a20a6b99e3 100644 --- a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/transformers/body/resolve/FirExpressionsResolveTransformer.kt +++ b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/transformers/body/resolve/FirExpressionsResolveTransformer.kt @@ -867,7 +867,10 @@ open class FirExpressionsResolveTransformer(transformer: FirAbstractBodyResolveT val firClass = type.lookupTag.toSymbol(session)?.fir ?: return this if (firClass.typeParameters.isEmpty()) return this - val originalType = argument.unwrapExpression().resolvedType + val originalType = argument.unwrapExpression().resolvedType.let { + components.context.inferenceSession.getAndSemiFixCurrentResultIfTypeVariable(it) ?: it + } + val outerClasses by lazy(LazyThreadSafetyMode.NONE) { firClass.symbol.getClassAndItsOuterClassesWhenLocal(session) } val newType = components.computeRepresentativeTypeForBareType(type, originalType) ?: if ( diff --git a/compiler/testData/diagnostics/tests/inference/builderInference/lambdaParameterForBareType.fir.kt b/compiler/testData/diagnostics/tests/inference/builderInference/lambdaParameterForBareType.fir.kt deleted file mode 100644 index a7bfbe72b77..00000000000 --- a/compiler/testData/diagnostics/tests/inference/builderInference/lambdaParameterForBareType.fir.kt +++ /dev/null @@ -1,20 +0,0 @@ -// ISSUE: KT-64840 (K2/PCLA difference) -class Controller { - fun yield(t: T): Boolean = true -} - -fun generate(g: suspend Controller.() -> Unit): S = TODO() - -interface A { - val a: F? -} - -interface B : A - -fun predicate(x: X, c: Controller, p: (X) -> Boolean) {} - -fun main(a: A<*>) { - generate { - predicate(a, this) { it is B } - }.a -} diff --git a/compiler/testData/diagnostics/tests/inference/builderInference/lambdaParameterForBareType.kt b/compiler/testData/diagnostics/tests/inference/builderInference/lambdaParameterForBareType.kt index a2138610b90..139ffc1aa19 100644 --- a/compiler/testData/diagnostics/tests/inference/builderInference/lambdaParameterForBareType.kt +++ b/compiler/testData/diagnostics/tests/inference/builderInference/lambdaParameterForBareType.kt @@ -1,3 +1,4 @@ +// FIR_IDENTICAL // ISSUE: KT-64840 (K2/PCLA difference) class Controller { fun yield(t: T): Boolean = true diff --git a/compiler/testData/diagnostics/tests/inference/builderInference/lambdaParameterForBareTypeAs.fir.kt b/compiler/testData/diagnostics/tests/inference/builderInference/lambdaParameterForBareTypeAs.fir.kt index ffd5d68faac..45a01687531 100644 --- a/compiler/testData/diagnostics/tests/inference/builderInference/lambdaParameterForBareTypeAs.fir.kt +++ b/compiler/testData/diagnostics/tests/inference/builderInference/lambdaParameterForBareTypeAs.fir.kt @@ -18,8 +18,8 @@ fun withCallback(x: X, c: Controller, p: (X) -> Unit) {} fun main(a: A) { val x = generate { withCallback(a, this) { - (it as B).b.length - it.b.length + (it as B).b.length + it.b.length it.a.length } } diff --git a/compiler/testData/diagnostics/tests/inference/builderInference/lambdaParameterForBareTypeEarlyFixationAffectsBehavior.fir.kt b/compiler/testData/diagnostics/tests/inference/builderInference/lambdaParameterForBareTypeEarlyFixationAffectsBehavior.fir.kt index 9bd5e18ba56..b7d9b042595 100644 --- a/compiler/testData/diagnostics/tests/inference/builderInference/lambdaParameterForBareTypeEarlyFixationAffectsBehavior.fir.kt +++ b/compiler/testData/diagnostics/tests/inference/builderInference/lambdaParameterForBareTypeEarlyFixationAffectsBehavior.fir.kt @@ -15,6 +15,8 @@ interface C : CommonSupertype fun predicate(x: X, c: Controller, p: (X) -> Unit) {} fun main(a: A<*>, c: C) { + // Without having `is` check + // This PCLA/BI call works in the same way both in K1 and K2 val x1 = generate { predicate(a, this) { x -> // x is B @@ -25,13 +27,19 @@ fun main(a: A<*>, c: C) { x1 + // But introducing `is` on the expression of `Xv` type as LHS and a bare type on RHS + // Leads to an early fixation of Xv to the current result type (A<*>) and automatically it leads to Sv fixation, too + // This case works differently in K1 (BI) and in K2 (PCLA), but in both cases it's red code + // Without the last `yield` call, it would be even green in K2 val x2 = generate { predicate(a, this) { x -> - x is B + x is B } - yield(c) + // For Sv we've got an EQUALITY constraint to A<*> + // Thus not allowing C type here + yield(c) } - x2 + ")!>x2 } diff --git a/compiler/testData/diagnostics/tests/inference/builderInference/lambdaParameterForBareTypeEarlyFixationAffectsBehavior.kt b/compiler/testData/diagnostics/tests/inference/builderInference/lambdaParameterForBareTypeEarlyFixationAffectsBehavior.kt index ca13fa480da..17eb54a2777 100644 --- a/compiler/testData/diagnostics/tests/inference/builderInference/lambdaParameterForBareTypeEarlyFixationAffectsBehavior.kt +++ b/compiler/testData/diagnostics/tests/inference/builderInference/lambdaParameterForBareTypeEarlyFixationAffectsBehavior.kt @@ -15,6 +15,8 @@ interface C : CommonSupertype fun predicate(x: X, c: Controller, p: (X) -> Unit) {} fun main(a: A<*>, c: C) { + // Without having `is` check + // This PCLA/BI call works in the same way both in K1 and K2 val x1 = generate { predicate(a, this) { x -> // x is B @@ -25,11 +27,17 @@ fun main(a: A<*>, c: C) { x1 + // But introducing `is` on the expression of `Xv` type as LHS and a bare type on RHS + // Leads to an early fixation of Xv to the current result type (A<*>) and automatically it leads to Sv fixation, too + // This case works differently in K1 (BI) and in K2 (PCLA), but in both cases it's red code + // Without the last `yield` call, it would be even green in K2 val x2 = generate { predicate(a, this) { x -> x is B } + // For Sv we've got an EQUALITY constraint to A<*> + // Thus not allowing C type here yield(c) } diff --git a/docs/fir/pcla.md b/docs/fir/pcla.md index 9bcd0bdf064..47c5ccac024 100644 --- a/docs/fir/pcla.md +++ b/docs/fir/pcla.md @@ -299,6 +299,35 @@ And that's how `addSubtypeConstraintIfCompatible` might be used. One of the ideas particularly for assignment is that they should be resolved via setter call, thus the necessary constraint would be introduced naturally when string literal would be an argument for `Fv` value parameter. +### getAndSemiFixCurrentResultIfTypeVariable + +Before deep-diving into this section, it's worth reading [On demand variable fixation](#on-demand-variable-fixation) section. + +Sometimes, besides computing member scope, there might be other cases when we need to fix a type variable on-demand. + +```kotlin +interface A +interface B : A + +fun predicate(x: X, c: MutableList, p: (X) -> Boolean) {} + +fun main(a: A<*>) { + buildList { + predicate(a, this) { + it is B + } + } +} +``` + +In this example, for `is` check, `B` type on the right-hand side is a bare type and to compute its arguments properly, we need to know +the proper type representation of `it` which is not proper yet (`Xv` variable). + +Potentially, we might've ignored that requiring full type arguments for `B`, but that would be a breaking change from a user project +([KT-64840](https://youtrack.jetbrains.com/issue/KT-64840)), so we decided to fix the type variable to the current result type. + +That's how this callback is currently used from the place before bare-type computation is started. + ## PCLA_POSTPONED_CALL completion mode This mode is assumed to be used for postponed nested calls inside PCLA lambdas instead of FULL mode (i.e., mostly for top-level calls).