Skip to content

Fix #5495: Reject invalid modifiers on enums #5525

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

Closed
wants to merge 3 commits into from
Closed
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
4 changes: 2 additions & 2 deletions compiler/src/dotty/tools/dotc/ast/DesugarEnums.scala
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,8 @@ object DesugarEnums {

/** Add implied flags to an enum class or an enum case */
def addEnumFlags(cdef: TypeDef)(implicit ctx: Context): TypeDef =
if (cdef.mods.isEnumClass) cdef.withMods(cdef.mods.withFlags(cdef.mods.flags | Abstract | Sealed))
else if (isEnumCase(cdef)) cdef.withMods(cdef.mods.withFlags(cdef.mods.flags | Final))
if (cdef.mods.isEnumClass) cdef.withMods(cdef.mods.toTypeFlags | Abstract | Sealed)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note the toTypeFlags here. It is to prevent a illegal flagset combination assertion failure that occurs when combining TermFlags with TypeFlags. For example when parsing:

lazy enum Foo {}

else if (isEnumCase(cdef)) cdef.withMods(cdef.mods | Final)
else cdef

private def valuesDot(name: String) = Select(Ident(nme.DOLLAR_VALUES), name.toTermName)
Expand Down
2 changes: 0 additions & 2 deletions compiler/src/dotty/tools/dotc/ast/untpd.scala
Original file line number Diff line number Diff line change
Expand Up @@ -131,8 +131,6 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo {
case class Lazy() extends Mod(Flags.Lazy)

case class Inline() extends Mod(Flags.Inline)

case class Enum() extends Mod(Flags.Enum)
}

/** Modifiers and annotations for definitions
Expand Down
16 changes: 12 additions & 4 deletions compiler/src/dotty/tools/dotc/parsing/Parsers.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2341,7 +2341,7 @@ object Parsers {
case CASEOBJECT =>
objectDef(start, posMods(start, mods | Case | Module))
case ENUM =>
enumDef(start, mods, atPos(in.skipToken()) { Mod.Enum() })
enumDef(start, posMods(start, mods | Enum))
case _ =>
syntaxErrorOrIncomplete(ExpectedStartOfTopLevelDefinition())
EmptyTree
Expand Down Expand Up @@ -2387,18 +2387,26 @@ object Parsers {

/** EnumDef ::= id ClassConstr [`extends' [ConstrApps]] EnumBody
*/
def enumDef(start: Offset, mods: Modifiers, enumMod: Mod): TypeDef = atPos(start, nameStart) {
def enumDef(start: Offset, mods: Modifiers): TypeDef = atPos(start, nameStart) {
val InvalidEnumClassModifiers = ModifierFlags &~ (Private | Protected)
if (mods.is(InvalidEnumClassModifiers))
syntaxError("Only access modifiers are allowed on enum definitions", mods.pos)

val mods1 = mods &~ InvalidEnumClassModifiers
val modName = ident()
val clsName = modName.toTypeName
val constr = classConstr(clsName)
val impl = templateOpt(constr, isEnum = true)
TypeDef(clsName, impl).withMods(addMod(mods, enumMod)).setComment(in.getDocComment(start))
TypeDef(clsName, impl).withMods(mods1).setComment(in.getDocComment(start))
}

/** EnumCase = `case' (id ClassConstr [`extends' ConstrApps] | ids)
*/
def enumCase(start: Offset, mods: Modifiers): DefTree = {
val mods1 = addMod(mods, atPos(in.offset)(Mod.Enum())) | Case
if (mods.is(ModifierFlags))
syntaxError("Modifiers are not allowed on enum cases", mods.pos)

val mods1 = mods &~ ModifierFlags | EnumCase
accept(CASE)

in.adjustSepRegions(ARROW)
Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/parsing/Tokens.scala
Original file line number Diff line number Diff line change
Expand Up @@ -236,7 +236,7 @@ object Tokens extends TokensCommon {

final val modifierTokensOrCase: TokenSet = modifierTokens | BitSet(CASE)

final val modifierFollowers = modifierTokens | defIntroTokens
final val modifierFollowers = modifierTokensOrCase | defIntroTokens

/** Is token only legal as start of statement (eof also included)? */
final val mustStartStatTokens: TokenSet = defIntroTokens | modifierTokens | BitSet(IMPORT, PACKAGE)
Expand Down
37 changes: 37 additions & 0 deletions tests/neg/i5495.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
abstract enum Foo1 {} // error: only access modifiers allowed
final enum Foo2 {} // error: only access modifiers allowed
sealed enum Foo3 {} // error: only access modifiers allowed
implicit enum Foo4 {} // error: only access modifiers allowed
lazy enum Foo5 {} // error: only access modifiers allowed
erased enum Foo6 {} // error: only access modifiers allowed
override enum Foo7 {} // error: only access modifiers allowed
inline enum Foo8 {} // error: only access modifiers allowed
opaque enum Foo9 {} // error: only access modifiers allowed

enum Foo10 {
abstract case C1() // error: no modifier allowed
final case C2() // error: no modifier allowed
sealed case C3() // error: no modifier allowed
implicit case C4() // error: no modifier allowed
lazy case C5() // error: no modifier allowed
erased case C6() // error: no modifier allowed
override case C7() // error: no modifier allowed
private case C8() // error: no modifier allowed
protected case C9() // error: no modifier allowed
inline case C10() // error: no modifier allowed
opaque case C11() // error: no modifier allowed
}

enum Foo11 {
abstract case C1 // error: no modifier allowed
final case C2 // error: no modifier allowed
sealed case C3 // error: no modifier allowed
implicit case C4 // error: no modifier allowed
lazy case C5 // error: no modifier allowed
erased case C6 // error: no modifier allowed
override case C7 // error: no modifier allowed
private case C8 // error: no modifier allowed
protected case C9 // error: no modifier allowed
inline case C10 // error: no modifier allowed
opaque case C11 // error: no modifier allowed
}
5 changes: 2 additions & 3 deletions tests/pos/i2906.scala
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
enum Foo {
case A
private case B
@deprecated("Will be removed") case C
@deprecated("Will be removed") private case D
@deprecated("Will be removed") case B
@deprecated("Will be removed") @volatile case C
}