Skip to content

Commit 5e31e59

Browse files
authored
Merge pull request #6154 from dotty-staging/change-enum-desugaring
Change enum Desugarings
2 parents 1818972 + d1edad6 commit 5e31e59

File tree

8 files changed

+154
-15
lines changed

8 files changed

+154
-15
lines changed

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

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -355,10 +355,14 @@ object desugar {
355355
val originalVparamss = constr1.vparamss
356356
lazy val derivedEnumParams = enumClass.typeParams.map(derivedTypeParam)
357357
val impliedTparams =
358-
if (isEnumCase && originalTparams.isEmpty)
359-
derivedEnumParams.map(tdef => tdef.withFlags(tdef.mods.flags | PrivateLocal))
360-
else
361-
originalTparams
358+
if (isEnumCase) {
359+
val tparamReferenced = typeParamIsReferenced(
360+
enumClass.typeParams, originalTparams, originalVparamss, parents)
361+
if (originalTparams.isEmpty && (parents.isEmpty || tparamReferenced))
362+
derivedEnumParams.map(tdef => tdef.withFlags(tdef.mods.flags | PrivateLocal))
363+
else originalTparams
364+
}
365+
else originalTparams
362366
val constrTparams = impliedTparams.map(toDefParam)
363367
val constrVparamss =
364368
if (originalVparamss.isEmpty) { // ensure parameter list is non-empty
@@ -594,10 +598,11 @@ object desugar {
594598
if (constrTparams.nonEmpty ||
595599
constrVparamss.length > 1 ||
596600
mods.is(Abstract) ||
597-
restrictedAccess) anyRef
601+
restrictedAccess ||
602+
isEnumCase) anyRef
598603
else
599604
// todo: also use anyRef if constructor has a dependent method type (or rule that out)!
600-
(constrVparamss :\ (if (isEnumCase) applyResultTpt else classTypeRef)) (
605+
(constrVparamss :\ classTypeRef) (
601606
(vparams, restpe) => Function(vparams map (_.tpt), restpe))
602607
def widenedCreatorExpr =
603608
(creatorExpr /: widenDefs)((rhs, meth) => Apply(Ident(meth.name), rhs :: Nil))
@@ -738,8 +743,11 @@ object desugar {
738743

739744
if (mods is Package)
740745
PackageDef(Ident(moduleName), cpy.ModuleDef(mdef)(nme.PACKAGE, impl).withMods(mods &~ Package) :: Nil)
741-
else if (isEnumCase)
746+
else if (isEnumCase) {
747+
typeParamIsReferenced(enumClass.typeParams, Nil, Nil, impl.parents)
748+
// used to check there are no illegal references to enum's type parameters in parents
742749
expandEnumModule(moduleName, impl, mods, mdef.span)
750+
}
743751
else {
744752
val clsName = moduleName.moduleClassName
745753
val clsRef = Ident(clsName)

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

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,59 @@ object DesugarEnums {
168168
}
169169
}
170170

171+
/** Is a type parameter in `enumTypeParams` referenced from an enum class case that has
172+
* given type parameters `caseTypeParams`, value parameters `vparamss` and parents `parents`?
173+
* Issues an error if that is the case but the reference is illegal.
174+
* The reference could be illegal for two reasons:
175+
* - explicit type parameters are given
176+
* - it's a value case, i.e. no value parameters are given
177+
*/
178+
def typeParamIsReferenced(
179+
enumTypeParams: List[TypeSymbol],
180+
caseTypeParams: List[TypeDef],
181+
vparamss: List[List[ValDef]],
182+
parents: List[Tree])(implicit ctx: Context): Boolean = {
183+
184+
object searchRef extends UntypedTreeAccumulator[Boolean] {
185+
var tparamNames = enumTypeParams.map(_.name).toSet[Name]
186+
def underBinders(binders: List[MemberDef], op: => Boolean): Boolean = {
187+
val saved = tparamNames
188+
tparamNames = tparamNames -- binders.map(_.name)
189+
try op
190+
finally tparamNames = saved
191+
}
192+
def apply(x: Boolean, tree: Tree)(implicit ctx: Context): Boolean = x || {
193+
tree match {
194+
case Ident(name) =>
195+
val matches = tparamNames.contains(name)
196+
if (matches && (caseTypeParams.nonEmpty || vparamss.isEmpty))
197+
ctx.error(i"illegal reference to type parameter $name from enum case", tree.sourcePos)
198+
matches
199+
case LambdaTypeTree(lambdaParams, body) =>
200+
underBinders(lambdaParams, foldOver(x, tree))
201+
case RefinedTypeTree(parent, refinements) =>
202+
val refinementDefs = refinements collect { case r: MemberDef => r }
203+
underBinders(refinementDefs, foldOver(x, tree))
204+
case _ => foldOver(x, tree)
205+
}
206+
}
207+
def apply(tree: Tree)(implicit ctx: Context): Boolean =
208+
underBinders(caseTypeParams, apply(false, tree))
209+
}
210+
211+
def typeHasRef(tpt: Tree) = searchRef(tpt)
212+
def valDefHasRef(vd: ValDef) = typeHasRef(vd.tpt)
213+
def parentHasRef(parent: Tree): Boolean = parent match {
214+
case Apply(fn, _) => parentHasRef(fn)
215+
case TypeApply(_, targs) => targs.exists(typeHasRef)
216+
case Select(nu, nme.CONSTRUCTOR) => parentHasRef(nu)
217+
case New(tpt) => typeHasRef(tpt)
218+
case parent => parent.isType && typeHasRef(parent)
219+
}
220+
221+
vparamss.exists(_.exists(valDefHasRef)) || parents.exists(parentHasRef)
222+
}
223+
171224
/** A pair consisting of
172225
* - the next enum tag
173226
* - scaffolding containing the necessary definitions for singleton enum cases

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1419,7 +1419,7 @@ object Trees {
14191419
}
14201420

14211421
def foldMoreCases(x: X, tree: Tree)(implicit ctx: Context): X = {
1422-
assert(ctx.reporter.errorsReported || ctx.mode.is(Mode.Interactive))
1422+
assert(ctx.reporter.errorsReported || ctx.mode.is(Mode.Interactive), tree)
14231423
// In interactive mode, errors might come from previous runs.
14241424
// In case of errors it may be that typed trees point to untyped ones.
14251425
// The IDE can still traverse inside such trees, either in the run where errors

docs/docs/reference/enums/desugarEnums.md

Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,9 @@ some terminology and notational conventions:
2525

2626
The desugaring rules imply that class cases are mapped to case classes, and singleton cases are mapped to `val` definitions.
2727

28-
There are eight desugaring rules. Rule (1) desugar enum definitions. Rules
28+
There are nine desugaring rules. Rule (1) desugar enum definitions. Rules
2929
(2) and (3) desugar simple cases. Rules (4) to (6) define extends clauses for cases that
30-
are missing them. Rules (7) and (8) define how such cases with extends clauses
30+
are missing them. Rules (7) to (9) define how such cases with extends clauses
3131
map into case classes or vals.
3232

3333
1. An `enum` definition
@@ -80,7 +80,7 @@ map into case classes or vals.
8080
case C extends E[B1, ..., Bn]
8181

8282
where `Bi` is `Li` if `Vi = '+'` and `Ui` if `Vi = '-'`. This result is then further
83-
rewritten with rule (7). Simple cases of enums with non-variant type
83+
rewritten with rule (8). Simple cases of enums with non-variant type
8484
parameters are not permitted.
8585

8686
5. A class case without an extends clause
@@ -91,7 +91,7 @@ map into case classes or vals.
9191

9292
case C <type-params> <value-params> extends E
9393

94-
This result is then further rewritten with rule (8).
94+
This result is then further rewritten with rule (9).
9595

9696
6. If `E` is an enum with type parameters `Ts`, a class case with neither type parameters nor an extends clause
9797

@@ -101,9 +101,20 @@ map into case classes or vals.
101101

102102
case C[Ts] <value-params> extends E[Ts]
103103

104-
This result is then further rewritten with rule (8). For class cases that have type parameters themselves, an extends clause needs to be given explicitly.
104+
This result is then further rewritten with rule (9). For class cases that have type parameters themselves, an extends clause needs to be given explicitly.
105105

106-
7. A value case
106+
7. If `E` is an enum with type parameters `Ts`, a class case without type parameters but with an extends clause
107+
108+
case C <value-params> extends <parents>
109+
110+
expands to
111+
112+
case C[Ts] <value-params> extends <parents>
113+
114+
provided at least one of the parameters `Ts` is mentioned in a parameter type in
115+
`<value-params>` or in a type argument in `<parents>`.
116+
117+
8. A value case
107118

108119
case C extends <parents>
109120

@@ -116,7 +127,10 @@ map into case classes or vals.
116127
as one of the `enumValues` of the enumeration (see below). `$values` is a
117128
compiler-defined private value in the companion object.
118129

119-
8. A class case
130+
It is an error if a value case refers to a type parameter of the enclosing `enum`
131+
in a type argument of `<parents>`.
132+
133+
9. A class case
120134

121135
case C <params> extends <parents>
122136

@@ -134,6 +148,11 @@ map into case classes or vals.
134148
where `n` is the ordinal number of the case in the companion object,
135149
starting from 0.
136150

151+
It is an error if a value case refers to a type parameter of the enclosing `enum`
152+
in a parameter type in `<params>` or in a type argument of `<parents>`, unless that parameter is already
153+
a type parameter of the case, i.e. the parameter name is defined in `<params>`.
154+
155+
137156
### Translation of Enumerations
138157

139158
Non-generic enums `E` that define one or more singleton cases

tests/neg/enum-tparams.scala

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
object Test {
2+
3+
enum Opt[+T] {
4+
case S(x: T) extends Opt[T]
5+
case I(x: Int) extends Opt[Int]
6+
case V() extends Opt[`T`]
7+
case P(x: List[T]) extends Opt[String]
8+
case N extends Opt[Nothing]
9+
}
10+
11+
type Id[_]
12+
13+
enum E[F[_], G[_]] {
14+
case C1() extends E[[X] => X, Id]
15+
case C2() extends E[[F] => F, Id]
16+
case C3() extends E[[X] => { type Y = F[Int] }, Id]
17+
case C4() extends E[[X] => { type F = Int }, Id]
18+
case C5() extends E[[F] => G[Int], Id]
19+
}
20+
21+
Opt.S[Int](1) // OK
22+
Opt.S(1) // OK
23+
Opt.I[Int](1) // error: does not take type parameters
24+
Opt.I(1) // OK
25+
Opt.V[Int]() // OK
26+
Opt.V() // OK
27+
Opt.P[Int](List(1, 2, 3)) // OK
28+
Opt.P(List(1, 2, 3)) // OK
29+
30+
E.C1[List, Id]() // error: does not take type parameters
31+
E.C1() // OK
32+
E.C2[List, Id]() // error: does not take type parameters
33+
E.C2() // OK
34+
E.C3[List, Id]() // OK
35+
E.C3() // OK
36+
E.C4[List, Id]() // error: does not take type parameters
37+
E.C4() // OK
38+
E.C5[List, Id]() // OK
39+
E.C5() // OK
40+
}

tests/neg/enums.scala

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,22 @@ enum E3[-T <: Ordered[T]] {
1919

2020
enum E4 {
2121
case C
22+
case C4(x: Int)
23+
}
24+
object E4 {
25+
val x1: Int => E4 = C4 // error: found: C4, required: Int => E4
26+
val x2: Int => E4 = C4(_) // ok
2227
}
2328

2429
case class C4() extends E4 // error: cannot extend enum
2530
case object O4 extends E4 // error: cannot extend enum
2631

32+
enum Captured[T] {
33+
case Case1[U](x: T) extends Captured[U] // error: illegal reference to type parameter T from enum case
34+
case Case2[U]() extends Captured[T] // error: illegal reference to type parameter T from enum case
35+
case Case3 extends Captured[T] // error: illegal reference to type parameter T from enum case
36+
}
37+
2738
enum Option[+T] derives Eql {
2839
case Some(x: T)
2940
case None

tests/pos/enums-capture.scala

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
class T
2+
3+
enum Foo[T](val foo: Any) {
4+
case Case1(x: Int) extends Foo(new T)
5+
case Case2[U](x: U) extends Foo(new T)
6+
case Case3 extends Foo(new T)
7+
}

tests/run/enums.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@ object Test6 {
105105
case Green extends Color(3)
106106
case Red extends Color(2)
107107
case Violet extends Color(Green.x + Red.x)
108+
case RGB(xx: Int) extends Color(xx)
108109
}
109110
}
110111

0 commit comments

Comments
 (0)