Skip to content

Commit

Permalink
Treat non-open classes as effectively sealed.
Browse files Browse the repository at this point in the history
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 (?).
  • Loading branch information
odersky committed Oct 30, 2019
1 parent a8b727e commit d98c2d6
Show file tree
Hide file tree
Showing 7 changed files with 42 additions and 29 deletions.
2 changes: 2 additions & 0 deletions compiler/src/dotty/tools/dotc/core/Flags.scala
Expand Up @@ -509,6 +509,7 @@ object Flags {
/** Flags retained in export forwarders */
val RetainedExportFlags = Given | Implicit | Extension

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

// ------- Other flag sets -------------------------------------
Expand All @@ -517,6 +518,7 @@ object Flags {
val AbstractOverride: FlagSet = Abstract | Override
val AbstractSealed: FlagSet = Abstract | Sealed
val AbstractOrTrait: FlagSet = Abstract | Trait
val EffectivelyOpenFlags = Abstract | JavaDefined | Open | Scala2x | Trait
val PrivateAccessor: FlagSet = Accessor | Private
val AccessorOrSynthetic: FlagSet = Accessor | Synthetic
val EnumCase: FlagSet = Case | Enum
Expand Down
28 changes: 17 additions & 11 deletions compiler/src/dotty/tools/dotc/core/SymDenotations.scala
Expand Up @@ -1071,7 +1071,13 @@ object SymDenotations {
final def isEffectivelyFinal(implicit ctx: Context): Boolean =
isOneOf(EffectivelyFinalFlags) || !owner.isClass || owner.isOneOf(FinalOrModuleClass) || owner.isAnonymousClass

/** The class containing this denotation which has the given effective name. */
/** A class is effectively sealed if has the `final` or `sealed` modifier, or it
* is defined in Scala 3 and is neither abstract nor open.
*/
final def isEffectivelySealed(given Context): Boolean =
isOneOf(FinalOrSealed) || isClass && !isOneOf(EffectivelyOpenFlags)

/** The class containing this denotation which has the given effective name. */
final def enclosingClassNamed(name: Name)(implicit ctx: Context): Symbol = {
val cls = enclosingClass
if (cls.effectiveName == name || !cls.exists) cls else cls.owner.enclosingClassNamed(name)
Expand Down Expand Up @@ -1369,16 +1375,16 @@ object SymDenotations {
*/
def typeParamCreationFlags: FlagSet = TypeParam

override def toString: String = {
val kindString =
if (myFlags.is(ModuleClass)) "module class"
else if (isClass) "class"
else if (isType) "type"
else if (myFlags.is(Module)) "module"
else if (myFlags.is(Method)) "method"
else "val"
s"$kindString $name"
}
def kindString: String =
if myFlags.is(ModuleClass) then "module class"
else if myFlags.is(Trait) then "trait"
else if isClass then "class"
else if isType then "type"
else if myFlags.is(Module) then "module"
else if myFlags.is(Method) then "method"
else "val"

override def toString: String = s"$kindString $name"

// ----- Sanity checks and debugging */

Expand Down
Expand Up @@ -1790,13 +1790,10 @@ object messages {
extends Message(ClassAndCompanionNameClashID) {
val kind: String = "Naming"
val msg: String = em"Name clash: both ${cls.owner} and its companion object defines ${cls.name.stripModuleClassSuffix}"
val explanation: String = {
val kind = if (cls.owner.is(Flags.Trait)) "trait" else "class"

em"""|A $kind and its companion object cannot both define a ${hl("class")}, ${hl("trait")} or ${hl("object")} with the same name:
val explanation: String =
em"""|A ${cls.kindString} and its companion object cannot both define a ${hl("class")}, ${hl("trait")} or ${hl("object")} with the same name:
| - ${cls.owner} defines ${cls}
| - ${other.owner} defines ${other}"""
}
}

case class TailrecNotApplicable(symbol: Symbol)(implicit ctx: Context)
Expand Down
20 changes: 13 additions & 7 deletions compiler/src/dotty/tools/dotc/transform/SymUtils.scala
Expand Up @@ -91,8 +91,10 @@ class SymUtils(val self: Symbol) extends AnyVal {
* - all of its children are generic products or singletons
*/
def whyNotGenericSum(implicit ctx: Context): String =
if (!self.is(Sealed))
s"it is not a sealed ${if (self.is(Trait)) "trait" else "class"}"
if !self.isEffectivelySealed then
s"it is not a sealed ${self.kindString}"
else if self.is(Final) then
s"it is a final class"
else {
val children = self.children
val companion = self.linkedClass
Expand Down Expand Up @@ -175,12 +177,16 @@ class SymUtils(val self: Symbol) extends AnyVal {
def isEnumAnonymClass(implicit ctx: Context): Boolean =
self.isAnonymousClass && (self.owner.name.eq(nme.DOLLAR_NEW) || self.owner.is(CaseVal))

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

/** If this is a sealed class, its known children in the order of textual occurrence */
def children(implicit ctx: Context): List[Symbol] = {
Expand Down
Expand Up @@ -509,7 +509,7 @@ class SyntheticMembers(thisPhase: DenotTransformer) {
if (clazz.is(Case)) makeSingletonMirror()
else if (linked.isGenericProduct) makeProductMirror(linked)
else if (linked.isGenericSum) makeSumMirror(linked)
else if (linked.is(Sealed))
else if linked.isEffectivelySealed then
derive.println(i"$linked is not a sum because ${linked.whyNotGenericSum}")
}
else if (impl.removeAttachment(ExtendsSingletonMirror).isDefined)
Expand Down
4 changes: 2 additions & 2 deletions compiler/src/dotty/tools/dotc/transform/patmat/Space.scala
Expand Up @@ -550,7 +550,7 @@ class SpaceEngine(implicit ctx: Context) extends SpaceLogic {
def canDecompose(tp: Type): Boolean = {
val dealiasedTp = tp.dealias
val res =
(tp.classSymbol.is(Sealed) &&
(tp.classSymbol.isEffectivelySealed &&
tp.classSymbol.isOneOf(AbstractOrTrait) &&
!tp.classSymbol.hasAnonymousChild &&
tp.classSymbol.children.nonEmpty ) ||
Expand Down Expand Up @@ -672,7 +672,7 @@ class SpaceEngine(implicit ctx: Context) extends SpaceLogic {
if (mergeList) "_: _*" else "_: List"
else if (scalaConsType.isRef(sym))
if (mergeList) "_, _: _*" else "List(_, _: _*)"
else if (tp.classSymbol.is(Sealed) && tp.classSymbol.hasAnonymousChild)
else if (tp.classSymbol.isEffectivelySealed && tp.classSymbol.hasAnonymousChild)
"_: " + showType(tp) + " (anonymous)"
else if (tp.classSymbol.is(CaseClass) && !hasCustomUnapply(tp.classSymbol))
// use constructor syntax for case class
Expand Down
8 changes: 5 additions & 3 deletions compiler/src/dotty/tools/dotc/typer/Namer.scala
Expand Up @@ -876,10 +876,12 @@ class Namer { typer: Typer =>

def register(child: Symbol, parent: Type) = {
val cls = parent.classSymbol
if (cls.is(Sealed))
if ((child.isInaccessibleChildOf(cls) || child.isAnonymousClass) && !sym.hasAnonymousChild)
if cls.isEffectivelySealed
&& child.associatedFile == cls.associatedFile // don't register ad-hoc extensions as children
then
if child.isInaccessibleChildOf(cls) && !sym.hasAnonymousChild then
addChild(cls, cls)
else if (!cls.is(ChildrenQueried))
else if !cls.is(ChildrenQueried) then
addChild(cls, child)
else
ctx.error(em"""children of $cls were already queried before $sym was discovered.
Expand Down

0 comments on commit d98c2d6

Please sign in to comment.