Skip to content

Commit 3c45d43

Browse files
authored
Merge pull request #3202 from dotty-staging/fix-#3149
Fix #3149: Fix pickling of child annotations to local classes
2 parents b41ec4c + acdf2e8 commit 3c45d43

File tree

12 files changed

+120
-35
lines changed

12 files changed

+120
-35
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: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -570,11 +570,21 @@ class TreePickler(pickler: TastyPickler) {
570570
if (flags is Covariant) writeByte(COVARIANT)
571571
if (flags is Contravariant) writeByte(CONTRAVARIANT)
572572
}
573-
sym.annotations.foreach(pickleAnnotation)
573+
sym.annotations.foreach(pickleAnnotation(sym, _))
574574
}
575575

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

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(late = true)
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: 2 additions & 15 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

@@ -257,14 +252,6 @@ class PostTyper extends MacroTransform with SymTransformer { thisTransformer =>
257252
ctx.compilationUnit.source.exists &&
258253
sym != defn.SourceFileAnnot)
259254
sym.addAnnotation(Annotation.makeSourceFile(ctx.compilationUnit.source.file.path))
260-
261-
// 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-
268255
tree
269256
}
270257
super.transform(tree)

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

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,4 +134,45 @@ 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+
* @param @late If true, register only inaccessible children (all others are already
141+
* entered at this point).
142+
*/
143+
def registerIfChild(late: Boolean = false)(implicit ctx: Context): Unit = {
144+
def register(child: Symbol, parent: Type) = {
145+
val cls = parent.classSymbol
146+
if (cls.is(Sealed) && (!late || child.isInaccessibleChildOf(cls)))
147+
cls.addAnnotation(Annotation.Child(child))
148+
}
149+
if (self.isClass && !self.isAnonymousClass)
150+
self.asClass.classParents.foreach { parent =>
151+
val child = if (self.is(Module)) self.sourceModule else self
152+
register(child, parent)
153+
}
154+
else if (self.is(CaseVal, butNot = Method | Module))
155+
register(self, self.info)
156+
}
157+
158+
/** Is this symbol defined locally (i.e. at some level owned by a term) and
159+
* defined in a different toplevel class than its supposed parent class `cls`?
160+
* Such children are not pickled, and have to be reconstituted manually.
161+
*/
162+
def isInaccessibleChildOf(cls: Symbol)(implicit ctx: Context) =
163+
self.isLocal && !cls.topLevelClass.isLinkedWith(self.topLevelClass)
164+
165+
/** If this is a sealed class, its known children */
166+
def children(implicit ctx: Context): List[Symbol] =
167+
self.annotations.collect {
168+
case Annotation.Child(child) => child
169+
}
170+
171+
/** Is symbol directly or indirectly owned by a term symbol? */
172+
@tailrec def isLocal(implicit ctx: Context): Boolean = {
173+
val owner = self.owner
174+
if (owner.isTerm) true
175+
else if (owner.is(Package)) false
176+
else owner.isLocal
177+
}
137178
}

compiler/src/dotty/tools/dotc/transform/patmat/Space.scala

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -514,12 +514,7 @@ class SpaceEngine(implicit ctx: Context) extends SpaceLogic {
514514

515515
/** Decompose a type into subspaces -- assume the type can be decomposed */
516516
def decompose(tp: Type): List[Space] = {
517-
val children = tp.classSymbol.annotations.filter(_.symbol == ctx.definitions.ChildAnnot).map { annot =>
518-
// refer to definition of Annotation.makeChild
519-
annot.tree match {
520-
case Apply(TypeApply(_, List(tpTree)), _) => tpTree.symbol
521-
}
522-
}
517+
val children = tp.classSymbol.children
523518

524519
debug.println(s"candidates for ${tp.show} : [${children.map(_.show).mkString(", ")}]")
525520

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

tests/pickling/i3149.scala

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
sealed class Foo
2+
3+
class Test {
4+
def f = {
5+
class Bar extends Foo
6+
}
7+
}

tests/pos/i3149.scala

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
sealed class Foo
2+
3+
class Test {
4+
def f = {
5+
class Bar extends Foo
6+
}
7+
class C {
8+
class Bar extends Foo
9+
}
10+
object O {
11+
class Bar extends Foo
12+
}
13+
}
14+

tests/pos/i3149a.scala

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
sealed class Foo
2+
3+
object Foo {
4+
def f = {
5+
class Bar extends Foo
6+
}
7+
class C {
8+
class Bar extends Foo
9+
}
10+
object O {
11+
class Bar extends Foo
12+
}
13+
}
14+

0 commit comments

Comments
 (0)