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) {}
|
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 {
|
companion object {
|
||||||
val DEFAULT: FirInferenceSession = object : FirInferenceSession() {
|
val DEFAULT: FirInferenceSession = object : FirInferenceSession() {
|
||||||
override fun <T> processPartiallyResolvedCall(
|
override fun <T> processPartiallyResolvedCall(
|
||||||
|
|||||||
+9
-6
@@ -135,7 +135,7 @@ class FirPCLAInferenceSession(
|
|||||||
val system = (this as? FirResolvable)?.candidate()?.system ?: currentCommonSystem
|
val system = (this as? FirResolvable)?.candidate()?.system ?: currentCommonSystem
|
||||||
|
|
||||||
if (resolutionMode is ResolutionMode.ReceiverResolution) {
|
if (resolutionMode is ResolutionMode.ReceiverResolution) {
|
||||||
fixVariablesForMemberScope(resolvedType, system)?.let { additionalBindings += it }
|
fixCurrentResultIfTypeVariableAndReturnBinding(resolvedType, system)?.let { additionalBindings += it }
|
||||||
}
|
}
|
||||||
|
|
||||||
val substitutor = system.buildCurrentSubstitutor(additionalBindings) as ConeSubstitutor
|
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,
|
type: ConeKotlinType,
|
||||||
myCs: NewConstraintSystemImpl,
|
myCs: NewConstraintSystemImpl,
|
||||||
): Pair<ConeTypeVariableTypeConstructor, ConeKotlinType>? {
|
): Pair<ConeTypeVariableTypeConstructor, ConeKotlinType>? {
|
||||||
return when (type) {
|
return when (type) {
|
||||||
is ConeFlexibleType -> fixVariablesForMemberScope(type.lowerBound, myCs)
|
is ConeFlexibleType -> fixCurrentResultIfTypeVariableAndReturnBinding(type.lowerBound, myCs)
|
||||||
is ConeDefinitelyNotNullType -> fixVariablesForMemberScope(type.original, myCs)
|
is ConeDefinitelyNotNullType -> fixCurrentResultIfTypeVariableAndReturnBinding(type.original, myCs)
|
||||||
is ConeTypeVariableType -> fixVariablesForMemberScope(type, myCs)
|
is ConeTypeVariableType -> fixCurrentResultForNestedTypeVariable(type, myCs)
|
||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun fixVariablesForMemberScope(
|
private fun fixCurrentResultForNestedTypeVariable(
|
||||||
type: ConeTypeVariableType,
|
type: ConeTypeVariableType,
|
||||||
myCs: NewConstraintSystemImpl,
|
myCs: NewConstraintSystemImpl,
|
||||||
): Pair<ConeTypeVariableTypeConstructor, ConeKotlinType>? {
|
): Pair<ConeTypeVariableTypeConstructor, ConeKotlinType>? {
|
||||||
|
|||||||
+1
-1
@@ -120,7 +120,7 @@ class PostponedArgumentsAnalyzer(
|
|||||||
// TODO: Fix variables for context receivers, too (KT-64859)
|
// TODO: Fix variables for context receivers, too (KT-64859)
|
||||||
buildMap {
|
buildMap {
|
||||||
lambda.receiver
|
lambda.receiver
|
||||||
?.let { pclaInferenceSession.fixVariablesForMemberScope(it, candidate.system) }
|
?.let { pclaInferenceSession.fixCurrentResultIfTypeVariableAndReturnBinding(it, candidate.system) }
|
||||||
?.let(this::plusAssign)
|
?.let(this::plusAssign)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-1
@@ -867,7 +867,10 @@ open class FirExpressionsResolveTransformer(transformer: FirAbstractBodyResolveT
|
|||||||
val firClass = type.lookupTag.toSymbol(session)?.fir ?: return this
|
val firClass = type.lookupTag.toSymbol(session)?.fir ?: return this
|
||||||
if (firClass.typeParameters.isEmpty()) 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 outerClasses by lazy(LazyThreadSafetyMode.NONE) { firClass.symbol.getClassAndItsOuterClassesWhenLocal(session) }
|
||||||
val newType = components.computeRepresentativeTypeForBareType(type, originalType)
|
val newType = components.computeRepresentativeTypeForBareType(type, originalType)
|
||||||
?: if (
|
?: 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)
|
// ISSUE: KT-64840 (K2/PCLA difference)
|
||||||
class Controller<T> {
|
class Controller<T> {
|
||||||
fun yield(t: T): Boolean = true
|
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>) {
|
fun main(a: A<String>) {
|
||||||
val x = generate {
|
val x = generate {
|
||||||
withCallback(a, this) {
|
withCallback(a, this) {
|
||||||
(it as <!NO_TYPE_ARGUMENTS_ON_RHS!>B<!>).<!UNRESOLVED_REFERENCE!>b<!>.length
|
(it as B).b.length
|
||||||
it.<!UNRESOLVED_REFERENCE!>b<!>.length
|
it.b.length
|
||||||
it.a.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 <X> predicate(x: X, c: Controller<X>, p: (X) -> Unit) {}
|
||||||
|
|
||||||
fun main(a: A<*>, c: C) {
|
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 {
|
val x1 = generate {
|
||||||
predicate(a, this) { x ->
|
predicate(a, this) { x ->
|
||||||
// x is B
|
// x is B
|
||||||
@@ -25,13 +27,19 @@ fun main(a: A<*>, c: C) {
|
|||||||
|
|
||||||
<!DEBUG_INFO_EXPRESSION_TYPE("CommonSupertype")!>x1<!>
|
<!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 {
|
val x2 = generate {
|
||||||
predicate(a, this) { x ->
|
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 <X> predicate(x: X, c: Controller<X>, p: (X) -> Unit) {}
|
||||||
|
|
||||||
fun main(a: A<*>, c: C) {
|
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 {
|
val x1 = generate {
|
||||||
predicate(a, this) { x ->
|
predicate(a, this) { x ->
|
||||||
// x is B
|
// x is B
|
||||||
@@ -25,11 +27,17 @@ fun main(a: A<*>, c: C) {
|
|||||||
|
|
||||||
<!DEBUG_INFO_EXPRESSION_TYPE("CommonSupertype")!>x1<!>
|
<!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 {
|
val x2 = generate {
|
||||||
predicate(a, this) { x ->
|
predicate(a, this) { x ->
|
||||||
<!USELESS_IS_CHECK!>x is <!NO_TYPE_ARGUMENTS_ON_RHS!>B<!><!>
|
<!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)
|
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
|
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.
|
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
|
## 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).
|
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