aboutsummaryrefslogblamecommitdiff
path: root/compiler/src/dotty/tools/dotc/ast/Positioned.scala
blob: 51949c6fefb5d151be83ac813a9963fe0784e62f (plain) (tree)
1
2
3
4
5
6
7
8
9




                        



                             












                                                                                
                                                                           


                                               
                        


                                                      


















                                                                                                   





                                                                       

                                                                     
                                            






                                                                                       

                                                        




                                                                                                           






                                                                             
                                                                  
                           
                              



                          
                                     




                                           
                       







                                               
                                      

                 


     
                                                                                                        
     
                              




                               
                                                   







                                                                         
                                                                 

                                                                   












                                             
                                       

                            
                                 





                                             




                                                                                    
                                                                            
                  

                                         



                                                                            



                                                        





                                                                               

                             


                                                                                







                                                                                    
           

                            

                                  










                                                                                       


                              
                                  
                






                                            
 
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 = {
    setPosUnchecked(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.
   */
  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 (xs0: List[_]) :: xs1 => unionPos(unionPos(pos, xs0), xs1)
    case _ :: xs1 => unionPos(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 (!found && n > 0) {
          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
  }
}