aboutsummaryrefslogtreecommitdiff
path: root/src/dotty/tools/dotc/core/TypeComparer.scala
diff options
context:
space:
mode:
Diffstat (limited to 'src/dotty/tools/dotc/core/TypeComparer.scala')
-rw-r--r--src/dotty/tools/dotc/core/TypeComparer.scala259
1 files changed, 1 insertions, 258 deletions
diff --git a/src/dotty/tools/dotc/core/TypeComparer.scala b/src/dotty/tools/dotc/core/TypeComparer.scala
index 83068ab31..8717a45df 100644
--- a/src/dotty/tools/dotc/core/TypeComparer.scala
+++ b/src/dotty/tools/dotc/core/TypeComparer.scala
@@ -16,7 +16,7 @@ import scala.util.control.NonFatal
/** Provides methods to compare types.
*/
-class TypeComparer(initctx: Context) extends DotClass with Skolemization {
+class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling with Skolemization {
implicit val ctx: Context = initctx
val state = ctx.typerState
@@ -25,35 +25,6 @@ class TypeComparer(initctx: Context) extends DotClass with Skolemization {
private var pendingSubTypes: mutable.Set[(Type, Type)] = null
private var recCount = 0
- /** If the constraint is frozen we cannot add new bounds to the constraint. */
- protected var frozenConstraint = false
-
- /** If the constraint is ignored, subtype checks only take into account
- * declared bounds of PolyParams. Used when forming unions and intersectons
- * of constraint bounds
- */
- private var ignoreConstraint = false
-
- private def ignoringConstraint[T](op: => T): T = {
- val savedIgnore = ignoreConstraint
- val savedFrozen = frozenConstraint
- ignoreConstraint = true
- frozenConstraint = true
- try op
- finally {
- ignoreConstraint = savedIgnore
- frozenConstraint = savedFrozen
- }
- }
-
- /** Compare a solution of the constraint instead of the constrained parameters.
- * The solution maps every parameter to its lower bound.
- */
- protected var solvedConstraint = false
-
- /** The parameters currently being constrained by addConstraint */
- private var pendingParams: Set[PolyParam] = Set()
-
private var needsGc = false
/** Is a subtype check in progress? In that case we may not
@@ -101,216 +72,6 @@ class TypeComparer(initctx: Context) extends DotClass with Skolemization {
myAnyType
}
- /* Constraint handling:
- *
- * Constraints are required to be in normalized form. This means
- * (1) if P <: Q in C then also Q >: P in C
- * (2) if P r Q in C and Q r R in C then also P r R in C, where r is <: or :>
- *
- * "P <: Q in C" means here: There is a constraint P <: H[Q],
- * where H is the multi-hole context given by:
- *
- * H = []
- * H & T
- * T & H
- * H | H
- *
- * (the idea is that a parameter Q in a H context is guaranteed to be a supertype of P).
- *
- * "P >: Q in C" means: There is a constraint P >: L[Q],
- * where L is the multi-hole context given by:
- *
- * L = []
- * L | T
- * T | L
- * L & L
- */
-
- /** Test whether the lower bounds of all parameters in this
- * constraint are a solution to the constraint.
- */
- def isSatisfiable: Boolean = {
- val saved = solvedConstraint
- solvedConstraint = true
- try
- constraint.forallParams { param =>
- val TypeBounds(lo, hi) = constraint.at(param)
- isSubType(lo, hi) || {
- ctx.log(i"sub fail $lo <:< $hi")
- ctx.log(i"approximated = ${approxParams(lo)} <:< ${approxParams(hi)}")
- false
- }
- }
- finally solvedConstraint = saved
- }
-
- /** Make p2 = p1, transfer all bounds of p2 to p1 */
- private def unify(p1: PolyParam, p2: PolyParam): Boolean = {
- constr.println(s"unifying $p1 $p2")
- val constraint1 = constraint.unify(p1, p2)
- val bounds = constraint1.bounds(p1)
- isSubType(bounds.lo, bounds.hi) && { constraint = constraint1; true }
- }
-
- /** If current constraint set is not frozen, add the constraint
- *
- * param >: bound if fromBelow is true
- * param <: bound otherwise
- *
- * to the bounds of `param`. If `bound` is itself a constrained parameter, also
- * add the dual constraint to `bound`.
- * @pre `param` is in the constraint's domain
- * @return Whether the augmented constraint is still satisfiable.
- */
- def addConstraint(param: PolyParam, bound0: Type, fromBelow: Boolean): Boolean = {
-
- /** Add bidirectional constraint. If new constraint implies 'A <: B' we also
- * make sure 'B >: A' gets added and vice versa. Furthermore, if the constraint
- * implies 'A <: B <: A', A and B get unified.
- */
- def addc(param: PolyParam, bound: Type, fromBelow: Boolean): Boolean =
- constraint.bounds(param) match {
- case TypeBounds(plo: PolyParam, phi) if (plo eq phi) && constraint.contains(plo) =>
- addc(plo, bound, fromBelow)
- case pbounds0 =>
- bound match {
- case bound: PolyParam if constraint contains bound =>
- val bbounds0 @ TypeBounds(lo, hi) = constraint.bounds(bound)
- if (lo eq hi)
- addc(param, lo, fromBelow)
- else if (param == bound)
- true
- else if (fromBelow && param.occursIn(lo, fromBelow = true))
- unify(param, bound)
- else if (!fromBelow && param.occursIn(hi, fromBelow = false))
- unify(bound, param)
- else {
- val pbounds = prepare(param, bound, fromBelow)
- val bbounds = prepare(bound, param, !fromBelow)
- pbounds.exists && bbounds.exists && {
- install(param, pbounds.bounds, pbounds0)
- install(bound, bbounds.bounds, bbounds0)
- true
- }
- }
- case bound: AndOrType if fromBelow != bound.isAnd =>
- addc(param, bound.tp1, fromBelow) &&
- addc(param, bound.tp2, fromBelow)
- case bound: WildcardType =>
- true
- case bound => // !!! remove to keep the originals
- val pbounds = prepare(param, bound, fromBelow)
- pbounds.exists && {
- install(param, pbounds.bounds, pbounds0)
- true
- }
- }
- }
-
- /** Install bounds for param */
- def install(param: PolyParam, newBounds: TypeBounds, oldBounds: TypeBounds): Unit = {
- val curBounds = constraint.bounds(param)
- constraint = constraint.updated(param, newBounds)
- if (curBounds ne oldBounds) {
- // In this case the bounds were updated previously by a recursive isSubType in
- // the satisfiability check of prepare. Reapply the previously added bounds, but
- // go through a full addConstraint in order to eliminate any cyclic dependencies
- // via unification.
- if (!ignoringConstraint(isSubType(curBounds.lo, newBounds.lo)))
- addConstraint(param, curBounds.lo, fromBelow)
- if (!ignoringConstraint(isSubType(newBounds.hi, curBounds.hi)))
- addConstraint(param, curBounds.hi, !fromBelow)
- }
- }
-
- /** Compute new bounds for `param` and check whether they are
- * satisfiable. The check might in turn trigger other additions to the constraint.
- * @return The new bounds for `param` (which are not installed yet), or
- * NoType, if the new constraint would not be satisfiable.
- */
- def prepare(param: PolyParam, bound: Type, fromBelow: Boolean): Type = {
- constr.println(s"prepare ${param.show} ${if (fromBelow) ">:>" else "<:<"} ${bound.show}")
- val oldBounds = constraint.bounds(param)
- val newBounds = ignoringConstraint {
- if (fromBelow) oldBounds.derivedTypeBounds(oldBounds.lo | bound, oldBounds.hi)
- else oldBounds.derivedTypeBounds(oldBounds.lo, oldBounds.hi & bound)
- }
- val ok =
- (param == bound) ||
- (oldBounds eq newBounds) ||
- {
- if (pendingParams contains param) {
- // Why the pendingParams test? It is possible that recursive subtype invocations
- // come back with another constraint for `param`. An example came up when compiling
- // ElimRepeated where we got the constraint
- //
- // Coll <: IterableLike[Tree, Coll]
- //
- // and added
- //
- // List[Tree] <: Coll
- //
- // The recursive bounds test is then
- //
- // List[Tree] <: IterableLike[Tree, Coll]
- //
- // and because of the F-bounded polymorphism in the supertype of List,
- // i.e. List[T] <: IterableLike[T, List[T]], this leads again to
- //
- // List[Tree] <: Coll
- //
- // If a parameter is already pending, we avoid revisiting it here.
- // Instead we combine the bounds computed here with the originally
- // computed bounds when installing the original type.
- constr.println(i"deferred bounds: $param $newBounds")
- true
- } else {
- pendingParams += param
- try isSubType(newBounds.lo, newBounds.hi)
- finally pendingParams -= param
- }
- }
- if (ok) newBounds else NoType
- }
-
- val bound = deSkolemize(bound0, toSuper = fromBelow).dealias.stripTypeVar
- def description = s"${param.show} ${if (fromBelow) ">:>" else "<:<"} ${bound.show} to ${constraint.show}"
- constr.println(s"adding $description")
- val res = addc(param, bound, fromBelow)
- constr.println(s"added $description")
- if (Config.checkConstraintsNonCyclicTrans) constraint.checkNonCyclicTrans()
- res
- }
-
- def isConstrained(param: PolyParam): Boolean =
- !frozenConstraint && !solvedConstraint && (constraint contains param)
-
- /** Solve constraint set for given type parameter `param`.
- * If `fromBelow` is true the parameter is approximated by its lower bound,
- * otherwise it is approximated by its upper bound. However, any occurrences
- * of the parameter in a refinement somewhere in the bound are removed.
- * (Such occurrences can arise for F-bounded types).
- * The constraint is left unchanged.
- * @return the instantiating type
- * @pre `param` is in the constraint's domain.
- */
- def approximation(param: PolyParam, fromBelow: Boolean): Type = {
- val avoidParam = new TypeMap {
- override def stopAtStatic = true
- def apply(tp: Type) = mapOver {
- tp match {
- case tp: RefinedType if param occursIn tp.refinedInfo => tp.parent
- case _ => tp
- }
- }
- }
- val bounds = constraint.bounds(param)
- val bound = if (fromBelow) bounds.lo else bounds.hi
- val inst = avoidParam(bound)
- typr.println(s"approx ${param.show}, from below = $fromBelow, bound = ${bound.show}, inst = ${inst.show}")
- inst
- }
-
// Subtype testing `<:<`
def topLevelSubType(tp1: Type, tp2: Type): Boolean = {
@@ -833,12 +594,6 @@ class TypeComparer(initctx: Context) extends DotClass with Skolemization {
case _ => false
}
- /** The current bounds of type parameter `param` */
- def bounds(param: PolyParam): TypeBounds = constraint at param match {
- case bounds: TypeBounds if !ignoreConstraint => bounds
- case _ => param.binder.paramBounds(param.paramNum)
- }
-
/** Defer constraining type variables when compared against prototypes */
def isMatchedByProto(proto: ProtoType, tp: Type) = tp.stripTypeVar match {
case tp: PolyParam if !solvedConstraint && (constraint contains tp) => true
@@ -1392,18 +1147,6 @@ class TypeComparer(initctx: Context) extends DotClass with Skolemization {
s"${tp1.show} <:< ${tp2.show}" +
(if (ctx.settings.verbose.value) s" ${tp1.getClass} ${tp2.getClass}${if (frozenConstraint) " frozen" else ""}" else "")
- /** Map that approximates each param in constraint by its lower bound.
- * Currently only used for diagnostics.
- */
- val approxParams = new TypeMap {
- def apply(tp: Type): Type = tp.stripTypeVar match {
- case tp: PolyParam if constraint contains tp =>
- this(constraint.bounds(tp).lo)
- case tp =>
- mapOver(tp)
- }
- }
-
/** Show subtype goal that led to an assertion failure */
def showGoal(tp1: Type, tp2: Type) = {
println(disambiguated(implicit ctx => s"assertion failure for ${tp1.show} <:< ${tp2.show}, frozen = $frozenConstraint"))