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 config.Printers._
import collection.immutable.BitSet
import reflect.ClassTag
object OrderingConstraint {
/** The type of `Constraint#boundsMap` */
type ParamBounds = SimpleMap[PolyType, Array[Type]]
/** The type of `Constraint#lowerMap`, `Constraint#upperMap` */
type ParamOrdering = SimpleMap[PolyType, Array[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.
*
* @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. P <: Q.
* @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. Q <: P.
*/
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 && entries(param.paramNum).isInstanceOf[TypeBounds]
}
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 bound type `tp` without dependent parameters
* NoType if type consists only of dependent parameters.
* @param seenFromBelow 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 dependent parameters.
* A top or bottom type if type consists only of dependent parameters.
* @param seenFromBelow 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)
}
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 = boundsLens.update(this, current, poly, i, 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 ------------------------------------------------------------
/** An updated partial order matrix that incorporates `less` and also reflects that `param` relates
* to `p2` wrt <:< if `inOrder` is true, `>:>` otherwise.
*/
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(param: PolyParam, tp: Type)(implicit ctx: Context): This =
boundsLens.update(this, 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 ------------------------------------------------------------
/** Drop parameter `PolyParam(poly, n)` from `bounds`,
* replacing with Nothing in the lower bound and by `Any` in the upper bound.
*/
private def dropParamIn(bounds: TypeBounds, poly: PolyType, n: Int)(implicit ctx: Context): TypeBounds = {
def drop(tp: Type): Type = tp match {
case tp: AndOrType =>
val tp1 = drop(tp.tp1)
val tp2 = drop(tp.tp2)
if (!tp1.exists) tp2
else if (!tp2.exists) tp1
else tp
case PolyParam(`poly`, `n`) => NoType
case _ => tp
}
def approx(tp: Type, limit: Type): Type = {
val tp1 = drop(tp)
if (tp1.exists || !tp.exists) tp1 else limit
}
bounds.derivedTypeBounds(
approx(bounds.lo, defn.NothingType), approx(bounds.hi, defn.AnyType))
}
def replace(param: PolyParam, tp: Type)(implicit ctx: Context): OrderingConstraint = {
val replacement = tp.dealias.stripTypeVar
if (param == replacement) this
else {
assert(replacement.isValueType)
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) = tp match {
case bounds: TypeBounds =>
val bounds1 = tp.substParam(param, replacement).asInstanceOf[TypeBounds]
if (bounds1 eq bounds) bounds
else dropParamIn(bounds1, atPoly, atIdx)
case _ => tp
}
var current = this
if (isRemovable(poly, idx)) current = remove(poly)
else {
current = updateEntry(param, replacement)
lowerLens.update(this, current, poly, idx, Nil)
upperLens.update(this, current, poly, idx, Nil)
}
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 =
newConstraint(boundsMap.remove(pt), lowerMap.remove(pt), upperMap.remove(pt))
def isRemovable(pt: PolyType, removedParam: Int = -1): Boolean = {
val entries = boundsMap(pt)
var noneLeft = true
var i = paramCount(entries)
while (noneLeft && i > 0) {
i -= 1
if (i != removedParam && isBounds(entries(i))) noneLeft = false
else typeVar(entries, i) match {
case tv: TypeVar =>
if (!tv.inst.exists) noneLeft = false // need to keep line around to compute instType
case _ =>
}
}
noneLeft
}
// ---------- Exploration --------------------------------------------------------
def domainPolys: List[PolyType] = boundsMap.keys
def domainParams: List[PolyParam] =
for {
(poly, entries) <- boundsMap.toList
n <- 0 until paramCount(entries)
if isBounds(entries(n))
} 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 _ =>
}
}
}
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, ")"))
}
}