package dotty.tools.dotc
package ast
import util.Positions._
import util.DotClass
import core.Contexts.Context
import core.Decorators._
import core.Flags.JavaDefined
import core.StdNames.nme
/** A base class for things that have positions (currently: modifiers and trees)
*/
abstract class Positioned extends DotClass with Product {
private[this] var curPos: Position = _
setPos(initialPos)
/** The item's position.
*/
def pos: Position = curPos
/** Destructively update `curPos` to given position. Also, set any missing
* positions in children.
*/
protected def setPos(pos: Position): Unit = {
curPos = pos
if (pos.exists) setChildPositions(pos.toSynthetic)
}
/** A positioned item like this one with the position set to `pos`.
* if the positioned item is source-derived, a clone is returned.
* If the positioned item is synthetic, the position is updated
* destructively and the item itself is returned.
*/
def withPos(pos: Position): this.type = {
val newpd = (if (pos == curPos || curPos.isSynthetic) this else clone).asInstanceOf[Positioned]
newpd.setPos(pos)
newpd.asInstanceOf[this.type]
}
def withPos(posd: Positioned): this.type =
if (posd == null) this else withPos(posd.pos)
/** This item with a position that's the union of the given `pos` and the
* current position.
*/
def addPos(pos: Position): this.type = withPos(pos union this.pos)
/** Set position of this tree only, without performing
* any checks of consistency with - or updates of - other positions.
* Called from Unpickler when entering positions.
*/
private[dotc] def setPosUnchecked(pos: Position) = curPos = pos
/** If any children of this node do not have positions,
* fit their positions between the positions of the known subtrees
* and transitively visit their children.
* The method is likely time-critical because it is invoked on any node
* we create, so we want to avoid object allocations in the common case.
* The method is naturally expressed as two mutually (tail-)recursive
* functions, one which computes the next element to consider or terminates if there
* is none and the other which propagates the position information to that element.
* But since mutual tail recursion is not supported in Scala, we express it instead
* as a while loop with a termination by return in the middle.
*/
private def setChildPositions(pos: Position): Unit = {
var n = productArity // subnodes are analyzed right to left
var elems: List[Any] = Nil // children in lists still to be considered, from right to left
var end = pos.end // the last defined offset, fill in positions up to this offset
var outstanding: List[Positioned] = Nil // nodes that need their positions filled once a start position
// is known, from left to right.
def fillIn(ps: List[Positioned], start: Int, end: Int): Unit = ps match {
case p :: ps1 =>
p.setPos(Position(start, end))
fillIn(ps1, end, end)
case nil =>
}
while (true) {
var nextChild: Any = null // the next child to be considered
if (elems.nonEmpty) {
nextChild = elems.head
elems = elems.tail
}
else if (n > 0) {
n = n - 1
nextChild = productElement(n)
}
else {
fillIn(outstanding, pos.start, end)
return
}
nextChild match {
case p: Positioned =>
if (p.pos.exists) {
fillIn(outstanding, p.pos.end, end)
outstanding = Nil
end = p.pos.start
}
else outstanding = p :: outstanding
case xs: List[_] =>
elems = elems ::: xs.reverse
case _ =>
}
}
}
/** The initial, synthetic position. This is usually the union of all positioned children's positions.
*/
protected def initialPos: Position = {
var n = productArity
var pos = NoPosition
while (n > 0) {
n -= 1
productElement(n) match {
case p: Positioned => pos = pos union p.pos
case xs: List[_] => pos = unionPos(pos, xs)
case _ =>
}
}
pos.toSynthetic
}
private def unionPos(pos: Position, xs: List[_]): Position = xs match {
case (p: Positioned) :: xs1 => unionPos(pos union p.pos, xs1)
case _ => pos
}
def contains(that: Positioned): Boolean = {
def isParent(x: Any): Boolean = x match {
case x: Positioned =>
x contains that
case xs: List[_] =>
xs exists isParent
case _ =>
false
}
(this eq that) ||
(this.pos contains that.pos) && {
var n = productArity
var found = false
while (n > 0 && !found) {
n -= 1
found = isParent(productElement(n))
}
found
}
}
/** Check that all positioned items in this tree satisfy the following conditions:
* - Parent positions contain child positions
* - If item is a non-empty tree, it has a position
*/
def checkPos(nonOverlapping: Boolean)(implicit ctx: Context): Unit = try {
import untpd._
var lastPositioned: Positioned = null
var lastPos = NoPosition
def check(p: Any): Unit = p match {
case p: Positioned =>
assert(pos contains p.pos,
s"""position error, parent position does not contain child positon
|parent = $this,
|parent position = $pos,
|child = $p,
|child position = ${p.pos}""".stripMargin)
p match {
case tree: Tree if !tree.isEmpty =>
assert(tree.pos.exists,
s"position error: position not set for $tree # ${tree.uniqueId}")
case _ =>
}
if (nonOverlapping) {
this match {
case _: WildcardFunction
if lastPositioned.isInstanceOf[ValDef] && !p.isInstanceOf[ValDef] =>
// ignore transition from last wildcard parameter to body
case _ =>
assert(!lastPos.exists || !p.pos.exists || lastPos.end <= p.pos.start,
s"""position error, child positions overlap or in wrong order
|parent = $this
|1st child = $lastPositioned
|1st child position = $lastPos
|2nd child = $p
|2nd child position = ${p.pos}""".stripMargin)
}
lastPositioned = p
lastPos = p.pos
}
p.checkPos(nonOverlapping)
case xs: List[_] =>
xs.foreach(check)
case _ =>
}
this match {
case tree: DefDef if tree.name == nme.CONSTRUCTOR && tree.mods.is(JavaDefined) =>
// Special treatment for constructors coming from Java:
// Leave out tparams, they are copied with wrong positions from parent class
check(tree.mods)
check(tree.vparamss)
case _ =>
val end = productArity
var n = 0
while (n < end) {
check(productElement(n))
n += 1
}
}
} catch {
case ex: AssertionError =>
println(i"error while checking $this")
throw ex
}
}