Skip to content

Commit d98c2d6

Browse files
committed
Treat non-open classes as effectively sealed.
This does not affect pattern matching exhaustivity (yet) since currently only abstract sealed classes and sealed traits are checked for exhaustivity, so defualt classes do not count. I wonder whether we should change that (?).
1 parent a8b727e commit d98c2d6

File tree

7 files changed

+42
-29
lines changed

7 files changed

+42
-29
lines changed

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -509,6 +509,7 @@ object Flags {
509509
/** Flags retained in export forwarders */
510510
val RetainedExportFlags = Given | Implicit | Extension
511511

512+
/** Flags that apply only to classes */
512513
val ClassOnlyFlags = Sealed | Open | Abstract.toTypeFlags
513514

514515
// ------- Other flag sets -------------------------------------
@@ -517,6 +518,7 @@ object Flags {
517518
val AbstractOverride: FlagSet = Abstract | Override
518519
val AbstractSealed: FlagSet = Abstract | Sealed
519520
val AbstractOrTrait: FlagSet = Abstract | Trait
521+
val EffectivelyOpenFlags = Abstract | JavaDefined | Open | Scala2x | Trait
520522
val PrivateAccessor: FlagSet = Accessor | Private
521523
val AccessorOrSynthetic: FlagSet = Accessor | Synthetic
522524
val EnumCase: FlagSet = Case | Enum

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

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1071,7 +1071,13 @@ object SymDenotations {
10711071
final def isEffectivelyFinal(implicit ctx: Context): Boolean =
10721072
isOneOf(EffectivelyFinalFlags) || !owner.isClass || owner.isOneOf(FinalOrModuleClass) || owner.isAnonymousClass
10731073

1074-
/** The class containing this denotation which has the given effective name. */
1074+
/** A class is effectively sealed if has the `final` or `sealed` modifier, or it
1075+
* is defined in Scala 3 and is neither abstract nor open.
1076+
*/
1077+
final def isEffectivelySealed(given Context): Boolean =
1078+
isOneOf(FinalOrSealed) || isClass && !isOneOf(EffectivelyOpenFlags)
1079+
1080+
/** The class containing this denotation which has the given effective name. */
10751081
final def enclosingClassNamed(name: Name)(implicit ctx: Context): Symbol = {
10761082
val cls = enclosingClass
10771083
if (cls.effectiveName == name || !cls.exists) cls else cls.owner.enclosingClassNamed(name)
@@ -1369,16 +1375,16 @@ object SymDenotations {
13691375
*/
13701376
def typeParamCreationFlags: FlagSet = TypeParam
13711377

1372-
override def toString: String = {
1373-
val kindString =
1374-
if (myFlags.is(ModuleClass)) "module class"
1375-
else if (isClass) "class"
1376-
else if (isType) "type"
1377-
else if (myFlags.is(Module)) "module"
1378-
else if (myFlags.is(Method)) "method"
1379-
else "val"
1380-
s"$kindString $name"
1381-
}
1378+
def kindString: String =
1379+
if myFlags.is(ModuleClass) then "module class"
1380+
else if myFlags.is(Trait) then "trait"
1381+
else if isClass then "class"
1382+
else if isType then "type"
1383+
else if myFlags.is(Module) then "module"
1384+
else if myFlags.is(Method) then "method"
1385+
else "val"
1386+
1387+
override def toString: String = s"$kindString $name"
13821388

13831389
// ----- Sanity checks and debugging */
13841390

compiler/src/dotty/tools/dotc/reporting/diagnostic/messages.scala

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1790,13 +1790,10 @@ object messages {
17901790
extends Message(ClassAndCompanionNameClashID) {
17911791
val kind: String = "Naming"
17921792
val msg: String = em"Name clash: both ${cls.owner} and its companion object defines ${cls.name.stripModuleClassSuffix}"
1793-
val explanation: String = {
1794-
val kind = if (cls.owner.is(Flags.Trait)) "trait" else "class"
1795-
1796-
em"""|A $kind and its companion object cannot both define a ${hl("class")}, ${hl("trait")} or ${hl("object")} with the same name:
1793+
val explanation: String =
1794+
em"""|A ${cls.kindString} and its companion object cannot both define a ${hl("class")}, ${hl("trait")} or ${hl("object")} with the same name:
17971795
| - ${cls.owner} defines ${cls}
17981796
| - ${other.owner} defines ${other}"""
1799-
}
18001797
}
18011798

18021799
case class TailrecNotApplicable(symbol: Symbol)(implicit ctx: Context)

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

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -91,8 +91,10 @@ class SymUtils(val self: Symbol) extends AnyVal {
9191
* - all of its children are generic products or singletons
9292
*/
9393
def whyNotGenericSum(implicit ctx: Context): String =
94-
if (!self.is(Sealed))
95-
s"it is not a sealed ${if (self.is(Trait)) "trait" else "class"}"
94+
if !self.isEffectivelySealed then
95+
s"it is not a sealed ${self.kindString}"
96+
else if self.is(Final) then
97+
s"it is a final class"
9698
else {
9799
val children = self.children
98100
val companion = self.linkedClass
@@ -175,12 +177,16 @@ class SymUtils(val self: Symbol) extends AnyVal {
175177
def isEnumAnonymClass(implicit ctx: Context): Boolean =
176178
self.isAnonymousClass && (self.owner.name.eq(nme.DOLLAR_NEW) || self.owner.is(CaseVal))
177179

178-
/** Is this symbol defined locally (i.e. at some level owned by a term) and
179-
* defined in a different toplevel class than its supposed parent class `cls`?
180-
* Such children are not pickled, and have to be reconstituted manually.
180+
/** Is this symbol defined locally (i.e. at some level owned by a term) so that
181+
* it cannot be seen from parent class `cls`?
181182
*/
182-
def isInaccessibleChildOf(cls: Symbol)(implicit ctx: Context): Boolean =
183-
self.isLocal && !cls.topLevelClass.isLinkedWith(self.topLevelClass)
183+
def isInaccessibleChildOf(cls: Symbol)(given Context): Boolean =
184+
def isAccessible(sym: Symbol): Boolean =
185+
sym == cls
186+
|| sym == cls.owner
187+
|| sym.owner.is(Package)
188+
|| sym.owner.isType && isAccessible(sym.owner)
189+
!isAccessible(self)
184190

185191
/** If this is a sealed class, its known children in the order of textual occurrence */
186192
def children(implicit ctx: Context): List[Symbol] = {

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -509,7 +509,7 @@ class SyntheticMembers(thisPhase: DenotTransformer) {
509509
if (clazz.is(Case)) makeSingletonMirror()
510510
else if (linked.isGenericProduct) makeProductMirror(linked)
511511
else if (linked.isGenericSum) makeSumMirror(linked)
512-
else if (linked.is(Sealed))
512+
else if linked.isEffectivelySealed then
513513
derive.println(i"$linked is not a sum because ${linked.whyNotGenericSum}")
514514
}
515515
else if (impl.removeAttachment(ExtendsSingletonMirror).isDefined)

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -550,7 +550,7 @@ class SpaceEngine(implicit ctx: Context) extends SpaceLogic {
550550
def canDecompose(tp: Type): Boolean = {
551551
val dealiasedTp = tp.dealias
552552
val res =
553-
(tp.classSymbol.is(Sealed) &&
553+
(tp.classSymbol.isEffectivelySealed &&
554554
tp.classSymbol.isOneOf(AbstractOrTrait) &&
555555
!tp.classSymbol.hasAnonymousChild &&
556556
tp.classSymbol.children.nonEmpty ) ||
@@ -672,7 +672,7 @@ class SpaceEngine(implicit ctx: Context) extends SpaceLogic {
672672
if (mergeList) "_: _*" else "_: List"
673673
else if (scalaConsType.isRef(sym))
674674
if (mergeList) "_, _: _*" else "List(_, _: _*)"
675-
else if (tp.classSymbol.is(Sealed) && tp.classSymbol.hasAnonymousChild)
675+
else if (tp.classSymbol.isEffectivelySealed && tp.classSymbol.hasAnonymousChild)
676676
"_: " + showType(tp) + " (anonymous)"
677677
else if (tp.classSymbol.is(CaseClass) && !hasCustomUnapply(tp.classSymbol))
678678
// use constructor syntax for case class

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

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -876,10 +876,12 @@ class Namer { typer: Typer =>
876876

877877
def register(child: Symbol, parent: Type) = {
878878
val cls = parent.classSymbol
879-
if (cls.is(Sealed))
880-
if ((child.isInaccessibleChildOf(cls) || child.isAnonymousClass) && !sym.hasAnonymousChild)
879+
if cls.isEffectivelySealed
880+
&& child.associatedFile == cls.associatedFile // don't register ad-hoc extensions as children
881+
then
882+
if child.isInaccessibleChildOf(cls) && !sym.hasAnonymousChild then
881883
addChild(cls, cls)
882-
else if (!cls.is(ChildrenQueried))
884+
else if !cls.is(ChildrenQueried) then
883885
addChild(cls, child)
884886
else
885887
ctx.error(em"""children of $cls were already queried before $sym was discovered.

0 commit comments

Comments
 (0)