Skip to content

Commit 283b095

Browse files
authored
Merge pull request #7850 from dotty-staging/fix-#5495
Fix #5495: Disallow lazy enums
2 parents 54a54d2 + 56c1f52 commit 283b095

File tree

10 files changed

+81
-68
lines changed

10 files changed

+81
-68
lines changed

compiler/src/dotty/tools/dotc/ast/DesugarEnums.scala

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -70,8 +70,8 @@ object DesugarEnums {
7070

7171
/** Add implied flags to an enum class or an enum case */
7272
def addEnumFlags(cdef: TypeDef)(implicit ctx: Context): TypeDef =
73-
if (cdef.mods.isEnumClass) cdef.withMods(cdef.mods.withFlags(cdef.mods.flags | Abstract | Sealed))
74-
else if (isEnumCase(cdef)) cdef.withMods(cdef.mods.withFlags(cdef.mods.flags | Final))
73+
if (cdef.mods.isEnumClass) cdef.withMods(cdef.mods.withAddedFlags(Abstract | Sealed, cdef.span))
74+
else if (isEnumCase(cdef)) cdef.withMods(cdef.mods.withAddedFlags(Final, cdef.span))
7575
else cdef
7676

7777
private def valuesDot(name: PreName)(implicit src: SourceFile) =
@@ -288,7 +288,7 @@ object DesugarEnums {
288288
val toStringDef = toStringMethLit(name.toString)
289289
val impl1 = cpy.Template(impl)(body = List(ordinalDef, toStringDef) ++ registerCall)
290290
.withAttachment(ExtendsSingletonMirror, ())
291-
val vdef = ValDef(name, TypeTree(), New(impl1)).withMods(mods | EnumValue)
291+
val vdef = ValDef(name, TypeTree(), New(impl1)).withMods(mods.withAddedFlags(EnumValue, span))
292292
flatTree(scaffolding ::: vdef :: Nil).withSpan(span)
293293
}
294294
}
@@ -304,7 +304,7 @@ object DesugarEnums {
304304
else {
305305
val (tag, scaffolding) = nextOrdinal(CaseKind.Simple)
306306
val creator = Apply(Ident(nme.DOLLAR_NEW), List(Literal(Constant(tag)), Literal(Constant(name.toString))))
307-
val vdef = ValDef(name, enumClassRef, creator).withMods(mods | EnumValue)
307+
val vdef = ValDef(name, enumClassRef, creator).withMods(mods.withAddedFlags(EnumValue, span))
308308
flatTree(scaffolding ::: vdef :: Nil).withSpan(span)
309309
}
310310
}

compiler/src/dotty/tools/dotc/ast/untpd.scala

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,11 @@ import core._
66
import Types._, Contexts._, Constants._, Names._, Flags._
77
import Symbols._, StdNames._, Trees._
88
import util.{Property, SourceFile, NoSource}
9+
import util.Spans.Span
910
import language.higherKinds
1011
import annotation.constructorOnly
1112
import annotation.internal.sharable
13+
import Decorators._
1214

1315
object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo {
1416

@@ -233,6 +235,25 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo {
233235
if (mods.exists(_ eq mod)) this
234236
else withMods(mods :+ mod)
235237

238+
private def compatible(flags1: FlagSet, flags2: FlagSet): Boolean =
239+
flags1.isEmpty || flags2.isEmpty
240+
|| flags1.isTermFlags && flags2.isTermFlags
241+
|| flags1.isTypeFlags && flags2.isTypeFlags
242+
243+
/** Add `flags` to thos modifier set, checking that there are no type/term conflicts.
244+
* If there are conflicts, issue an error and return the modifiers consisting of
245+
* the added flags only. The reason to do it this way is that the added flags usually
246+
* describe the core of a construct whereas the existing set are the modifiers
247+
* given in the source.
248+
*/
249+
def withAddedFlags(flags: FlagSet, span: Span)(given ctx: Context): Modifiers =
250+
if this.flags.isAllOf(flags) then this
251+
else if compatible(this.flags, flags) then this | flags
252+
else
253+
val what = if flags.isTermFlags then "values" else "types"
254+
ctx.error(em"${(flags & ModifierFlags).flagsString} $what cannot be ${this.flags.flagsString}", ctx.source.atSpan(span))
255+
Modifiers(flags)
256+
236257
/** Modifiers with given list of Mods. It is checked that
237258
* all modifiers are already accounted for in `flags` and `privateWithin`.
238259
*/

compiler/src/dotty/tools/dotc/core/Flags.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -339,7 +339,7 @@ object Flags {
339339
val (_, DefaultMethod @ _, _) = newFlags(38, "<defaultmethod>")
340340

341341
/** Symbol is an enum class or enum case (if used with case) */
342-
val (Enum @ _, _, _) = newFlags(40, "<enum>")
342+
val (Enum @ _, _, _) = newFlags(40, "enum")
343343

344344
/** An export forwarder */
345345
val (Exported @ _, _, _) = newFlags(41, "exported")

compiler/src/dotty/tools/dotc/parsing/Parsers.scala

Lines changed: 11 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -2631,23 +2631,8 @@ object Parsers {
26312631
addMod(mods, mod)
26322632
}
26332633

2634-
private def compatible(flags1: FlagSet, flags2: FlagSet): Boolean = (
2635-
flags1.isEmpty
2636-
|| flags2.isEmpty
2637-
|| flags1.isTermFlags && flags2.isTermFlags
2638-
|| flags1.isTypeFlags && flags2.isTypeFlags
2639-
)
2640-
2641-
def addFlag(mods: Modifiers, flag: FlagSet): Modifiers = {
2642-
def getPrintableTypeFromFlagSet =
2643-
Map(Trait -> "trait", Method -> "method", Mutable -> "variable").get(flag)
2644-
2645-
if (compatible(mods.flags, flag)) mods | flag
2646-
else {
2647-
syntaxError(ModifiersNotAllowed(mods.flags, getPrintableTypeFromFlagSet))
2648-
Modifiers(flag)
2649-
}
2650-
}
2634+
def addFlag(mods: Modifiers, flag: FlagSet): Modifiers =
2635+
mods.withAddedFlags(flag, Span(in.offset))
26512636

26522637
/** Always add the syntactic `mod`, but check and conditionally add semantic `mod.flags`
26532638
*/
@@ -3345,22 +3330,29 @@ object Parsers {
33453330
}
33463331
}
33473332

3333+
private def checkAccessOnly(mods: Modifiers, where: String): Modifiers =
3334+
val mods1 = mods & (AccessFlags | Enum)
3335+
if mods1 ne mods then
3336+
syntaxError(s"Only access modifiers are allowed on enum $where")
3337+
mods1
3338+
33483339
/** EnumDef ::= id ClassConstr InheritClauses [‘with’] EnumBody
33493340
*/
33503341
def enumDef(start: Offset, mods: Modifiers): TypeDef = atSpan(start, nameStart) {
3342+
val mods1 = checkAccessOnly(mods, "definitions")
33513343
val modulName = ident()
33523344
in.endMarkerScope(modulName) {
33533345
val clsName = modulName.toTypeName
33543346
val constr = classConstr()
33553347
val templ = template(constr, isEnum = true)
3356-
finalizeDef(TypeDef(clsName, templ), mods, start)
3348+
finalizeDef(TypeDef(clsName, templ), mods1, start)
33573349
}
33583350
}
33593351

33603352
/** EnumCase = `case' (id ClassConstr [`extends' ConstrApps] | ids)
33613353
*/
33623354
def enumCase(start: Offset, mods: Modifiers): DefTree = {
3363-
val mods1 = mods | EnumCase
3355+
val mods1 = checkAccessOnly(mods, "cases") | EnumCase
33643356
accept(CASE)
33653357

33663358
atSpan(start, nameStart) {

compiler/src/dotty/tools/dotc/reporting/diagnostic/ErrorMessageID.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ enum ErrorMessageID extends java.lang.Enum[ErrorMessageID] {
9191
ExpectedTopLevelDefID,
9292
AnonymousFunctionMissingParamTypeID,
9393
SuperCallsNotAllowedInlineableID,
94-
ModifiersNotAllowedID,
94+
UNUSED1, // not used anymore, but left so that error numbers stay the same
9595
WildcardOnTypeArgumentNotAllowedOnNewID,
9696
FunctionTypeNeedsNonEmptyParameterListID,
9797
WrongNumberOfParametersID,

compiler/src/dotty/tools/dotc/reporting/diagnostic/messages.scala

Lines changed: 0 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1695,27 +1695,6 @@ object messages {
16951695
val explanation: String = "Method inlining prohibits calling superclass methods, as it may lead to confusion about which super is being called."
16961696
}
16971697

1698-
case class ModifiersNotAllowed(flags: FlagSet, printableType: Option[String])(implicit ctx: Context)
1699-
extends Message(ModifiersNotAllowedID) {
1700-
val kind: String = "Syntax"
1701-
val msg: String = em"Modifier(s) `${flags.flagsString}` not allowed for ${printableType.getOrElse("combination")}"
1702-
val explanation: String = {
1703-
val first = "sealed def y: Int = 1"
1704-
val second = "sealed lazy class z"
1705-
em"""You tried to use a modifier that is inapplicable for the type of item under modification
1706-
|
1707-
| Please see the official Scala Language Specification section on modifiers:
1708-
| https://www.scala-lang.org/files/archive/spec/2.11/05-classes-and-objects.html#modifiers
1709-
|
1710-
|Consider the following example:
1711-
|$first
1712-
|In this instance, the modifier 'sealed' is not applicable to the item type 'def' (method)
1713-
|$second
1714-
|In this instance, the modifier combination is not supported
1715-
"""
1716-
}
1717-
}
1718-
17191698
case class WrongNumberOfParameters(expected: Int)(implicit ctx: Context)
17201699
extends Message(WrongNumberOfParametersID) {
17211700
val kind: String = "Syntax"

compiler/test/dotty/tools/dotc/reporting/ErrorMessagesTests.scala

Lines changed: 0 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1005,24 +1005,6 @@ class ErrorMessagesTests extends ErrorMessagesTest {
10051005
assertEquals("method bar", symbol.show)
10061006
}
10071007

1008-
@Test def modifiersNotAllowed =
1009-
verifyModifiersNotAllowed("lazy trait T", "lazy", Some("trait"))
1010-
1011-
@Test def modifiersOtherThanTraitMethodVariable =
1012-
verifyModifiersNotAllowed("sealed lazy class x", "sealed")
1013-
1014-
private def verifyModifiersNotAllowed(code: String, modifierAssertion: String,
1015-
typeAssertion: Option[String] = None) = {
1016-
checkMessagesAfter(RefChecks.name)(code)
1017-
.expect { (ictx, messages) =>
1018-
implicit val ctx: Context = ictx
1019-
assertMessageCount(1, messages)
1020-
val ModifiersNotAllowed(flags, sort) :: Nil = messages
1021-
assertEquals(modifierAssertion, flags.flagsString)
1022-
assertEquals(typeAssertion, sort)
1023-
}
1024-
}
1025-
10261008
@Test def wildcardOnTypeArgumentNotAllowedOnNew =
10271009
checkMessagesAfter(RefChecks.name) {
10281010
"""

tests/neg/i5495.scala

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
lazy enum LazyList[+A] { // error: sealed abstract types cannot be lazy enum
2+
case :: (head: A, tail: LazyList[A])
3+
case Nil
4+
}

tests/neg/i5525.scala

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
abstract enum Foo1 {} // error: only access modifiers allowed
2+
final enum Foo2 {} // error: only access modifiers allowed
3+
sealed enum Foo3 {} // error: only access modifiers allowed
4+
implicit enum Foo4 {} // error: only access modifiers allowed
5+
lazy enum Foo5 {} // error: only access modifiers allowed
6+
erased enum Foo6 {} // error: only access modifiers allowed
7+
override enum Foo7 {} // error: only access modifiers allowed
8+
inline enum Foo8 {} // error: only access modifiers allowed
9+
opaque enum Foo9 {} // error: only access modifiers allowed
10+
11+
enum Foo10 {
12+
abstract case C1() // error: only access modifiers allowed
13+
final case C2() // error: only access modifiers allowed
14+
sealed case C3() // error: only access modifiers allowed
15+
implicit case C4() // error: only access modifiers allowed
16+
lazy case C5() // error: only access modifiers allowed
17+
erased case C6() // error: only access modifiers allowed
18+
override case C7() // error: only access modifiers allowed
19+
private case C8() // ok
20+
protected case C9() // ok
21+
}
22+
23+
enum Foo11 {
24+
abstract case C1 // error: only access modifiers allowed
25+
final case C2 // error: only access modifiers allowed
26+
sealed case C3 // error: only access modifiers allowed
27+
implicit case C4 // error: only access modifiers allowed
28+
lazy case C5 // error: only access modifiers allowed
29+
erased case C6 // error: only access modifiers allowed
30+
override case C7 // error: only access modifiers allowed
31+
private case C8 // ok
32+
protected case C9 // ok
33+
}
34+
35+
enum Foo12 { // error: enums must contain at least one case
36+
inline case C10() // error // error // error (inline treated as ident here)
37+
}

tests/neg/i6795-b.check

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
1-
-- [E083] Syntax Error: tests/neg/i6795-b.scala:1:11 -------------------------------------------------------------------
1+
-- Error: tests/neg/i6795-b.scala:1:11 ---------------------------------------------------------------------------------
22
1 |sealed def y: Int = 1 // error
33
| ^
4-
| Modifier(s) `sealed` not allowed for method
5-
6-
longer explanation available when compiling with `-explain`
4+
| values cannot be sealed

0 commit comments

Comments
 (0)