Skip to content

Commit 3dbfd7d

Browse files
committed
Refactor TASTy Attributes (#19091)
We generalize the internal encoding of `Attributes` to be a list of tags. Then we add add helper methods to have simpler ways to interact with this abstraction using booleans. This implies that the pickling/unpickling can be agnostic of the semantics of each tag. Therefore reducing the number of places that need to be updated when we add a new tag. Useful for #19074, #19033, and #18948.
2 parents b730be1 + 540f20a commit 3dbfd7d

14 files changed

+134
-62
lines changed

compiler/src/dotty/tools/dotc/core/Symbols.scala

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -287,6 +287,22 @@ object Symbols extends SymUtils {
287287
if compUnitInfo == null then None
288288
else compUnitInfo.tastyInfo
289289

290+
/** The source file from which the symbol was compiled, None if not applicable. */
291+
def sourceFile(using Context): Option[String] =
292+
def sourceFromSourceFileAnnot = atPhaseNoLater(flattenPhase) {
293+
// Annotation present in sources compiled with 3.0-3.3
294+
denot.topLevelClass.unforcedAnnotation(defn.SourceFileAnnot) match
295+
case Some(sourceAnnot) => sourceAnnot.argumentConstant(0) match
296+
case Some(Constant(path: String)) => Some(path)
297+
case none => None
298+
case none => None
299+
}
300+
val compUnitInfo = compilationUnitInfo
301+
if compUnitInfo == null then sourceFromSourceFileAnnot
302+
else compUnitInfo.tastyInfo match
303+
case Some(tastyInfo) => tastyInfo.attributes.sourceFile.orElse(sourceFromSourceFileAnnot)
304+
case _ => sourceFromSourceFileAnnot
305+
290306
/** The class file from which this class was generated, null if not applicable. */
291307
final def binaryFile(using Context): AbstractFile | Null = {
292308
val file = associatedFile
@@ -487,13 +503,7 @@ object Symbols extends SymUtils {
487503
else
488504
mySource = defn.patchSource(this)
489505
if !mySource.exists then
490-
mySource = atPhaseNoLater(flattenPhase) {
491-
denot.topLevelClass.unforcedAnnotation(defn.SourceFileAnnot) match
492-
case Some(sourceAnnot) => sourceAnnot.argumentConstant(0) match
493-
case Some(Constant(path: String)) => ctx.getSource(path)
494-
case none => NoSource
495-
case none => NoSource
496-
}
506+
mySource = sourceFile.map(ctx.getSource).getOrElse(NoSource)
497507
mySource
498508
}
499509

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

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,21 +5,21 @@ import dotty.tools.dotc.ast.{tpd, untpd}
55
import dotty.tools.tasty.TastyBuffer
66
import dotty.tools.tasty.TastyFormat, TastyFormat.AttributesSection
77

8-
import java.nio.charset.StandardCharsets
9-
108
object AttributePickler:
119

1210
def pickleAttributes(
1311
attributes: Attributes,
1412
pickler: TastyPickler,
1513
buf: TastyBuffer
1614
): Unit =
17-
if attributes.scala2StandardLibrary || attributes.explicitNulls then // or any other attribute is set
18-
pickler.newSection(AttributesSection, buf)
19-
// Pickle attributes
20-
if attributes.scala2StandardLibrary then buf.writeNat(TastyFormat.SCALA2STANDARDLIBRARYattr)
21-
if attributes.explicitNulls then buf.writeNat(TastyFormat.EXPLICITNULLSattr)
22-
end if
15+
pickler.newSection(AttributesSection, buf)
16+
17+
for tag <- attributes.booleanTags do
18+
buf.writeByte(tag)
19+
20+
for (tag, value) <- attributes.stringTagValues do
21+
buf.writeByte(tag)
22+
buf.writeUtf8(value)
2323

2424
end pickleAttributes
2525

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

Lines changed: 11 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -5,29 +5,21 @@ import scala.language.unsafeNulls
55

66
import dotty.tools.tasty.{TastyFormat, TastyReader, TastyBuffer}
77

8-
import java.nio.charset.StandardCharsets
9-
108
class AttributeUnpickler(reader: TastyReader):
119
import reader._
1210

13-
lazy val attributeTags: List[Int] =
14-
val listBuilder = List.newBuilder[Int]
15-
while !isAtEnd do listBuilder += readNat()
16-
listBuilder.result()
17-
1811
lazy val attributes: Attributes = {
19-
var scala2StandardLibrary = false
20-
var explicitNulls = false
21-
for attributeTag <- attributeTags do
22-
attributeTag match
23-
case TastyFormat.SCALA2STANDARDLIBRARYattr => scala2StandardLibrary = true
24-
case TastyFormat.EXPLICITNULLSattr => explicitNulls = true
25-
case attribute =>
26-
assert(false, "Unexpected attribute value: " + attribute)
27-
Attributes(
28-
scala2StandardLibrary,
29-
explicitNulls,
30-
)
12+
val booleanTags = List.newBuilder[Int]
13+
val stringTagValue = List.newBuilder[(Int, String)]
14+
15+
while !isAtEnd do
16+
val tag = readByte()
17+
if tag < TastyFormat.firstStringAttrTag then
18+
booleanTags += tag
19+
else
20+
stringTagValue += ((tag, readUtf8()))
21+
22+
new Attributes(booleanTags.result(), stringTagValue.result())
3123
}
3224

3325
end AttributeUnpickler
Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,32 @@
11
package dotty.tools.dotc.core.tasty
22

3+
import dotty.tools.tasty.TastyFormat
4+
35
class Attributes(
4-
val scala2StandardLibrary: Boolean,
5-
val explicitNulls: Boolean,
6-
)
6+
val booleanTags: List[Int],
7+
val stringTagValues: List[(Int, String)],
8+
) {
9+
def scala2StandardLibrary: Boolean =
10+
booleanTags.contains(TastyFormat.SCALA2STANDARDLIBRARYattr)
11+
def explicitNulls: Boolean =
12+
booleanTags.contains(TastyFormat.EXPLICITNULLSattr)
13+
def sourceFile: Option[String] =
14+
stringTagValues.find(_._1 == TastyFormat.SOURCEFILEattr).map(_._2)
15+
}
16+
17+
object Attributes:
18+
def apply(
19+
sourceFile: String,
20+
scala2StandardLibrary: Boolean,
21+
explicitNulls: Boolean,
22+
): Attributes =
23+
val booleanTags = List.newBuilder[Int]
24+
if scala2StandardLibrary then booleanTags += TastyFormat.SCALA2STANDARDLIBRARYattr
25+
if explicitNulls then booleanTags += TastyFormat.EXPLICITNULLSattr
26+
27+
val stringTagValues = List(
28+
(TastyFormat.SOURCEFILEattr, sourceFile)
29+
)
30+
31+
new Attributes(booleanTags.result(), stringTagValues)
32+
end apply

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

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,8 @@ object CommentPickler:
2222

2323
def pickleComment(addr: Addr, comment: Comment): Unit =
2424
if addr != NoAddr then
25-
val bytes = comment.raw.getBytes(StandardCharsets.UTF_8).nn
26-
val length = bytes.length
2725
buf.writeAddr(addr)
28-
buf.writeNat(length)
29-
buf.writeBytes(bytes, length)
26+
buf.writeUtf8(comment.raw)
3027
buf.writeLongInt(comment.span.coords)
3128

3229
def traverse(x: Any): Unit = x match

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

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,9 @@ class CommentUnpickler(reader: TastyReader) {
1919
val comments = new HashMap[Addr, Comment]
2020
while (!isAtEnd) {
2121
val addr = readAddr()
22-
val length = readNat()
23-
if (length > 0) {
24-
val bytes = readBytes(length)
22+
val rawComment = readUtf8()
23+
if (rawComment != "") {
2524
val position = new Span(readLongInt())
26-
val rawComment = new String(bytes, StandardCharsets.UTF_8)
2725
comments(addr) = Comment(position, rawComment)
2826
}
2927
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ class DottyUnpickler(bytes: Array[Byte], mode: UnpickleMode = UnpickleMode.TopLe
5757
private val treeUnpickler = unpickler.unpickle(treeSectionUnpickler(posUnpicklerOpt, commentUnpicklerOpt, attributeUnpicklerOpt)).get
5858

5959
def tastyAttributes: Attributes =
60-
attributeUnpicklerOpt.map(_.attributes).getOrElse(Attributes(false, false))
60+
attributeUnpicklerOpt.map(_.attributes).getOrElse(new Attributes(booleanTags = Nil, stringTagValues = Nil))
6161

6262
/** Enter all toplevel classes and objects into their scopes
6363
* @param roots a set of SymDenotations that should be overwritten by unpickling

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

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import util.Spans.offsetToInt
1212
import dotty.tools.tasty.TastyFormat.{ASTsSection, PositionsSection, CommentsSection, AttributesSection}
1313
import java.nio.file.{Files, Paths}
1414
import dotty.tools.io.{JarArchive, Path}
15+
import java.nio.charset.StandardCharsets
1516

1617
object TastyPrinter:
1718

@@ -225,15 +226,22 @@ class TastyPrinter(bytes: Array[Byte]) {
225226
}
226227

227228
class AttributesSectionUnpickler extends SectionUnpickler[String](AttributesSection) {
228-
import dotty.tools.tasty.TastyFormat.attributeTagToString
229+
import dotty.tools.tasty.TastyFormat.*
230+
229231
private val sb: StringBuilder = new StringBuilder
230232

231233
def unpickle(reader: TastyReader, tastyName: NameTable): String = {
234+
import reader.*
232235
sb.append(s" ${reader.endAddr.index - reader.currentAddr.index}")
233-
val attributeTags = new AttributeUnpickler(reader).attributeTags
236+
val attributes = new AttributeUnpickler(reader).attributes
234237
sb.append(s" attributes bytes:\n")
235-
for attributeTag <- attributeTags do
236-
sb.append(" ").append(attributeTagToString(attributeTag)).append("\n")
238+
239+
for tag <- attributes.booleanTags do
240+
sb.append(" ").append(attributeTagToString(tag)).append("\n")
241+
for (tag, value) <- attributes.stringTagValues do
242+
sb.append(" ").append(attributeTagToString(tag))
243+
.append(" ").append(value).append("\n")
244+
237245
sb.result
238246
}
239247
}

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,10 @@ class TreeUnpickler(reader: TastyReader,
106106
private val explicitNulls =
107107
attributeUnpicklerOpt.exists(_.attributes.explicitNulls)
108108

109+
/** Source file in the TASTy attributes */
110+
private val sourceFile: Option[String] =
111+
attributeUnpicklerOpt.flatMap(_.attributes.sourceFile)
112+
109113
private def registerSym(addr: Addr, sym: Symbol) =
110114
symAtAddr(addr) = sym
111115

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,11 @@ class Pickler extends Phase {
108108
pickler, treePkl.buf.addrOfTree, treePkl.docString, tree,
109109
scratch.commentBuffer)
110110

111+
val sourceRelativePath =
112+
val reference = ctx.settings.sourceroot.value
113+
util.SourceFile.relativePath(unit.source, reference)
111114
val attributes = Attributes(
115+
sourceFile = sourceRelativePath,
112116
scala2StandardLibrary = ctx.settings.YcompileScala2Library.value,
113117
explicitNulls = ctx.settings.YexplicitNulls.value,
114118
)

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -419,6 +419,7 @@ class PostTyper extends MacroTransform with InfoTransformer { thisPhase =>
419419
em"The type of a class parent cannot refer to constructor parameters, but ${parent.tpe} refers to ${illegalRefs.map(_.name.show).mkString(",")}", parent.srcPos)
420420
// Add SourceFile annotation to top-level classes
421421
if sym.owner.is(Package) then
422+
// TODO do not add SourceFile annotation
422423
if ctx.compilationUnit.source.exists && sym != defn.SourceFileAnnot then
423424
val reference = ctx.settings.sourceroot.value
424425
val relativePath = util.SourceFile.relativePath(ctx.compilationUnit.source, reference)

tasty/src/dotty/tools/tasty/TastyBuffer.scala

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package dotty.tools.tasty
22

33
import util.Util.dble
4+
import java.nio.charset.StandardCharsets
45

56
object TastyBuffer {
67

@@ -115,6 +116,14 @@ class TastyBuffer(initialSize: Int) {
115116
writeBytes(bytes, 8)
116117
}
117118

119+
/** Write a UTF8 string encoded as `Length UTF8-CodePoint*` */
120+
def writeUtf8(x: String): Unit = {
121+
val bytes = x.getBytes(StandardCharsets.UTF_8)
122+
val length = bytes.length
123+
writeNat(length)
124+
writeBytes(bytes, length)
125+
}
126+
118127
// -- Address handling --------------------------------------------
119128

120129
/** Write natural number `x` right-adjusted in a field of `width` bytes

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

Lines changed: 27 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -234,11 +234,11 @@ Note: The signature of a SELECTin or TERMREFin node is the signature of the sele
234234
235235
Note: Tree tags are grouped into 5 categories that determine what follows, and thus allow to compute the size of the tagged tree in a generic way.
236236
```none
237-
Category 1 (tags 1-59) : tag
238-
Category 2 (tags 60-89) : tag Nat
239-
Category 3 (tags 90-109) : tag AST
240-
Category 4 (tags 110-127): tag Nat AST
241-
Category 5 (tags 128-255): tag Length <payload>
237+
Tree Category 1 (tags 1-59) : tag
238+
Tree Category 2 (tags 60-89) : tag Nat
239+
Tree Category 3 (tags 90-109) : tag AST
240+
Tree Category 4 (tags 110-127): tag Nat AST
241+
Tree Category 5 (tags 128-255): tag Length <payload>
242242
```
243243
244244
Standard-Section: "Positions" LinesSizes Assoc*
@@ -272,6 +272,13 @@ Standard Section: "Attributes" Attribute*
272272
```none
273273
Attribute = SCALA2STANDARDLIBRARYattr
274274
EXPLICITNULLSattr
275+
SOURCEFILEattr UTF8
276+
```
277+
278+
Note: Attribute tags are grouped into 2 categories that determine what follows, and thus allow to compute the size of the tagged tree in a generic way.
279+
```none
280+
Attribute Category 1 (tags 1-127) : tag
281+
Attribute Category 2 (tags 128-255): tag UTF8
275282
```
276283
277284
**************************************************************************************/
@@ -436,9 +443,8 @@ object TastyFormat {
436443
final val SOURCE = 4
437444

438445
// AST tags
439-
// Cat. 1: tag
446+
// Tree Cat. 1: tag
440447

441-
final val firstSimpleTreeTag = UNITconst
442448
// final val ??? = 1
443449
final val UNITconst = 2
444450
final val FALSEconst = 3
@@ -486,7 +492,7 @@ object TastyFormat {
486492
final val EMPTYCLAUSE = 45
487493
final val SPLITCLAUSE = 46
488494

489-
// Cat. 2: tag Nat
495+
// Tree Cat. 2: tag Nat
490496

491497
final val SHAREDterm = 60
492498
final val SHAREDtype = 61
@@ -506,7 +512,7 @@ object TastyFormat {
506512
final val IMPORTED = 75
507513
final val RENAMED = 76
508514

509-
// Cat. 3: tag AST
515+
// Tree Cat. 3: tag AST
510516

511517
final val THIS = 90
512518
final val QUALTHIS = 91
@@ -524,7 +530,7 @@ object TastyFormat {
524530
final val EXPLICITtpt = 103
525531

526532

527-
// Cat. 4: tag Nat AST
533+
// Tree Cat. 4: tag Nat AST
528534

529535
final val IDENT = 110
530536
final val IDENTtpt = 111
@@ -537,7 +543,7 @@ object TastyFormat {
537543
final val SELFDEF = 118
538544
final val NAMEDARG = 119
539545

540-
// Cat. 5: tag Length ...
546+
// Tree Cat. 5: tag Length ...
541547

542548
final val PACKAGE = 128
543549
final val VALDEF = 129
@@ -600,17 +606,25 @@ object TastyFormat {
600606

601607
final val HOLE = 255
602608

609+
final val firstSimpleTreeTag = UNITconst
603610
final val firstNatTreeTag = SHAREDterm
604611
final val firstASTTreeTag = THIS
605612
final val firstNatASTTreeTag = IDENT
606613
final val firstLengthTreeTag = PACKAGE
607614

608615

609-
// Attributes tags
616+
// Attributes tags
610617

618+
// Attr Cat. 1: tag
611619
final val SCALA2STANDARDLIBRARYattr = 1
612620
final val EXPLICITNULLSattr = 2
613621

622+
// Attr Cat. 2: tag UTF8
623+
final val SOURCEFILEattr = 128
624+
625+
final val firstBooleanAttrTag = SCALA2STANDARDLIBRARYattr
626+
final val firstStringAttrTag = SOURCEFILEattr
627+
614628
/** Useful for debugging */
615629
def isLegalTag(tag: Int): Boolean =
616630
firstSimpleTreeTag <= tag && tag <= SPLITCLAUSE ||
@@ -829,6 +843,7 @@ object TastyFormat {
829843
def attributeTagToString(tag: Int): String = tag match {
830844
case SCALA2STANDARDLIBRARYattr => "SCALA2STANDARDLIBRARYattr"
831845
case EXPLICITNULLSattr => "EXPLICITNULLSattr"
846+
case SOURCEFILEattr => "SOURCEFILEattr"
832847
}
833848

834849
/** @return If non-negative, the number of leading references (represented as nats) of a length/trees entry.

0 commit comments

Comments
 (0)