[NI] Check type compatibility before fixation.
If for type variable we have upper and lower bounds, then sometimes our approximation before fixation give us incorrect result for type variable and we should chose other bound as result. Example: Int & Byte <: T <: Byte. If we run approximation for lower bound we get Int as result and it isn't subtype of Byte.
This commit is contained in:
committed by
Mikhail Zarechenskiy
parent
d1263c5dc3
commit
5eb56dca60
+71
-35
@@ -22,6 +22,7 @@ import org.jetbrains.kotlin.resolve.calls.inference.components.FixationOrderCalc
|
||||
import org.jetbrains.kotlin.resolve.calls.inference.model.Constraint
|
||||
import org.jetbrains.kotlin.resolve.calls.inference.model.ConstraintKind
|
||||
import org.jetbrains.kotlin.resolve.calls.inference.model.VariableWithConstraints
|
||||
import org.jetbrains.kotlin.resolve.calls.inference.model.checkConstraint
|
||||
import org.jetbrains.kotlin.resolve.constants.IntegerValueTypeConstructor
|
||||
import org.jetbrains.kotlin.types.*
|
||||
import org.jetbrains.kotlin.types.checker.intersectTypes
|
||||
@@ -37,49 +38,84 @@ class ResultTypeResolver(
|
||||
fun findResultType(c: Context, variableWithConstraints: VariableWithConstraints, direction: ResolveDirection): UnwrappedType? {
|
||||
findResultIfThereIsEqualsConstraint(c, variableWithConstraints, allowedFixToNotProperType = false)?.let { return it }
|
||||
|
||||
val builtIns = variableWithConstraints.typeVariable.freshTypeConstructor.builtIns
|
||||
|
||||
if (direction == ResolveDirection.TO_SUBTYPE || direction == ResolveDirection.UNKNOWN) {
|
||||
val lowerConstraints = variableWithConstraints.constraints.filter { it.kind == ConstraintKind.LOWER && c.isProperType(it.type) }
|
||||
if (lowerConstraints.isNotEmpty()) {
|
||||
val commonSupertype = NewCommonSuperTypeCalculator.commonSuperType(convertLowerTypesWithKnowledgeOfNumberTypes(lowerConstraints))
|
||||
/**
|
||||
*
|
||||
* 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.CapturedTypesApproximation) ?: commonSupertype
|
||||
}
|
||||
val subType = findSubType(c, variableWithConstraints)
|
||||
val superType = findSuperType(c, variableWithConstraints)
|
||||
val result = if (direction == ResolveDirection.TO_SUBTYPE || direction == ResolveDirection.UNKNOWN) {
|
||||
c.resultType(subType, superType, variableWithConstraints)
|
||||
}
|
||||
else {
|
||||
c.resultType(superType, subType, variableWithConstraints)
|
||||
}
|
||||
|
||||
// direction == TO_SUPER or there is no LOWER bounds
|
||||
if (result != null) return result
|
||||
|
||||
// no proper constraints
|
||||
return variableWithConstraints.typeVariable.freshTypeConstructor.builtIns.run {
|
||||
if (direction == ResolveDirection.TO_SUBTYPE) nothingType else anyType
|
||||
}
|
||||
}
|
||||
|
||||
private fun Context.resultType(
|
||||
firstCandidate: UnwrappedType?,
|
||||
secondCandidate: UnwrappedType?,
|
||||
variableWithConstraints: VariableWithConstraints
|
||||
): UnwrappedType? {
|
||||
if (firstCandidate == null || secondCandidate == null) return firstCandidate ?: secondCandidate
|
||||
|
||||
if (isSuitableType(firstCandidate, variableWithConstraints)) return firstCandidate
|
||||
|
||||
if (isSuitableType(secondCandidate, variableWithConstraints)) {
|
||||
return secondCandidate
|
||||
}
|
||||
else {
|
||||
return firstCandidate
|
||||
}
|
||||
}
|
||||
|
||||
private fun Context.isSuitableType(resultType: UnwrappedType, variableWithConstraints: VariableWithConstraints): Boolean {
|
||||
for (constraint in variableWithConstraints.constraints) {
|
||||
if (!isProperType(constraint.type)) continue
|
||||
if (!checkConstraint(constraint.type, constraint.kind, resultType)) return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
private fun findSubType(c: Context, variableWithConstraints: VariableWithConstraints): UnwrappedType? {
|
||||
val lowerConstraints = variableWithConstraints.constraints.filter { it.kind == ConstraintKind.LOWER && c.isProperType(it.type) }
|
||||
if (lowerConstraints.isNotEmpty()) {
|
||||
val commonSupertype = NewCommonSuperTypeCalculator.commonSuperType(convertLowerTypesWithKnowledgeOfNumberTypes(lowerConstraints))
|
||||
/**
|
||||
*
|
||||
* 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.CapturedTypesApproximation) ?: commonSupertype
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
private fun findSuperType(c: Context, variableWithConstraints: VariableWithConstraints): UnwrappedType? {
|
||||
val upperConstraints = variableWithConstraints.constraints.filter { it.kind == ConstraintKind.UPPER && c.isProperType(it.type) }
|
||||
if (upperConstraints.isNotEmpty()) {
|
||||
val upperType = intersectTypes(upperConstraints.map { it.type })
|
||||
|
||||
return typeApproximator.approximateToSubType(upperType, TypeApproximatorConfiguration.CapturedTypesApproximation) ?: upperType
|
||||
}
|
||||
|
||||
// no proper constraints
|
||||
if (direction == ResolveDirection.TO_SUBTYPE) {
|
||||
return builtIns.nothingType
|
||||
}
|
||||
else {
|
||||
return builtIns.anyType
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
fun findResultIfThereIsEqualsConstraint(
|
||||
|
||||
+8
-4
@@ -128,10 +128,14 @@ class InitialConstraint(
|
||||
fun InitialConstraint.checkConstraint(substitutor: TypeSubstitutor): Boolean {
|
||||
val newA = substitutor.substitute(a)
|
||||
val newB = substitutor.substitute(a)
|
||||
return checkConstraint(newB, constraintKind, newA)
|
||||
}
|
||||
|
||||
fun checkConstraint(constraintType: UnwrappedType, constraintKind: ConstraintKind, resultType: UnwrappedType): Boolean {
|
||||
val typeChecker = KotlinTypeChecker.DEFAULT
|
||||
return when (constraintKind) {
|
||||
ConstraintKind.EQUALITY -> typeChecker.equalTypes(newA, newB)
|
||||
ConstraintKind.UPPER -> typeChecker.isSubtypeOf(newA, newB)
|
||||
ConstraintKind.LOWER -> typeChecker.isSubtypeOf(newB, newA)
|
||||
ConstraintKind.EQUALITY -> typeChecker.equalTypes(constraintType, resultType)
|
||||
ConstraintKind.LOWER -> typeChecker.isSubtypeOf(constraintType, resultType)
|
||||
ConstraintKind.UPPER -> typeChecker.isSubtypeOf(resultType, constraintType)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user