Skip to content

Commit 319e865

Browse files
Add attributes section to TASTy and use it for Stdlib TASTy (#18599)
The `Attributes` section contains a list `Attribute` tags. At this point, the only Scala 2 TASTy is the one from the standard library. To know that a TASTy contains the definitions of the standard library, we add the `SCALA2STANDARDLIBRARYattr` attribute to the TASTy file. We mark all unpickled classes from those TASTy files with `Scala2x | Scala2Tasty`. Attributes will also be used to mark files that were compiled with explicit nulls using the `EXPLICITNULLSattr` attribute.
2 parents 8c64fed + 16966fd commit 319e865

File tree

12 files changed

+152
-17
lines changed

12 files changed

+152
-17
lines changed

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -308,8 +308,8 @@ object Flags {
308308
*/
309309
val (_, StableRealizable @ _, _) = newFlags(24, "<stable>")
310310

311-
/** A case parameter accessor */
312-
val (_, CaseAccessor @ _, _) = newFlags(25, "<caseaccessor>")
311+
/** A case parameter accessor / an unpickled Scala 2 TASTy (only for Scala 2 stdlib) */
312+
val (_, CaseAccessor @ _, Scala2Tasty @ _) = newFlags(25, "<caseaccessor>", "<scala-2-tasty>")
313313

314314
/** A Scala 2x super accessor / an unpickled Scala 2.x class */
315315
val (SuperParamAliasOrScala2x @ _, SuperParamAlias @ _, Scala2x @ _) = newFlags(26, "<super-param-alias>", "<scala-2.x>")
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package dotty.tools.dotc.core.tasty
2+
3+
import dotty.tools.dotc.ast.{tpd, untpd}
4+
5+
import dotty.tools.tasty.TastyBuffer
6+
import dotty.tools.tasty.TastyFormat, TastyFormat.AttributesSection
7+
8+
import java.nio.charset.StandardCharsets
9+
10+
object AttributePickler:
11+
12+
def pickleAttributes(
13+
attributes: Attributes,
14+
pickler: TastyPickler,
15+
buf: TastyBuffer
16+
): 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
23+
24+
end pickleAttributes
25+
26+
end AttributePickler
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package dotty.tools.dotc
2+
package core.tasty
3+
4+
import scala.language.unsafeNulls
5+
6+
import dotty.tools.tasty.{TastyFormat, TastyReader, TastyBuffer}
7+
8+
import java.nio.charset.StandardCharsets
9+
10+
class AttributeUnpickler(reader: TastyReader):
11+
import reader._
12+
13+
lazy val attributeTags: List[Int] =
14+
val listBuilder = List.newBuilder[Int]
15+
while !isAtEnd do listBuilder += readNat()
16+
listBuilder.result()
17+
18+
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+
)
31+
}
32+
33+
end AttributeUnpickler
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
package dotty.tools.dotc.core.tasty
2+
3+
class Attributes(
4+
val scala2StandardLibrary: Boolean,
5+
val explicitNulls: Boolean,
6+
)

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

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,17 +13,17 @@ import Names.SimpleName
1313
import TreeUnpickler.UnpickleMode
1414

1515
import dotty.tools.tasty.TastyReader
16-
import dotty.tools.tasty.TastyFormat.{ASTsSection, PositionsSection, CommentsSection}
16+
import dotty.tools.tasty.TastyFormat.{ASTsSection, PositionsSection, CommentsSection, AttributesSection}
1717

1818
object DottyUnpickler {
1919

2020
/** Exception thrown if classfile is corrupted */
2121
class BadSignature(msg: String) extends RuntimeException(msg)
2222

23-
class TreeSectionUnpickler(posUnpickler: Option[PositionUnpickler], commentUnpickler: Option[CommentUnpickler])
23+
class TreeSectionUnpickler(posUnpickler: Option[PositionUnpickler], commentUnpickler: Option[CommentUnpickler], attributeUnpickler: Option[AttributeUnpickler])
2424
extends SectionUnpickler[TreeUnpickler](ASTsSection) {
2525
def unpickle(reader: TastyReader, nameAtRef: NameTable): TreeUnpickler =
26-
new TreeUnpickler(reader, nameAtRef, posUnpickler, commentUnpickler)
26+
new TreeUnpickler(reader, nameAtRef, posUnpickler, commentUnpickler, attributeUnpickler)
2727
}
2828

2929
class PositionsSectionUnpickler extends SectionUnpickler[PositionUnpickler](PositionsSection) {
@@ -35,6 +35,10 @@ object DottyUnpickler {
3535
def unpickle(reader: TastyReader, nameAtRef: NameTable): CommentUnpickler =
3636
new CommentUnpickler(reader)
3737
}
38+
class AttributesSectionUnpickler extends SectionUnpickler[AttributeUnpickler](AttributesSection) {
39+
def unpickle(reader: TastyReader, nameAtRef: NameTable): AttributeUnpickler =
40+
new AttributeUnpickler(reader)
41+
}
3842
}
3943

4044
/** A class for unpickling Tasty trees and symbols.
@@ -48,16 +52,21 @@ class DottyUnpickler(bytes: Array[Byte], mode: UnpickleMode = UnpickleMode.TopLe
4852
val unpickler: TastyUnpickler = new TastyUnpickler(bytes)
4953
private val posUnpicklerOpt = unpickler.unpickle(new PositionsSectionUnpickler)
5054
private val commentUnpicklerOpt = unpickler.unpickle(new CommentsSectionUnpickler)
51-
private val treeUnpickler = unpickler.unpickle(treeSectionUnpickler(posUnpicklerOpt, commentUnpicklerOpt)).get
55+
private val attributeUnpicklerOpt = unpickler.unpickle(new AttributesSectionUnpickler)
56+
private val treeUnpickler = unpickler.unpickle(treeSectionUnpickler(posUnpicklerOpt, commentUnpicklerOpt, attributeUnpicklerOpt)).get
5257

5358
/** Enter all toplevel classes and objects into their scopes
5459
* @param roots a set of SymDenotations that should be overwritten by unpickling
5560
*/
5661
def enter(roots: Set[SymDenotation])(using Context): Unit =
5762
treeUnpickler.enter(roots)
5863

59-
protected def treeSectionUnpickler(posUnpicklerOpt: Option[PositionUnpickler], commentUnpicklerOpt: Option[CommentUnpickler]): TreeSectionUnpickler =
60-
new TreeSectionUnpickler(posUnpicklerOpt, commentUnpicklerOpt)
64+
protected def treeSectionUnpickler(
65+
posUnpicklerOpt: Option[PositionUnpickler],
66+
commentUnpicklerOpt: Option[CommentUnpickler],
67+
attributeUnpicklerOpt: Option[AttributeUnpickler]
68+
): TreeSectionUnpickler =
69+
new TreeSectionUnpickler(posUnpicklerOpt, commentUnpicklerOpt, attributeUnpicklerOpt)
6170

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

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,13 @@ class ScratchData:
1010
val pickledIndices = new mutable.BitSet
1111

1212
val commentBuffer = new TastyBuffer(5000)
13+
val attributeBuffer = new TastyBuffer(32)
1314

1415
def reset() =
1516
assert(delta ne delta1)
1617
assert(delta.length == delta1.length)
1718
positionBuffer.reset()
1819
pickledIndices.clear()
1920
commentBuffer.reset()
21+
attributeBuffer.reset()
2022

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

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import Contexts.*, Decorators.*
99
import Names.Name
1010
import TastyUnpickler.*
1111
import util.Spans.offsetToInt
12-
import dotty.tools.tasty.TastyFormat.{ASTsSection, PositionsSection, CommentsSection}
12+
import dotty.tools.tasty.TastyFormat.{ASTsSection, PositionsSection, CommentsSection, AttributesSection}
1313
import java.nio.file.{Files, Paths}
1414
import dotty.tools.io.{JarArchive, Path}
1515

@@ -84,14 +84,16 @@ class TastyPrinter(bytes: Array[Byte]) {
8484
case Some(s) => sb.append(s)
8585
case _ =>
8686
}
87-
sb.append("\n\n")
8887
unpickle(new PositionSectionUnpickler) match {
89-
case Some(s) => sb.append(s)
88+
case Some(s) => sb.append("\n\n").append(s)
9089
case _ =>
9190
}
92-
sb.append("\n\n")
9391
unpickle(new CommentSectionUnpickler) match {
94-
case Some(s) => sb.append(s)
92+
case Some(s) => sb.append("\n\n").append(s)
93+
case _ =>
94+
}
95+
unpickle(new AttributesSectionUnpickler) match {
96+
case Some(s) => sb.append("\n\n").append(s)
9597
case _ =>
9698
}
9799
sb.result
@@ -222,6 +224,20 @@ class TastyPrinter(bytes: Array[Byte]) {
222224
}
223225
}
224226

227+
class AttributesSectionUnpickler extends SectionUnpickler[String](AttributesSection) {
228+
import dotty.tools.tasty.TastyFormat.attributeTagToString
229+
private val sb: StringBuilder = new StringBuilder
230+
231+
def unpickle(reader: TastyReader, tastyName: NameTable): String = {
232+
sb.append(s" ${reader.endAddr.index - reader.currentAddr.index}")
233+
val attributeTags = new AttributeUnpickler(reader).attributeTags
234+
sb.append(s" attributes bytes:\n")
235+
for attributeTag <- attributeTags do
236+
sb.append(" ").append(attributeTagToString(attributeTag)).append("\n")
237+
sb.result
238+
}
239+
}
240+
225241
protected def nameStr(str: String): String = str
226242
protected def treeStr(str: String): String = str
227243
protected def lengthStr(str: String): String = str

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

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,11 +52,13 @@ import scala.compiletime.uninitialized
5252
* @param reader the reader from which to unpickle
5353
* @param posUnpicklerOpt the unpickler for positions, if it exists
5454
* @param commentUnpicklerOpt the unpickler for comments, if it exists
55+
* @param attributeUnpicklerOpt the unpickler for attributes, if it exists
5556
*/
5657
class TreeUnpickler(reader: TastyReader,
5758
nameAtRef: NameTable,
5859
posUnpicklerOpt: Option[PositionUnpickler],
59-
commentUnpicklerOpt: Option[CommentUnpickler]) {
60+
commentUnpicklerOpt: Option[CommentUnpickler],
61+
attributeUnpicklerOpt: Option[AttributeUnpickler]) {
6062
import TreeUnpickler.*
6163
import tpd.*
6264

@@ -97,6 +99,14 @@ class TreeUnpickler(reader: TastyReader,
9799
/** Was unpickled class compiled with capture checks? */
98100
private var withCaptureChecks: Boolean = false
99101

102+
private val unpicklingScala2Library =
103+
attributeUnpicklerOpt.exists(_.attributes.scala2StandardLibrary)
104+
105+
/** This dependency was compiled with explicit nulls enabled */
106+
// TODO Use this to tag the symbols of this dependency as compiled with explicit nulls (see use of unpicklingScala2Library).
107+
private val explicitNulls =
108+
attributeUnpicklerOpt.exists(_.attributes.explicitNulls)
109+
100110
private def registerSym(addr: Addr, sym: Symbol) =
101111
symAtAddr(addr) = sym
102112

@@ -601,7 +611,8 @@ class TreeUnpickler(reader: TastyReader,
601611
val rhsStart = currentAddr
602612
val rhsIsEmpty = nothingButMods(end)
603613
if (!rhsIsEmpty) skipTree()
604-
val (givenFlags, annotFns, privateWithin) = readModifiers(end)
614+
val (givenFlags0, annotFns, privateWithin) = readModifiers(end)
615+
val givenFlags = if isClass && unpicklingScala2Library then givenFlags0 | Scala2x | Scala2Tasty else givenFlags0
605616
pickling.println(i"creating symbol $name at $start with flags ${givenFlags.flagsString}, isAbsType = $isAbsType, $ttag")
606617
val flags = normalizeFlags(tag, givenFlags, name, isAbsType, rhsIsEmpty)
607618
def adjustIfModule(completer: LazyType) =

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ class ExtensionMethods extends MiniPhase with DenotTransformer with FullParamete
7777

7878
// Create extension methods, except if the class comes from Scala 2
7979
// because it adds extension methods before pickling.
80-
if (!(valueClass.is(Scala2x)))
80+
if !valueClass.is(Scala2x, butNot = Scala2Tasty) then
8181
for (decl <- valueClass.classInfo.decls)
8282
if isMethodWithExtension(decl) then
8383
enterInModuleClass(createExtensionMethod(decl, moduleClassSym.symbol))

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

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

111+
val attributes = Attributes(
112+
scala2StandardLibrary = ctx.settings.YcompileScala2Library.value,
113+
explicitNulls = ctx.settings.YexplicitNulls.value,
114+
)
115+
AttributePickler.pickleAttributes(attributes, pickler, scratch.attributeBuffer)
116+
111117
val pickled = pickler.assembleParts()
112118

113119
def rawBytes = // not needed right now, but useful to print raw format.

scala2-library-tasty-tests/src/Main.scala

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ object HelloWorld:
1717
testScala2ObjectParents()
1818
testScala2CaseClassUnderscoreMembers()
1919
testScalaNumberUnderlying()
20+
testArrayOps()
2021
scala.collection.mutable.UnrolledBufferTest.test()
2122
}
2223

@@ -68,3 +69,9 @@ object HelloWorld:
6869
val _: Object = MyNumber2(BigInt(1)).underlying
6970
val _: Object = (MyNumber2(BigInt(1)): ScalaNumber).underlying
7071
}
72+
73+
def testArrayOps() = {
74+
new collection.ArrayOps[String](Array[String]("foo")).exists(x => true)
75+
}
76+
77+
end HelloWorld

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

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -265,8 +265,15 @@ All elements of a position section are serialized as Ints
265265
266266
Standard Section: "Comments" Comment*
267267
```none
268-
Comment = Length Bytes LongInt // Raw comment's bytes encoded as UTF-8, followed by the comment's coordinates.
268+
Comment = UTF8 LongInt // Raw comment's bytes encoded as UTF-8, followed by the comment's coordinates.
269269
```
270+
271+
Standard Section: "Attributes" Attribute*
272+
```none
273+
Attribute = SCALA2STANDARDLIBRARYattr
274+
EXPLICITNULLSattr
275+
```
276+
270277
**************************************************************************************/
271278

272279
object TastyFormat {
@@ -361,6 +368,7 @@ object TastyFormat {
361368
final val ASTsSection = "ASTs"
362369
final val PositionsSection = "Positions"
363370
final val CommentsSection = "Comments"
371+
final val AttributesSection = "Attributes"
364372

365373
/** Tags used to serialize names, should update [[TastyFormat$.nameTagToString]] if a new constant is added */
366374
class NameTags {
@@ -597,6 +605,12 @@ object TastyFormat {
597605
final val firstNatASTTreeTag = IDENT
598606
final val firstLengthTreeTag = PACKAGE
599607

608+
609+
// Attributes tags
610+
611+
final val SCALA2STANDARDLIBRARYattr = 1
612+
final val EXPLICITNULLSattr = 2
613+
600614
/** Useful for debugging */
601615
def isLegalTag(tag: Int): Boolean =
602616
firstSimpleTreeTag <= tag && tag <= SPLITCLAUSE ||
@@ -812,6 +826,11 @@ object TastyFormat {
812826
case HOLE => "HOLE"
813827
}
814828

829+
def attributeTagToString(tag: Int): String = tag match {
830+
case SCALA2STANDARDLIBRARYattr => "SCALA2STANDARDLIBRARYattr"
831+
case EXPLICITNULLSattr => "EXPLICITNULLSattr"
832+
}
833+
815834
/** @return If non-negative, the number of leading references (represented as nats) of a length/trees entry.
816835
* If negative, minus the number of leading non-reference trees.
817836
*/

0 commit comments

Comments
 (0)