Skip to content

Commit 4d4da9c

Browse files
committed
Fix #6542: Pickle line sizes in TASTy
1 parent 31c9ebe commit 4d4da9c

File tree

10 files changed

+153
-21
lines changed

10 files changed

+153
-21
lines changed

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

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

21-
class TreeSectionUnpickler(posUnpickler: Option[PositionUnpickler], commentUnpickler: Option[CommentUnpickler])
21+
class TreeSectionUnpickler(posUnpickler: Option[PositionUnpickler], lineSizesUnpickler: Option[LineSizesUnpickler], commentUnpickler: Option[CommentUnpickler])
2222
extends SectionUnpickler[TreeUnpickler](ASTsSection) {
2323
def unpickle(reader: TastyReader, nameAtRef: NameTable): TreeUnpickler =
24-
new TreeUnpickler(reader, nameAtRef, posUnpickler, commentUnpickler)
24+
new TreeUnpickler(reader, nameAtRef, posUnpickler, lineSizesUnpickler, commentUnpickler)
2525
}
2626

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

32+
class LineSizesSectionUnpickler extends SectionUnpickler[LineSizesUnpickler]("LineSizes") {
33+
def unpickle(reader: TastyReader, nameAtRef: NameTable): LineSizesUnpickler =
34+
new LineSizesUnpickler(reader)
35+
}
36+
3237
class CommentsSectionUnpickler extends SectionUnpickler[CommentUnpickler](CommentsSection) {
3338
def unpickle(reader: TastyReader, nameAtRef: NameTable): CommentUnpickler =
3439
new CommentUnpickler(reader)
@@ -45,17 +50,18 @@ class DottyUnpickler(bytes: Array[Byte], mode: UnpickleMode = UnpickleMode.TopLe
4550

4651
val unpickler: TastyUnpickler = new TastyUnpickler(bytes)
4752
private val posUnpicklerOpt = unpickler.unpickle(new PositionsSectionUnpickler)
53+
private val lineSizesUnpicklerOpt = unpickler.unpickle(new LineSizesSectionUnpickler)
4854
private val commentUnpicklerOpt = unpickler.unpickle(new CommentsSectionUnpickler)
49-
private val treeUnpickler = unpickler.unpickle(treeSectionUnpickler(posUnpicklerOpt, commentUnpicklerOpt)).get
55+
private val treeUnpickler = unpickler.unpickle(treeSectionUnpickler(posUnpicklerOpt, lineSizesUnpicklerOpt, commentUnpicklerOpt)).get
5056

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

57-
protected def treeSectionUnpickler(posUnpicklerOpt: Option[PositionUnpickler], commentUnpicklerOpt: Option[CommentUnpickler]): TreeSectionUnpickler =
58-
new TreeSectionUnpickler(posUnpicklerOpt, commentUnpicklerOpt)
63+
protected def treeSectionUnpickler(posUnpicklerOpt: Option[PositionUnpickler], lineSizesUnpicklerOpt: Option[LineSizesUnpickler], commentUnpicklerOpt: Option[CommentUnpickler]): TreeSectionUnpickler =
64+
new TreeSectionUnpickler(posUnpicklerOpt, lineSizesUnpicklerOpt, commentUnpicklerOpt)
5965

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

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
@@ -51,6 +51,11 @@ class TastyPrinter(bytes: Array[Byte]) {
5151
case _ =>
5252
}
5353
sb.append("\n\n")
54+
unpickle(new LineSizesSectionUnpickler) match {
55+
case Some(s) => sb.append(s)
56+
case _ =>
57+
}
58+
sb.append("\n\n")
5459
unpickle(new CommentSectionUnpickler) match {
5560
case Some(s) => sb.append(s)
5661
case _ =>
@@ -141,7 +146,7 @@ class TastyPrinter(bytes: Array[Byte]) {
141146
def unpickle(reader: TastyReader, tastyName: NameTable): String = {
142147
sb.append(s" ${reader.endAddr.index - reader.currentAddr.index}")
143148
val spans = new PositionUnpickler(reader, tastyName).spans
144-
sb.append(s" position bytes:\n")
149+
sb.append(" position bytes:\n")
145150
val sorted = spans.toSeq.sortBy(_._1.index)
146151
for ((addr, pos) <- sorted) {
147152
sb.append(treeStr("%10d".format(addr.index)))
@@ -151,6 +156,20 @@ class TastyPrinter(bytes: Array[Byte]) {
151156
}
152157
}
153158

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

156175
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._
@@ -1364,8 +1366,13 @@ class TreeUnpickler(reader: TastyReader,
13641366
def sourceChangeContext(addr: Addr = currentAddr)(using Context): Context = {
13651367
val path = sourcePathAt(addr)
13661368
if (path.nonEmpty) {
1369+
val sourceFile = ctx.getSource(path)
1370+
lineSizesUnpicklerOpt match
1371+
case Some(lineSizesUnpickler) =>
1372+
sourceFile.setLineIndices(lineSizesUnpickler.lineIndices)
1373+
case _ =>
13671374
pickling.println(i"source change at $addr: $path")
1368-
ctx.withSource(ctx.getSource(path))
1375+
ctx.withSource(sourceFile)
13691376
}
13701377
else ctx
13711378
}

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

+9-1
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,7 +246,13 @@ Standard-Section: "Positions" Assoc*
244246
245247
All elements of a position section are serialized as Ints
246248
247-
Standard-Section: "Comments" Comment*
249+
250+
Standard-Section: "LineSizes" LineSize*
251+
252+
LineSize = Int // Size the i-th line not counting the trailing `\n`
253+
254+
255+
Standard Section: "Comments" Comment*
248256
249257
Comment = Length Bytes LongInt // Raw comment's bytes encoded as UTF-8, followed by the comment's coordinates.
250258

0 commit comments

Comments
 (0)