From 7d369e2b7f3b2e4a437e52bb94bedbae87d12555 Mon Sep 17 00:00:00 2001 From: odersky Date: Fri, 22 Aug 2025 14:15:28 +0200 Subject: [PATCH 1/2] Allow into on enums --- .../dotty/tools/dotc/parsing/Parsers.scala | 9 +- docs/_docs/reference/enums/enums.md | 3 + tests/neg/i23400.scala | 2 +- tests/neg/i5525.check | 114 ++++++++++++++++++ tests/neg/i5525.scala | 16 +++ tests/pos/infix-enum.scala | 5 + 6 files changed, 145 insertions(+), 4 deletions(-) create mode 100644 tests/neg/i5525.check create mode 100644 tests/pos/infix-enum.scala diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index 9d53de5a2482..e14cf1604d34 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -4266,10 +4266,13 @@ object Parsers { private def checkAccessOnly(mods: Modifiers, where: String): Modifiers = // We allow `infix to mark the `enum`s type as infix. // Syntax rules disallow the soft infix modifier on `case`s. - val mods1 = mods & (AccessFlags | Enum | Infix) - if mods1 ne mods then + + val flags = mods.flags.toTypeFlags + val flags1 = flags & (AccessFlags | Enum | Infix | Into).toTypeFlags + if flags1 != flags then syntaxError(em"Only access modifiers are allowed on enum $where") - mods1 + mods.withFlags(flags1) + else mods /** EnumDef ::= id ClassConstr InheritClauses EnumBody */ diff --git a/docs/_docs/reference/enums/enums.md b/docs/_docs/reference/enums/enums.md index 4cad29cbd76a..3bd40f88c368 100644 --- a/docs/_docs/reference/enums/enums.md +++ b/docs/_docs/reference/enums/enums.md @@ -117,6 +117,9 @@ The fields referenced by `Mercury` are not visible, and the fields referenced by be referenced directly (using `import Planet.*`). You must use an indirect reference, such as demonstrated with `Earth`. +Enum cases accept only access modifiers. +Enum classes accept only access modifiers and `into` or `infix`. + ## Deprecation of Enum Cases As a library author, you may want to signal that an enum case is no longer intended for use. However you could still want to gracefully handle the removal of a case from your public API, such as special casing deprecated cases. diff --git a/tests/neg/i23400.scala b/tests/neg/i23400.scala index 08c75d279b87..99a41d7a072b 100644 --- a/tests/neg/i23400.scala +++ b/tests/neg/i23400.scala @@ -14,7 +14,7 @@ case class Baz(foo: MyInto[Foo]) given Conversion[Int, Foo] = Foo(_) -into enum Color: // error +into enum Color: // ok case Red, Green def test = diff --git a/tests/neg/i5525.check b/tests/neg/i5525.check new file mode 100644 index 000000000000..ebbcd7542d60 --- /dev/null +++ b/tests/neg/i5525.check @@ -0,0 +1,114 @@ +-- Error: tests/neg/i5525.scala:1:14 ----------------------------------------------------------------------------------- +1 |abstract enum Foo1 {} // error: only access modifiers allowed + | ^^^^ + | Only access modifiers are allowed on enum definitions +-- Error: tests/neg/i5525.scala:2:14 ----------------------------------------------------------------------------------- +2 |final enum Foo2 {} // error: only access modifiers allowed + | ^^^^ + | Only access modifiers are allowed on enum definitions +-- Error: tests/neg/i5525.scala:3:14 ----------------------------------------------------------------------------------- +3 |sealed enum Foo3 {} // error: only access modifiers allowed + | ^^^^ + | Only access modifiers are allowed on enum definitions +-- Error: tests/neg/i5525.scala:4:14 ----------------------------------------------------------------------------------- +4 |implicit enum Foo4 {} // error: only access modifiers allowed + | ^^^^ + | Only access modifiers are allowed on enum definitions +-- Error: tests/neg/i5525.scala:5:14 ----------------------------------------------------------------------------------- +5 |lazy enum Foo5 {} // error: only access modifiers allowed + | ^^^^ + | Only access modifiers are allowed on enum definitions +-- Error: tests/neg/i5525.scala:6:14 ----------------------------------------------------------------------------------- +6 |override enum Foo7 {} // error: only access modifiers allowed + | ^^^^ + | Only access modifiers are allowed on enum definitions +-- Error: tests/neg/i5525.scala:7:14 ----------------------------------------------------------------------------------- +7 |inline enum Foo8 {} // error: only access modifiers allowed + | ^^^^ + | Only access modifiers are allowed on enum definitions +-- Error: tests/neg/i5525.scala:8:14 ----------------------------------------------------------------------------------- +8 |opaque enum Foo9 {} // error: only access modifiers allowed + | ^^^^ + | Only access modifiers are allowed on enum definitions +-- Error: tests/neg/i5525.scala:11:12 ---------------------------------------------------------------------------------- +11 | abstract case C1() // error: only access modifiers allowed + | ^^^^ + | Only access modifiers are allowed on enum cases +-- Error: tests/neg/i5525.scala:12:12 ---------------------------------------------------------------------------------- +12 | final case C2() // error: only access modifiers allowed + | ^^^^ + | Only access modifiers are allowed on enum cases +-- Error: tests/neg/i5525.scala:13:12 ---------------------------------------------------------------------------------- +13 | sealed case C3() // error: only access modifiers allowed + | ^^^^ + | Only access modifiers are allowed on enum cases +-- Error: tests/neg/i5525.scala:14:12 ---------------------------------------------------------------------------------- +14 | implicit case C4() // error: only access modifiers allowed + | ^^^^ + | Only access modifiers are allowed on enum cases +-- Error: tests/neg/i5525.scala:15:12 ---------------------------------------------------------------------------------- +15 | lazy case C5() // error: only access modifiers allowed + | ^^^^ + | Only access modifiers are allowed on enum cases +-- Error: tests/neg/i5525.scala:16:12 ---------------------------------------------------------------------------------- +16 | override case C7() // error: only access modifiers allowed + | ^^^^ + | Only access modifiers are allowed on enum cases +-- Error: tests/neg/i5525.scala:22:12 ---------------------------------------------------------------------------------- +22 | abstract case C1 // error: only access modifiers allowed + | ^^^^ + | Only access modifiers are allowed on enum cases +-- Error: tests/neg/i5525.scala:23:12 ---------------------------------------------------------------------------------- +23 | final case C2 // error: only access modifiers allowed + | ^^^^ + | Only access modifiers are allowed on enum cases +-- Error: tests/neg/i5525.scala:24:12 ---------------------------------------------------------------------------------- +24 | sealed case C3 // error: only access modifiers allowed + | ^^^^ + | Only access modifiers are allowed on enum cases +-- Error: tests/neg/i5525.scala:25:12 ---------------------------------------------------------------------------------- +25 | implicit case C4 // error: only access modifiers allowed + | ^^^^ + | Only access modifiers are allowed on enum cases +-- Error: tests/neg/i5525.scala:26:12 ---------------------------------------------------------------------------------- +26 | lazy case C5 // error: only access modifiers allowed + | ^^^^ + | Only access modifiers are allowed on enum cases +-- Error: tests/neg/i5525.scala:27:12 ---------------------------------------------------------------------------------- +27 | override case C7 // error: only access modifiers allowed + | ^^^^ + | Only access modifiers are allowed on enum cases +-- Error: tests/neg/i5525.scala:33:12 ---------------------------------------------------------------------------------- +33 | inline case C10() // error: only access modifiers allowed + | ^^^^ + | end of statement expected but 'case' found +-- Error: tests/neg/i5525.scala:36:11 ---------------------------------------------------------------------------------- +36 |final enum Foo13 { // error: only access modifiers and `into` allowed + | ^^^^^ + | Only access modifiers are allowed on enum definitions +-- Error: tests/neg/i5525.scala:42:8 ----------------------------------------------------------------------------------- +42 | infix case C2 extends Foo14[Int, Int] // error // error + | ^^^^ + | end of statement expected but 'case' found +-- Error: tests/neg/i5525.scala:49:7 ----------------------------------------------------------------------------------- +49 | into case C1 // error + | ^^^^ + | end of statement expected but 'case' found +-- [E145] Syntax Error: tests/neg/i5525.scala:32:5 --------------------------------------------------------------------- +32 |enum Foo12 { // error: Enumerations must contain at least one case + | ^^^^^ + | Enumerations must contain at least one case + | + | longer explanation available when compiling with `-explain` +-- [E006] Not Found Error: tests/neg/i5525.scala:42:2 ------------------------------------------------------------------ +42 | infix case C2 extends Foo14[Int, Int] // error // error + | ^^^^^ + | Not found: infix + | + | longer explanation available when compiling with `-explain` +-- [E129] Potential Issue Warning: tests/neg/i5525.scala:49:2 ---------------------------------------------------------- +49 | into case C1 // error + | ^^^^ + | A pure expression does nothing in statement position + | + | longer explanation available when compiling with `-explain` diff --git a/tests/neg/i5525.scala b/tests/neg/i5525.scala index 12ffb4704ba9..9e361050eebb 100644 --- a/tests/neg/i5525.scala +++ b/tests/neg/i5525.scala @@ -31,4 +31,20 @@ enum Foo11 { enum Foo12 { // error: Enumerations must contain at least one case inline case C10() // error: only access modifiers allowed +} + +final enum Foo13 { // error: only access modifiers and `into` allowed + case C1 +} + +infix enum Foo14[A, B]{ // OK + case C1 extends Foo14[Int, Int] + infix case C2 extends Foo14[Int, Int] // error // error +} + +import language.experimental.into + +into enum Foo15 { // OK + case C0 + into case C1 // error } \ No newline at end of file diff --git a/tests/pos/infix-enum.scala b/tests/pos/infix-enum.scala new file mode 100644 index 000000000000..35f46bcc6e14 --- /dev/null +++ b/tests/pos/infix-enum.scala @@ -0,0 +1,5 @@ +infix enum Foo[A, B]: + case C1 extends Foo[Int, Int] + +val x: Int Foo Int = Foo.C1 + From 8005ac31a1f4aa5faf29e5bd22155efbfee088e3 Mon Sep 17 00:00:00 2001 From: odersky Date: Fri, 22 Aug 2025 15:13:27 +0200 Subject: [PATCH 2/2] Improve error message for disallowed modifiers on enums --- .../dotty/tools/dotc/parsing/Parsers.scala | 24 +-- tests/neg/i5525.check | 142 +++++++++--------- tests/neg/i5525.scala | 16 +- tests/neg/i5525b.scala | 2 +- 4 files changed, 92 insertions(+), 92 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index e14cf1604d34..c4a77f17060c 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -4263,21 +4263,21 @@ object Parsers { finalizeDef(ModuleDef(name, templ), mods, start) } - private def checkAccessOnly(mods: Modifiers, where: String): Modifiers = - // We allow `infix to mark the `enum`s type as infix. - // Syntax rules disallow the soft infix modifier on `case`s. - - val flags = mods.flags.toTypeFlags - val flags1 = flags & (AccessFlags | Enum | Infix | Into).toTypeFlags - if flags1 != flags then - syntaxError(em"Only access modifiers are allowed on enum $where") - mods.withFlags(flags1) - else mods + private def checkAccessOnly(mods: Modifiers, caseStr: String): Modifiers = + // We allow `infix` and `into` on `enum` definitions. + // Syntax rules disallow these soft infix modifiers on `case`s. + val flags = mods.flags + var flags1 = flags + for mod <- mods.mods do + if !mod.flags.isOneOf(AccessFlags | Enum | Infix | Into) then + syntaxError(em"This modifier is not allowed on an enum$caseStr", mod.span) + flags1 = flags1 &~ mod.flags + if flags1 != flags then mods.withFlags(flags1) else mods /** EnumDef ::= id ClassConstr InheritClauses EnumBody */ def enumDef(start: Offset, mods: Modifiers): TypeDef = atSpan(start, nameStart) { - val mods1 = checkAccessOnly(mods, "definitions") + val mods1 = checkAccessOnly(mods, "") val modulName = ident() val clsName = modulName.toTypeName val constr = classConstr(ParamOwner.Class) @@ -4288,7 +4288,7 @@ object Parsers { /** EnumCase = `case' (id ClassConstr [`extends' ConstrApps] | ids) */ def enumCase(start: Offset, mods: Modifiers): DefTree = { - val mods1 = checkAccessOnly(mods, "cases") | EnumCase + val mods1 = checkAccessOnly(mods, " case") | EnumCase accept(CASE) atSpan(start, nameStart) { diff --git a/tests/neg/i5525.check b/tests/neg/i5525.check index ebbcd7542d60..81e352cd368f 100644 --- a/tests/neg/i5525.check +++ b/tests/neg/i5525.check @@ -1,91 +1,91 @@ --- Error: tests/neg/i5525.scala:1:14 ----------------------------------------------------------------------------------- -1 |abstract enum Foo1 {} // error: only access modifiers allowed - | ^^^^ - | Only access modifiers are allowed on enum definitions --- Error: tests/neg/i5525.scala:2:14 ----------------------------------------------------------------------------------- -2 |final enum Foo2 {} // error: only access modifiers allowed - | ^^^^ - | Only access modifiers are allowed on enum definitions --- Error: tests/neg/i5525.scala:3:14 ----------------------------------------------------------------------------------- -3 |sealed enum Foo3 {} // error: only access modifiers allowed - | ^^^^ - | Only access modifiers are allowed on enum definitions --- Error: tests/neg/i5525.scala:4:14 ----------------------------------------------------------------------------------- -4 |implicit enum Foo4 {} // error: only access modifiers allowed - | ^^^^ - | Only access modifiers are allowed on enum definitions --- Error: tests/neg/i5525.scala:5:14 ----------------------------------------------------------------------------------- -5 |lazy enum Foo5 {} // error: only access modifiers allowed - | ^^^^ - | Only access modifiers are allowed on enum definitions --- Error: tests/neg/i5525.scala:6:14 ----------------------------------------------------------------------------------- -6 |override enum Foo7 {} // error: only access modifiers allowed - | ^^^^ - | Only access modifiers are allowed on enum definitions --- Error: tests/neg/i5525.scala:7:14 ----------------------------------------------------------------------------------- -7 |inline enum Foo8 {} // error: only access modifiers allowed - | ^^^^ - | Only access modifiers are allowed on enum definitions --- Error: tests/neg/i5525.scala:8:14 ----------------------------------------------------------------------------------- -8 |opaque enum Foo9 {} // error: only access modifiers allowed - | ^^^^ - | Only access modifiers are allowed on enum definitions --- Error: tests/neg/i5525.scala:11:12 ---------------------------------------------------------------------------------- +-- Error: tests/neg/i5525.scala:1:0 ------------------------------------------------------------------------------------ +1 |abstract enum Foo1 { case C } // error: only access modifiers allowed + |^^^^^^^^ + |This modifier is not allowed on an enum +-- Error: tests/neg/i5525.scala:2:0 ------------------------------------------------------------------------------------ +2 |final enum Foo2 { case C } // error: only access modifiers allowed + |^^^^^ + |This modifier is not allowed on an enum +-- Error: tests/neg/i5525.scala:3:0 ------------------------------------------------------------------------------------ +3 |sealed enum Foo3 { case C } // error: only access modifiers allowed + |^^^^^^ + |This modifier is not allowed on an enum +-- Error: tests/neg/i5525.scala:4:0 ------------------------------------------------------------------------------------ +4 |implicit enum Foo4 { case C } // error: only access modifiers allowed + |^^^^^^^^ + |This modifier is not allowed on an enum +-- Error: tests/neg/i5525.scala:5:0 ------------------------------------------------------------------------------------ +5 |lazy enum Foo5 { case C } // error: only access modifiers allowed + |^^^^ + |This modifier is not allowed on an enum +-- Error: tests/neg/i5525.scala:6:0 ------------------------------------------------------------------------------------ +6 |override enum Foo7 { case C } // error: only access modifiers allowed + |^^^^^^^^ + |This modifier is not allowed on an enum +-- Error: tests/neg/i5525.scala:7:0 ------------------------------------------------------------------------------------ +7 |inline enum Foo8 { case C } // error: only access modifiers allowed + |^^^^^^ + |This modifier is not allowed on an enum +-- Error: tests/neg/i5525.scala:8:0 ------------------------------------------------------------------------------------ +8 |opaque enum Foo9 { case C } // error: only access modifiers allowed + |^^^^^^ + |This modifier is not allowed on an enum +-- Error: tests/neg/i5525.scala:11:2 ----------------------------------------------------------------------------------- 11 | abstract case C1() // error: only access modifiers allowed - | ^^^^ - | Only access modifiers are allowed on enum cases --- Error: tests/neg/i5525.scala:12:12 ---------------------------------------------------------------------------------- + | ^^^^^^^^ + | This modifier is not allowed on an enum case +-- Error: tests/neg/i5525.scala:12:2 ----------------------------------------------------------------------------------- 12 | final case C2() // error: only access modifiers allowed - | ^^^^ - | Only access modifiers are allowed on enum cases --- Error: tests/neg/i5525.scala:13:12 ---------------------------------------------------------------------------------- + | ^^^^^ + | This modifier is not allowed on an enum case +-- Error: tests/neg/i5525.scala:13:2 ----------------------------------------------------------------------------------- 13 | sealed case C3() // error: only access modifiers allowed - | ^^^^ - | Only access modifiers are allowed on enum cases --- Error: tests/neg/i5525.scala:14:12 ---------------------------------------------------------------------------------- + | ^^^^^^ + | This modifier is not allowed on an enum case +-- Error: tests/neg/i5525.scala:14:2 ----------------------------------------------------------------------------------- 14 | implicit case C4() // error: only access modifiers allowed - | ^^^^ - | Only access modifiers are allowed on enum cases --- Error: tests/neg/i5525.scala:15:12 ---------------------------------------------------------------------------------- + | ^^^^^^^^ + | This modifier is not allowed on an enum case +-- Error: tests/neg/i5525.scala:15:2 ----------------------------------------------------------------------------------- 15 | lazy case C5() // error: only access modifiers allowed - | ^^^^ - | Only access modifiers are allowed on enum cases --- Error: tests/neg/i5525.scala:16:12 ---------------------------------------------------------------------------------- + | ^^^^ + | This modifier is not allowed on an enum case +-- Error: tests/neg/i5525.scala:16:2 ----------------------------------------------------------------------------------- 16 | override case C7() // error: only access modifiers allowed - | ^^^^ - | Only access modifiers are allowed on enum cases --- Error: tests/neg/i5525.scala:22:12 ---------------------------------------------------------------------------------- + | ^^^^^^^^ + | This modifier is not allowed on an enum case +-- Error: tests/neg/i5525.scala:22:2 ----------------------------------------------------------------------------------- 22 | abstract case C1 // error: only access modifiers allowed - | ^^^^ - | Only access modifiers are allowed on enum cases --- Error: tests/neg/i5525.scala:23:12 ---------------------------------------------------------------------------------- + | ^^^^^^^^ + | This modifier is not allowed on an enum case +-- Error: tests/neg/i5525.scala:23:2 ----------------------------------------------------------------------------------- 23 | final case C2 // error: only access modifiers allowed - | ^^^^ - | Only access modifiers are allowed on enum cases --- Error: tests/neg/i5525.scala:24:12 ---------------------------------------------------------------------------------- + | ^^^^^ + | This modifier is not allowed on an enum case +-- Error: tests/neg/i5525.scala:24:2 ----------------------------------------------------------------------------------- 24 | sealed case C3 // error: only access modifiers allowed - | ^^^^ - | Only access modifiers are allowed on enum cases --- Error: tests/neg/i5525.scala:25:12 ---------------------------------------------------------------------------------- + | ^^^^^^ + | This modifier is not allowed on an enum case +-- Error: tests/neg/i5525.scala:25:2 ----------------------------------------------------------------------------------- 25 | implicit case C4 // error: only access modifiers allowed - | ^^^^ - | Only access modifiers are allowed on enum cases --- Error: tests/neg/i5525.scala:26:12 ---------------------------------------------------------------------------------- + | ^^^^^^^^ + | This modifier is not allowed on an enum case +-- Error: tests/neg/i5525.scala:26:2 ----------------------------------------------------------------------------------- 26 | lazy case C5 // error: only access modifiers allowed - | ^^^^ - | Only access modifiers are allowed on enum cases --- Error: tests/neg/i5525.scala:27:12 ---------------------------------------------------------------------------------- + | ^^^^ + | This modifier is not allowed on an enum case +-- Error: tests/neg/i5525.scala:27:2 ----------------------------------------------------------------------------------- 27 | override case C7 // error: only access modifiers allowed - | ^^^^ - | Only access modifiers are allowed on enum cases + | ^^^^^^^^ + | This modifier is not allowed on an enum case -- Error: tests/neg/i5525.scala:33:12 ---------------------------------------------------------------------------------- 33 | inline case C10() // error: only access modifiers allowed | ^^^^ | end of statement expected but 'case' found --- Error: tests/neg/i5525.scala:36:11 ---------------------------------------------------------------------------------- +-- Error: tests/neg/i5525.scala:36:0 ----------------------------------------------------------------------------------- 36 |final enum Foo13 { // error: only access modifiers and `into` allowed - | ^^^^^ - | Only access modifiers are allowed on enum definitions + |^^^^^ + |This modifier is not allowed on an enum -- Error: tests/neg/i5525.scala:42:8 ----------------------------------------------------------------------------------- 42 | infix case C2 extends Foo14[Int, Int] // error // error | ^^^^ diff --git a/tests/neg/i5525.scala b/tests/neg/i5525.scala index 9e361050eebb..eff8d07fdddd 100644 --- a/tests/neg/i5525.scala +++ b/tests/neg/i5525.scala @@ -1,11 +1,11 @@ -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 -override enum Foo7 {} // error: only access modifiers allowed -inline enum Foo8 {} // error: only access modifiers allowed -opaque enum Foo9 {} // error: only access modifiers allowed +abstract enum Foo1 { case C } // error: only access modifiers allowed +final enum Foo2 { case C } // error: only access modifiers allowed +sealed enum Foo3 { case C } // error: only access modifiers allowed +implicit enum Foo4 { case C } // error: only access modifiers allowed +lazy enum Foo5 { case C } // error: only access modifiers allowed +override enum Foo7 { case C } // error: only access modifiers allowed +inline enum Foo8 { case C } // error: only access modifiers allowed +opaque enum Foo9 { case C } // error: only access modifiers allowed enum Foo10 { abstract case C1() // error: only access modifiers allowed diff --git a/tests/neg/i5525b.scala b/tests/neg/i5525b.scala index d51564ad52c1..78bad61e3d50 100644 --- a/tests/neg/i5525b.scala +++ b/tests/neg/i5525b.scala @@ -1,6 +1,6 @@ //> using options -language:experimental.erasedDefinitions -erased enum Foo6 {} // error: only access modifiers allowed +erased enum Foo6 { case C } // error: only access modifiers allowed enum Foo10 { // error: Enumerations must contain at least one case erased case C6() // error // error