Skip to content

Commit e1470f4

Browse files
committed
Implement new enum scheme
Implement as is described in the docs. Update docs and tests where necessary.
1 parent 4ecf1ad commit e1470f4

File tree

16 files changed

+140
-137
lines changed

16 files changed

+140
-137
lines changed

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

Lines changed: 65 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -305,14 +305,19 @@ object desugar {
305305
val isCaseObject = mods.is(Case) && mods.is(Module)
306306
val isImplicit = mods.is(Implicit)
307307
val isEnum = mods.hasMod[Mod.Enum] && !mods.is(Module)
308-
val isEnumCase = isLegalEnumCase(cdef)
308+
val isEnumCase = mods.hasMod[Mod.EnumCase]
309309
val isValueClass = parents.nonEmpty && isAnyVal(parents.head)
310-
// This is not watertight, but `extends AnyVal` will be replaced by `inline` later.
311-
310+
// This is not watertight, but `extends AnyVal` will be replaced by `inline` later.
312311

313312
val originalTparams = constr1.tparams
314313
val originalVparamss = constr1.vparamss
315-
val constrTparams = originalTparams.map(toDefParam)
314+
lazy val derivedEnumParams = enumClass.typeParams.map(derivedTypeParam)
315+
val impliedTparams =
316+
if (isEnumCase && originalTparams.isEmpty)
317+
derivedEnumParams.map(tdef => tdef.withFlags(tdef.mods.flags | PrivateLocal))
318+
else
319+
originalTparams
320+
val constrTparams = impliedTparams.map(toDefParam)
316321
val constrVparamss =
317322
if (originalVparamss.isEmpty) { // ensure parameter list is non-empty
318323
if (isCaseClass) ctx.error(CaseClassMissingParamList(cdef), cdef.namePos)
@@ -321,18 +326,34 @@ object desugar {
321326
else originalVparamss.nestedMap(toDefParam)
322327
val constr = cpy.DefDef(constr1)(tparams = constrTparams, vparamss = constrVparamss)
323328

324-
// Add constructor type parameters and evidence implicit parameters
325-
// to auxiliary constructors
326-
val normalizedBody = impl.body map {
327-
case ddef: DefDef if ddef.name.isConstructorName =>
328-
decompose(
329-
defDef(
330-
addEvidenceParams(
331-
cpy.DefDef(ddef)(tparams = constrTparams),
332-
evidenceParams(constr1).map(toDefParam))))
333-
case stat =>
334-
stat
329+
val (normalizedBody, enumCases, enumCompanionRef) = {
330+
// Add constructor type parameters and evidence implicit parameters
331+
// to auxiliary constructors; set defaultGetters as a side effect.
332+
def expandConstructor(tree: Tree) = tree match {
333+
case ddef: DefDef if ddef.name.isConstructorName =>
334+
decompose(
335+
defDef(
336+
addEvidenceParams(
337+
cpy.DefDef(ddef)(tparams = constrTparams),
338+
evidenceParams(constr1).map(toDefParam))))
339+
case stat =>
340+
stat
341+
}
342+
// The Identifiers defined by a case
343+
def caseIds(tree: Tree) = tree match {
344+
case tree: MemberDef => Ident(tree.name.toTermName) :: Nil
345+
case PatDef(_, ids, _, _) => ids
346+
}
347+
val stats = impl.body.map(expandConstructor)
348+
if (isEnum) {
349+
val (enumCases, enumStats) = stats.partition(DesugarEnums.isEnumCase)
350+
val enumCompanionRef = new TermRefTree()
351+
val enumImport = Import(enumCompanionRef, enumCases.flatMap(caseIds))
352+
(enumImport :: enumStats, enumCases, enumCompanionRef)
353+
}
354+
else (stats, Nil, EmptyTree)
335355
}
356+
336357
def anyRef = ref(defn.AnyRefAlias.typeRef)
337358

338359
val derivedTparams = constrTparams.map(derivedTypeParam(_))
@@ -365,20 +386,16 @@ object desugar {
365386
val classTypeRef = appliedRef(classTycon)
366387

367388
// a reference to `enumClass`, with type parameters coming from the case constructor
368-
lazy val enumClassTypeRef = enumClass.primaryConstructor.info match {
369-
case info: PolyType =>
370-
if (constrTparams.isEmpty)
371-
interpolatedEnumParent(cdef.pos.startPos)
372-
else if ((constrTparams.corresponds(info.paramNames))((param, name) => param.name == name))
373-
appliedRef(enumClassRef)
374-
else {
375-
ctx.error(i"explicit extends clause needed because type parameters of case and enum class differ"
376-
, cdef.pos.startPos)
377-
appliedTypeTree(enumClassRef, constrTparams map (_ => anyRef))
378-
}
379-
case _ =>
389+
lazy val enumClassTypeRef =
390+
if (enumClass.typeParams.isEmpty)
380391
enumClassRef
381-
}
392+
else if (originalTparams.isEmpty)
393+
appliedRef(enumClassRef)
394+
else {
395+
ctx.error(i"explicit extends clause needed because both enum case and enum class have type parameters"
396+
, cdef.pos.startPos)
397+
appliedTypeTree(enumClassRef, constrTparams map (_ => anyRef))
398+
}
382399

383400
// new C[Ts](paramss)
384401
lazy val creatorExpr = New(classTypeRef, constrVparamss nestedMap refOfDef)
@@ -432,6 +449,7 @@ object desugar {
432449
}
433450

434451
// Case classes and case objects get Product parents
452+
// Enum cases get an inferred parent if no parents are given
435453
var parents1 = parents
436454
if (isEnumCase && parents.isEmpty)
437455
parents1 = enumClassTypeRef :: Nil
@@ -477,7 +495,7 @@ object desugar {
477495
.withMods(companionMods | Synthetic))
478496
.withPos(cdef.pos).toList
479497

480-
val companionMeths = defaultGetters ::: eqInstances
498+
val companionMembers = defaultGetters ::: eqInstances ::: enumCases
481499

482500
// The companion object definitions, if a companion is needed, Nil otherwise.
483501
// companion definitions include:
@@ -490,18 +508,17 @@ object desugar {
490508
// For all other classes, the parent is AnyRef.
491509
val companions =
492510
if (isCaseClass) {
493-
// The return type of the `apply` method
511+
// The return type of the `apply` method, and an (empty or singleton) list
512+
// of widening coercions
494513
val (applyResultTpt, widenDefs) =
495514
if (!isEnumCase)
496515
(TypeTree(), Nil)
497516
else if (parents.isEmpty || enumClass.typeParams.isEmpty)
498517
(enumClassTypeRef, Nil)
499-
else {
500-
val tparams = enumClass.typeParams.map(derivedTypeParam)
501-
enumApplyResult(cdef, parents, tparams, appliedRef(enumClassRef, tparams))
502-
}
518+
else
519+
enumApplyResult(cdef, parents, derivedEnumParams, appliedRef(enumClassRef, derivedEnumParams))
503520

504-
val parent =
521+
val companionParent =
505522
if (constrTparams.nonEmpty ||
506523
constrVparamss.length > 1 ||
507524
mods.is(Abstract) ||
@@ -523,10 +540,10 @@ object desugar {
523540
DefDef(nme.unapply, derivedTparams, (unapplyParam :: Nil) :: Nil, TypeTree(), unapplyRHS)
524541
.withMods(synthetic)
525542
}
526-
companionDefs(parent, applyMeths ::: unapplyMeth :: companionMeths)
543+
companionDefs(companionParent, applyMeths ::: unapplyMeth :: companionMembers)
527544
}
528-
else if (companionMeths.nonEmpty)
529-
companionDefs(anyRef, companionMeths)
545+
else if (companionMembers.nonEmpty)
546+
companionDefs(anyRef, companionMembers)
530547
else if (isValueClass) {
531548
constr0.vparamss match {
532549
case (_ :: Nil) :: _ => companionDefs(anyRef, Nil)
@@ -535,6 +552,13 @@ object desugar {
535552
}
536553
else Nil
537554

555+
enumCompanionRef match {
556+
case ref: TermRefTree => // have the enum import watch the companion object
557+
val (modVal: ValDef) :: _ = companions
558+
ref.watching(modVal)
559+
case _ =>
560+
}
561+
538562
// For an implicit class C[Ts](p11: T11, ..., p1N: T1N) ... (pM1: TM1, .., pMN: TMN), the method
539563
// synthetic implicit C[Ts](p11: T11, ..., p1N: T1N) ... (pM1: TM1, ..., pMN: TMN): C[Ts] =
540564
// new C[Ts](p11, ..., p1N) ... (pM1, ..., pMN) =
@@ -567,7 +591,7 @@ object desugar {
567591
}
568592

569593
val cdef1 = addEnumFlags {
570-
val originalTparamsIt = originalTparams.toIterator
594+
val originalTparamsIt = impliedTparams.toIterator
571595
val originalVparamsIt = originalVparamss.toIterator.flatten
572596
val tparamAccessors = derivedTparams.map(_.withMods(originalTparamsIt.next().mods))
573597
val caseAccessor = if (isCaseClass) CaseAccessor else EmptyFlags
@@ -607,7 +631,7 @@ object desugar {
607631
val moduleName = checkNotReservedName(mdef).asTermName
608632
val impl = mdef.impl
609633
val mods = mdef.mods
610-
lazy val isEnumCase = isLegalEnumCase(mdef)
634+
lazy val isEnumCase = mods.hasMod[Mod.EnumCase]
611635
if (mods is Package)
612636
PackageDef(Ident(moduleName), cpy.ModuleDef(mdef)(nme.PACKAGE, impl).withMods(mods &~ Package) :: Nil)
613637
else if (isEnumCase)
@@ -654,7 +678,7 @@ object desugar {
654678
*/
655679
def patDef(pdef: PatDef)(implicit ctx: Context): Tree = flatTree {
656680
val PatDef(mods, pats, tpt, rhs) = pdef
657-
if (mods.hasMod[Mod.EnumCase] && enumCaseIsLegal(pdef))
681+
if (mods.hasMod[Mod.EnumCase])
658682
pats map {
659683
case id: Ident =>
660684
expandSimpleEnumCase(id.name.asTermName, mods,

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

Lines changed: 20 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import core._
66
import util.Positions._, Types._, Contexts._, Constants._, Names._, NameOps._, Flags._
77
import SymDenotations._, Symbols._, StdNames._, Annotations._, Trees._
88
import Decorators._
9-
import reporting.diagnostic.messages.EnumCaseDefinitionInNonEnumOwner
109
import collection.mutable.ListBuffer
1110
import util.Property
1211
import typer.ErrorReporting._
@@ -23,20 +22,21 @@ object DesugarEnums {
2322
/** Attachment containing the number of enum cases and the smallest kind that was seen so far. */
2423
val EnumCaseCount = new Property.Key[(Int, CaseKind.Value)]
2524

26-
/** the enumeration class that is a companion of the current object */
27-
def enumClass(implicit ctx: Context) = ctx.owner.linkedClass
28-
29-
/** Is this an enum case that's situated in a companion object of an enum class? */
30-
def isLegalEnumCase(tree: MemberDef)(implicit ctx: Context): Boolean =
31-
tree.mods.hasMod[Mod.EnumCase] && enumCaseIsLegal(tree)
25+
/** The enumeration class that belongs to an enum case. This works no matter
26+
* whether the case is still in the enum class or it has been transferred to the
27+
* companion object.
28+
*/
29+
def enumClass(implicit ctx: Context): Symbol = {
30+
val cls = ctx.owner
31+
if (cls.is(Module)) cls.linkedClass else cls
32+
}
3233

33-
/** Is enum case `tree` situated in a companion object of an enum class? */
34-
def enumCaseIsLegal(tree: Tree)(implicit ctx: Context): Boolean = (
35-
ctx.owner.is(ModuleClass) && enumClass.derivesFrom(defn.EnumClass)
36-
|| { ctx.error(EnumCaseDefinitionInNonEnumOwner(ctx.owner), tree.pos)
37-
false
38-
}
39-
)
34+
/** Is `tree` an (untyped) enum case? */
35+
def isEnumCase(tree: Tree)(implicit ctx: Context): Boolean = tree match {
36+
case tree: MemberDef => tree.mods.hasMod[Mod.EnumCase]
37+
case PatDef(mods, _, _, _) => mods.hasMod[Mod.EnumCase]
38+
case _ => false
39+
}
4040

4141
/** A reference to the enum class `E`, possibly followed by type arguments.
4242
* Each covariant type parameter is approximated by its lower bound.
@@ -69,7 +69,7 @@ object DesugarEnums {
6969
/** Add implied flags to an enum class or an enum case */
7070
def addEnumFlags(cdef: TypeDef)(implicit ctx: Context) =
7171
if (cdef.mods.hasMod[Mod.Enum]) cdef.withFlags(cdef.mods.flags | Abstract | Sealed)
72-
else if (isLegalEnumCase(cdef)) cdef.withFlags(cdef.mods.flags | Final)
72+
else if (isEnumCase(cdef)) cdef.withFlags(cdef.mods.flags | Final)
7373
else cdef
7474

7575
private def valuesDot(name: String) = Select(Ident(nme.DOLLAR_VALUES), name.toTermName)
@@ -193,24 +193,20 @@ object DesugarEnums {
193193
}
194194

195195
/** Expand a module definition representing a parameterless enum case */
196-
def expandEnumModule(name: TermName, impl: Template, mods: Modifiers, pos: Position)(implicit ctx: Context): Tree =
196+
def expandEnumModule(name: TermName, impl: Template, mods: Modifiers, pos: Position)(implicit ctx: Context): Tree = {
197+
assert(impl.body.isEmpty)
197198
if (impl.parents.isEmpty)
198-
if (impl.body.isEmpty)
199-
expandSimpleEnumCase(name, mods, pos)
200-
else {
201-
val parent = interpolatedEnumParent(pos)
202-
expandEnumModule(name, cpy.Template(impl)(parents = parent :: Nil), mods, pos)
203-
}
199+
expandSimpleEnumCase(name, mods, pos)
204200
else {
205201
def toStringMeth =
206202
DefDef(nme.toString_, Nil, Nil, TypeTree(defn.StringType), Literal(Constant(name.toString)))
207203
.withFlags(Override)
208204
val (tagMeth, scaffolding) = enumTagMeth(CaseKind.Object)
209-
val impl1 = cpy.Template(impl)(body =
210-
impl.body ++ List(tagMeth, toStringMeth) ++ registerCall)
205+
val impl1 = cpy.Template(impl)(body = List(tagMeth, toStringMeth) ++ registerCall)
211206
val vdef = ValDef(name, TypeTree(), New(impl1)).withMods(mods | Final)
212207
flatTree(scaffolding ::: vdef :: Nil).withPos(pos)
213208
}
209+
}
214210

215211
/** Expand a simple enum case */
216212
def expandSimpleEnumCase(name: TermName, mods: Modifiers, pos: Position)(implicit ctx: Context): Tree =

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

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1783,16 +1783,6 @@ object messages {
17831783
hl"""A class marked with the ${"final"} keyword cannot be extended"""
17841784
}
17851785

1786-
case class EnumCaseDefinitionInNonEnumOwner(owner: Symbol)(implicit ctx: Context)
1787-
extends Message(EnumCaseDefinitionInNonEnumOwnerID) {
1788-
val kind = "Syntax"
1789-
val msg = em"case not allowed here, since owner ${owner} is not an ${"enum"} object"
1790-
val explanation =
1791-
hl"""${"enum"} cases are only allowed within the companion ${"object"} of an ${"enum class"}.
1792-
|If you want to create an ${"enum"} case, make sure the corresponding ${"enum class"} exists
1793-
|and has the ${"enum"} keyword."""
1794-
}
1795-
17961786
case class ExpectedTypeBoundOrEquals(found: Token)(implicit ctx: Context)
17971787
extends Message(ExpectedTypeBoundOrEqualsID) {
17981788
val kind = "Syntax"

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

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -986,19 +986,6 @@ class ErrorMessagesTests extends ErrorMessagesTest {
986986
assertEquals(parent.show, "class A")
987987
}
988988

989-
@Test def enumCaseDefinitionInNonEnumOwner =
990-
checkMessagesAfter("frontend") {
991-
"""object Qux {
992-
| case Foo
993-
|}
994-
""".stripMargin
995-
}.expect { (ictx, messages) =>
996-
implicit val ctx: Context = ictx
997-
assertMessageCount(1, messages)
998-
val EnumCaseDefinitionInNonEnumOwner(owner) :: Nil = messages
999-
assertEquals("object Qux", owner.show)
1000-
}
1001-
1002989
@Test def tailrecNotApplicableNeitherPrivateNorFinal =
1003990
checkMessagesAfter("tailrec") {
1004991
"""

docs/docs/reference/enums/desugarEnums.md

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,13 @@ map into case classes or vals.
3636

3737
expands to a `sealed` `abstract` class that extends the `scala.Enum` trait and
3838
an associated companion object that contains the defined cases, expanded according
39-
to rules (2 - 8).
39+
to rules (2 - 8). The enum trait starts with a compiler-generated import that imports
40+
the names `<caseIds>` of all cases so that they can be used without prefix in the trait.
4041

41-
sealed abstract class E ... extends <parents> with scala.Enum { <defs> }
42+
sealed abstract class E ... extends <parents> with scala.Enum {
43+
import E.{ <caseIds> }
44+
<defs>
45+
}
4246
object E { <cases> }
4347

4448
2. A simple case consisting of a comma-separated list of enum names

tests/run/enum-List3.scala renamed to tests/neg/enum-List3.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
enum List[+T] {
2-
case Cons[T](x: T, xs: List[T])
2+
case Cons[T](x: T, xs: List[T]) // error: missing extends
33
case Nil extends List[Nothing]
44
}
55
object Test {

tests/neg/enums.scala

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,8 @@
11
package enums
22

33
enum List[+T] {
4-
case Cons[T](x: T, xs: List[T]) // ok
5-
case Snoc[U](xs: List[U], x: U) // error: different type parameters
6-
}
7-
8-
enum class X {
9-
case Y // error: case not allowed here
4+
case Cons[T](x: T, xs: List[T]) // error: missing extends
5+
case Snoc[U](xs: List[U], x: U) // error: missing extends
106
}
117

128
enum E1[T] {
@@ -22,7 +18,7 @@ enum E3[-T <: Ordered[T]] {
2218
}
2319

2420
enum Option[+T] {
25-
case Some[T](x: T)
21+
case Some(x: T)
2622
case None
2723
}
2824

tests/patmat/enum-Tree.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ enum Tree[T] {
55
case Succ(n: Tree[Int]) extends Tree[Int]
66
case Pred(n: Tree[Int]) extends Tree[Int]
77
case IsZero(n: Tree[Int]) extends Tree[Boolean]
8-
case If(cond: Tree[Boolean], thenp: Tree[T], elsep: Tree[T]) extends Tree[T]
8+
case If[X](cond: Tree[Boolean], thenp: Tree[X], elsep: Tree[X]) extends Tree[X]
99
}
1010

1111
object Test {

tests/pos/i3460.scala

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
enum class Ducks
2-
3-
object Ducks {
1+
enum Ducks {
42
case Dewey
3+
}
54

5+
object Ducks {
66
def wooohoo: Int = 1
77
}

tests/pos/objXfun.scala

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ object Foo extends (Int => Int) { // OK
22
def apply(x: Int) = x
33
}
44

5-
enum class E(x: Int) // used to generate Int => new E(x) as the parent of object E --> crash
6-
object E {
5+
enum E(x: Int) { // used to generate Int => new E(x) as the parent of object E --> crash
76
case C(x: Int) extends E(x)
87
}

0 commit comments

Comments
 (0)