Skip to content

Commit 4859415

Browse files
authored
fix(#19806): wrong tasty of scala module class reference (#19827)
This commit makes the following diff to TASTy for i17255 files. The TASTy before this commit relied on the compiler (aka all TASTy clients) intrinsically knowing how to resolve Module$ when the definition is actually Module[ModuleClass]. ```sh scalac tests/run/i17255/J.java tests/run/i17255/Module.scala -Yprint-tasty -Yjava-tasty ``` ```diff 90: EMPTYCLAUSE 91: TERMREF 17 [Module] 93: SHAREDtype 12 95: ELIDED 96: SHAREDtype 91 98: STATIC 99: DEFDEF(12) 18 [module] 102: EMPTYCLAUSE - 103: SELECTtpt 19 [Module$] + 103: SELECTtpt 19 [Module[ModuleClass]] 105: SHAREDtype 3 107: ELIDED 108: TYPEREF 17 [Module] 110: SHAREDtype 3 112: STATIC ``` fixes #19806
2 parents 3680925 + 1d5db00 commit 4859415

File tree

12 files changed

+406
-65
lines changed

12 files changed

+406
-65
lines changed

compiler/src/dotty/tools/dotc/config/ScalaSettings.scala

+1
Original file line numberDiff line numberDiff line change
@@ -389,6 +389,7 @@ private sealed trait YSettings:
389389
val YshowPrintErrors: Setting[Boolean] = BooleanSetting("-Yshow-print-errors", "Don't suppress exceptions thrown during tree printing.")
390390
val YprintTasty: Setting[Boolean] = BooleanSetting("-Yprint-tasty", "Prints the generated TASTY to stdout.")
391391
val YtestPickler: Setting[Boolean] = BooleanSetting("-Ytest-pickler", "Self-test for pickling functionality; should be used with -Ystop-after:pickler.")
392+
val YtestPicklerCheck: Setting[Boolean] = BooleanSetting("-Ytest-pickler-check", "Self-test for pickling -print-tasty output; should be used with -Ytest-pickler.")
392393
val YcheckReentrant: Setting[Boolean] = BooleanSetting("-Ycheck-reentrant", "Check that compiled program does not contain vars that can be accessed from a global root.")
393394
val YdropComments: Setting[Boolean] = BooleanSetting("-Ydrop-docs", "Drop documentation when scanning source files.", aliases = List("-Ydrop-comments"))
394395
val YcookComments: Setting[Boolean] = BooleanSetting("-Ycook-docs", "Cook the documentation (type check `@usecase`, etc.)", aliases = List("-Ycook-comments"))

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

+1-4
Original file line numberDiff line numberDiff line change
@@ -64,10 +64,7 @@ object ContextOps:
6464
val directSearch =
6565
def asModule =
6666
if name.isTypeName && name.endsWith(StdNames.str.MODULE_SUFFIX) then
67-
pre.findMember(name.stripModuleClassSuffix.moduleClassName, pre, required, excluded) match
68-
case NoDenotation => NoDenotation
69-
case symDenot: SymDenotation =>
70-
symDenot.companionModule.denot
67+
pre.findMember(name.stripModuleClassSuffix.moduleClassName, pre, required, excluded)
7168
else NoDenotation
7269
pre.findMember(name, pre, required, excluded) match
7370
case NoDenotation => asModule

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

+4-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,10 @@ package dotty.tools.dotc
22
package core
33
package tasty
44

5-
class TastyAnsiiPrinter(bytes: Array[Byte]) extends TastyPrinter(bytes) {
5+
class TastyAnsiiPrinter(bytes: Array[Byte], testPickler: Boolean) extends TastyPrinter(bytes, testPickler) {
6+
7+
def this(bytes: Array[Byte]) = this(bytes, testPickler = false)
8+
69
override protected def nameStr(str: String): String = Console.MAGENTA + str + Console.RESET
710
override protected def treeStr(str: String): String = Console.YELLOW + str + Console.RESET
811
override protected def lengthStr(str: String): String = Console.CYAN + str + Console.RESET

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

+99-31
Original file line numberDiff line numberDiff line change
@@ -13,16 +13,21 @@ import dotty.tools.tasty.TastyFormat.{ASTsSection, PositionsSection, CommentsSec
1313
import java.nio.file.{Files, Paths}
1414
import dotty.tools.io.{JarArchive, Path}
1515
import dotty.tools.tasty.TastyFormat.header
16+
import scala.collection.immutable.BitSet
1617

1718
import scala.compiletime.uninitialized
1819
import dotty.tools.tasty.TastyBuffer.Addr
20+
import dotty.tools.dotc.core.Names.TermName
1921

2022
object TastyPrinter:
2123

2224
def showContents(bytes: Array[Byte], noColor: Boolean): String =
25+
showContents(bytes, noColor, testPickler = false)
26+
27+
def showContents(bytes: Array[Byte], noColor: Boolean, testPickler: Boolean = false): String =
2328
val printer =
24-
if noColor then new TastyPrinter(bytes)
25-
else new TastyAnsiiPrinter(bytes)
29+
if noColor then new TastyPrinter(bytes, testPickler)
30+
else new TastyAnsiiPrinter(bytes, testPickler)
2631
printer.showContents()
2732

2833
def main(args: Array[String]): Unit = {
@@ -62,7 +67,9 @@ object TastyPrinter:
6267
println(line)
6368
}
6469

65-
class TastyPrinter(bytes: Array[Byte]) {
70+
class TastyPrinter(bytes: Array[Byte], val testPickler: Boolean) {
71+
72+
def this(bytes: Array[Byte]) = this(bytes, testPickler = false)
6673

6774
class TastyPrinterUnpickler extends TastyUnpickler(bytes) {
6875
var namesStart: Addr = uninitialized
@@ -77,39 +84,53 @@ class TastyPrinter(bytes: Array[Byte]) {
7784
private val unpickler: TastyPrinterUnpickler = new TastyPrinterUnpickler
7885
import unpickler.{nameAtRef, unpickle}
7986

80-
private def nameToString(name: Name): String = name.debugString
81-
82-
private def nameRefToString(ref: NameRef): String = nameToString(nameAtRef(ref))
83-
8487
private def printHeader(sb: StringBuilder): Unit =
8588
val header = unpickler.header
8689
sb.append("Header:\n")
87-
sb.append(s" version: ${header.majorVersion}.${header.minorVersion}.${header.experimentalVersion}\n")
88-
sb.append(" tooling: ").append(header.toolingVersion).append("\n")
89-
sb.append(" UUID: ").append(header.uuid).append("\n")
90-
sb.append("\n")
90+
if testPickler then
91+
// these fields are not stable when the TASTy/compiler versions change, so not useful for testing
92+
sb.append(" version: <elided>\n")
93+
sb.append(" tooling: <elided>\n")
94+
sb.append(" UUID: <elided>\n")
95+
else
96+
sb.append(s" version: ${header.majorVersion}.${header.minorVersion}.${header.experimentalVersion}\n")
97+
sb.append(" tooling: ").append(header.toolingVersion).append("\n")
98+
sb.append(" UUID: ").append(header.uuid).append("\n")
99+
end if
91100

92-
private def printNames(sb: StringBuilder): Unit =
93-
sb.append(s"Names (${unpickler.namesEnd.index - unpickler.namesStart.index} bytes, starting from ${unpickler.namesStart.index}):\n")
101+
private def printNames(sb: StringBuilder)(using refs: NameRefs): Unit =
102+
sb.append(sectionHeader(
103+
name = "Names",
104+
count = (unpickler.namesEnd.index - unpickler.namesStart.index).toString,
105+
base = showBase(unpickler.namesStart.index),
106+
lineEnd = true
107+
))
94108
for ((name, idx) <- nameAtRef.contents.zipWithIndex) {
95109
val index = nameStr("%6d".format(idx))
96-
sb.append(index).append(": ").append(nameToString(name)).append("\n")
110+
sb.append(index).append(": ").append(refs.nameRefToString(NameRef(idx))).append("\n")
97111
}
98112

99113
def showContents(): String = {
100114
val sb: StringBuilder = new StringBuilder
115+
given NameRefs = unpickle0(new SourceFileUnpickler)(using NameRefs.empty).getOrElse(NameRefs.empty)
101116
printHeader(sb)
102117
printNames(sb)
103-
unpickle(new TreeSectionUnpickler(sb))
104-
unpickle(new PositionSectionUnpickler(sb))
105-
unpickle(new CommentSectionUnpickler(sb))
106-
unpickle(new AttributesSectionUnpickler(sb))
118+
unpickle0(new TreeSectionUnpickler(sb))
119+
unpickle0(new PositionSectionUnpickler(sb))
120+
unpickle0(new CommentSectionUnpickler(sb))
121+
unpickle0(new AttributesSectionUnpickler(sb))
107122
sb.result
108123
}
109124

110-
class TreeSectionUnpickler(sb: StringBuilder) extends SectionUnpickler[Unit](ASTsSection) {
125+
def unpickle0[R](sec: PrinterSectionUnpickler[R])(using NameRefs): Option[R] =
126+
unpickle(new SectionUnpickler[R](sec.name) {
127+
def unpickle(reader: TastyReader, nameAtRef: NameTable): R =
128+
sec.unpickle0(reader.subReader(reader.startAddr, reader.endAddr)) // fork so we can visit multiple times
129+
})
130+
131+
class TreeSectionUnpickler(sb: StringBuilder) extends PrinterSectionUnpickler[Unit](ASTsSection) {
111132
import dotty.tools.tasty.TastyFormat.*
112-
def unpickle(reader: TastyReader, tastyName: NameTable): Unit = {
133+
def unpickle0(reader: TastyReader)(using refs: NameRefs): Unit = {
113134
import reader.*
114135
var indent = 0
115136
def newLine() = {
@@ -119,7 +140,7 @@ class TastyPrinter(bytes: Array[Byte]) {
119140
def printNat() = sb.append(treeStr(" " + readNat()))
120141
def printName() = {
121142
val idx = readNat()
122-
sb.append(nameStr(" " + idx + " [" + nameRefToString(NameRef(idx)) + "]"))
143+
sb.append(nameStr(" " + idx + " [" + refs.nameRefToString(NameRef(idx)) + "]"))
123144
}
124145
def printTree(): Unit = {
125146
newLine()
@@ -170,19 +191,20 @@ class TastyPrinter(bytes: Array[Byte]) {
170191
}
171192
indent -= 2
172193
}
173-
sb.append(s"\n\nTrees (${endAddr.index - startAddr.index} bytes, starting from $base):")
194+
sb.append(sectionHeader("Trees", reader, lineEnd = false))
174195
while (!isAtEnd) {
175196
printTree()
176197
newLine()
177198
}
199+
sb.append("\n")
178200
}
179201
}
180202

181-
class PositionSectionUnpickler(sb: StringBuilder) extends SectionUnpickler[Unit](PositionsSection) {
182-
def unpickle(reader: TastyReader, tastyName: NameTable): Unit = {
203+
class PositionSectionUnpickler(sb: StringBuilder) extends PrinterSectionUnpickler[Unit](PositionsSection) {
204+
def unpickle0(reader: TastyReader)(using tastyName: NameRefs): Unit = {
183205
import reader.*
184206
val posUnpickler = new PositionUnpickler(reader, tastyName)
185-
sb.append(s"\n\nPositions (${reader.endAddr.index - reader.startAddr.index} bytes, starting from $base):\n")
207+
sb.append(sectionHeader("Positions", reader))
186208
val lineSizes = posUnpickler.lineSizes
187209
sb.append(s" lines: ${lineSizes.length}\n")
188210
sb.append(s" line sizes:\n")
@@ -210,12 +232,12 @@ class TastyPrinter(bytes: Array[Byte]) {
210232
}
211233
}
212234

213-
class CommentSectionUnpickler(sb: StringBuilder) extends SectionUnpickler[Unit](CommentsSection) {
214-
def unpickle(reader: TastyReader, tastyName: NameTable): Unit = {
235+
class CommentSectionUnpickler(sb: StringBuilder) extends PrinterSectionUnpickler[Unit](CommentsSection) {
236+
def unpickle0(reader: TastyReader)(using NameRefs): Unit = {
215237
import reader.*
216238
val comments = new CommentUnpickler(reader).comments
217239
if !comments.isEmpty then
218-
sb.append(s"\n\nComments (${reader.endAddr.index - reader.startAddr.index} bytes, starting from $base):\n")
240+
sb.append(sectionHeader("Comments", reader))
219241
val sorted = comments.toSeq.sortBy(_._1.index)
220242
for ((addr, cmt) <- sorted) {
221243
sb.append(treeStr("%6d".format(addr.index)))
@@ -224,12 +246,14 @@ class TastyPrinter(bytes: Array[Byte]) {
224246
}
225247
}
226248

227-
class AttributesSectionUnpickler(sb: StringBuilder) extends SectionUnpickler[Unit](AttributesSection) {
249+
class AttributesSectionUnpickler(sb: StringBuilder) extends PrinterSectionUnpickler[Unit](AttributesSection) {
228250
import dotty.tools.tasty.TastyFormat.*
229-
def unpickle(reader: TastyReader, tastyName: NameTable): Unit = {
251+
def unpickle0(reader: TastyReader)(using nameAtRef: NameRefs): Unit = {
230252
import reader.*
231-
sb.append(s"\n\nAttributes (${reader.endAddr.index - reader.startAddr.index} bytes, starting from $base):\n")
253+
sb.append(sectionHeader("Attributes", reader))
232254
while !isAtEnd do
255+
// TODO: Should we elide attributes under testPickler? (i.e.
256+
// if we add new attributes many check files will need to be updated)
233257
val tag = readByte()
234258
sb.append(" ").append(attributeTagToString(tag))
235259
if isBooleanAttrTag(tag) then ()
@@ -242,6 +266,50 @@ class TastyPrinter(bytes: Array[Byte]) {
242266
}
243267
}
244268

269+
class NameRefs(sourceFileRefs: Set[NameRef]) extends (NameRef => TermName):
270+
private val isSourceFile = sourceFileRefs.map(_.index).to(BitSet)
271+
272+
def nameRefToString(ref: NameRef): String = this(ref).debugString
273+
274+
def apply(ref: NameRef): TermName =
275+
if isSourceFile(ref.index) then NameRefs.elidedSourceFile
276+
else nameAtRef(ref)
277+
278+
object NameRefs:
279+
import dotty.tools.dotc.core.Names.termName
280+
281+
private val elidedSourceFile = termName("<elided source file name>")
282+
val empty = NameRefs(Set.empty)
283+
284+
285+
class SourceFileUnpickler extends PrinterSectionUnpickler[NameRefs](PositionsSection) {
286+
def unpickle0(reader: TastyReader)(using nameAtRef: NameRefs): NameRefs = {
287+
if !testPickler then return NameRefs.empty
288+
val buf = Set.newBuilder[NameRef]
289+
val posUnpickler = new PositionUnpickler(reader, nameAtRef)
290+
val sources = posUnpickler.sourceNameRefs
291+
for ((_, nameRef) <- sources.iterator) {
292+
buf += nameRef
293+
}
294+
NameRefs(buf.result)
295+
}
296+
}
297+
298+
private final def showBase(index: Int): String =
299+
if testPickler then "<elided base index>" else index.toString()
300+
301+
private final def sectionHeader(name: String, reader: TastyReader, lineEnd: Boolean = true): String =
302+
val count = reader.endAddr.index - reader.startAddr.index
303+
sectionHeader(name, count.toString, {showBase(reader.base)}, lineEnd)
304+
305+
private final def sectionHeader(name: String, count: String, base: String, lineEnd: Boolean): String =
306+
val suffix = if lineEnd then "\n" else ""
307+
s"\n$name ($count bytes, starting from $base):$suffix"
308+
309+
abstract class PrinterSectionUnpickler[T](val name: String) {
310+
def unpickle0(reader: TastyReader)(using refs: NameRefs): T
311+
}
312+
245313
protected def nameStr(str: String): String = str
246314
protected def treeStr(str: String): String = str
247315
protected def lengthStr(str: String): String = str

compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala

+4-1
Original file line numberDiff line numberDiff line change
@@ -1010,7 +1010,10 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) {
10101010
var modsText = modText(constr.mods, constr.symbol, "", isType = false)
10111011
if (!modsText.isEmpty) modsText = " " ~ modsText
10121012
if (constr.mods.hasAnnotations && !constr.mods.hasFlags) modsText = modsText ~~ " this"
1013-
withEnclosingDef(constr) { addParamssText(tparamsTxt ~~ modsText, constr.trailingParamss) }
1013+
val ctorParamss =
1014+
// for fake `(x$1: Unit): Foo` constructor, don't print the param (span is not reconstructed correctly)
1015+
if constr.symbol.isAllOf(JavaParsers.fakeFlags) then Nil else constr.trailingParamss
1016+
withEnclosingDef(constr) { addParamssText(tparamsTxt ~~ modsText, ctorParamss) }
10141017
}
10151018
val parentsText = Text(impl.parents.map(constrText), if (ofNew) keywordStr(" with ") else ", ")
10161019
val derivedText = Text(impl.derived.map(toText(_)), ", ")

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

+54-3
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ class Pickler extends Phase {
6969

7070
// Maps that keep a record if -Ytest-pickler is set.
7171
private val beforePickling = new mutable.HashMap[ClassSymbol, String]
72+
private val printedTasty = new mutable.HashMap[ClassSymbol, String]
7273
private val pickledBytes = new mutable.HashMap[ClassSymbol, (CompilationUnit, Array[Byte])]
7374

7475
/** Drop any elements of this list that are linked module classes of other elements in the list */
@@ -184,7 +185,10 @@ class Pickler extends Phase {
184185
else
185186
val pickled = computePickled()
186187
reportPositionWarnings()
187-
if ctx.settings.YtestPickler.value then pickledBytes(cls) = (unit, pickled)
188+
if ctx.settings.YtestPickler.value then
189+
pickledBytes(cls) = (unit, pickled)
190+
if ctx.settings.YtestPicklerCheck.value then
191+
printedTasty(cls) = TastyPrinter.showContents(pickled, noColor = true, testPickler = true)
188192
() => pickled
189193

190194
unit.pickled += (cls -> demandPickled)
@@ -251,15 +255,22 @@ class Pickler extends Phase {
251255
private def testUnpickler(using Context): Unit =
252256
pickling.println(i"testing unpickler at run ${ctx.runId}")
253257
ctx.initialize()
258+
val resolveCheck = ctx.settings.YtestPicklerCheck.value
254259
val unpicklers =
255260
for ((cls, (unit, bytes)) <- pickledBytes) yield {
256261
val unpickler = new DottyUnpickler(unit.source.file, bytes)
257262
unpickler.enter(roots = Set.empty)
258-
cls -> (unit, unpickler)
263+
val optCheck =
264+
if resolveCheck then
265+
val resolved = unit.source.file.resolveSibling(s"${cls.name.mangledString}.tastycheck")
266+
if resolved == null then None
267+
else Some(resolved)
268+
else None
269+
cls -> (unit, unpickler, optCheck)
259270
}
260271
pickling.println("************* entered toplevel ***********")
261272
val rootCtx = ctx
262-
for ((cls, (unit, unpickler)) <- unpicklers) do
273+
for ((cls, (unit, unpickler, optCheck)) <- unpicklers) do
263274
val testJava = unit.typedAsJava
264275
if testJava then
265276
if unpickler.unpickler.nameAtRef.contents.exists(_ == nme.FromJavaObject) then
@@ -268,6 +279,15 @@ class Pickler extends Phase {
268279
val freshUnit = CompilationUnit(rootCtx.compilationUnit.source)
269280
freshUnit.needsCaptureChecking = unit.needsCaptureChecking
270281
freshUnit.knowsPureFuns = unit.knowsPureFuns
282+
optCheck match
283+
case Some(check) =>
284+
import java.nio.charset.StandardCharsets.UTF_8
285+
val checkContents = String(check.toByteArray, UTF_8)
286+
inContext(rootCtx.fresh.setCompilationUnit(freshUnit)):
287+
testSamePrinted(printedTasty(cls), checkContents, cls, check)
288+
case None =>
289+
()
290+
271291
inContext(printerContext(testJava)(using rootCtx.fresh.setCompilationUnit(freshUnit))):
272292
testSame(i"$unpickled%\n%", beforePickling(cls), cls)
273293

@@ -283,4 +303,35 @@ class Pickler extends Phase {
283303
|
284304
| diff before-pickling.txt after-pickling.txt""")
285305
end testSame
306+
307+
private def testSamePrinted(printed: String, checkContents: String, cls: ClassSymbol, check: AbstractFile)(using Context): Unit = {
308+
for lines <- diff(printed, checkContents) do
309+
output("after-printing.txt", printed)
310+
report.error(em"""TASTy printer difference for $cls in ${cls.source}, did not match ${check},
311+
| output dumped in after-printing.txt, check diff with `git diff --no-index -- $check after-printing.txt`
312+
| actual output:
313+
|$lines%\n%""")
314+
}
315+
316+
/** Reuse diff logic from compiler/test/dotty/tools/vulpix/FileDiff.scala */
317+
private def diff(actual: String, expect: String): Option[Seq[String]] =
318+
import scala.util.Using
319+
import scala.io.Source
320+
val actualLines = Using(Source.fromString(actual))(_.getLines().toList).get
321+
val expectLines = Using(Source.fromString(expect))(_.getLines().toList).get
322+
Option.when(!matches(actualLines, expectLines))(actualLines)
323+
324+
private def matches(actual: String, expect: String): Boolean = {
325+
import java.io.File
326+
val actual1 = actual.stripLineEnd
327+
val expect1 = expect.stripLineEnd
328+
329+
// handle check file path mismatch on windows
330+
actual1 == expect1 || File.separatorChar == '\\' && actual1.replace('\\', '/') == expect1
331+
}
332+
333+
private def matches(actual: Seq[String], expect: Seq[String]): Boolean = {
334+
actual.length == expect.length
335+
&& actual.lazyZip(expect).forall(matches)
336+
}
286337
}

0 commit comments

Comments
 (0)