Skip to content

Commit 7947349

Browse files
committed
Revise PositionPickler
The decision when to pickle a position is a time sink. Try to optimize this by not going through Positioned#{initialSpan,elemSource} functionality.
1 parent 036546f commit 7947349

File tree

3 files changed

+103
-23
lines changed

3 files changed

+103
-23
lines changed

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

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,8 @@ abstract class Positioned(implicit @transientParam src: SourceInfo) extends Prod
139139
}
140140
else outstanding = p :: outstanding
141141
case m: untpd.Modifiers =>
142-
elems = elems ::: m.mods.reverse ::: m.annotations.reverse
142+
if (m.mods.nonEmpty || m.annotations.nonEmpty)
143+
elems = elems ::: m.mods.reverse ::: m.annotations.reverse
143144
case xs: List[_] =>
144145
elems = elems ::: xs.reverse
145146
case _ =>
@@ -217,6 +218,11 @@ abstract class Positioned(implicit @transientParam src: SourceInfo) extends Prod
217218
span.toSynthetic
218219
}
219220

221+
/** How many elements to consider when computing the span.
222+
* Normally: all, overridden in Inlined.
223+
*/
224+
def relevantElemCount = productArity
225+
220226
private def sameSource(that: Positioned) = source `eq` that.source
221227

222228
def contains(that: Positioned): Boolean = {

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

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

608608
/** A type tree that represents an existing or inferred type */

compiler/src/dotty/tools/dotc/core/tasty/PositionPickler.scala

Lines changed: 95 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,19 @@ class PositionPickler(pickler: TastyPickler, addrOfTree: untpd.Tree => Option[Ad
2626
(addrDelta << 3) | (toInt(hasStartDelta) << 2) | (toInt(hasEndDelta) << 1) | toInt(hasPoint)
2727
}
2828

29+
/** The result type of `matchDegree`, which encodes the kind of match between
30+
* the position of a Positioned item and the inferred position computed by Positioned#setInitialPos.
31+
*/
32+
private type MatchDegree = Int
33+
private final val Unknown = 0 // Nothing known yet
34+
private final val StartMatch = 1 // We know that at least one element matches `start`
35+
private final val EndMatch = 2 // We know that at least one element matches `end`
36+
private final val SourceMatch = 4 // We know that the first element matches `source`
37+
private final val SpanMismatch = 8 // We know that the span is different from initialSpan
38+
private final val SourceMismatch = 16 // We know the source is different from the first element
39+
private final val SpanMatch = StartMatch | EndMatch
40+
private final val AllMatch = SpanMatch | SourceMatch
41+
2942
def picklePositions(roots: List[Tree])(implicit ctx: Context): Unit = {
3043
var lastIndex = 0
3144
var lastSpan = Span(0, 0)
@@ -66,30 +79,91 @@ class PositionPickler(pickler: TastyPickler, addrOfTree: untpd.Tree => Option[Ad
6679
case _ => false
6780
}
6881

82+
//val msgs = new mutable.ListBuffer[String]
83+
84+
/** The degree to which the source position of `x` matches the initial position computed
85+
* from its elements.
86+
* @pre The span of `x` exists
87+
*/
88+
def matchDegree(x: Positioned): MatchDegree = {
89+
val src = x.source
90+
val start = x.span.start
91+
val end = x.span.end
92+
93+
def checkElem(acc: MatchDegree, elem: Any): MatchDegree = elem match {
94+
case _: Trees.TypeTree[_] =>
95+
// TypeTrees contribute nothing since they are pickled as types
96+
acc
97+
case elem: Positioned =>
98+
val espan = elem.span
99+
if (!espan.exists) acc // elements without position don't contribute
100+
else {
101+
val esrc = elem.source
102+
if (!esrc.exists) acc // elements without source don't contribute
103+
else if (esrc `ne` src)
104+
if ((acc & SourceMatch) == 0) SourceMismatch // first element source is different -> no match
105+
else acc // subsequent elements with different source don't contribute
106+
else {
107+
var matches = acc | SourceMatch
108+
val estart = espan.start
109+
val eend = espan.end
110+
if (estart == start) matches |= StartMatch
111+
if (eend == end) matches |= EndMatch
112+
if (estart < start || eend > end) matches |= SpanMismatch
113+
matches
114+
}
115+
}
116+
case elem: List[_] =>
117+
checkElems(acc, elem)
118+
case m: untpd.Modifiers =>
119+
checkElems(checkElems(acc, m.mods), m.annotations)
120+
case _ =>
121+
acc
122+
}
123+
def checkElems(acc: MatchDegree, elems: List[Any]): MatchDegree = elems match {
124+
case elem :: elems1 => checkElems(checkElem(acc, elem), elems1)
125+
case nil => acc
126+
}
127+
128+
val limit = x.relevantElemCount
129+
var n = 0
130+
var acc: MatchDegree = Unknown
131+
while (n < limit) {
132+
acc = checkElem(acc, x.productElement(n))
133+
n += 1
134+
}
135+
acc
136+
}
137+
69138
def traverse(x: Any, current: SourceFile): Unit = x match {
70139
case x: untpd.Tree =>
71140
var sourceFile = current
72-
val span = if (x.isInstanceOf[untpd.MemberDef]) x.span else x.span.toSynthetic
73-
val sourceChange =
74-
x.source != x.elemsSource ||
75-
!x.elemsSource.exists && x.source != current
76-
if (span.exists && (
77-
span != x.initialSpan(ignoreTypeTrees = true).toSynthetic ||
78-
sourceChange ||
79-
alwaysNeedsPos(x))) {
80-
addrOfTree(x) match {
81-
case Some(addr) if !pickledIndices.contains(addr.index) || sourceChange =>
82-
// we currently do not share trees when unpickling, so if one path to a tree contains
83-
// a source change while another does not, we have to record the position of the tree twice
84-
// in order not to miss the source change. Test case is t3232a.scala.
85-
//println(i"pickling $x with $span at $addr")
86-
pickleDeltas(addr.index, span)
87-
if (x.source != current) {
88-
pickleSource(x.source)
89-
sourceFile = x.source
90-
}
91-
case _ =>
92-
//println(i"no address for $x")
141+
if (x.span.exists) {
142+
val mdegree = matchDegree(x)
143+
val sourceChange =
144+
mdegree == SourceMismatch || // source different from initial source, or
145+
(mdegree & SourceMatch) == 0 && // initial source unknown, and
146+
(x.source `ne` current) // source different from context
147+
val needPos =
148+
(mdegree & SpanMismatch) != 0 || // initial span exceeds current in some direction, or
149+
(mdegree & SpanMatch) != SpanMatch || // initial span smaller than current, or
150+
sourceChange || // source needs to be specified, or
151+
alwaysNeedsPos(x) // always needs position anyway
152+
if (needPos) {
153+
addrOfTree(x) match {
154+
case Some(addr) if !pickledIndices.contains(addr.index) || sourceChange =>
155+
// we currently do not share trees when unpickling, so if one path to a tree contains
156+
// a source change while another does not, we have to record the position of the tree twice
157+
// in order not to miss the source change. Test case is t3232a.scala.
158+
//println(i"pickling $x with $span at $addr")
159+
pickleDeltas(addr.index, x.span)
160+
if (x.source != current) {
161+
pickleSource(x.source)
162+
sourceFile = x.source
163+
}
164+
case _ =>
165+
//println(i"no address for $x")
166+
}
93167
}
94168
}
95169
//else if (x.span.exists) println(i"skipping $x")

0 commit comments

Comments
 (0)