-
Notifications
You must be signed in to change notification settings - Fork 1k
/
Positioned.scala
249 lines (230 loc) · 8.22 KB
/
Positioned.scala
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
package dotty.tools
package dotc
package ast
import util.Spans._
import util.{SourceFile, NoSource, SourcePosition, SrcPos}
import core.Contexts._
import core.Decorators._
import core.Flags.{JavaDefined, Extension}
import core.StdNames.nme
import ast.Trees.mods
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 SrcPos, Product, Cloneable {
private var myUniqueId: Int = _
private 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(using Context): SourcePosition = source.atSpan(span)
/** This positioned item, widened to `SrcPos`. Used to make clear we only need the
* position, typically for error reporting.
*/
final def srcPos: SrcPos = this
/** 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
// assert(newpd.uniqueId != 2208, s"source = $this, ${this.uniqueId}, ${this.span}")
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)(using 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(tree.tparams)
check(vparams2)
check(vparams1)
check(rest)
case _ =>
check(tree.tparams)
check(tree.vparamss)
}
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(using Context): Unit =
debugId = ctx.settings.YdebugTreeWithId.value
}