[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:
Stanislav Erokhin
2017-06-07 11:05:01 +03:00
committed by Mikhail Zarechenskiy
parent d1263c5dc3
commit 5eb56dca60
2 changed files with 79 additions and 39 deletions
@@ -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(
@@ -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)
}
}
}