Skip to content

Commit b256b21

Browse files
committed
Fix #6542: Pickle line sizes in TASTy
1 parent 3c02d0c commit b256b21

File tree

9 files changed

+156
-20
lines changed

9 files changed

+156
-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,41 @@
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+
28+
val numLines = content.count(_ == '\n')
29+
buf.writeInt(numLines + 1)
30+
31+
var lastIndex = content.indexOf('\n', 0)
32+
buf.writeInt(lastIndex) // size of first line
33+
while lastIndex != -1 do
34+
val nextIndex = content.indexOf('\n', lastIndex + 1)
35+
if nextIndex != -1 then
36+
buf.writeInt(nextIndex - lastIndex - 1) // size of the next line
37+
else
38+
buf.writeInt(content.length - 1 - lastIndex) // size of the last line
39+
lastIndex = nextIndex
40+
}
41+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
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 myLines: Int = _
19+
private var mySizes: Array[Int] = _
20+
private var isDefined = false
21+
22+
def ensureDefined(): Unit = {
23+
if (!isDefined) {
24+
myLines = readInt()
25+
mySizes = new Array(myLines)
26+
for i <- 0 until myLines do
27+
mySizes(i) = readInt()
28+
isDefined = true
29+
}
30+
}
31+
32+
private[tasty] def lines: Int =
33+
ensureDefined()
34+
myLines
35+
36+
private[tasty] def sizes: Array[Int] =
37+
ensureDefined()
38+
mySizes
39+
40+
private[tasty] def lineIndices: Array[Int] =
41+
val szs = sizes
42+
val indices = new Array[Int](sizes.length + 1)
43+
var i = 0
44+
val penultimate = szs.length - 1
45+
while i < penultimate do
46+
indices(i + 1) = indices(i) + szs(i) + 1 // `+1` for the '\n' at the end of the line
47+
i += 1
48+
indices(szs.length) = indices(penultimate) + szs(penultimate) // last line does not end with '\n'
49+
indices
50+
}

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

+21-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,21 @@ 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(" lines: ").append(lineSizes.lines).append("\n")
167+
sb.append(" sizes: ")
168+
sb.append(lineSizes.sizes.mkString(", "))
169+
sb.result
170+
}
171+
}
172+
153173
class CommentSectionUnpickler extends SectionUnpickler[String]("Comments") {
154174

155175
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._
@@ -1361,8 +1363,13 @@ class TreeUnpickler(reader: TastyReader,
13611363
def sourceChangeContext(addr: Addr = currentAddr)(using Context): Context = {
13621364
val path = sourcePathAt(addr)
13631365
if (path.nonEmpty) {
1366+
val sourceFile = ctx.getSource(path)
1367+
lineSizesUnpicklerOpt match
1368+
case Some(lineSizesUnpickler) =>
1369+
sourceFile.setLineIndices(lineSizesUnpickler.lineIndices)
1370+
case _ =>
13641371
pickling.println(i"source change at $addr: $path")
1365-
ctx.withSource(ctx.getSource(path))
1372+
ctx.withSource(sourceFile)
13661373
}
13671374
else ctx
13681375
}

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)

0 commit comments

Comments
 (0)