package dotty.tools
package dotc
package core
import Types._, Contexts._, Symbols._, Decorators._
import util.SimpleMap
import collection.mutable
import printing.{Printer, Showable}
import printing.Texts._
import config.Config
import collection.immutable.BitSet
import reflect.ClassTag
import annotation.tailrec
object OrderingConstraint {
type ArrayValuedMap[T] = SimpleMap[PolyType, Array[T]]
/** The type of `OrderingConstraint#boundsMap` */
type ParamBounds = ArrayValuedMap[Type]
/** The type of `OrderingConstraint#lowerMap`, `OrderingConstraint#upperMap` */
type ParamOrdering = ArrayValuedMap[List[PolyParam]]
/** A new constraint with given maps */
private def newConstraint(boundsMap: ParamBounds, lowerMap: ParamOrdering, upperMap: ParamOrdering)(implicit ctx: Context) : OrderingConstraint = {
val result = new OrderingConstraint(boundsMap, lowerMap, upperMap)
if (Config.checkConstraintsNonCyclic) result.checkNonCyclic()
ctx.runInfo.recordConstraintSize(result, result.boundsMap.size)
result
}
/** A lens for updating a single entry array in one of the three constraint maps */
abstract class ConstraintLens[T <: AnyRef: ClassTag] {
def entries(c: OrderingConstraint, poly: PolyType): Array[T]
def updateEntries(c: OrderingConstraint, poly: PolyType, entries: Array[T])(implicit ctx: Context): OrderingConstraint
def initial: T
def apply(c: OrderingConstraint, poly: PolyType, idx: Int) = {
val es = entries(c, poly)
if (es == null) initial else es(idx)
}
/** The `current` constraint but with the entry for `param` updated to `entry`.
* `current` is used linearly. If it is different from `prev` it is
* known to be dead after the call. Hence it is OK to update destructively
* parts of `current` which are not shared by `prev`.
*/
def update(prev: OrderingConstraint, current: OrderingConstraint,
poly: PolyType, idx: Int, entry: T)(implicit ctx: Context): OrderingConstraint = {
var es = entries(current, poly)
if (es != null && (es(idx) eq entry)) current
else {
val result =
if (es == null) {
es = Array.fill(poly.paramNames.length)(initial)
updateEntries(current, poly, es)
}
else if (es ne entries(prev, poly))
current // can re-use existing entries array.
else {
es = es.clone
updateEntries(current, poly, es)
}
es(idx) = entry
result
}
}
def update(prev: OrderingConstraint, current: OrderingConstraint,
param: PolyParam, entry: T)(implicit ctx: Context): OrderingConstraint =
update(prev, current, param.binder, param.paramNum, entry)
def map(prev: OrderingConstraint, current: OrderingConstraint,
poly: PolyType, idx: Int, f: T => T)(implicit ctx: Context): OrderingConstraint =
update(prev, current, poly, idx, f(apply(current, poly, idx)))
def map(prev: OrderingConstraint, current: OrderingConstraint,
param: PolyParam, f: T => T)(implicit ctx: Context): OrderingConstraint =
map(prev, current, param.binder, param.paramNum, f)
}
val boundsLens = new ConstraintLens[Type] {
def entries(c: OrderingConstraint, poly: PolyType): Array[Type] =
c.boundsMap(poly)
def updateEntries(c: OrderingConstraint, poly: PolyType, entries: Array[Type])(implicit ctx: Context): OrderingConstraint =
newConstraint(c.boundsMap.updated(poly, entries), c.lowerMap, c.upperMap)
def initial = NoType
}
val lowerLens = new ConstraintLens[List[PolyParam]] {
def entries(c: OrderingConstraint, poly: PolyType): Array[List[PolyParam]] =
c.lowerMap(poly)
def updateEntries(c: OrderingConstraint, poly: PolyType, entries: Array[List[PolyParam]])(implicit ctx: Context): OrderingConstraint =
newConstraint(c.boundsMap, c.lowerMap.updated(poly, entries), c.upperMap)
def initial = Nil
}
val upperLens = new ConstraintLens[List[PolyParam]] {
def entries(c: OrderingConstraint, poly: PolyType): Array[List[PolyParam]] =
c.upperMap(poly)
def updateEntries(c: OrderingConstraint, poly: PolyType, entries: Array[List[PolyParam]])(implicit ctx: Context): OrderingConstraint =
newConstraint(c.boundsMap, c.lowerMap, c.upperMap.updated(poly, entries))
def initial = Nil
}
}
import OrderingConstraint._
/** Constraint over undetermined type parameters that keeps separate maps to
* reflect parameter orderings.
* @param boundsMap a map from PolyType to arrays.
* Each array contains twice the number of entries as there a type parameters
* in the PolyType. The first half of the array contains the type bounds that constrain the
* polytype's type parameters. The second half might contain type variables that
* track the corresponding parameters, or is left empty (filled with nulls).
* An instantiated type parameter is represented by having its instance type in
* the corresponding array entry. The dual use of arrays for poly params
* and typevars is to save space and hopefully gain some speed.
*
* @param lowerMap a map from PolyTypes to arrays. Each array entry corresponds
* to a parameter P of the polytype; it contains all constrained parameters
* Q that are known to be smaller than P, i.e. Q <: P.
* @param upperMap a map from PolyTypes to arrays. Each array entry corresponds
* to a parameter P of the polytype; it contains all constrained parameters
* Q that are known to be greater than P, i.e. P <: Q.
*/
class OrderingConstraint(private val boundsMap: ParamBounds,
private val lowerMap : ParamOrdering,
private val upperMap : ParamOrdering) extends Constraint {
type This = OrderingConstraint
// ----------- Basic indices --------------------------------------------------
/** The number of type parameters in the given entry array */
private def paramCount(entries: Array[Type]) = entries.length >> 1
/** The type variable corresponding to parameter numbered `n`, null if none was created */
private def typeVar(entries: Array[Type], n: Int): Type =
entries(paramCount(entries) + n)
/** The `boundsMap` entry corresponding to `param` */
def entry(param: PolyParam): Type = {
val entries = boundsMap(param.binder)
if (entries == null) NoType
else entries(param.paramNum)
}
// ----------- Contains tests --------------------------------------------------
def contains(pt: PolyType): Boolean = boundsMap(pt) != null
def contains(param: PolyParam): Boolean = {
val entries = boundsMap(param.binder)
entries != null && isBounds(entries(param.paramNum))
}
def contains(tvar: TypeVar): Boolean = {
val origin = tvar.origin
val entries = boundsMap(origin.binder)
val pnum = origin.paramNum
entries != null && isBounds(entries(pnum)) && (typeVar(entries, pnum) eq tvar)
}
private def isBounds(tp: Type) = tp.isInstanceOf[TypeBounds]
// ---------- Dependency handling ----------------------------------------------
def lower(param: PolyParam): List[PolyParam] = lowerLens(this, param.binder, param.paramNum)
def upper(param: PolyParam): List[PolyParam] = upperLens(this, param.binder, param.paramNum)
def minLower(param: PolyParam): List[PolyParam] = {
val all = lower(param)
all.filterNot(p => all.exists(isLess(p, _)))
}
def minUpper(param: PolyParam): List[PolyParam] = {
val all = upper(param)
all.filterNot(p => all.exists(isLess(_, p)))
}
def exclusiveLower(param: PolyParam, butNot: PolyParam): List[PolyParam] =
lower(param).filterNot(isLess(_, butNot))
def exclusiveUpper(param: PolyParam, butNot: PolyParam): List[PolyParam] =
upper(param).filterNot(isLess(butNot, _))
// ---------- Info related to PolyParams -------------------------------------------
def isLess(param1: PolyParam, param2: PolyParam): Boolean =
upper(param1).contains(param2)
def nonParamBounds(param: PolyParam): TypeBounds =
entry(param).asInstanceOf[TypeBounds]
def fullLowerBound(param: PolyParam)(implicit ctx: Context): Type =
(nonParamBounds(param).lo /: minLower(param))(_ | _)
def fullUpperBound(param: PolyParam)(implicit ctx: Context): Type =
(nonParamBounds(param).hi /: minUpper(param))(_ & _)
def fullBounds(param: PolyParam)(implicit ctx: Context): TypeBounds =
nonParamBounds(param).derivedTypeBounds(fullLowerBound(param), fullUpperBound(param))
def typeVarOfParam(param: PolyParam): Type = {
val entries = boundsMap(param.binder)
if (entries == null) NoType
else {
val tvar = typeVar(entries, param.paramNum)
if (tvar != null) tvar else NoType
}
}
// ---------- Adding PolyTypes --------------------------------------------------
/** The list of parameters P such that, for a fresh type parameter Q:
*
* Q <: tp implies Q <: P and isUpper = true, or
* tp <: Q implies P <: Q and isUpper = false
*/
def dependentParams(tp: Type, isUpper: Boolean): List[PolyParam] = tp match {
case param: PolyParam if contains(param) =>
param :: (if (isUpper) upper(param) else lower(param))
case tp: AndOrType =>
val ps1 = dependentParams(tp.tp1, isUpper)
val ps2 = dependentParams(tp.tp2, isUpper)
if (isUpper == tp.isAnd) ps1.union(ps2) else ps1.intersect(ps2)
case _ =>
Nil
}
/** The bound type `tp` without constrained parameters which are clearly
* dependent. A parameter in an upper bound is clearly dependent if it appears
* in a hole of a context H given by:
*
* H = []
* H & T
* T & H
*
* (the idea is that a parameter P in a H context is guaranteed to be a supertype of the
* bounded parameter.)
* Analogously, a parameter in a lower bound is clearly dependent if it appears
* in a hole of a context H given by:
*
* L = []
* L | T
* T | L
*
* "Clearly dependent" is not synonymous with "dependent" in the sense
* it is defined in `dependentParams`. Dependent parameters are handled
* in `updateEntry`. The idea of stripping off clearly dependent parameters
* and to handle them separately is for efficiency, so that type expressions
* used as bounds become smaller.
*
* @param isUpper If true, `bound` is an upper bound, else a lower bound.
*/
private def stripParams(tp: Type, paramBuf: mutable.ListBuffer[PolyParam],
isUpper: Boolean)(implicit ctx: Context): Type = tp match {
case param: PolyParam if contains(param) =>
if (!paramBuf.contains(param)) paramBuf += param
NoType
case tp: AndOrType if isUpper == tp.isAnd =>
val tp1 = stripParams(tp.tp1, paramBuf, isUpper)
val tp2 = stripParams(tp.tp2, paramBuf, isUpper)
if (tp1.exists)
if (tp2.exists) tp.derivedAndOrType(tp1, tp2)
else tp1
else tp2
case _ =>
tp
}
/** The bound type `tp` without clearly dependent parameters.
* A top or bottom type if type consists only of dependent parameters.
* @param isUpper If true, `bound` is an upper bound, else a lower bound.
*/
private def normalizedType(tp: Type, paramBuf: mutable.ListBuffer[PolyParam],
isUpper: Boolean)(implicit ctx: Context): Type =
stripParams(tp, paramBuf, isUpper)
.orElse(if (isUpper) defn.AnyType else defn.NothingType)
def add(poly: PolyType, tvars: List[TypeVar])(implicit ctx: Context): This = {
assert(!contains(poly))
val nparams = poly.paramNames.length
val entries1 = new Array[Type](nparams * 2)
poly.paramBounds.copyToArray(entries1, 0)
tvars.copyToArray(entries1, nparams)
newConstraint(boundsMap.updated(poly, entries1), lowerMap, upperMap).init(poly)
}
/** Split dependent parameters off the bounds for parameters in `poly`.
* Update all bounds to be normalized and update ordering to account for
* dependent parameters.
*/
private def init(poly: PolyType)(implicit ctx: Context): This = {
var current = this
val loBuf, hiBuf = new mutable.ListBuffer[PolyParam]
var i = 0
while (i < poly.paramNames.length) {
val param = PolyParam(poly, i)
val bounds = nonParamBounds(param)
val lo = normalizedType(bounds.lo, loBuf, isUpper = false)
val hi = normalizedType(bounds.hi, hiBuf, isUpper = true)
current = updateEntry(current, param, bounds.derivedTypeBounds(lo, hi))
current = (current /: loBuf)(order(_, _, param))
current = (current /: hiBuf)(order(_, param, _))
loBuf.clear()
hiBuf.clear()
i += 1
}
if (Config.checkConstraintsNonCyclic) checkNonCyclic()
current
}
// ---------- Updates ------------------------------------------------------------
/** Add the fact `param1 <: param2` to the constraint `current` and propagate
* `<:<` relationships between parameters ("edges") but not bounds.
*/
private def order(current: This, param1: PolyParam, param2: PolyParam)(implicit ctx: Context): This =
if (param1 == param2 || current.isLess(param1, param2)) this
else {
assert(contains(param1))
assert(contains(param2))
val newUpper = param2 :: exclusiveUpper(param2, param1)
val newLower = param1 :: exclusiveLower(param1, param2)
val current1 = (current /: newLower)(upperLens.map(this, _, _, newUpper ::: _))
val current2 = (current1 /: newUpper)(lowerLens.map(this, _, _, newLower ::: _))
current2
}
def addLess(param1: PolyParam, param2: PolyParam)(implicit ctx: Context): This =
order(this, param1, param2)
def updateEntry(current: This, param: PolyParam, tp: Type)(implicit ctx: Context): This = {
var current1 = boundsLens.update(this, current, param, tp)
tp match {
case TypeBounds(lo, hi) =>
for (p <- dependentParams(lo, isUpper = false))
current1 = order(current1, p, param)
for (p <- dependentParams(hi, isUpper = true))
current1 = order(current1, param, p)
case _ =>
}
current1
}
def updateEntry(param: PolyParam, tp: Type)(implicit ctx: Context): This =
updateEntry(this, param, tp)
def unify(p1: PolyParam, p2: PolyParam)(implicit ctx: Context): This = {
val p1Bounds = (nonParamBounds(p1) & nonParamBounds(p2)).substParam(p2, p1)
updateEntry(p1, p1Bounds).replace(p2, p1)
}
def narrowBound(param: PolyParam, bound: Type, isUpper: Boolean)(implicit ctx: Context): This = {
val oldBounds @ TypeBounds(lo, hi) = nonParamBounds(param)
val newBounds =
if (isUpper) oldBounds.derivedTypeBounds(lo, hi & bound)
else oldBounds.derivedTypeBounds(lo | bound, hi)
updateEntry(param, newBounds)
}
// ---------- Removals ------------------------------------------------------------
/** A new constraint which is derived from this constraint by removing
* the type parameter `param` from the domain and replacing all top-level occurrences
* of the parameter elsewhere in the constraint by type `tp`, or a conservative
* approximation of it if that is needed to avoid cycles.
* Occurrences nested inside a refinement or prefix are not affected.
*
* The reason we need to substitute top-level occurrences of the parameter
* is to deal with situations like the following. Say we have in the constraint
*
* P <: Q & String
* Q
*
* and we replace Q with P. Then substitution gives
*
* P <: P & String
*
* this would be a cyclic constraint is therefore changed by `normalize` and
* `recombine` below to
*
* P <: String
*
* approximating the RHS occurrence of P with Any. Without the substitution we
* would not find out where we need to approximate. Occurrences of parameters
* that are not top-level are not affected.
*/
def replace(param: PolyParam, tp: Type)(implicit ctx: Context): OrderingConstraint = {
val replacement = tp.dealias.stripTypeVar
if (param == replacement) this
else {
assert(replacement.isValueTypeOrLambda)
val poly = param.binder
val idx = param.paramNum
def removeParam(ps: List[PolyParam]) =
ps.filterNot(p => p.binder.eq(poly) && p.paramNum == idx)
def replaceParam(tp: Type, atPoly: PolyType, atIdx: Int): Type = tp match {
case bounds @ TypeBounds(lo, hi) =>
def recombine(andor: AndOrType, op: (Type, Boolean) => Type, isUpper: Boolean): Type = {
val tp1 = op(andor.tp1, isUpper)
val tp2 = op(andor.tp2, isUpper)
if ((tp1 eq andor.tp1) && (tp2 eq andor.tp2)) andor
else if (andor.isAnd) tp1 & tp2
else tp1 | tp2
}
def normalize(tp: Type, isUpper: Boolean): Type = tp match {
case p: PolyParam if p.binder == atPoly && p.paramNum == atIdx =>
if (isUpper) defn.AnyType else defn.NothingType
case tp: AndOrType if isUpper == tp.isAnd => recombine(tp, normalize, isUpper)
case _ => tp
}
def replaceIn(tp: Type, isUpper: Boolean): Type = tp match {
case `param` => normalize(replacement, isUpper)
case tp: AndOrType if isUpper == tp.isAnd => recombine(tp, replaceIn, isUpper)
case _ => tp.substParam(param, replacement)
}
bounds.derivedTypeBounds(replaceIn(lo, isUpper = false), replaceIn(hi, isUpper = true))
case _ =>
tp.substParam(param, replacement)
}
var current =
if (isRemovable(poly)) remove(poly) else updateEntry(param, replacement)
current.foreachParam {(p, i) =>
current = boundsLens.map(this, current, p, i, replaceParam(_, p, i))
current = lowerLens.map(this, current, p, i, removeParam)
current = upperLens.map(this, current, p, i, removeParam)
}
current
}
}
def remove(pt: PolyType)(implicit ctx: Context): This = {
def removeFromOrdering(po: ParamOrdering) = {
def removeFromBoundss(key: PolyType, bndss: Array[List[PolyParam]]): Array[List[PolyParam]] = {
val bndss1 = bndss.map(_.filterConserve(_.binder ne pt))
if (bndss.corresponds(bndss1)(_ eq _)) bndss else bndss1
}
po.remove(pt).mapValuesNow(removeFromBoundss)
}
newConstraint(boundsMap.remove(pt), removeFromOrdering(lowerMap), removeFromOrdering(upperMap))
}
def isRemovable(pt: PolyType): Boolean = {
val entries = boundsMap(pt)
@tailrec def allRemovable(last: Int): Boolean =
if (last < 0) true
else typeVar(entries, last) match {
case tv: TypeVar => tv.inst.exists && allRemovable(last - 1)
case _ => false
}
allRemovable(paramCount(entries) - 1)
}
// ---------- Exploration --------------------------------------------------------
def domainPolys: List[PolyType] = boundsMap.keys
def domainParams: List[PolyParam] =
for {
(poly, entries) <- boundsMap.toList
n <- 0 until paramCount(entries)
if entries(n).exists
} yield PolyParam(poly, n)
def forallParams(p: PolyParam => Boolean): Boolean = {
boundsMap.foreachBinding { (poly, entries) =>
for (i <- 0 until paramCount(entries))
if (isBounds(entries(i)) && !p(PolyParam(poly, i))) return false
}
true
}
def foreachParam(p: (PolyType, Int) => Unit): Unit =
boundsMap.foreachBinding { (poly, entries) =>
0.until(poly.paramNames.length).foreach(p(poly, _))
}
def foreachTypeVar(op: TypeVar => Unit): Unit =
boundsMap.foreachBinding { (poly, entries) =>
for (i <- 0 until paramCount(entries)) {
typeVar(entries, i) match {
case tv: TypeVar if !tv.inst.exists => op(tv)
case _ =>
}
}
}
def & (other: Constraint)(implicit ctx: Context) = {
def merge[T](m1: ArrayValuedMap[T], m2: ArrayValuedMap[T], join: (T, T) => T): ArrayValuedMap[T] = {
var merged = m1
def mergeArrays(xs1: Array[T], xs2: Array[T]) = {
val xs = xs1.clone
for (i <- xs.indices) xs(i) = join(xs1(i), xs2(i))
xs
}
m2.foreachBinding { (poly, xs2) =>
merged = merged.updated(poly,
if (m1.contains(poly)) mergeArrays(m1(poly), xs2) else xs2)
}
merged
}
def mergeParams(ps1: List[PolyParam], ps2: List[PolyParam]) =
(ps1 /: ps2)((ps1, p2) => if (ps1.contains(p2)) ps1 else p2 :: ps1)
def mergeEntries(e1: Type, e2: Type): Type = e1 match {
case e1: TypeBounds =>
e2 match {
case e2: TypeBounds => e1 & e2
case _ if e1 contains e2 => e2
case _ => mergeError
}
case tv1: TypeVar =>
e2 match {
case tv2: TypeVar if tv1.instanceOpt eq tv2.instanceOpt => e1
case _ => mergeError
}
case _ if e1 eq e2 => e1
case _ => mergeError
}
def mergeError = throw new AssertionError(i"cannot merge $this with $other")
val that = other.asInstanceOf[OrderingConstraint]
new OrderingConstraint(
merge(this.boundsMap, that.boundsMap, mergeEntries),
merge(this.lowerMap, that.lowerMap, mergeParams),
merge(this.upperMap, that.upperMap, mergeParams))
}
override def checkClosed()(implicit ctx: Context): Unit = {
def isFreePolyParam(tp: Type) = tp match {
case PolyParam(binder: PolyType, _) => !contains(binder)
case _ => false
}
def checkClosedType(tp: Type, where: String) =
if (tp != null)
assert(!tp.existsPart(isFreePolyParam), i"unclosed constraint: $this refers to $tp in $where")
boundsMap.foreachBinding((_, tps) => tps.foreach(checkClosedType(_, "bounds")))
lowerMap.foreachBinding((_, paramss) => paramss.foreach(_.foreach(checkClosedType(_, "lower"))))
upperMap.foreachBinding((_, paramss) => paramss.foreach(_.foreach(checkClosedType(_, "upper"))))
}
private var myUninstVars: mutable.ArrayBuffer[TypeVar] = _
/** The uninstantiated typevars of this constraint */
def uninstVars: collection.Seq[TypeVar] = {
if (myUninstVars == null) {
myUninstVars = new mutable.ArrayBuffer[TypeVar]
boundsMap.foreachBinding { (poly, entries) =>
for (i <- 0 until paramCount(entries)) {
typeVar(entries, i) match {
case tv: TypeVar if !tv.inst.exists && isBounds(entries(i)) => myUninstVars += tv
case _ =>
}
}
}
}
myUninstVars
}
// ---------- Cyclic checking -------------------------------------------
def checkNonCyclic()(implicit ctx: Context): Unit =
domainParams.foreach(checkNonCyclic)
private def checkNonCyclic(param: PolyParam)(implicit ctx: Context): Unit =
assert(!isLess(param, param), i"cyclic constraint involving $param in $this")
// ---------- toText -----------------------------------------------------
override def toText(printer: Printer): Text = {
def entryText(tp: Type) = tp match {
case tp: TypeBounds =>
tp.toText(printer)
case _ =>
" := " ~ tp.toText(printer)
}
val indent = 3
val header: Text = "Constraint("
val uninstVarsText = " uninstVars = " ~
Text(uninstVars map (_.toText(printer)), ", ") ~ ";"
val constrainedText =
" constrained types = " ~ Text(domainPolys map (_.toText(printer)), ", ")
val boundsText =
" bounds = " ~ {
val assocs =
for (param <- domainParams)
yield (" " * indent) ~ param.toText(printer) ~ entryText(entry(param))
Text(assocs, "\n")
}
val orderingText =
" ordering = " ~ {
val deps =
for {
param <- domainParams
ups = minUpper(param)
if ups.nonEmpty
}
yield
(" " * indent) ~ param.toText(printer) ~ " <: " ~
Text(ups.map(_.toText(printer)), ", ")
Text(deps, "\n")
}
Text.lines(List(header, uninstVarsText, constrainedText, boundsText, orderingText, ")"))
}
override def toString: String = {
def entryText(tp: Type): String = tp match {
case tp: TypeBounds => tp.toString
case _ =>" := " + tp
}
val constrainedText =
" constrained types = " + domainPolys.mkString("\n")
val boundsText =
" bounds = " + {
val assocs =
for (param <- domainParams)
yield
param.binder.paramNames(param.paramNum) + ": " + entryText(entry(param))
assocs.mkString("\n")
}
constrainedText + "\n" + boundsText
}
}