Skip to content

Fix #5008: forbid any class extending enums #5031

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jan 27, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ public enum ErrorMessageID {
ImportRenamedTwiceID,
TypeTestAlwaysSucceedsID,
TermMemberNeedsNeedsResultTypeForImplicitSearchID,
CaseClassCannotExtendEnumID,
ClassCannotExtendEnumID,
ValueClassParameterMayNotBeCallByNameID,
NotAnExtractorID,
MemberWithSameNameAsStaticID,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2126,9 +2126,9 @@ object messages {
|""".stripMargin
}

case class CaseClassCannotExtendEnum(cls: Symbol, parent: Symbol)(implicit ctx: Context) extends Message(CaseClassCannotExtendEnumID) {
case class ClassCannotExtendEnum(cls: Symbol, parent: Symbol)(implicit ctx: Context) extends Message(ClassCannotExtendEnumID) {
override def kind: String = "Syntax"
override def msg: String = hl"""Normal case class cannot extend an enum. case $cls in ${cls.owner} is extending enum ${parent.name}."""
override def msg: String = hl"""$cls in ${cls.owner} extends enum ${parent.name}, but extending enums is prohibited."""
override def explanation: String = ""
}

Expand Down
15 changes: 11 additions & 4 deletions compiler/src/dotty/tools/dotc/typer/Checking.scala
Original file line number Diff line number Diff line change
Expand Up @@ -901,14 +901,21 @@ trait Checking {
}

/** Check that all case classes that extend `scala.Enum` are `enum` cases */
def checkEnum(cdef: untpd.TypeDef, cls: Symbol, parent: Symbol)(implicit ctx: Context): Unit = {
def checkEnum(cdef: untpd.TypeDef, cls: Symbol, firstParent: Symbol)(implicit ctx: Context): Unit = {
import untpd.modsDeco
def isEnumAnonCls =
cls.isAnonymousClass &&
cls.owner.isTerm &&
(cls.owner.flagsUNSAFE.is(Case) || cls.owner.name == nme.DOLLAR_NEW)
if (!cdef.mods.isEnumCase && !isEnumAnonCls)
ctx.error(CaseClassCannotExtendEnum(cls, parent), cdef.sourcePos)
if (!cdef.mods.isEnumCase && !isEnumAnonCls) {
// Since enums are classes and Namer checks that classes don't extend multiple classes, we only check the class
// parent.
//
// Unlike firstParent.derivesFrom(defn.EnumClass), this test allows inheriting from `Enum` by hand;
// see enum-List-control.scala.
if (cls.is(Case) || firstParent.is(Enum))
ctx.error(ClassCannotExtendEnum(cls, firstParent), cdef.sourcePos)
}
}

/** Check that all references coming from enum cases in an enum companion object
Expand Down Expand Up @@ -975,7 +982,7 @@ trait Checking {

trait ReChecking extends Checking {
import tpd._
override def checkEnum(cdef: untpd.TypeDef, cls: Symbol, parent: Symbol)(implicit ctx: Context): Unit = ()
override def checkEnum(cdef: untpd.TypeDef, cls: Symbol, firstParent: Symbol)(implicit ctx: Context): Unit = ()
override def checkRefsLegal(tree: tpd.Tree, badOwner: Symbol, allowed: (Name, Symbol) => Boolean, where: String)(implicit ctx: Context): Unit = ()
override def checkEnumCaseRefsLegal(cdef: TypeDef, enumCtx: Context)(implicit ctx: Context): Unit = ()
}
Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/typer/Typer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1651,7 +1651,7 @@ class Typer extends Namer
.withType(dummy.termRef)
if (!cls.is(AbstractOrTrait) && !ctx.isAfterTyper)
checkRealizableBounds(cls, cdef.sourcePos.withSpan(cdef.nameSpan))
if (cls.is(Case) && cls.derivesFrom(defn.EnumClass)) {
if (cls.derivesFrom(defn.EnumClass)) {
val firstParent = parents1.head.tpe.dealias.typeSymbol
checkEnum(cdef, cls, firstParent)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ class ErrorMessagesTests extends ErrorMessagesTest {
implicit val ctx: Context = ictx
assertMessageCount(1, messages)
val errorMsg = messages.head
val CaseClassCannotExtendEnum(cls, parent) :: Nil = messages
val ClassCannotExtendEnum(cls, parent) :: Nil = messages
assertEquals("Bar", cls.name.show)
assertEquals("Foo", parent.name.show)
assertEquals("<empty>", cls.owner.name.show)
Expand Down
8 changes: 8 additions & 0 deletions tests/neg/i5008.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
enum Foo { case A }
enum Bar { case A }
enum Baz extends Foo { case Z } // error

enum Quux extends Foo with Bar { case Z } // error

class Quuw extends Foo // error
class Quuz extends Foo { val enumTag = 1 } // error
2 changes: 0 additions & 2 deletions tests/pending/neg/i5008

This file was deleted.