Skip to content

Commit 65afab0

Browse files
committed
Fix #6542: Pickle line sizes in TASTy
1 parent 80597a2 commit 65afab0

File tree

10 files changed

+152
-20
lines changed

10 files changed

+152
-20
lines changed

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

+11-5
Original file line numberDiff line numberDiff line change
@@ -17,17 +17,22 @@ object DottyUnpickler {
1717
/** Exception thrown if classfile is corrupted */
1818
class BadSignature(msg: String) extends RuntimeException(msg)
1919

20-
class TreeSectionUnpickler(posUnpickler: Option[PositionUnpickler], commentUnpickler: Option[CommentUnpickler])
20+
class TreeSectionUnpickler(posUnpickler: Option[PositionUnpickler], lineSizesUnpickler: Option[LineSizesUnpickler], commentUnpickler: Option[CommentUnpickler])
2121
extends SectionUnpickler[TreeUnpickler](TreePickler.sectionName) {
2222
def unpickle(reader: TastyReader, nameAtRef: NameTable): TreeUnpickler =
23-
new TreeUnpickler(reader, nameAtRef, posUnpickler, commentUnpickler)
23+
new TreeUnpickler(reader, nameAtRef, posUnpickler, lineSizesUnpickler, commentUnpickler)
2424
}
2525

2626
class PositionsSectionUnpickler extends SectionUnpickler[PositionUnpickler]("Positions") {
2727
def unpickle(reader: TastyReader, nameAtRef: NameTable): PositionUnpickler =
2828
new PositionUnpickler(reader, nameAtRef)
2929
}
3030

31+
class LineSizesSectionUnpickler extends SectionUnpickler[LineSizesUnpickler]("LineSizes") {
32+
def unpickle(reader: TastyReader, nameAtRef: NameTable): LineSizesUnpickler =
33+
new LineSizesUnpickler(reader)
34+
}
35+
3136
class CommentsSectionUnpickler extends SectionUnpickler[CommentUnpickler]("Comments") {
3237
def unpickle(reader: TastyReader, nameAtRef: NameTable): CommentUnpickler =
3338
new CommentUnpickler(reader)
@@ -44,17 +49,18 @@ class DottyUnpickler(bytes: Array[Byte], mode: UnpickleMode = UnpickleMode.TopLe
4449

4550
val unpickler: TastyUnpickler = new TastyUnpickler(bytes)
4651
private val posUnpicklerOpt = unpickler.unpickle(new PositionsSectionUnpickler)
52+
private val lineSizesUnpicklerOpt = unpickler.unpickle(new LineSizesSectionUnpickler)
4753
private val commentUnpicklerOpt = unpickler.unpickle(new CommentsSectionUnpickler)
48-
private val treeUnpickler = unpickler.unpickle(treeSectionUnpickler(posUnpicklerOpt, commentUnpicklerOpt)).get
54+
private val treeUnpickler = unpickler.unpickle(treeSectionUnpickler(posUnpicklerOpt, lineSizesUnpicklerOpt, commentUnpicklerOpt)).get
4955

5056
/** Enter all toplevel classes and objects into their scopes
5157
* @param roots a set of SymDenotations that should be overwritten by unpickling
5258
*/
5359
def enter(roots: Set[SymDenotation])(using Context): Unit =
5460
treeUnpickler.enter(roots)
5561

56-
protected def treeSectionUnpickler(posUnpicklerOpt: Option[PositionUnpickler], commentUnpicklerOpt: Option[CommentUnpickler]): TreeSectionUnpickler =
57-
new TreeSectionUnpickler(posUnpicklerOpt, commentUnpicklerOpt)
62+
protected def treeSectionUnpickler(posUnpicklerOpt: Option[PositionUnpickler], lineSizesUnpicklerOpt: Option[LineSizesUnpickler], commentUnpicklerOpt: Option[CommentUnpickler]): TreeSectionUnpickler =
63+
new TreeSectionUnpickler(posUnpicklerOpt, lineSizesUnpicklerOpt, commentUnpicklerOpt)
5864

5965
protected def computeRootTrees(using Context): List[Tree] = treeUnpickler.unpickle(mode)
6066

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package dotty.tools
2+
package dotc
3+
package core
4+
package tasty
5+
6+
import dotty.tools.tasty.TastyFormat.SOURCE
7+
import dotty.tools.tasty.TastyBuffer
8+
import TastyBuffer._
9+
10+
import ast._
11+
import ast.Trees._
12+
import ast.Trees.WithLazyField
13+
import util.{SourceFile, NoSource}
14+
import core._
15+
import Contexts._, Symbols._, Annotations._, Decorators._
16+
import collection.mutable
17+
import util.Spans._
18+
19+
class LineSizesPickler(pickler: TastyPickler) {
20+
21+
import ast.tpd._
22+
val buf: TastyBuffer = new TastyBuffer(5000)
23+
pickler.newSection("LineSizes", buf)
24+
25+
def pickleLineNumbers(source: SourceFile): Unit =
26+
val content = source.content()
27+
var lastIndex = content.indexOf('\n', 0)
28+
buf.writeInt(lastIndex) // size of first line
29+
while lastIndex != -1 do
30+
val nextIndex = content.indexOf('\n', lastIndex + 1)
31+
val end = if nextIndex != -1 then nextIndex else content.length
32+
buf.writeInt(end - lastIndex - 1) // size of the next line
33+
lastIndex = nextIndex
34+
35+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package dotty.tools
2+
package dotc
3+
package core
4+
package tasty
5+
6+
import dotty.tools.tasty.{TastyFormat, TastyBuffer, TastyReader}
7+
import TastyFormat.SOURCE
8+
import TastyBuffer.{Addr, NameRef}
9+
10+
import util.Spans._
11+
import collection.{mutable, Map}
12+
import Names.TermName
13+
14+
/** Unpickler for tree positions */
15+
class LineSizesUnpickler(reader: TastyReader) {
16+
import reader._
17+
18+
private var mySizes: Array[Int] = _
19+
private var isDefined = false
20+
21+
def ensureDefined(): Unit =
22+
if !isDefined then
23+
val sizeBuf = Array.newBuilder[Int]
24+
// Number of lines if all lines are 127 characters or less
25+
var lowSizeBound = endAddr.index - currentAddr.index
26+
sizeBuf.sizeHint(lowSizeBound)
27+
while !isAtEnd do sizeBuf += readInt()
28+
mySizes = sizeBuf.result()
29+
isDefined = true
30+
31+
private[tasty] def sizes: Array[Int] =
32+
ensureDefined()
33+
mySizes
34+
35+
private[tasty] def lineIndices: Array[Int] =
36+
val szs = sizes
37+
val indices = new Array[Int](sizes.length + 1)
38+
var i = 0
39+
val penultimate = szs.length - 1
40+
while i < penultimate do
41+
indices(i + 1) = indices(i) + szs(i) + 1 // `+1` for the '\n' at the end of the line
42+
i += 1
43+
indices(szs.length) = indices(penultimate) + szs(penultimate) // last line does not end with '\n'
44+
indices
45+
}

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

+20-1
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,11 @@ class TastyPrinter(bytes: Array[Byte]) {
5050
case _ =>
5151
}
5252
sb.append("\n\n")
53+
unpickle(new LineSizesSectionUnpickler) match {
54+
case Some(s) => sb.append(s)
55+
case _ =>
56+
}
57+
sb.append("\n\n")
5358
unpickle(new CommentSectionUnpickler) match {
5459
case Some(s) => sb.append(s)
5560
case _ =>
@@ -140,7 +145,7 @@ class TastyPrinter(bytes: Array[Byte]) {
140145
def unpickle(reader: TastyReader, tastyName: NameTable): String = {
141146
sb.append(s" ${reader.endAddr.index - reader.currentAddr.index}")
142147
val spans = new PositionUnpickler(reader, tastyName).spans
143-
sb.append(s" position bytes:\n")
148+
sb.append(" position bytes:\n")
144149
val sorted = spans.toSeq.sortBy(_._1.index)
145150
for ((addr, pos) <- sorted) {
146151
sb.append(treeStr("%10d".format(addr.index)))
@@ -150,6 +155,20 @@ class TastyPrinter(bytes: Array[Byte]) {
150155
}
151156
}
152157

158+
class LineSizesSectionUnpickler extends SectionUnpickler[String]("LineSizes") {
159+
160+
private val sb: StringBuilder = new StringBuilder
161+
162+
def unpickle(reader: TastyReader, tastyName: NameTable): String = {
163+
sb.append(" ").append(reader.endAddr.index - reader.currentAddr.index)
164+
sb.append(" line sizes bytes:\n")
165+
val lineSizes = new LineSizesUnpickler(reader)
166+
sb.append(" sizes: ")
167+
sb.append(lineSizes.sizes.mkString(", "))
168+
sb.result
169+
}
170+
}
171+
153172
class CommentSectionUnpickler extends SectionUnpickler[String]("Comments") {
154173

155174
private val sb: StringBuilder = new StringBuilder

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

+11-4
Original file line numberDiff line numberDiff line change
@@ -47,13 +47,15 @@ import scala.annotation.constructorOnly
4747
import scala.annotation.internal.sharable
4848

4949
/** Unpickler for typed trees
50-
* @param reader the reader from which to unpickle
51-
* @param posUnpicklerOpt the unpickler for positions, if it exists
52-
* @param commentUnpicklerOpt the unpickler for comments, if it exists
50+
* @param reader the reader from which to unpickle
51+
* @param posUnpicklerOpt the unpickler for positions, if it exists
52+
* @param lineSizesUnpicklerOpt the unpickler for line sizes, if it exists
53+
* @param commentUnpicklerOpt the unpickler for comments, if it exists
5354
*/
5455
class TreeUnpickler(reader: TastyReader,
5556
nameAtRef: NameTable,
5657
posUnpicklerOpt: Option[PositionUnpickler],
58+
lineSizesUnpicklerOpt: Option[LineSizesUnpickler],
5759
commentUnpicklerOpt: Option[CommentUnpickler]) {
5860
import TreeUnpickler._
5961
import tpd._
@@ -1363,8 +1365,13 @@ class TreeUnpickler(reader: TastyReader,
13631365
def sourceChangeContext(addr: Addr = currentAddr)(using Context): Context = {
13641366
val path = sourcePathAt(addr)
13651367
if (path.nonEmpty) {
1368+
val sourceFile = ctx.getSource(path)
1369+
lineSizesUnpicklerOpt match
1370+
case Some(lineSizesUnpickler) =>
1371+
sourceFile.setLineIndices(lineSizesUnpickler.lineIndices)
1372+
case _ =>
13661373
pickling.println(i"source change at $addr: $path")
1367-
ctx.withSource(ctx.getSource(path))
1374+
ctx.withSource(sourceFile)
13681375
}
13691376
else ctx
13701377
}

compiler/src/dotty/tools/dotc/reporting/MessageRendering.scala

+1-1
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,7 @@ trait MessageRendering {
147147
val sb = mutable.StringBuilder()
148148
val posString = posStr(pos, diagnosticLevel, msg)
149149
if (posString.nonEmpty) sb.append(posString).append(EOL)
150-
if (pos.exists && pos.source.file.exists) {
150+
if (pos.exists) {
151151
val pos1 = pos.nonInlined
152152
val (srcBefore, srcAfter, offset) = sourceLines(pos1, diagnosticLevel)
153153
val marker = columnMarker(pos1, offset, diagnosticLevel)

compiler/src/dotty/tools/dotc/transform/Pickler.scala

+2
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,8 @@ class Pickler extends Phase {
7474
if tree.span.exists then
7575
new PositionPickler(pickler, treePkl.buf.addrOfTree, treePkl.treeAnnots)
7676
.picklePositions(tree :: Nil, positionWarnings)
77+
new LineSizesPickler(pickler)
78+
.pickleLineNumbers(unit.source)
7779

7880
if !ctx.settings.YdropComments.value then
7981
new CommentPickler(pickler, treePkl.buf.addrOfTree, treePkl.docString)

compiler/src/dotty/tools/dotc/util/SourceFile.scala

+13-3
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,9 @@ class SourceFile(val file: AbstractFile, computeContent: => Array[Char]) extends
8282

8383
def apply(idx: Int): Char = content().apply(idx)
8484

85-
def length: Int = content().length
85+
def length: Int =
86+
if lineIndicesCache ne null then lineIndicesCache.last
87+
else content().length
8688

8789
/** true for all source files except `NoSource` */
8890
def exists: Boolean = true
@@ -105,7 +107,8 @@ class SourceFile(val file: AbstractFile, computeContent: => Array[Char]) extends
105107
def positionInUltimateSource(position: SourcePosition): SourcePosition =
106108
SourcePosition(underlying, position.span shift start)
107109

108-
private def calculateLineIndices(cs: Array[Char]) = {
110+
private def calculateLineIndicesFromContents() = {
111+
val cs = content()
109112
val buf = new ArrayBuffer[Int]
110113
buf += 0
111114
var i = 0
@@ -120,7 +123,14 @@ class SourceFile(val file: AbstractFile, computeContent: => Array[Char]) extends
120123
buf += cs.length // sentinel, so that findLine below works smoother
121124
buf.toArray
122125
}
123-
private lazy val lineIndices: Array[Int] = calculateLineIndices(content())
126+
127+
private var lineIndicesCache: Array[Int] = _
128+
private def lineIndices: Array[Int] =
129+
if lineIndicesCache eq null then
130+
lineIndicesCache = calculateLineIndicesFromContents()
131+
lineIndicesCache
132+
def setLineIndices(indices: Array[Int]): Unit =
133+
lineIndicesCache = indices
124134

125135
/** Map line to offset of first character in line */
126136
def lineToOffset(index: Int): Int = lineIndices(index)

compiler/src/dotty/tools/dotc/util/SourcePosition.scala

+6-6
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ extends SrcPos, interfaces.SourcePosition, Showable {
2525

2626
def point: Int = span.point
2727

28-
def line: Int = if (source.length != 0) source.offsetToLine(point) else -1
28+
def line: Int = source.offsetToLine(point)
2929

3030
/** Extracts the lines from the underlying source file as `Array[Char]`*/
3131
def linesSlice: Array[Char] =
@@ -45,16 +45,16 @@ extends SrcPos, interfaces.SourcePosition, Showable {
4545
def beforeAndAfterPoint: (List[Int], List[Int]) =
4646
lineOffsets.partition(_ <= point)
4747

48-
def column: Int = if (source.content().length != 0) source.column(point) else -1
48+
def column: Int = source.column(point)
4949

5050
def start: Int = span.start
51-
def startLine: Int = if (source.content().length != 0) source.offsetToLine(start) else -1
52-
def startColumn: Int = if (source.content().length != 0) source.column(start) else -1
51+
def startLine: Int = source.offsetToLine(start)
52+
def startColumn: Int = source.column(start)
5353
def startColumnPadding: String = source.startColumnPadding(start)
5454

5555
def end: Int = span.end
56-
def endLine: Int = if (source.content().length != 0) source.offsetToLine(end) else -1
57-
def endColumn: Int = if (source.content().length != 0) source.column(end) else -1
56+
def endLine: Int = source.offsetToLine(end)
57+
def endColumn: Int = source.column(end)
5858

5959
def withOuter(outer: SourcePosition): SourcePosition = SourcePosition(source, span, outer)
6060
def withSpan(range: Span) = SourcePosition(source, range, outer)

tasty/src/dotty/tools/tasty/TastyFormat.scala

+8
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ Macro-format:
5050
Note: Unqualified names in the name table are strings. The context decides whether a name is
5151
a type-name or a term-name. The same string can represent both.
5252
53+
5354
Standard-Section: "ASTs" TopLevelStat*
5455
5556
TopLevelStat = PACKAGE Length Path TopLevelStat* -- package path { topLevelStats }
@@ -226,6 +227,7 @@ Note: Tree tags are grouped into 5 categories that determine what follows, and t
226227
Category 4 (tags 110-127): tag Nat AST
227228
Category 5 (tags 128-255): tag Length <payload>
228229
230+
229231
Standard Section: "Positions" Assoc*
230232
231233
Assoc = Header offset_Delta? offset_Delta? point_Delta?
@@ -244,6 +246,12 @@ Standard Section: "Positions" Assoc*
244246
245247
All elements of a position section are serialized as Ints
246248
249+
250+
Standard Section: "LineSizes" LineSize*
251+
252+
LineSize = Int // Size the i-th line not counting the trailing `\n`
253+
254+
247255
Standard Section: "Comments" Comment*
248256
249257
Comment = Length Bytes LongInt // Raw comment's bytes encoded as UTF-8, followed by the comment's coordinates.

0 commit comments

Comments
 (0)