Skip to content

Commit c816b6e

Browse files
committed
Simplify source positions
New scheme: - The source of a tree is always given explicitly when we construct the tree - The initial span of the tree is computed from all children that have the same source of the tree - Computing the initial span also fills in children's spans where possible.
1 parent 30ae7ae commit c816b6e

13 files changed

+197
-318
lines changed

compiler/src/dotty/tools/dotc/ast/Positioned.scala

Lines changed: 86 additions & 148 deletions
Original file line numberDiff line numberDiff line change
@@ -15,179 +15,117 @@ import annotation.internal.sharable
1515
*/
1616
abstract class Positioned(implicit @transientParam src: SourceFile) extends Product with Cloneable {
1717

18+
private[this] var myUniqueId: Int = _
19+
private[this] var mySpan: Span = _
20+
1821
/** A unique identifier. Among other things, used for determining the source file
1922
* component of the position.
2023
*/
21-
private var myUniqueId: Int = -1
22-
private[this] var mySpan: Span = NoSpan
24+
def uniqueId: Int = myUniqueId
25+
26+
def uniqueId_=(id: Int): Unit = {
27+
//assert(id != 2523, this)
28+
myUniqueId = id
29+
}
2330

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

27-
/** item's id */
28-
def uniqueId: Int = myUniqueId
34+
def span_=(span: Span): Unit = {
35+
mySpan = span
36+
}
37+
38+
uniqueId = src.nextId
39+
span = envelope(src)
2940

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

33-
//setId(initialSource(src).nextId)
34-
setPos(initialSpan(src), source)
35-
36-
protected def setId(id: Int): Unit = {
37-
myUniqueId = id
38-
//assert(id != 2067, getClass)
39-
}
40-
41-
/** Destructively update `mySpan` to given span and potentially update `id` so that
42-
* it refers to `file`. Also, set any missing positions in children.
43-
*/
44-
protected def setPos(span: Span, file: SourceFile): Unit = {
45-
setOnePos(span, file)
46-
if (span.exists) setChildPositions(span.toSynthetic, file)
47-
}
48-
4944
/** A positioned item like this one with given `span`.
5045
* If the positioned item is source-derived, a clone is returned.
5146
* If the positioned item is synthetic, the position is updated
5247
* destructively and the item itself is returned.
5348
*/
54-
def withSpan(span: Span): this.type = {
55-
val ownSpan = this.span
56-
val newpd: this.type =
57-
if (span == ownSpan || ownSpan.isSynthetic) this else cloneIn(source)
58-
newpd.setPos(span, source)
59-
newpd
60-
}
61-
62-
/** Set span of this tree only, without updating children spans.
63-
* Called from Unpickler when entering positions.
64-
*/
65-
private[dotc] def setOnePos(span: Span, file: SourceFile = this.source): Unit = {
66-
if (file `ne` this.source) setId(file.nextId)
67-
mySpan = span
68-
}
69-
70-
/** If any children of this node do not have spans,
71-
* fit their spans between the spans of the known subtrees
72-
* and transitively visit their children.
73-
* The method is likely time-critical because it is invoked on any node
74-
* we create, so we want to avoid object allocations in the common case.
75-
* The method is naturally expressed as two mutually (tail-)recursive
76-
* functions, one which computes the next element to consider or terminates if there
77-
* is none and the other which propagates the span information to that element.
78-
* But since mutual tail recursion is not supported in Scala, we express it instead
79-
* as a while loop with a termination by return in the middle.
80-
*/
81-
private def setChildPositions(span: Span, file: SourceFile): Unit = {
82-
var n = productArity // subnodes are analyzed right to left
83-
var elems: List[Any] = Nil // children in lists still to be considered, from right to left
84-
var end = span.end // the last defined offset, fill in spans up to this offset
85-
var outstanding: List[Positioned] = Nil // nodes that need their spans filled once a start offset
86-
// is known, from left to right.
87-
def fillIn(ps: List[Positioned], start: Int, end: Int): Unit = ps match {
88-
case p :: ps1 =>
89-
// If a tree has no span or a zero-extent span, it should be
90-
// synthetic. We can preserve this invariant by always setting a
91-
// zero-extent span for these trees here.
92-
if (!p.span.exists || p.span.isZeroExtent) {
93-
p.setPos(Span(start, start), file)
94-
fillIn(ps1, start, end)
95-
} else {
96-
p.setPos(Span(start, end), file)
97-
fillIn(ps1, end, end)
49+
def withSpan(span: Span): this.type =
50+
if (span == mySpan) this
51+
else {
52+
val newpd: this.type =
53+
if (mySpan.isSynthetic) {
54+
if (!mySpan.exists && span.exists)
55+
envelope(source, span.startPos) // fill in children spans
56+
this
9857
}
99-
case nil =>
58+
else cloneIn(source)
59+
newpd.span = span
60+
newpd
10061
}
101-
while (true) {
102-
var nextChild: Any = null // the next child to be considered
103-
if (elems.nonEmpty) {
104-
nextChild = elems.head
105-
elems = elems.tail
106-
}
107-
else if (n > 0) {
108-
n = n - 1
109-
nextChild = productElement(n)
110-
}
111-
else {
112-
fillIn(outstanding, span.start, end)
113-
return
114-
}
115-
nextChild match {
62+
63+
/** The union of startSpan and the spans of all positioned children that
64+
* have the same source as this node, except that Inlined nodes only
65+
* consider their `call` child.
66+
*
67+
* Side effect: Any descendants without spans have but with the same source as this
68+
* node have their span set to the end position of the envelope of all children to
69+
* the left, or, if that one does not exist, to the start position of the envelope
70+
* of all children to the right.
71+
*
72+
* @param ignoreTypeTrees If true, don't count type trees in the union.
73+
* This is used to decide whether we need to pickle a position for a tree.
74+
* TypeTreesare pickled as types and therefore contribute nothing to the span union.
75+
*/
76+
def envelope(src: SourceFile, startSpan: Span = NoSpan, ignoreTypeTrees: Boolean = false): Span = this match {
77+
case Trees.Inlined(call, _, _) =>
78+
//println(s"envelope of $this # $uniqueId = ${call.span}")
79+
call.span
80+
case _ =>
81+
def include(span: Span, x: Any): Span = x match {
82+
case p: Trees.TypeTree[_] if ignoreTypeTrees =>
83+
span
84+
case core.tasty.TreePickler.Hole if ignoreTypeTrees =>
85+
span
11686
case p: Positioned =>
117-
if (p.span.exists) {
118-
fillIn(outstanding, p.span.end, end)
119-
outstanding = Nil
120-
end = p.span.start
87+
if (p.source `ne` src) span
88+
else if (p.span.exists) span.union(p.span)
89+
else if (span.exists) {
90+
if (span.end != MaxOffset)
91+
p.span = p.envelope(src, span.endPos, ignoreTypeTrees)
92+
span
12193
}
122-
else outstanding = p :: outstanding
94+
else // No span available to assign yet, signal this by returning a span with MaxOffset end
95+
Span(MaxOffset, MaxOffset)
12396
case m: untpd.Modifiers =>
124-
if (m.mods.nonEmpty || m.annotations.nonEmpty)
125-
elems = elems ::: m.mods.reverse ::: m.annotations.reverse
126-
case xs: List[_] =>
127-
elems = elems ::: xs.reverse
128-
case _ =>
97+
include(include(span, m.mods), m.annotations)
98+
case y :: ys =>
99+
include(include(span, y), ys)
100+
case _ => span
129101
}
130-
}
102+
val limit = productArity
103+
def includeChildren(span: Span, n: Int): Span =
104+
if (n < limit) includeChildren(include(span, productElement(n)), n + 1)
105+
else span
106+
val span1 = includeChildren(startSpan, 0)
107+
val span2 =
108+
if (!span1.exists || span1.end != MaxOffset)
109+
span1
110+
else if (span1.start == MaxOffset)
111+
// No positioned child was found
112+
NoSpan
113+
else {
114+
///println(s"revisit $uniqueId with $span1")
115+
// We have some children left whose span could not be assigned.
116+
// Go through it again with the known start position.
117+
includeChildren(span1.startPos, 0)
118+
}
119+
span2.toSynthetic
131120
}
132121

133122
/** Clone this node but assign it a fresh id which marks it as a node in `file`. */
134-
protected def cloneIn(file: SourceFile): this.type = {
123+
def cloneIn(src: SourceFile): this.type = {
135124
val newpd: this.type = clone.asInstanceOf[this.type]
136-
newpd.setId(file.nextId)
125+
newpd.uniqueId = src.nextId
137126
newpd
138127
}
139128

140-
/** The initial, synthetic span. This is usually the union of all positioned children's spans.
141-
*/
142-
def initialSpan(givenSource: SourceFile): Span = {
143-
144-
def include(span1: Span, p2: Positioned): Span = {
145-
val span2 = p2.span
146-
if (span2.exists) {
147-
var src = if (uniqueId == -1) NoSource else source
148-
val src2 = p2.source
149-
if (src `eq` src2) span1.union(span2)
150-
else if (!src.exists) {
151-
setId(src2.nextId)
152-
if (span1.exists) initialSpan(givenSource) // we might have some mis-classified children; re-run everything
153-
else span2
154-
}
155-
else span1 // sources differ: ignore child span
156-
}
157-
else span1
158-
}
159-
160-
def includeAll(span: Span, xs: List[_]): Span = xs match {
161-
case Nil => span
162-
case (p: Positioned) :: xs1 => includeAll(include(span, p), xs1)
163-
case (xs0: List[_]) :: xs1 => includeAll(includeAll(span, xs0), xs1)
164-
case _ :: xs1 => includeAll(span, xs1)
165-
}
166-
167-
val limit = relevantElemCount
168-
var n = 0
169-
var span = NoSpan
170-
while (n < limit) {
171-
productElement(n) match {
172-
case p: Positioned =>
173-
span = include(span, p)
174-
case m: untpd.Modifiers =>
175-
span = includeAll(includeAll(span, m.mods), m.annotations)
176-
case xs: ::[_] =>
177-
span = includeAll(span, xs)
178-
case _ =>
179-
}
180-
n += 1
181-
}
182-
if (uniqueId == -1) setId(givenSource.nextId)
183-
span.toSynthetic
184-
}
185-
186-
/** How many elements to consider when computing the span.
187-
* Normally: all, overridden in Inlined.
188-
*/
189-
def relevantElemCount = productArity
190-
191129
def contains(that: Positioned): Boolean = {
192130
def isParent(x: Any): Boolean = x match {
193131
case x: Positioned =>
@@ -222,10 +160,10 @@ abstract class Positioned(implicit @transientParam src: SourceFile) extends Prod
222160
def check(p: Any): Unit = p match {
223161
case p: Positioned =>
224162
assert(span contains p.span,
225-
s"""position error, parent span does not contain child span
226-
|parent = $this,
163+
i"""position error, parent span does not contain child span
164+
|parent = $this # $uniqueId,
227165
|parent span = $span,
228-
|child = $p,
166+
|child = $p # ${p.uniqueId},
229167
|child span = ${p.span}""".stripMargin)
230168
p match {
231169
case tree: Tree if !tree.isEmpty =>
@@ -242,7 +180,7 @@ abstract class Positioned(implicit @transientParam src: SourceFile) extends Prod
242180
// ignore transition from last wildcard parameter to body
243181
case _ =>
244182
assert(!lastSpan.exists || !p.span.exists || lastSpan.end <= p.span.start,
245-
s"""position error, child positions overlap or in wrong order
183+
i"""position error, child positions overlap or in wrong order
246184
|parent = $this
247185
|1st child = $lastPositioned
248186
|1st child span = $lastSpan

compiler/src/dotty/tools/dotc/ast/TreeInfo.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -498,11 +498,11 @@ trait TypedTreeInfo extends TreeInfo[Type] { self: Trees.Instance[Type] =>
498498
val tree1 = ConstFold(tree)
499499
tree1.tpe.widenTermRefExpr match {
500500
case ConstantType(value) =>
501-
if (isIdempotentExpr(tree1)) Literal(value)
501+
if (isIdempotentExpr(tree1)) Literal(value).withSpan(tree.span)
502502
else tree1 match {
503503
case Select(qual, _) if tree1.tpe.isInstanceOf[ConstantType] =>
504504
// it's a primitive unary operator; Simplify `pre.op` to `{ pre; v }` where `v` is the value of `pre.op`
505-
Block(qual :: Nil, Literal(value))
505+
Block(qual :: Nil, Literal(value)).withSpan(tree.span)
506506
case _ =>
507507
tree1
508508
}

compiler/src/dotty/tools/dotc/ast/Trees.scala

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -602,7 +602,6 @@ object Trees {
602602
case class Inlined[-T >: Untyped] private[ast] (call: tpd.Tree, bindings: List[MemberDef[T]], expansion: Tree[T])(implicit @transientParam src: SourceFile)
603603
extends Tree[T] {
604604
type ThisTree[-T >: Untyped] = Inlined[T]
605-
override def relevantElemCount = 1 // only consider call when computing span
606605
}
607606

608607
/** A type tree that represents an existing or inferred type */
@@ -776,7 +775,7 @@ object Trees {
776775
trait WithoutTypeOrPos[-T >: Untyped] extends Tree[T] {
777776
override def withTypeUnchecked(tpe: Type): ThisTree[Type] = this.asInstanceOf[ThisTree[Type]]
778777
override def span: Span = NoSpan
779-
override def setPos(span: Span, src: SourceFile): Unit = {}
778+
override def span_=(span: Span): Unit = {}
780779
}
781780

782781
/** Temporary class that results from translation of ModuleDefs

0 commit comments

Comments
 (0)