K2: Fix PCLA breaking change when using a bare type on a type variable

See the change at docs/fir/pcla.md and the kdoc
at `getAndSemiFixCurrentResultIfTypeVariable` for clarification

^KT-64840 Fixed
This commit is contained in:
Denis.Zharkov
2024-03-08 19:38:58 +01:00
committed by Space Team
parent 141be17b4b
commit 232c3aeadc
10 changed files with 78 additions and 33 deletions
@@ -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 <T> processPartiallyResolvedCall(
@@ -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<ConeTypeVariableTypeConstructor, ConeKotlinType>? {
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<ConeTypeVariableTypeConstructor, ConeKotlinType>? {
@@ -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)
}
}
@@ -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 (
@@ -1,20 +0,0 @@
// ISSUE: KT-64840 (K2/PCLA difference)
class Controller<T> {
fun yield(t: T): Boolean = true
}
fun <S> generate(g: suspend Controller<S>.() -> Unit): S = TODO()
interface A<F> {
val a: F?
}
interface B<G> : A<G>
fun <X> predicate(x: X, c: Controller<in X>, p: (X) -> Boolean) {}
fun main(a: A<*>) {
generate {
predicate(a, this) { it is <!NO_TYPE_ARGUMENTS_ON_RHS!>B<!> }
}.a
}
@@ -1,3 +1,4 @@
// FIR_IDENTICAL
// ISSUE: KT-64840 (K2/PCLA difference)
class Controller<T> {
fun yield(t: T): Boolean = true
@@ -18,8 +18,8 @@ fun <X> withCallback(x: X, c: Controller<in X>, p: (X) -> Unit) {}
fun main(a: A<String>) {
val x = generate {
withCallback(a, this) {
(it as <!NO_TYPE_ARGUMENTS_ON_RHS!>B<!>).<!UNRESOLVED_REFERENCE!>b<!>.length
it.<!UNRESOLVED_REFERENCE!>b<!>.length
(it as B).b.length
it.b.length
it.a.length
}
}
@@ -15,6 +15,8 @@ interface C : CommonSupertype
fun <X> predicate(x: X, c: Controller<X>, 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) {
<!DEBUG_INFO_EXPRESSION_TYPE("CommonSupertype")!>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 <!NO_TYPE_ARGUMENTS_ON_RHS!>B<!>
x is B
}
yield(c)
// For Sv we've got an EQUALITY constraint to A<*>
// Thus not allowing C type here
yield(<!ARGUMENT_TYPE_MISMATCH!>c<!>)
}
<!DEBUG_INFO_EXPRESSION_TYPE("CommonSupertype")!>x2<!>
<!DEBUG_INFO_EXPRESSION_TYPE("A<*>")!>x2<!>
}
@@ -15,6 +15,8 @@ interface C : CommonSupertype
fun <X> predicate(x: X, c: Controller<X>, 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) {
<!DEBUG_INFO_EXPRESSION_TYPE("CommonSupertype")!>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 ->
<!USELESS_IS_CHECK!>x is <!NO_TYPE_ARGUMENTS_ON_RHS!>B<!><!>
}
// For Sv we've got an EQUALITY constraint to A<*>
// Thus not allowing C type here
yield(c)
}