Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Change enum Desugarings #6154

Merged
merged 7 commits into from Mar 29, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
22 changes: 15 additions & 7 deletions compiler/src/dotty/tools/dotc/ast/Desugar.scala
Expand Up @@ -355,10 +355,14 @@ object desugar {
val originalVparamss = constr1.vparamss
lazy val derivedEnumParams = enumClass.typeParams.map(derivedTypeParam)
val impliedTparams =
if (isEnumCase && originalTparams.isEmpty)
derivedEnumParams.map(tdef => tdef.withFlags(tdef.mods.flags | PrivateLocal))
else
originalTparams
if (isEnumCase) {
val tparamReferenced = typeParamIsReferenced(
enumClass.typeParams, originalTparams, originalVparamss, parents)
if (originalTparams.isEmpty && (parents.isEmpty || tparamReferenced))
derivedEnumParams.map(tdef => tdef.withFlags(tdef.mods.flags | PrivateLocal))
else originalTparams
}
else originalTparams
val constrTparams = impliedTparams.map(toDefParam)
val constrVparamss =
if (originalVparamss.isEmpty) { // ensure parameter list is non-empty
Expand Down Expand Up @@ -594,10 +598,11 @@ object desugar {
if (constrTparams.nonEmpty ||
constrVparamss.length > 1 ||
mods.is(Abstract) ||
restrictedAccess) anyRef
restrictedAccess ||
isEnumCase) anyRef
else
// todo: also use anyRef if constructor has a dependent method type (or rule that out)!
(constrVparamss :\ (if (isEnumCase) applyResultTpt else classTypeRef)) (
(constrVparamss :\ classTypeRef) (
(vparams, restpe) => Function(vparams map (_.tpt), restpe))
def widenedCreatorExpr =
(creatorExpr /: widenDefs)((rhs, meth) => Apply(Ident(meth.name), rhs :: Nil))
Expand Down Expand Up @@ -738,8 +743,11 @@ object desugar {

if (mods is Package)
PackageDef(Ident(moduleName), cpy.ModuleDef(mdef)(nme.PACKAGE, impl).withMods(mods &~ Package) :: Nil)
else if (isEnumCase)
else if (isEnumCase) {
typeParamIsReferenced(enumClass.typeParams, Nil, Nil, impl.parents)
// used to check there are no illegal references to enum's type parameters in parents
expandEnumModule(moduleName, impl, mods, mdef.span)
}
else {
val clsName = moduleName.moduleClassName
val clsRef = Ident(clsName)
Expand Down
53 changes: 53 additions & 0 deletions compiler/src/dotty/tools/dotc/ast/DesugarEnums.scala
Expand Up @@ -168,6 +168,59 @@ object DesugarEnums {
}
}

/** Is a type parameter in `enumTypeParams` referenced from an enum class case that has
* given type parameters `caseTypeParams`, value parameters `vparamss` and parents `parents`?
* Issues an error if that is the case but the reference is illegal.
* The reference could be illegal for two reasons:
* - explicit type parameters are given
* - it's a value case, i.e. no value parameters are given
*/
def typeParamIsReferenced(
enumTypeParams: List[TypeSymbol],
caseTypeParams: List[TypeDef],
vparamss: List[List[ValDef]],
parents: List[Tree])(implicit ctx: Context): Boolean = {

object searchRef extends UntypedTreeAccumulator[Boolean] {
var tparamNames = enumTypeParams.map(_.name).toSet[Name]
def underBinders(binders: List[MemberDef], op: => Boolean): Boolean = {
val saved = tparamNames
tparamNames = tparamNames -- binders.map(_.name)
try op
finally tparamNames = saved
}
def apply(x: Boolean, tree: Tree)(implicit ctx: Context): Boolean = x || {
tree match {
case Ident(name) =>
val matches = tparamNames.contains(name)
if (matches && (caseTypeParams.nonEmpty || vparamss.isEmpty))
ctx.error(i"illegal reference to type parameter $name from enum case", tree.sourcePos)
matches
case LambdaTypeTree(lambdaParams, body) =>
underBinders(lambdaParams, foldOver(x, tree))
case RefinedTypeTree(parent, refinements) =>
val refinementDefs = refinements collect { case r: MemberDef => r }
underBinders(refinementDefs, foldOver(x, tree))
case _ => foldOver(x, tree)
}
}
def apply(tree: Tree)(implicit ctx: Context): Boolean =
underBinders(caseTypeParams, apply(false, tree))
}

def typeHasRef(tpt: Tree) = searchRef(tpt)
def valDefHasRef(vd: ValDef) = typeHasRef(vd.tpt)
def parentHasRef(parent: Tree): Boolean = parent match {
case Apply(fn, _) => parentHasRef(fn)
case TypeApply(_, targs) => targs.exists(typeHasRef)
case Select(nu, nme.CONSTRUCTOR) => parentHasRef(nu)
case New(tpt) => typeHasRef(tpt)
case parent => parent.isType && typeHasRef(parent)
}

vparamss.exists(_.exists(valDefHasRef)) || parents.exists(parentHasRef)
}

/** A pair consisting of
* - the next enum tag
* - scaffolding containing the necessary definitions for singleton enum cases
Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/ast/Trees.scala
Expand Up @@ -1419,7 +1419,7 @@ object Trees {
}

def foldMoreCases(x: X, tree: Tree)(implicit ctx: Context): X = {
assert(ctx.reporter.errorsReported || ctx.mode.is(Mode.Interactive))
assert(ctx.reporter.errorsReported || ctx.mode.is(Mode.Interactive), tree)
// In interactive mode, errors might come from previous runs.
// In case of errors it may be that typed trees point to untyped ones.
// The IDE can still traverse inside such trees, either in the run where errors
Expand Down
33 changes: 26 additions & 7 deletions docs/docs/reference/enums/desugarEnums.md
Expand Up @@ -25,9 +25,9 @@ some terminology and notational conventions:

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

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

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

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

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

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

This result is then further rewritten with rule (8).
This result is then further rewritten with rule (9).

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

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

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

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.
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.

7. A value case
7. If `E` is an enum with type parameters `Ts`, a class case without type parameters but with an extends clause

case C <value-params> extends <parents>

expands to

case C[Ts] <value-params> extends <parents>

provided at least one of the parameters `Ts` is mentioned in a parameter type in
`<value-params>` or in a type argument in `<parents>`.

8. A value case

case C extends <parents>

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

8. A class case
It is an error if a value case refers to a type parameter of the enclosing `enum`
in a type argument of `<parents>`.

9. A class case

case C <params> extends <parents>

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

It is an error if a value case refers to a type parameter of the enclosing `enum`
in a parameter type in `<params>` or in a type argument of `<parents>`, unless that parameter is already
a type parameter of the case, i.e. the parameter name is defined in `<params>`.


### Translation of Enumerations

Non-generic enums `E` that define one or more singleton cases
Expand Down
40 changes: 40 additions & 0 deletions tests/neg/enum-tparams.scala
@@ -0,0 +1,40 @@
object Test {

enum Opt[+T] {
case S(x: T) extends Opt[T]
case I(x: Int) extends Opt[Int]
case V() extends Opt[`T`]
case P(x: List[T]) extends Opt[String]
case N extends Opt[Nothing]
}

type Id[_]

enum E[F[_], G[_]] {
case C1() extends E[[X] => X, Id]
case C2() extends E[[F] => F, Id]
case C3() extends E[[X] => { type Y = F[Int] }, Id]
case C4() extends E[[X] => { type F = Int }, Id]
case C5() extends E[[F] => G[Int], Id]
}

Opt.S[Int](1) // OK
Opt.S(1) // OK
Opt.I[Int](1) // error: does not take type parameters
Opt.I(1) // OK
Opt.V[Int]() // OK
Opt.V() // OK
Opt.P[Int](List(1, 2, 3)) // OK
Opt.P(List(1, 2, 3)) // OK

E.C1[List, Id]() // error: does not take type parameters
E.C1() // OK
E.C2[List, Id]() // error: does not take type parameters
E.C2() // OK
E.C3[List, Id]() // OK
E.C3() // OK
E.C4[List, Id]() // error: does not take type parameters
E.C4() // OK
E.C5[List, Id]() // OK
E.C5() // OK
}
11 changes: 11 additions & 0 deletions tests/neg/enums.scala
Expand Up @@ -19,11 +19,22 @@ enum E3[-T <: Ordered[T]] {

enum E4 {
case C
case C4(x: Int)
}
object E4 {
val x1: Int => E4 = C4 // error: found: C4, required: Int => E4
val x2: Int => E4 = C4(_) // ok
}

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

enum Captured[T] {
case Case1[U](x: T) extends Captured[U] // error: illegal reference to type parameter T from enum case
case Case2[U]() extends Captured[T] // error: illegal reference to type parameter T from enum case
case Case3 extends Captured[T] // error: illegal reference to type parameter T from enum case
}

enum Option[+T] derives Eql {
case Some(x: T)
case None
Expand Down
7 changes: 7 additions & 0 deletions tests/pos/enums-capture.scala
@@ -0,0 +1,7 @@
class T

enum Foo[T](val foo: Any) {
case Case1(x: Int) extends Foo(new T)
case Case2[U](x: U) extends Foo(new T)
case Case3 extends Foo(new T)
}
1 change: 1 addition & 0 deletions tests/run/enums.scala
Expand Up @@ -105,6 +105,7 @@ object Test6 {
case Green extends Color(3)
case Red extends Color(2)
case Violet extends Color(Green.x + Red.x)
case RGB(xx: Int) extends Color(xx)
}
}

Expand Down