From 644053acbab79776f4a9d4a02b3db5d47fad075c Mon Sep 17 00:00:00 2001 From: "Paolo G. Giarrusso" Date: Tue, 15 Jan 2019 18:36:11 +0100 Subject: [PATCH 1/2] Refactorings --- .../dotc/reporting/diagnostic/ErrorMessageID.java | 2 +- .../tools/dotc/reporting/diagnostic/messages.scala | 2 +- compiler/src/dotty/tools/dotc/typer/Checking.scala | 10 ++++++---- compiler/src/dotty/tools/dotc/typer/Typer.scala | 2 +- .../tools/dotc/reporting/ErrorMessagesTests.scala | 2 +- 5 files changed, 10 insertions(+), 8 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/reporting/diagnostic/ErrorMessageID.java b/compiler/src/dotty/tools/dotc/reporting/diagnostic/ErrorMessageID.java index c9d60bdb772b..cc67c76c33b9 100644 --- a/compiler/src/dotty/tools/dotc/reporting/diagnostic/ErrorMessageID.java +++ b/compiler/src/dotty/tools/dotc/reporting/diagnostic/ErrorMessageID.java @@ -132,7 +132,7 @@ public enum ErrorMessageID { ImportRenamedTwiceID, TypeTestAlwaysSucceedsID, TermMemberNeedsNeedsResultTypeForImplicitSearchID, - CaseClassCannotExtendEnumID, + ClassCannotExtendEnumID, ValueClassParameterMayNotBeCallByNameID, NotAnExtractorID, MemberWithSameNameAsStaticID, diff --git a/compiler/src/dotty/tools/dotc/reporting/diagnostic/messages.scala b/compiler/src/dotty/tools/dotc/reporting/diagnostic/messages.scala index 47c4a34ad972..1197b295cc1c 100644 --- a/compiler/src/dotty/tools/dotc/reporting/diagnostic/messages.scala +++ b/compiler/src/dotty/tools/dotc/reporting/diagnostic/messages.scala @@ -2126,7 +2126,7 @@ 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 explanation: String = "" diff --git a/compiler/src/dotty/tools/dotc/typer/Checking.scala b/compiler/src/dotty/tools/dotc/typer/Checking.scala index a195b81c86b6..8022a3f77e43 100644 --- a/compiler/src/dotty/tools/dotc/typer/Checking.scala +++ b/compiler/src/dotty/tools/dotc/typer/Checking.scala @@ -901,14 +901,16 @@ 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) { + if (cls.is(Case)) + ctx.error(ClassCannotExtendEnum(cls, firstParent), cdef.sourcePos) + } } /** Check that all references coming from enum cases in an enum companion object @@ -975,7 +977,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 = () } diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 03d33f330927..7705b5c91d86 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -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) } diff --git a/compiler/test/dotty/tools/dotc/reporting/ErrorMessagesTests.scala b/compiler/test/dotty/tools/dotc/reporting/ErrorMessagesTests.scala index 394afa7a90d9..fdec58fa1933 100644 --- a/compiler/test/dotty/tools/dotc/reporting/ErrorMessagesTests.scala +++ b/compiler/test/dotty/tools/dotc/reporting/ErrorMessagesTests.scala @@ -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("", cls.owner.name.show) From 2dfd81afaff4c7127112535ec9f3ccbb7560b3f9 Mon Sep 17 00:00:00 2001 From: "Paolo G. Giarrusso" Date: Tue, 15 Jan 2019 18:49:40 +0100 Subject: [PATCH 2/2] Fix #5008: forbid any class from extending Enums --- .../dotty/tools/dotc/reporting/diagnostic/messages.scala | 2 +- compiler/src/dotty/tools/dotc/typer/Checking.scala | 7 ++++++- tests/neg/i5008.scala | 8 ++++++++ tests/pending/neg/i5008 | 2 -- 4 files changed, 15 insertions(+), 4 deletions(-) create mode 100644 tests/neg/i5008.scala delete mode 100644 tests/pending/neg/i5008 diff --git a/compiler/src/dotty/tools/dotc/reporting/diagnostic/messages.scala b/compiler/src/dotty/tools/dotc/reporting/diagnostic/messages.scala index 1197b295cc1c..6cbc0526b9d6 100644 --- a/compiler/src/dotty/tools/dotc/reporting/diagnostic/messages.scala +++ b/compiler/src/dotty/tools/dotc/reporting/diagnostic/messages.scala @@ -2128,7 +2128,7 @@ object messages { 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 = "" } diff --git a/compiler/src/dotty/tools/dotc/typer/Checking.scala b/compiler/src/dotty/tools/dotc/typer/Checking.scala index 8022a3f77e43..b787dc678367 100644 --- a/compiler/src/dotty/tools/dotc/typer/Checking.scala +++ b/compiler/src/dotty/tools/dotc/typer/Checking.scala @@ -908,7 +908,12 @@ trait Checking { cls.owner.isTerm && (cls.owner.flagsUNSAFE.is(Case) || cls.owner.name == nme.DOLLAR_NEW) if (!cdef.mods.isEnumCase && !isEnumAnonCls) { - if (cls.is(Case)) + // 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) } } diff --git a/tests/neg/i5008.scala b/tests/neg/i5008.scala new file mode 100644 index 000000000000..25e19f423982 --- /dev/null +++ b/tests/neg/i5008.scala @@ -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 diff --git a/tests/pending/neg/i5008 b/tests/pending/neg/i5008 deleted file mode 100644 index 2dc75e6b199c..000000000000 --- a/tests/pending/neg/i5008 +++ /dev/null @@ -1,2 +0,0 @@ -enum Foo {} -enum Bar extends Foo {} // error