Skip to content

Commit 4dc8b6c

Browse files
committed
Fix #3149: Fix pickling of child annotations to local classes
i3149.scala is an interesting test case: A sealed top-level class `Foo` has a child which is a local (i.e., term-owned) definition in a different top-level class. It is then impossible to establish a symbolic reference to the child class from `Foo`, and it is also impossible to refer to `Foo` using a path. We deal with this by avoiding pickling such child annotations and re-establishing the annotation when the body of the child class is unpickled.
1 parent 7bf86d2 commit 4dc8b6c

File tree

8 files changed

+75
-25
lines changed

8 files changed

+75
-25
lines changed

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

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -142,15 +142,28 @@ object Annotations {
142142
apply(defn.AliasAnnot, List(
143143
ref(TermRef(sym.owner.thisType, sym.name, sym))))
144144

145-
def makeChild(delayedSym: Context => Symbol)(implicit ctx: Context): Annotation = {
146-
def makeChildLater(implicit ctx: Context) = {
147-
val sym = delayedSym(ctx)
148-
New(defn.ChildAnnotType.appliedTo(sym.owner.thisType.select(sym.name, sym)), Nil)
145+
/** Extractor for child annotations */
146+
object Child {
147+
148+
/** A deferred annotation to the result of a given child computation */
149+
def apply(delayedSym: Context => Symbol)(implicit ctx: Context): Annotation = {
150+
def makeChildLater(implicit ctx: Context) = {
151+
val sym = delayedSym(ctx)
152+
New(defn.ChildAnnotType.appliedTo(sym.owner.thisType.select(sym.name, sym)), Nil)
153+
}
154+
deferred(defn.ChildAnnot, implicit ctx => makeChildLater(ctx))
149155
}
150-
deferred(defn.ChildAnnot, implicit ctx => makeChildLater(ctx))
151-
}
152156

153-
def makeChild(sym: Symbol)(implicit ctx: Context): Annotation = makeChild(_ => sym)
157+
/** A regular, non-deferred Child annotation */
158+
def apply(sym: Symbol)(implicit ctx: Context): Annotation = apply(_ => sym)
159+
160+
def unapply(ann: Annotation)(implicit ctx: Context): Option[Symbol] =
161+
if (ann.symbol == defn.ChildAnnot) {
162+
val AppliedType(tycon, (arg: NamedType) :: Nil) = ann.tree.tpe
163+
Some(arg.symbol)
164+
}
165+
else None
166+
}
154167

155168
def makeSourceFile(path: String)(implicit ctx: Context) =
156169
apply(defn.SourceFileAnnot, Literal(Constant(path)))

compiler/src/dotty/tools/dotc/core/classfile/ClassfileParser.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -258,7 +258,7 @@ class ClassfileParser(
258258
ctx.warning(s"no linked class for java enum $sym in ${sym.owner}. A referencing class file might be missing an InnerClasses entry.")
259259
else {
260260
if (!(enumClass is Flags.Sealed)) enumClass.setFlag(Flags.AbstractSealed)
261-
enumClass.addAnnotation(Annotation.makeChild(sym))
261+
enumClass.addAnnotation(Annotation.Child(sym))
262262
}
263263
}
264264
} finally {

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

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -573,8 +573,19 @@ class TreePickler(pickler: TastyPickler) {
573573
sym.annotations.foreach(pickleAnnotation)
574574
}
575575

576+
private def isUnpicklable(ann: Annotation)(implicit ctx: Context) = ann match {
577+
case Annotation.Child(sym) =>
578+
sym.isLocal && ctx.owner.topLevelClass != sym.topLevelClass
579+
// If child annotation refers to a local class or enum value under
580+
// a different toplevel class, it is impossible to pickle a reference to it.
581+
// Such annotations will be reconstituted when unpickling the child class.
582+
// See tests/pickling/i3149.scala
583+
case _ => ann.symbol == defn.BodyAnnot
584+
// inline bodies are reconstituted automatically when unpickling
585+
}
586+
576587
def pickleAnnotation(ann: Annotation)(implicit ctx: Context) =
577-
if (ann.symbol != defn.BodyAnnot) { // inline bodies are reconstituted automatically when unpickling
588+
if (!isUnpicklable(ann)) {
578589
writeByte(ANNOTATION)
579590
withLength { pickleType(ann.symbol.typeRef); pickleTree(ann.tree) }
580591
}

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

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import util.Positions._
1111
import ast.{tpd, Trees, untpd}
1212
import Trees._
1313
import Decorators._
14+
import transform.SymUtils._
1415
import TastyUnpickler._, TastyBuffer._
1516
import scala.annotation.{tailrec, switch}
1617
import scala.collection.mutable.ListBuffer
@@ -663,7 +664,6 @@ class TreeUnpickler(reader: TastyReader, nameAtRef: NameRef => TermName, posUnpi
663664
def isCodefined =
664665
roots.contains(companion.denot) == seenRoots.contains(companion)
665666
if (companion.exists && isCodefined) {
666-
import transform.SymUtils._
667667
if (sym is Flags.ModuleClass) sym.registerCompanionMethod(nme.COMPANION_CLASS_METHOD, companion)
668668
else sym.registerCompanionMethod(nme.COMPANION_MODULE_METHOD, companion)
669669
}
@@ -702,6 +702,10 @@ class TreeUnpickler(reader: TastyReader, nameAtRef: NameRef => TermName, posUnpi
702702
if (!sym.isType) { // Only terms might have leaky aliases, see the documentation of `checkNoPrivateLeaks`
703703
sym.info = ta.avoidPrivateLeaks(sym, tree.pos)
704704
}
705+
if ((sym.isClass || sym.is(CaseVal)) && sym.isLocal)
706+
// Child annotations for local classes and enum values are not pickled, so
707+
// need to be re-established here.
708+
sym.registerIfChild()
705709
tree
706710
}
707711

compiler/src/dotty/tools/dotc/core/unpickleScala2/Scala2Unpickler.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -838,7 +838,7 @@ class Scala2Unpickler(bytes: Array[Byte], classRoot: ClassDenotation, moduleClas
838838
val start = readIndex
839839
readNat() // skip reference for now
840840
target.addAnnotation(
841-
Annotation.makeChild(implicit ctx =>
841+
Annotation.Child(implicit ctx =>
842842
atReadPos(start, () => readSymbolRef())))
843843
}
844844
}

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

Lines changed: 3 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import SymDenotations._, Symbols._, StdNames._, Annotations._, Trees._, Scopes._
1414
import util.Positions._
1515
import Decorators._
1616
import config.Printers.typr
17-
import Symbols._, TypeUtils._
17+
import Symbols._, TypeUtils._, SymUtils._
1818
import reporting.diagnostic.messages.SuperCallsNotAllowedInline
1919

2020
/** A macro transform that runs immediately after typer and that performs the following functions:
@@ -124,14 +124,9 @@ class PostTyper extends MacroTransform with SymTransformer { thisTransformer =>
124124
private def transformAnnot(annot: Annotation)(implicit ctx: Context): Annotation =
125125
annot.derivedAnnotation(transformAnnot(annot.tree))
126126

127-
private def registerChild(sym: Symbol, tp: Type)(implicit ctx: Context) = {
128-
val cls = tp.classSymbol
129-
if (cls.is(Sealed)) cls.addAnnotation(Annotation.makeChild(sym))
130-
}
131-
132127
private def transformMemberDef(tree: MemberDef)(implicit ctx: Context): Unit = {
133128
val sym = tree.symbol
134-
if (sym.is(CaseVal, butNot = Method | Module)) registerChild(sym, sym.info)
129+
sym.registerIfChild()
135130
sym.transformAnnotations(transformAnnot)
136131
}
137132

@@ -259,12 +254,7 @@ class PostTyper extends MacroTransform with SymTransformer { thisTransformer =>
259254
sym.addAnnotation(Annotation.makeSourceFile(ctx.compilationUnit.source.file.path))
260255

261256
// Add Child annotation to sealed parents unless current class is anonymous
262-
if (!sym.isAnonymousClass) // ignore anonymous class
263-
sym.asClass.classParents.foreach { parent =>
264-
val sym2 = if (sym.is(Module)) sym.sourceModule else sym
265-
registerChild(sym2, parent)
266-
}
267-
257+
sym.registerIfChild()
268258
tree
269259
}
270260
super.transform(tree)

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

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,4 +134,36 @@ class SymUtils(val self: Symbol) extends AnyVal {
134134
}
135135
}
136136
}
137+
138+
/** If this symbol is an enum value or a named class, register it as a child
139+
* in all direct parent classes which are sealed.
140+
*/
141+
def registerIfChild()(implicit ctx: Context): Unit = {
142+
def register(child: Symbol, parent: Type) = {
143+
val cls = parent.classSymbol
144+
if (cls.is(Sealed) && !cls.children.contains(child))
145+
cls.addAnnotation(Annotation.Child(child))
146+
}
147+
if (self.isClass && !self.isAnonymousClass)
148+
self.asClass.classParents.foreach { parent =>
149+
val child = if (self.is(Module)) self.sourceModule else self
150+
register(child, parent)
151+
}
152+
else if (self.is(CaseVal, butNot = Method | Module))
153+
register(self, self.info)
154+
}
155+
156+
/** If this is a sealed class, its known children */
157+
def children(implicit ctx: Context): List[Symbol] =
158+
self.annotations.collect {
159+
case Annotation.Child(child) => child
160+
}
161+
162+
/** Is symbol directly or indirectly owned by a term symbol? */
163+
@tailrec def isLocal(implicit ctx: Context): Boolean = {
164+
val owner = self.owner
165+
if (owner.isTerm) true
166+
else if (owner.is(Package)) false
167+
else owner.isLocal
168+
}
137169
}

compiler/src/dotty/tools/dotc/typer/Namer.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -470,7 +470,7 @@ class Namer { typer: Typer =>
470470
case vdef: ValDef if (isEnumConstant(vdef)) =>
471471
val enumClass = sym.owner.linkedClass
472472
if (!(enumClass is Flags.Sealed)) enumClass.setFlag(Flags.AbstractSealed)
473-
enumClass.addAnnotation(Annotation.makeChild(sym))
473+
enumClass.addAnnotation(Annotation.Child(sym))
474474
case _ =>
475475
}
476476

0 commit comments

Comments
 (0)