Skip to content

Commit 1a8eefe

Browse files
committed
Record anonymous child classes for exhaustivity check
If a sealed class A has local or anonymous classes, add the annotation `Child(A)` to `A`. With this simple change, the exhaustivity check works without any modification.
1 parent f2b0b8a commit 1a8eefe

File tree

7 files changed

+43
-11
lines changed

7 files changed

+43
-11
lines changed

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

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -842,10 +842,7 @@ class TreeUnpickler(reader: TastyReader,
842842
if (!sym.isType) { // Only terms might have leaky aliases, see the documentation of `checkNoPrivateLeaks`
843843
sym.info = ta.avoidPrivateLeaks(sym, tree.pos)
844844
}
845-
if ((sym.isClass || sym.is(CaseVal)) && sym.isLocal)
846-
// Child annotations for local classes and enum values are not pickled, so
847-
// need to be re-established here.
848-
sym.registerIfChild(late = true)
845+
849846
sym.defTree = tree
850847

851848
if (ctx.mode.is(Mode.ReadComments)) {

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

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import Symbols._
88
import SymDenotations._
99
import Names._
1010
import NameOps._
11+
import StdNames._
1112
import NameKinds._
1213
import Flags._
1314
import Annotations._
@@ -132,16 +133,17 @@ class SymUtils(val self: Symbol) extends AnyVal {
132133

133134
/** If this symbol is an enum value or a named class, register it as a child
134135
* in all direct parent classes which are sealed.
135-
* @param @late If true, register only inaccessible children (all others are already
136-
* entered at this point).
137136
*/
138-
def registerIfChild(late: Boolean = false)(implicit ctx: Context): Unit = {
137+
def registerIfChild()(implicit ctx: Context): Unit = {
139138
def register(child: Symbol, parent: Type) = {
140139
val cls = parent.classSymbol
141-
if (cls.is(Sealed) && (!late || child.isInaccessibleChildOf(cls)))
142-
cls.addAnnotation(Annotation.Child(child))
140+
if (cls.is(Sealed)) {
141+
if ((child.isInaccessibleChildOf(cls) || child.isAnonymousClass) && !self.hasAnonymousChild)
142+
cls.addAnnotation(Annotation.Child(cls))
143+
else cls.addAnnotation(Annotation.Child(child))
144+
}
143145
}
144-
if (self.isClass && !self.isAnonymousClass)
146+
if (self.isClass && !self.isEnumAnonymClass)
145147
self.asClass.classParents.foreach { parent =>
146148
val child = if (self.is(Module)) self.sourceModule else self
147149
register(child, parent)
@@ -150,6 +152,10 @@ class SymUtils(val self: Symbol) extends AnyVal {
150152
register(self, self.info)
151153
}
152154

155+
/** Does this symbol refer to anonymous classes synthesized by enum desugaring? */
156+
def isEnumAnonymClass(implicit ctx: Context): Boolean =
157+
self.isAnonymousClass && (self.owner.name.eq(nme.DOLLAR_NEW) || self.owner.is(CaseVal))
158+
153159
/** Is this symbol defined locally (i.e. at some level owned by a term) and
154160
* defined in a different toplevel class than its supposed parent class `cls`?
155161
* Such children are not pickled, and have to be reconstituted manually.
@@ -163,6 +169,9 @@ class SymUtils(val self: Symbol) extends AnyVal {
163169
case Annotation.Child(child) => child
164170
}
165171

172+
def hasAnonymousChild(implicit ctx: Context): Boolean =
173+
children.exists(_ `eq` self)
174+
166175
/** Is symbol directly or indirectly owned by a term symbol? */
167176
@tailrec final def isLocal(implicit ctx: Context): Boolean = {
168177
val owner = self.owner

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -730,6 +730,7 @@ class SpaceEngine(implicit ctx: Context) extends SpaceLogic {
730730
val res =
731731
(tp.classSymbol.is(Sealed) &&
732732
tp.classSymbol.is(AbstractOrTrait) &&
733+
!tp.classSymbol.hasAnonymousChild &&
733734
tp.classSymbol.children.nonEmpty ) ||
734735
dealiasedTp.isInstanceOf[OrType] ||
735736
(dealiasedTp.isInstanceOf[AndType] && {
@@ -849,6 +850,8 @@ class SpaceEngine(implicit ctx: Context) extends SpaceLogic {
849850
if (mergeList) "_: _*" else "_: List"
850851
else if (scalaConsType.isRef(sym))
851852
if (mergeList) "_, _: _*" else "List(_, _: _*)"
853+
else if (tp.classSymbol.is(Sealed) && tp.classSymbol.hasAnonymousChild)
854+
"_: " + showType(tp) + " (anonymous)"
852855
else if (tp.classSymbol.is(CaseClass) && !hasCustomUnapply(tp.classSymbol))
853856
// use constructor syntax for case class
854857
showType(tp) + params(tp).map(_ => "_").mkString("(", ", ", ")")

tests/patmat/anonym.check

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
10: Pattern Match Exhaustivity: _: Base (anonymous)

tests/patmat/anonym.scala

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// spurious dead-code
2+
class Foo {
3+
def bar(x: Either[String, Base]): Unit = {
4+
x match {
5+
case Left(s) => println("l")
6+
case Right(Base(s)) => println("r")
7+
}
8+
}
9+
10+
def bar(x: Base): Unit = x match {
11+
case Zero =>
12+
}
13+
14+
def anonymous: Base = new Base("bla") {}
15+
}
16+
17+
sealed abstract case class Base(value: String) {
18+
override def toString = value // ok?
19+
}
20+
21+
object Zero extends Base("zero")

tests/patmat/t9677.check

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
18: Pattern Match Exhaustivity: _: Base (anonymous)

tests/patmat/t9677.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ object ExhaustiveMatchWarning {
1515

1616
def test: Unit = {
1717
val b: Base = A("blabla")
18-
b match {
18+
b match { // A.apply creates anonymous class <: Base
1919
case A.Root => println("Root")
2020
case path: A => println("Not root")
2121
}

0 commit comments

Comments
 (0)