K2: don't approximate captured types in ResultTypeResolver in certain cases
To be more precises, ResultTypeResolver for K2 now searches for similar LOWER/UPPER constraints pair based on the same flexible type, like LOWER(CapturedType&Any..CapturedType?) and UPPER(CapturedType!). If such a pair is found, the CapturedType is not approximated. This is done to avoid a big difference between this case and completely same constraints, like LOWER(CapturedType!) and UPPER(CapturedType!). In this case we squash them to EQUAL(CapturedType!) even before ResultTypeResolver, and captured types which is got from an EQUAL constraint are not approximated even before this commit. This commit fixes back a case from KT-50134 and a problem with intellij.vcs.git #KT-65596 Fixed
This commit is contained in:
committed by
Space Team
parent
c966533d73
commit
d04625666a
+1
-1
@@ -1,6 +1,6 @@
|
||||
FILE: javaCollector.kt
|
||||
public final fun foo(): R|kotlin/Unit| {
|
||||
R|kotlin/collections/listOf|<R|kotlin/String|>(String()).R|SubstitutionOverride<kotlin/collections/List.stream: R|@EnhancedNullability java/util/stream/Stream<@EnhancedNullability kotlin/String>|>|().R|SubstitutionOverride<java/util/stream/Stream.collect: R|R!|><Inapplicable(INAPPLICABLE): java/util/stream/Stream.collect>#|<<ERROR TYPE REF: Cannot infer argument for type parameter R>, <ERROR TYPE REF: Cannot infer argument for type parameter A>>(Q|java/util/stream/Collectors|.R|java/util/stream/Collectors.groupingBy*s<CS errors: java/util/stream/Collectors.groupingBy>#|<R|kotlin/String!|, <ERROR TYPE REF: Cannot infer argument for type parameter K>, R|kotlin/Any!|, R|kotlin/Int!|>(SAM(groupingBy@fun <anonymous>(it: R|@EnhancedNullability kotlin/String!|): <ERROR TYPE REF: Cannot infer argument for type parameter K> <inline=NoInline> {
|
||||
R|kotlin/collections/listOf|<R|kotlin/String|>(String()).R|SubstitutionOverride<kotlin/collections/List.stream: R|@EnhancedNullability java/util/stream/Stream<@EnhancedNullability kotlin/String>|>|().R|SubstitutionOverride<java/util/stream/Stream.collect: R|R!|>|<R|ft<kotlin/collections/MutableMap<kotlin/String!, kotlin/Int!>, kotlin/collections/Map<kotlin/String!, kotlin/Int!>?>|, R|ft<CapturedType(*), CapturedType(*)?>|>(Q|java/util/stream/Collectors|.R|java/util/stream/Collectors.groupingBy*s|<R|kotlin/String!|, R|kotlin/String!|, R|ft<CapturedType(*), CapturedType(*)?>|, R|kotlin/Int!|>(SAM(groupingBy@fun <anonymous>(it: R|@EnhancedNullability kotlin/String!|): R|@EnhancedNullability kotlin/String!| <inline=NoInline> {
|
||||
^ R|<local>/it|
|
||||
}
|
||||
), Q|java/util/stream/Collectors|.R|java/util/stream/Collectors.collectingAndThen*s|<R|kotlin/String!|, R|ft<CapturedType(*), CapturedType(*)?>|, R|kotlin/Long!|, R|kotlin/Int!|>(Q|java/util/stream/Collectors|.R|java/util/stream/Collectors.counting*s|<R|kotlin/String!|>(), SAM(Q|kotlin/Long|::R|kotlin/Long.toInt|))))
|
||||
|
||||
@@ -6,12 +6,12 @@ import java.util.stream.Collectors
|
||||
|
||||
fun foo(){
|
||||
listOf("").stream().collect(
|
||||
<!ARGUMENT_TYPE_MISMATCH, NEW_INFERENCE_ERROR!>Collectors.groupingBy(
|
||||
Collectors.groupingBy(
|
||||
{ it },
|
||||
Collectors.collectingAndThen(
|
||||
Collectors.counting<String>(),
|
||||
Long::toInt
|
||||
)
|
||||
)<!>
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
+76
-36
@@ -85,13 +85,67 @@ class ResultTypeResolver(
|
||||
}
|
||||
|
||||
val subType = c.findSubType(variableWithConstraints)
|
||||
// Super type should be the most flexible, sub type should be the least one
|
||||
val superType = c.findSuperType(variableWithConstraints).makeFlexibleIfNecessary(c, variableWithConstraints.constraints)
|
||||
val superType = c.findSuperType(variableWithConstraints)
|
||||
|
||||
val similarCapturedTypesInK2 = with(c) {
|
||||
isK2 && similarCapturedTypes(subType, superType)
|
||||
}
|
||||
|
||||
/**
|
||||
* The special logic for handling similar captured types in K2 is needed because of the following.
|
||||
* Currently, it's allowed to squash LOWER(type) and UPPER(type) constraints with the same type
|
||||
* into one EQUALS(type) constraint.
|
||||
* E.g. it's possible for constraints like LOWER(SomeCapturedType!) & UPPER(SomeCapturedTypes!).
|
||||
* In such a situation [ResultTypeResolver] infers a relevant type variable into SomeCapturedType!,
|
||||
* without any approximation.
|
||||
* However, complex handling of DNN/non-DNN types sometimes lead to a situation (see e.g. KT-50134 example)
|
||||
* when we have a pair of LOWER(SomeCapturedType&Any..SomeCapturedType?) and UPPER(SomeCapturedType!).
|
||||
* These constraints use almost the same type, but they cannot be squashed.
|
||||
* Moreover, [ResultTypeResolver] approximates captured from expression types when they came from UPPER or LOWER constraint.
|
||||
* See [AbstractTypeApproximator.approximateToSuperType] and [AbstractTypeApproximator.approximateToSubType] calls below.
|
||||
* As a result, [ResultTypeResolver] infers a relevant type variable to e.g. Any!,
|
||||
* despite the fact the situation is almost the same as in case with matched constraints.
|
||||
* This can produce unexpected NEW_INFERENCE_ERROR because other constraints are unmatched.
|
||||
*
|
||||
* To handle this situation, approximation of captured types for this case with similar constraints is disabled in K2.
|
||||
*/
|
||||
// TODO: consider skipping captured types approximation unconditionally (KT-66346)
|
||||
val preparedSubType = when {
|
||||
subType == null -> null
|
||||
similarCapturedTypesInK2 -> subType
|
||||
/**
|
||||
*
|
||||
* fun <T> Array<out T>.intersect(other: Iterable<T>) {
|
||||
* val set = toMutableSet()
|
||||
* set.retainAll(other)
|
||||
* }
|
||||
* fun <X> Array<out X>.toMutableSet(): MutableSet<X> = ...
|
||||
* fun <Y> MutableCollection<in Y>.retainAll(elements: Iterable<Y>) {}
|
||||
*
|
||||
* Here, when we solve type system for `toMutableSet` we have the following constrains:
|
||||
* Array<C(out T)> <: Array<out X> => C(out X) <: T.
|
||||
* If we fix it to T = C(out X) then return type of `toMutableSet()` will be `MutableSet<C(out X)>`
|
||||
* and type of variable `set` will be `MutableSet<out T>` and the following line will have contradiction.
|
||||
*
|
||||
* To fix this problem when we fix variable, we will approximate captured types before fixation.
|
||||
*
|
||||
*/
|
||||
else -> typeApproximator.approximateToSuperType(subType, TypeApproximatorConfiguration.InternalTypesApproximation) ?: subType
|
||||
}
|
||||
|
||||
// TODO: consider skipping captured types approximation unconditionally (KT-66346)
|
||||
val preparedSuperType = when {
|
||||
superType == null -> null
|
||||
similarCapturedTypesInK2 -> superType
|
||||
c.isK2 && c.hasRecursiveTypeParametersWithGivenSelfType(superType.typeConstructor(c)) -> superType
|
||||
else -> typeApproximator.approximateToSubType(superType, TypeApproximatorConfiguration.InternalTypesApproximation) ?: superType
|
||||
// Super type should be the most flexible, sub type should be the least one
|
||||
}.makeFlexibleIfNecessary(c, variableWithConstraints.constraints)
|
||||
|
||||
val resultTypeFromDirection = if (direction == ResolveDirection.TO_SUBTYPE || direction == ResolveDirection.UNKNOWN) {
|
||||
c.resultType(subType, superType, variableWithConstraints)
|
||||
c.resultType(preparedSubType, preparedSuperType, variableWithConstraints)
|
||||
} else {
|
||||
c.resultType(superType, subType, variableWithConstraints)
|
||||
c.resultType(preparedSuperType, preparedSubType, variableWithConstraints)
|
||||
}
|
||||
// In the general case, we can have here two types, one from EQUAL constraint which must be ILT-based,
|
||||
// and the second one from UPPER/LOWER constraints (subType/superType based)
|
||||
@@ -108,6 +162,22 @@ class ResultTypeResolver(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This function returns true for a pair of captured-type based types like
|
||||
* CapturedType&Any..CapturedType? and CapturedType..CapturedType?.
|
||||
* Type constructors of lower/upper bound of both types should be the same captured types, to get true result.
|
||||
* This is needed to avoid captured types approximation in such situation.
|
||||
*/
|
||||
private fun Context.similarCapturedTypes(subType: KotlinTypeMarker?, superType: KotlinTypeMarker?): Boolean {
|
||||
if (subType == null) return false
|
||||
if (superType == null) return false
|
||||
val typeConstructor = subType.lowerBoundIfFlexible().typeConstructor()
|
||||
return typeConstructor.isCapturedTypeConstructor() &&
|
||||
typeConstructor == subType.upperBoundIfFlexible().typeConstructor() &&
|
||||
typeConstructor == superType.lowerBoundIfFlexible().typeConstructor() &&
|
||||
typeConstructor == superType.upperBoundIfFlexible().typeConstructor()
|
||||
}
|
||||
|
||||
/*
|
||||
* We propagate nullness flexibility into the result type from type variables in other constraints
|
||||
* to prevent variable fixation into less flexible type.
|
||||
@@ -238,28 +308,7 @@ class ResultTypeResolver(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* fun <T> Array<out T>.intersect(other: Iterable<T>) {
|
||||
* val set = toMutableSet()
|
||||
* set.retainAll(other)
|
||||
* }
|
||||
* fun <X> Array<out X>.toMutableSet(): MutableSet<X> = ...
|
||||
* fun <Y> MutableCollection<in Y>.retainAll(elements: Iterable<Y>) {}
|
||||
*
|
||||
* Here, when we solve type system for `toMutableSet` we have the following constrains:
|
||||
* Array<C(out T)> <: Array<out X> => C(out X) <: T.
|
||||
* If we fix it to T = C(out X) then return type of `toMutableSet()` will be `MutableSet<C(out X)>`
|
||||
* and type of variable `set` will be `MutableSet<out T>` and the following line will have contradiction.
|
||||
*
|
||||
* To fix this problem when we fix variable, we will approximate captured types before fixation.
|
||||
*
|
||||
*/
|
||||
|
||||
return typeApproximator.approximateToSuperType(
|
||||
commonSuperType,
|
||||
TypeApproximatorConfiguration.InternalTypesApproximation
|
||||
) ?: commonSuperType
|
||||
return commonSuperType
|
||||
}
|
||||
|
||||
return null
|
||||
@@ -348,16 +397,7 @@ class ResultTypeResolver(
|
||||
variableWithConstraints.constraints.filter { it.kind == ConstraintKind.UPPER && this@findSuperType.isProperTypeForFixation(it.type) }
|
||||
|
||||
if (upperConstraints.isNotEmpty()) {
|
||||
val upperType = computeUpperType(upperConstraints)
|
||||
|
||||
if (isK2 && hasRecursiveTypeParametersWithGivenSelfType(upperType.typeConstructor())) {
|
||||
return upperType
|
||||
}
|
||||
|
||||
return typeApproximator.approximateToSubType(
|
||||
upperType,
|
||||
TypeApproximatorConfiguration.InternalTypesApproximation
|
||||
) ?: upperType
|
||||
return computeUpperType(upperConstraints)
|
||||
}
|
||||
|
||||
return null
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
// FULL_JDK
|
||||
// JVM_TARGET: 1.8
|
||||
|
||||
// FILE: test.kt
|
||||
|
||||
fun waitTestConnection(configurable: A) {
|
||||
val res = configurable.test()
|
||||
<!NEW_INFERENCE_ERROR!>C.wait(res!!.toFuture())<!>
|
||||
}
|
||||
|
||||
// FILE: A.java
|
||||
|
||||
import org.jetbrains.annotations.*;
|
||||
|
||||
public interface A {
|
||||
@Nullable B<?> test();
|
||||
}
|
||||
|
||||
// FILE: B.kt
|
||||
|
||||
import java.util.concurrent.CompletableFuture
|
||||
|
||||
abstract class B<S> {
|
||||
abstract fun toFuture(): CompletableFuture<S>
|
||||
}
|
||||
|
||||
// FILE: C.java
|
||||
|
||||
import java.util.concurrent.Future;
|
||||
|
||||
public class C {
|
||||
public static <F> F wait(Future<F> future) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
// FIR_IDENTICAL
|
||||
// FULL_JDK
|
||||
// JVM_TARGET: 1.8
|
||||
|
||||
|
||||
Reference in New Issue
Block a user