From 5eb56dca60dcd91cb7ebc7f08a0275194170fca4 Mon Sep 17 00:00:00 2001 From: Stanislav Erokhin Date: Wed, 7 Jun 2017 11:05:01 +0300 Subject: [PATCH] [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. --- .../components/ResultTypeResolver.kt | 106 ++++++++++++------ .../inference/model/ConstraintStorage.kt | 12 +- 2 files changed, 79 insertions(+), 39 deletions(-) diff --git a/compiler/resolution/src/org/jetbrains/kotlin/resolve/calls/inference/components/ResultTypeResolver.kt b/compiler/resolution/src/org/jetbrains/kotlin/resolve/calls/inference/components/ResultTypeResolver.kt index 253e9f26c96..dabe49b4ad8 100644 --- a/compiler/resolution/src/org/jetbrains/kotlin/resolve/calls/inference/components/ResultTypeResolver.kt +++ b/compiler/resolution/src/org/jetbrains/kotlin/resolve/calls/inference/components/ResultTypeResolver.kt @@ -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 Array.intersect(other: Iterable) { - * val set = toMutableSet() - * set.retainAll(other) - * } - * fun Array.toMutableSet(): MutableSet = ... - * fun MutableCollection.retainAll(elements: Iterable) {} - * - * Here, when we solve type system for `toMutableSet` we have the following constrains: - * Array <: Array => C(out X) <: T. - * If we fix it to T = C(out X) then return type of `toMutableSet()` will be `MutableSet` - * and type of variable `set` will be `MutableSet` 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 Array.intersect(other: Iterable) { + * val set = toMutableSet() + * set.retainAll(other) + * } + * fun Array.toMutableSet(): MutableSet = ... + * fun MutableCollection.retainAll(elements: Iterable) {} + * + * Here, when we solve type system for `toMutableSet` we have the following constrains: + * Array <: Array => C(out X) <: T. + * If we fix it to T = C(out X) then return type of `toMutableSet()` will be `MutableSet` + * and type of variable `set` will be `MutableSet` 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( diff --git a/compiler/resolution/src/org/jetbrains/kotlin/resolve/calls/inference/model/ConstraintStorage.kt b/compiler/resolution/src/org/jetbrains/kotlin/resolve/calls/inference/model/ConstraintStorage.kt index 6c43f263976..82f443059ac 100644 --- a/compiler/resolution/src/org/jetbrains/kotlin/resolve/calls/inference/model/ConstraintStorage.kt +++ b/compiler/resolution/src/org/jetbrains/kotlin/resolve/calls/inference/model/ConstraintStorage.kt @@ -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) } -} +} \ No newline at end of file