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:
committed by
Space Team
parent
141be17b4b
commit
232c3aeadc
+13
@@ -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(
|
||||
|
||||
+9
-6
@@ -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>? {
|
||||
|
||||
+1
-1
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
+4
-1
@@ -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 (
|
||||
|
||||
Vendored
-20
@@ -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
|
||||
}
|
||||
Vendored
+1
@@ -1,3 +1,4 @@
|
||||
// FIR_IDENTICAL
|
||||
// ISSUE: KT-64840 (K2/PCLA difference)
|
||||
class Controller<T> {
|
||||
fun yield(t: T): Boolean = true
|
||||
|
||||
Vendored
+2
-2
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
+11
-3
@@ -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<!>
|
||||
}
|
||||
|
||||
+8
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
@@ -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<F>
|
||||
interface B<G> : A<G>
|
||||
|
||||
fun <X> predicate(x: X, c: MutableList<in X>, 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).
|
||||
|
||||
Reference in New Issue
Block a user