Skip to content

Commit 31b7106

Browse files
retronymadriaanm
authored andcommitted
Preserve order of decls through pickle/unpickle (scala#6285)
Preserve order of decls through pickle/unpickle The pickle format does not explicitly encode the order of decls. Instead, symbols are entered into an index in the order that they are found by the pickler, either as a definition or as a reference. During unpickling, symbols are read and entered into the owner's decls in that order. This is a cause of unstable compiler output: a class that mixes in members from some trait will have a different order if it is compiled jointly with / separately from that trait. This commit modifies the pickler with an initial pass that reserves index entries for all declarations in the declaration order. The pickle format and the unpickler are unchanged. Also tighten up the definition of "companion"-s so that we no longer add a type alias in a package object into the pickle of a same-named module. Finally, scalap should not print class type param annots in decls The annotation removed in this diff was actually from `R`!
1 parent 5c5f7cf commit 31b7106

File tree

5 files changed

+68
-17
lines changed

5 files changed

+68
-17
lines changed

src/compiler/scala/tools/nsc/symtab/classfile/Pickler.scala

Lines changed: 26 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -35,24 +35,30 @@ abstract class Pickler extends SubComponent {
3535
class PicklePhase(prev: Phase) extends StdPhase(prev) {
3636
def apply(unit: CompilationUnit): Unit = {
3737
def pickle(tree: Tree): Unit = {
38-
def add(sym: Symbol, pickle: Pickle) = {
39-
if (currentRun.compiles(sym) && !currentRun.symData.contains(sym)) {
40-
debuglog("pickling " + sym)
41-
pickle putSymbol sym
42-
currentRun.symData(sym) = pickle
43-
}
44-
}
45-
4638
tree match {
4739
case PackageDef(_, stats) =>
4840
stats foreach pickle
4941
case ClassDef(_, _, _, _) | ModuleDef(_, _, _) =>
5042
val sym = tree.symbol
51-
val pickle = new Pickle(sym)
52-
add(sym, pickle)
53-
add(sym.companionSymbol, pickle)
54-
pickle.writeArray()
55-
currentRun registerPickle sym
43+
def shouldPickle(sym: Symbol) = currentRun.compiles(sym) && !currentRun.symData.contains(sym)
44+
if (shouldPickle(sym)) {
45+
val pickle = new Pickle(sym)
46+
def reserveDeclEntries(sym: Symbol): Unit = {
47+
pickle.reserveEntry(sym)
48+
if (sym.isClass) sym.info.decls.foreach(reserveDeclEntries)
49+
else if (sym.isModule) reserveDeclEntries(sym.moduleClass)
50+
}
51+
52+
val companion = sym.companionSymbol.filter(_.owner == sym.owner) // exclude companionship between package- and package object-owned symbols.
53+
val syms = sym :: (if (shouldPickle(companion)) companion :: Nil else Nil)
54+
syms.foreach(reserveDeclEntries)
55+
syms.foreach { sym =>
56+
pickle.putSymbol(sym)
57+
currentRun.symData(sym) = pickle
58+
}
59+
pickle.writeArray()
60+
currentRun registerPickle sym
61+
}
5662
case _ =>
5763
}
5864
}
@@ -121,14 +127,20 @@ abstract class Pickler extends SubComponent {
121127
private def isExternalSymbol(sym: Symbol): Boolean = (sym != NoSymbol) && !isLocalToPickle(sym)
122128

123129
// Phase 1 methods: Populate entries/index ------------------------------------
130+
private val reserved = mutable.BitSet()
131+
final def reserveEntry(sym: Symbol): Unit = {
132+
reserved(ep) = true
133+
putEntry(sym)
134+
}
124135

125136
/** Store entry e in index at next available position unless
126137
* it is already there.
127138
*
128139
* @return true iff entry is new.
129140
*/
130141
private def putEntry(entry: AnyRef): Boolean = index.get(entry) match {
131-
case Some(_) => false
142+
case Some(i) =>
143+
reserved.remove(i)
132144
case None =>
133145
if (ep == entries.length) {
134146
val entries1 = new Array[AnyRef](ep * 2)

src/scalap/scala/tools/scalap/scalax/rules/scalasig/ScalaSigPrinter.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ class ScalaSigPrinter(stream: PrintStream, printPrivates: Boolean) {
6060
case a: AliasSymbol =>
6161
indent
6262
printAlias(level, a)
63-
case t: TypeSymbol if !t.isParam && !t.name.matches("_\\$\\d+")=>
63+
case t: TypeSymbol if !t.name.matches("_\\$\\d+")=>
6464
indent
6565
printTypeSymbol(level, t)
6666
case s =>

src/scalap/scala/tools/scalap/scalax/rules/scalasig/Symbol.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ abstract class ScalaSigSymbol extends Symbol {
2727
def entry: ScalaSig#Entry
2828
def index = entry.index
2929

30-
lazy val children: Seq[Symbol] = applyScalaSigRule(ScalaSigParsers.symbols) filter (_.parent == Some(this))
30+
lazy val children: Seq[Symbol] = applyScalaSigRule(ScalaSigParsers.symbols) filter (sym => sym.parent == Some(this) && !sym.isParam)
3131
lazy val attributes: Seq[AttributeInfo] = applyScalaSigRule(ScalaSigParsers.attributes) filter (_.symbol == this)
3232
}
3333

test/files/scalap/typeAnnotations.check

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
abstract class TypeAnnotations[@scala.specialized R] extends scala.AnyRef {
22
def this() = { /* compiled code */ }
3-
@scala.specialized
43
val x: scala.Int = { /* compiled code */ }
54
@scala.specialized
65
type T
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
package scala.tools.nsc.symtab.classfile
2+
3+
import org.junit.{Assert, Test}
4+
import org.junit.runner.RunWith
5+
import org.junit.runners.JUnit4
6+
7+
import scala.reflect.io.VirtualDirectory
8+
import scala.tools.nsc.Global
9+
import scala.tools.nsc.classpath.{AggregateClassPath, VirtualDirectoryClassPath}
10+
import scala.tools.testing.BytecodeTesting
11+
12+
@RunWith(classOf[JUnit4])
13+
class PicklerTest extends BytecodeTesting {
14+
@Test
15+
def pickleUnpicklePreserveDeclOrder(): Unit = {
16+
assertStableDecls("package p1; trait C { def x: T; def y: Int; class T }", "p1.C")
17+
assertStableDecls("package p1; class D; object D { def x: T = null; def y: Int = 0; class T }", "p1.D")
18+
}
19+
20+
def assertStableDecls(source: String, name: String): Unit = {
21+
val compiler1 = BytecodeTesting.newCompiler(extraArgs = compilerArgs)
22+
val r = new compiler1.global.Run
23+
r.compileSources(compiler1.global.newSourceFile(source) :: Nil)
24+
val compiler2 = BytecodeTesting.newCompiler(extraArgs = compilerArgs)
25+
val out = compiler1.global.settings.outputDirs.getSingleOutput.get.asInstanceOf[VirtualDirectory]
26+
def showDecls(global: Global): Seq[String] = global.exitingPickler {
27+
val classSym = global.rootMirror.getClassIfDefined(name)
28+
val moduleSym = global.rootMirror.getModuleIfDefined(name).moduleClass
29+
val syms = List(classSym, moduleSym).filter(sym => sym.exists)
30+
Assert.assertTrue(syms.nonEmpty)
31+
syms.flatMap(sym => sym.name.toString :: sym.info.decls.toList.map(decl => global.definitions.fullyInitializeSymbol(decl).defString))
32+
}
33+
val decls1 = showDecls(compiler1.global)
34+
compiler2.global.classPath
35+
compiler2.global.platform.currentClassPath = Some(AggregateClassPath(new VirtualDirectoryClassPath(out) :: compiler2.global.platform.currentClassPath.get :: Nil))
36+
new compiler2.global.Run
37+
val decls2 = showDecls(compiler2.global)
38+
Assert.assertEquals(decls1, decls2)
39+
}
40+
}

0 commit comments

Comments
 (0)