Skip to content

Commit 9dc2916

Browse files
committed
Check for illegal references to enum type parameters
1 parent 3f42377 commit 9dc2916

File tree

5 files changed

+50
-18
lines changed

5 files changed

+50
-18
lines changed

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

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -355,11 +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-
typeParamIsReferenced(enumClass.typeParams, originalVparamss, parents))
360-
derivedEnumParams.map(tdef => tdef.withFlags(tdef.mods.flags | PrivateLocal))
361-
else
362-
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
363366
val constrTparams = impliedTparams.map(toDefParam)
364367
val constrVparamss =
365368
if (originalVparamss.isEmpty) { // ensure parameter list is non-empty
@@ -739,8 +742,11 @@ object desugar {
739742

740743
if (mods is Package)
741744
PackageDef(Ident(moduleName), cpy.ModuleDef(mdef)(nme.PACKAGE, impl).withMods(mods &~ Package) :: Nil)
742-
else if (isEnumCase)
745+
else if (isEnumCase) {
746+
typeParamIsReferenced(enumClass.typeParams, Nil, Nil, impl.parents)
747+
// used to check there are no illegal references to enum's type parameters in parents
743748
expandEnumModule(moduleName, impl, mods, mdef.span)
749+
}
744750
else {
745751
val clsName = moduleName.moduleClassName
746752
val clsRef = Ident(clsName)

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

Lines changed: 23 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -168,22 +168,34 @@ object DesugarEnums {
168168
}
169169
}
170170

171-
/** Is a type parameter in `tparams` referenced from an enum class case that has
172-
* given value parameters `vparamss` and given parents `parents`?
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
173177
*/
174-
def typeParamIsReferenced(tparams: List[TypeSymbol], vparamss: List[List[ValDef]], parents: List[Tree])(implicit ctx: Context): Boolean = {
178+
def typeParamIsReferenced(
179+
enumTypeParams: List[TypeSymbol],
180+
caseTypeParams: List[TypeDef],
181+
vparamss: List[List[ValDef]],
182+
parents: List[Tree])(implicit ctx: Context): Boolean = {
175183

176-
val searchRef = new untpd.TreeAccumulator[Boolean] {
177-
var tparamNames = tparams.map(_.name).toSet[Name]
184+
object searchRef extends UntypedTreeAccumulator[Boolean] {
185+
var tparamNames = enumTypeParams.map(_.name).toSet[Name]
178186
def underBinders(binders: List[MemberDef], op: => Boolean): Boolean = {
179187
val saved = tparamNames
180188
tparamNames = tparamNames -- binders.map(_.name)
181189
try op
182190
finally tparamNames = saved
183191
}
184-
def apply(x: Boolean, tree: Tree)(implicit ctx: Context) = x || {
192+
def apply(x: Boolean, tree: Tree)(implicit ctx: Context): Boolean = x || {
185193
tree match {
186-
case Ident(name) => tparamNames.contains(name)
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
187199
case LambdaTypeTree(lambdaParams, body) =>
188200
underBinders(lambdaParams, foldOver(x, tree))
189201
case RefinedTypeTree(parent, refinements) =>
@@ -192,9 +204,11 @@ object DesugarEnums {
192204
case _ => foldOver(x, tree)
193205
}
194206
}
207+
def apply(tree: Tree)(implicit ctx: Context): Boolean =
208+
underBinders(caseTypeParams, apply(false, tree))
195209
}
196210

197-
def typeHasRef(tpt: Tree) = searchRef(false, tpt)
211+
def typeHasRef(tpt: Tree) = searchRef(tpt)
198212
def valDefHasRef(vd: ValDef) = typeHasRef(vd.tpt)
199213
def parentHasRef(parent: Tree): Boolean = parent match {
200214
case Apply(fn, _) => parentHasRef(fn)
@@ -204,9 +218,7 @@ object DesugarEnums {
204218
case parent => parent.isType && typeHasRef(parent)
205219
}
206220

207-
parents.isEmpty || // a parent class that refers to type parameters will be generated in this case
208-
vparamss.exists(_.exists(valDefHasRef)) ||
209-
parents.exists(parentHasRef)
221+
vparamss.exists(_.exists(valDefHasRef)) || parents.exists(parentHasRef)
210222
}
211223

212224
/** A pair consisting of

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: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,9 @@ map into case classes or vals.
127127
as one of the `enumValues` of the enumeration (see below). `$values` is a
128128
compiler-defined private value in the companion object.
129129

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+
130133
9. A class case
131134

132135
case C <params> extends <parents>
@@ -145,6 +148,11 @@ map into case classes or vals.
145148
where `n` is the ordinal number of the case in the companion object,
146149
starting from 0.
147150

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+
148156
### Translation of Enumerations
149157

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

tests/neg/enums.scala

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,12 @@ enum E4 {
2424
case class C4() extends E4 // error: cannot extend enum
2525
case object O4 extends E4 // error: cannot extend enum
2626

27+
enum Captured[T] {
28+
case Case1[U](x: T) extends Captured[U] // error: illegal reference to type parameter T from enum case
29+
case Case2[U]() extends Captured[T] // error: illegal reference to type parameter T from enum case
30+
case Case3 extends Captured[T] // error: illegal reference to type parameter T from enum case
31+
}
32+
2733
enum Option[+T] derives Eql {
2834
case Some(x: T)
2935
case None

0 commit comments

Comments
 (0)