diff --git a/compiler/src/dotty/tools/dotc/ast/Desugar.scala b/compiler/src/dotty/tools/dotc/ast/Desugar.scala index 17d5cac7ce0b..098920f7dbcb 100644 --- a/compiler/src/dotty/tools/dotc/ast/Desugar.scala +++ b/compiler/src/dotty/tools/dotc/ast/Desugar.scala @@ -488,7 +488,7 @@ object desugar { if (isEnum) { val (enumCases, enumStats) = stats.partition(DesugarEnums.isEnumCase) if (enumCases.isEmpty) - ctx.error("Enumerations must contain at least one case", namePos) + ctx.error(EnumerationsShouldNotBeEmpty(cdef), namePos) val enumCompanionRef = TermRefTree() val enumImport = Import(enumCompanionRef, enumCases.flatMap(caseIds).map(ImportSelector(_))) @@ -883,12 +883,12 @@ object desugar { def flagSourcePos(flag: FlagSet) = mods.mods.find(_.flags == flag).fold(mdef.sourcePos)(_.sourcePos) if (mods.is(Abstract)) - ctx.error(em"${hl("abstract")} modifier cannot be used for objects", flagSourcePos(Abstract)) + ctx.error(AbstractCannotBeUsedForObjects(mdef), flagSourcePos(Abstract)) if (mods.is(Sealed)) - ctx.error(em"${hl("sealed")} modifier is redundant for objects", flagSourcePos(Sealed)) + ctx.error(ModifierRedundantForObjects(mdef, "sealed"), flagSourcePos(Sealed)) // Maybe this should be an error; see https://github.com/scala/bug/issues/11094. if (mods.is(Final) && !mods.is(Synthetic)) - ctx.warning(em"${hl("final")} modifier is redundant for objects", flagSourcePos(Final)) + ctx.warning(ModifierRedundantForObjects(mdef, "final"), flagSourcePos(Final)) if (mods.is(Package)) packageModuleDef(mdef) diff --git a/compiler/src/dotty/tools/dotc/reporting/diagnostic/ErrorMessageID.scala b/compiler/src/dotty/tools/dotc/reporting/diagnostic/ErrorMessageID.scala index 41399a285620..c5e5f2168c16 100644 --- a/compiler/src/dotty/tools/dotc/reporting/diagnostic/ErrorMessageID.scala +++ b/compiler/src/dotty/tools/dotc/reporting/diagnostic/ErrorMessageID.scala @@ -152,7 +152,10 @@ enum ErrorMessageID extends java.lang.Enum[ErrorMessageID] { MissingTypeParameterInTypeAppID, ImplicitTypesCanOnlyBeFunctionTypesID, ErasedTypesCanOnlyBeFunctionTypesID, - CaseClassMissingNonImplicitParamListID + CaseClassMissingNonImplicitParamListID, + EnumerationsShouldNotBeEmptyID, + AbstractCannotBeUsedForObjectsID, + ModifierRedundantForObjectsID def errorNumber = ordinal - 2 } diff --git a/compiler/src/dotty/tools/dotc/reporting/diagnostic/messages.scala b/compiler/src/dotty/tools/dotc/reporting/diagnostic/messages.scala index 22fd2b9f89c6..7343c08d7250 100644 --- a/compiler/src/dotty/tools/dotc/reporting/diagnostic/messages.scala +++ b/compiler/src/dotty/tools/dotc/reporting/diagnostic/messages.scala @@ -2408,4 +2408,46 @@ object messages { | if you're aiming to have a case class parametrized only by implicit ones, you should | add an explicit ${hl("()")} as a parameter list to ${cdef.name}.""".stripMargin } + + case class EnumerationsShouldNotBeEmpty(cdef: untpd.TypeDef)(implicit ctx: Context) + extends Message(EnumerationsShouldNotBeEmptyID) { + val kind: String = "Syntax" + val msg: String = "Enumerations must contain at least one case" + + val explanation: String = + em"""|Enumeration ${cdef.name} must contain at least one case + |Example Usage: + | ${hl("enum")} ${cdef.name} { + | ${hl("case")} Option1, Option2 + | } + |""".stripMargin + } + + case class AbstractCannotBeUsedForObjects(mdef: untpd.ModuleDef)(implicit ctx: Context) + extends Message(AbstractCannotBeUsedForObjectsID) { + val kind: String = "Syntax" + val msg: String = em"${hl("abstract")} modifier cannot be used for objects" + + val explanation: String = + em"""|Objects are final and cannot be extended, thus cannot have the ${hl("abstract")} modifier + | + |You may want to define an abstract class: + | ${hl("abstract")} ${hl("class")} Abstract${mdef.name} { } + | + |And extend it in an object: + | ${hl("object")} ${mdef.name} ${hl("extends")} Abstract${mdef.name} { } + |""".stripMargin + } + + case class ModifierRedundantForObjects(mdef: untpd.ModuleDef, modifier: String)(implicit ctx: Context) + extends Message(ModifierRedundantForObjectsID) { + val kind: String = "Syntax" + val msg: String = em"${hl(modifier)} modifier is redundant for objects" + + val explanation: String = + em"""|Objects cannot be extended making the ${hl(modifier)} modifier redundant. + |You may want to define the object without it: + | ${hl("object")} ${mdef.name} { } + |""".stripMargin + } } diff --git a/compiler/test/dotty/tools/dotc/reporting/ErrorMessagesTests.scala b/compiler/test/dotty/tools/dotc/reporting/ErrorMessagesTests.scala index bf97994f1170..5eb8dcf32fc2 100644 --- a/compiler/test/dotty/tools/dotc/reporting/ErrorMessagesTests.scala +++ b/compiler/test/dotty/tools/dotc/reporting/ErrorMessagesTests.scala @@ -1738,4 +1738,49 @@ class ErrorMessagesTests extends ErrorMessagesTest { val CaseClassMissingNonImplicitParamList(tpe) :: Nil = messages assertEquals("A case class must have at least one non-implicit parameter list", messages.head.msg) } + + @Test def enumMustContainOneCase = + checkMessagesAfter(RefChecks.name) { + """ + |enum Foo { } + """.stripMargin + } + .expect { (ictx, messages) ⇒ + implicit val ctx: Context = ictx + assertMessageCount(1, messages) + val errorMsg = messages.head.msg + val EnumerationsShouldNotBeEmpty(typeDef) :: Nil = messages + assertEquals("Enumerations must contain at least one case", errorMsg) + assertEquals("Foo", typeDef.name.toString) + } + + @Test def objectsCannotBeAbstract = + checkMessagesAfter(RefChecks.name) { + """ + |abstract object Foo { } + """.stripMargin + } + .expect { (ictx, messages) ⇒ + implicit val ctx: Context = ictx + assertMessageCount(1, messages) + val errorMsg = messages.head.msg + val AbstractCannotBeUsedForObjects(mdef) :: Nil = messages + assertEquals("abstract modifier cannot be used for objects", errorMsg) + assertEquals("Foo", mdef.name.toString) + } + + @Test def sealedOnObjectsIsRedundant = + checkMessagesAfter(RefChecks.name) { + """ + |sealed object Foo { } + """.stripMargin + } + .expect { (ictx, messages) ⇒ + implicit val ctx: Context = ictx + assertMessageCount(1, messages) + val errorMsg = messages.head.msg + val ModifierRedundantForObjects(mdef, "sealed") :: Nil = messages + assertEquals("sealed modifier is redundant for objects", errorMsg) + assertEquals("Foo", mdef.name.toString) + } } diff --git a/tests/neg/AbstractObject.check b/tests/neg/AbstractObject.check new file mode 100644 index 000000000000..4f3200ba2439 --- /dev/null +++ b/tests/neg/AbstractObject.check @@ -0,0 +1,6 @@ +-- [E146] Syntax Error: tests/neg/AbstractObject.scala:1:0 ------------------------------------------------------------- +1 |abstract object A {} // error + |^^^^^^^^ + |abstract modifier cannot be used for objects + +longer explanation available when compiling with `-explain` diff --git a/tests/neg/AbstractObject.scala b/tests/neg/AbstractObject.scala new file mode 100644 index 000000000000..0abd82d2a7b5 --- /dev/null +++ b/tests/neg/AbstractObject.scala @@ -0,0 +1 @@ +abstract object A {} // error diff --git a/tests/neg/EmptyEnum.check b/tests/neg/EmptyEnum.check new file mode 100644 index 000000000000..91281e6904e7 --- /dev/null +++ b/tests/neg/EmptyEnum.check @@ -0,0 +1,6 @@ +-- [E145] Syntax Error: tests/neg/EmptyEnum.scala:1:5 ------------------------------------------------------------------ +1 |enum EmptyEnum {} // error + | ^^^^^^^^^ + | Enumerations must contain at least one case + +longer explanation available when compiling with `-explain` diff --git a/tests/neg/EmptyEnum.scala b/tests/neg/EmptyEnum.scala new file mode 100644 index 000000000000..30abb3c34986 --- /dev/null +++ b/tests/neg/EmptyEnum.scala @@ -0,0 +1 @@ +enum EmptyEnum {} // error \ No newline at end of file diff --git a/tests/neg/SealedObject.check b/tests/neg/SealedObject.check new file mode 100644 index 000000000000..d63079f07a24 --- /dev/null +++ b/tests/neg/SealedObject.check @@ -0,0 +1,6 @@ +-- [E147] Syntax Error: tests/neg/SealedObject.scala:1:0 --------------------------------------------------------------- +1 |sealed object A {} // error + |^^^^^^ + |sealed modifier is redundant for objects + +longer explanation available when compiling with `-explain` diff --git a/tests/neg/SealedObject.scala b/tests/neg/SealedObject.scala new file mode 100644 index 000000000000..ecee2ea4857a --- /dev/null +++ b/tests/neg/SealedObject.scala @@ -0,0 +1 @@ +sealed object A {} // error \ No newline at end of file