All Downloads are FREE. Search and download functionalities are using the official Maven repository.

dotty.tools.dotc.ast.Positioned.scala Maven / Gradle / Ivy

The newest version!
package dotty.tools
package dotc
package ast

import util.Spans._
import util.{SourceFile, NoSource, SourcePosition}
import core.Contexts.Context
import core.Decorators._
import core.Flags.{JavaDefined, Extension}
import core.StdNames.nme
import annotation.constructorOnly
import annotation.internal.sharable
import reporting.Reporter

import java.io.{ PrintWriter }

/** A base class for things that have positions (currently: modifiers and trees)
 */
abstract class Positioned(implicit @constructorOnly src: SourceFile) extends Product with Cloneable {

  private[this] var myUniqueId: Int = _
  private[this] var mySpan: Span = _

  /** A unique identifier. Among other things, used for determining the source file
   *  component of the position.
   */
  def uniqueId: Int = myUniqueId

  def uniqueId_=(id: Int): Unit = {
    def printTrace() = {
      println(s"Debug tree (id=${Positioned.debugId}) creation \n$this\n")
      Reporter.displayPrompt(Console.in, new PrintWriter(Console.err, true))
    }
    if (Positioned.debugId == id) printTrace()
    myUniqueId = id
  }

  /** The span part of the item's position */
  def span: Span = mySpan

  def span_=(span: Span): Unit = {
    mySpan = span
  }

  uniqueId = src.nextId
  span = envelope(src)

  def source: SourceFile = SourceFile.fromId(uniqueId)
  def sourcePos(implicit ctx: Context): SourcePosition = source.atSpan(span)

  /** A positioned item like this one with given `span`.
   *  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 withSpan(span: Span): this.type =
    if (span == mySpan) this
    else {
      val newpd: this.type =
        if (mySpan.isSynthetic) {
          if (!mySpan.exists && span.exists) {
            envelope(source, span.startPos) // fill in children spans
          }
          this
        }
        else cloneIn(source)
      newpd.span = span
      newpd
    }

  /** The union of startSpan and the spans of all positioned children that
   *  have the same source as this node, except that Inlined nodes only
   *  consider their `call` child.
   *
   *  Side effect: Any descendants without spans have but with the same source as this
   *  node have their span set to the end position of the envelope of all children to
   *  the left, or, if that one does not exist, to the start position of the envelope
   *  of all children to the right.
   */
  def envelope(src: SourceFile, startSpan: Span = NoSpan): Span = this match {
    case Trees.Inlined(call, _, _) =>
      call.span
    case _ =>
      def include(span: Span, x: Any): Span = x match {
        case p: Positioned =>
          if (p.source != src) span
          else if (p.span.exists) span.union(p.span)
          else if (span.exists) {
            if (span.end != MaxOffset)
              p.span = p.envelope(src, span.endPos)
            span
          }
          else // No span available to assign yet, signal this by returning a span with MaxOffset end
            Span(MaxOffset, MaxOffset)
        case m: untpd.Modifiers =>
          include(include(span, m.mods), m.annotations)
        case y :: ys =>
          include(include(span, y), ys)
        case _ => span
      }
      val limit = productArity
      def includeChildren(span: Span, n: Int): Span =
        if (n < limit) includeChildren(include(span, productElement(n)), n + 1)
        else span
      val span1 = includeChildren(startSpan, 0)
      val span2 =
        if (!span1.exists || span1.end != MaxOffset)
          span1
        else if (span1.start == MaxOffset)
          // No positioned child was found
          NoSpan
        else {
          ///println(s"revisit $uniqueId with $span1")
          // We have some children left whose span could not be assigned.
          // Go through it again with the known start position.
          includeChildren(span1.startPos, 0)
        }
      span2.toSynthetic
  }

  /** Clone this node but assign it a fresh id which marks it as a node in `file`. */
  def cloneIn(src: SourceFile): this.type = {
    val newpd: this.type = clone.asInstanceOf[this.type]
    newpd.uniqueId = src.nextId
    newpd
  }

  def contains(that: Positioned): Boolean = {
    def isParent(x: Any): Boolean = x match {
      case x: Positioned =>
        x.contains(that)
      case m: untpd.Modifiers =>
        m.mods.exists(isParent) || m.annotations.exists(isParent)
      case xs: List[_] =>
        xs.exists(isParent)
      case _ =>
        false
    }
    (this eq that) ||
      (this.span contains that.span) && {
        var n = productArity
        var found = false
        while (!found && n > 0) {
          n -= 1
          found = isParent(productElement(n))
        }
        found
      }
  }

  /** A hook that can be overridden if overlap checking in `checkPos` should be
   *  disabled for this node.
   */
  def disableOverlapChecks = false

  /** Check that all positioned items in this tree satisfy the following conditions:
   *  - Parent spans contain child spans
   *  - 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 lastSpan = NoSpan
    def check(p: Any): Unit = p match {
      case p: Positioned =>
        assert(span contains p.span,
          i"""position error, parent span does not contain child span
             |parent      = $this # $uniqueId,
             |parent span = $span,
             |child       = $p # ${p.uniqueId},
             |child span  = ${p.span}""".stripMargin)
        p match {
          case tree: Tree if !tree.isEmpty =>
            assert(tree.span.exists,
              s"position error: position not set for $tree # ${tree.uniqueId}")
          case _ =>
        }
        if (nonOverlapping && !disableOverlapChecks) {
          this match {
            case _: XMLBlock =>
              // FIXME: Trees generated by the XML parser do not satisfy `checkPos`
            case _: WildcardFunction
            if lastPositioned.isInstanceOf[ValDef] && !p.isInstanceOf[ValDef] =>
              // ignore transition from last wildcard parameter to body
            case _ =>
              assert(!lastSpan.exists || !p.span.exists || lastSpan.end <= p.span.start,
                i"""position error, child positions overlap or in wrong order
                   |parent         = $this
                   |1st child      = $lastPositioned
                   |1st child span = $lastSpan
                   |2nd child      = $p
                   |2nd child span = ${p.span}""".stripMargin)
          }
          lastPositioned = p
          lastSpan = p.span
        }
        p.checkPos(nonOverlapping)
      case m: untpd.Modifiers =>
        m.annotations.foreach(check)
        m.mods.foreach(check)
      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 tree: DefDef if tree.mods.is(Extension) =>
        tree.vparamss match {
          case vparams1 :: vparams2 :: rest if !isLeftAssoc(tree.name) =>
            check(vparams2)
            check(tree.tparams)
            check(vparams1)
            check(rest)
          case vparams1 :: rest =>
            check(vparams1)
            check(tree.tparams)
            check(rest)
          case _ =>
            check(tree.tparams)
        }
        check(tree.tpt)
        check(tree.rhs)
      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
  }
}

object Positioned {
  @sharable private[Positioned] var debugId = Int.MinValue

  def updateDebugPos(implicit ctx: Context): Unit = {
    debugId = ctx.settings.YdebugTreeWithId.value
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy