Skip to content

Commit 84de445

Browse files
committed
Add tests, refactor checking
1 parent b527f64 commit 84de445

File tree

4 files changed

+93
-20
lines changed

4 files changed

+93
-20
lines changed

compiler/src/dotty/tools/dotc/typer/Checking.scala

Lines changed: 19 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -630,11 +630,6 @@ object Checking {
630630
}
631631
}
632632

633-
def optEnumCase(cls: Symbol)(using Context): Symbol =
634-
(if cls.isAllOf(EnumCase) then cls
635-
else if cls.isAnonymousClass && cls.owner.isAllOf(EnumCase) then cls.owner
636-
else NoSymbol).filter(s => s.exists && s.owner.linkedClass.derivesFrom(defn.EnumClass))
637-
638633
/** Check the inline override methods only use inline parameters if they override an inline parameter. */
639634
def checkInlineOverrideParameters(sym: Symbol)(using Context): Unit =
640635
lazy val params = sym.paramSymss.flatten
@@ -1088,7 +1083,7 @@ trait Checking {
10881083
* 2. Check that case class `enum` cases do not extend java.lang.Enum.
10891084
* 3. Check that the firstParent derives from the declaring enum class.
10901085
*/
1091-
def checkEnum(cdef: untpd.TypeDef, cls: Symbol, enumCase: Symbol, firstParent: Symbol)(using Context): Boolean = {
1086+
def checkEnum(cdef: untpd.TypeDef, cls: Symbol, firstParent: Symbol)(using Context): Unit = {
10921087
def isEnumAnonCls =
10931088
cls.isAnonymousClass &&
10941089
cls.owner.isTerm &&
@@ -1105,12 +1100,22 @@ trait Checking {
11051100
// Unlike firstParent.derivesFrom(defn.EnumClass), this test allows inheriting from `Enum` by hand;
11061101
// see enum-List-control.scala.
11071102
report.error(ClassCannotExtendEnum(cls, firstParent), cdef.sourcePos)
1108-
val enumCls = enumCase.owner.linkedClass
1109-
if !firstParent.derivesFrom(enumCls) then
1110-
report.error(i"enum case does not extend its enum $enumCls", enumCase.sourcePos)
1111-
false
1112-
else
1113-
true
1103+
}
1104+
1105+
/** Check that the firstParent derives from the declaring enum class.
1106+
*/
1107+
def checkEnumParent(cls: Symbol, firstParent: Symbol)(using Context): Boolean = {
1108+
val enumCase =
1109+
if cls.isAllOf(EnumCase) then cls
1110+
else if cls.isAnonymousClass && cls.owner.isAllOf(EnumCase) then cls.owner
1111+
else NoSymbol
1112+
def parentDerivesFrom(enumCls: Symbol)(using Context) =
1113+
if !firstParent.derivesFrom(enumCls) then
1114+
report.error(i"enum case does not extend its enum $enumCls", enumCase.sourcePos)
1115+
false
1116+
else
1117+
true
1118+
!enumCase.exists || parentDerivesFrom(enumCase.owner.linkedClass)
11141119
}
11151120

11161121
/** Check that all references coming from enum cases in an enum companion object
@@ -1206,7 +1211,8 @@ trait Checking {
12061211

12071212
trait ReChecking extends Checking {
12081213
import tpd._
1209-
override def checkEnum(cdef: untpd.TypeDef, cls: Symbol, enumCase: Symbol, firstParent: Symbol)(using Context): Boolean = true
1214+
override def checkEnumParent(cls: Symbol, firstParent: Symbol)(using Context): Boolean = true
1215+
override def checkEnum(cdef: untpd.TypeDef, cls: Symbol, firstParent: Symbol)(using Context): Unit = ()
12101216
override def checkRefsLegal(tree: tpd.Tree, badOwner: Symbol, allowed: (Name, Symbol) => Boolean, where: String)(using Context): Unit = ()
12111217
override def checkFullyAppliedType(tree: Tree)(using Context): Unit = ()
12121218
override def checkEnumCaseRefsLegal(cdef: TypeDef, enumCtx: Context)(using Context): Unit = ()

compiler/src/dotty/tools/dotc/typer/Typer.scala

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2071,11 +2071,7 @@ class Typer extends Namer
20712071
val parentsWithClass = ensureFirstTreeIsClass(parents.mapconserve(typedParent).filterConserve(!_.isEmpty), cdef.nameSpan)
20722072
val parents1 = ensureConstrCall(cls, parentsWithClass)(using superCtx)
20732073

2074-
var forceEmptyBody: Boolean = false
2075-
val enumCaseDef = optEnumCase(cls)
2076-
if enumCaseDef.exists then
2077-
val firstParent = parents1.head.tpe.dealias.typeSymbol
2078-
forceEmptyBody = !checkEnum(cdef, cls, enumCaseDef, firstParent) // don't bother looking inside the template
2074+
lazy val firstParent = parents1.head.tpe.dealias.typeSymbol
20792075

20802076
val self1 = typed(self)(using ctx.outer).asInstanceOf[ValDef] // outer context where class members are not visible
20812077
if (self1.tpt.tpe.isError || classExistsOnSelf(cls.unforcedDecls, self1))
@@ -2084,14 +2080,18 @@ class Typer extends Namer
20842080
else {
20852081
val dummy = localDummy(cls, impl)
20862082
val body1 =
2087-
if forceEmptyBody then Nil
2088-
else addAccessorDefs(cls, typedStats(impl.body, dummy)(using ctx.inClassContext(self1.symbol))._1)
2083+
if checkEnumParent(cls, firstParent) then
2084+
addAccessorDefs(cls, typedStats(impl.body, dummy)(using ctx.inClassContext(self1.symbol))._1)
2085+
else
2086+
Nil
20892087

20902088
checkNoDoubleDeclaration(cls)
20912089
val impl1 = cpy.Template(impl)(constr1, parents1, Nil, self1, body1)
20922090
.withType(dummy.termRef)
20932091
if (!cls.isOneOf(AbstractOrTrait) && !ctx.isAfterTyper)
20942092
checkRealizableBounds(cls, cdef.sourcePos.withSpan(cdef.nameSpan))
2093+
if cls.derivesFrom(defn.EnumClass) then
2094+
checkEnum(cdef, cls, firstParent)
20952095
val cdef1 = assignType(cpy.TypeDef(cdef)(name, impl1), cls)
20962096

20972097
val reportDynamicInheritance =

tests/neg/i6601.scala

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,13 @@ object GADTs2 {
77
case Lit[G](n: Int) extends Expr[G, Int]
88
// case S[A, G](x:
99
}
10+
enum Covariant[+T] {
11+
case Bottom extends AnyRef // error
12+
}
13+
enum Contravariant[-T] {
14+
case Top extends AnyRef // error
15+
}
16+
enum Color {
17+
case Red extends AnyRef // error
18+
}
1019
}

tests/run/enum-values.scala

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
enum Color:
2+
case Red, Green, Blue
3+
4+
enum Tag[T]:
5+
case Int extends Tag[Int]
6+
case String extends Tag[String]
7+
case OfClass[T]()(using val tag: reflect.ClassTag[T]) extends Tag[T]
8+
9+
enum Expr[-T >: Null]:
10+
case EmptyTree extends Expr[Null]
11+
case AnyTree
12+
13+
enum ListLike[+T]:
14+
case Cons(head: T, tail: ListLike[T])
15+
case EmptyListLike
16+
17+
enum TypeCtorsK[F[_]]:
18+
case List extends TypeCtorsK[List]
19+
case Option extends TypeCtorsK[Option]
20+
case Const[T]() extends TypeCtorsK[[U] =>> T]
21+
22+
enum MixedParams[F[_], G[X,Y] <: collection.Map[X,Y], T]:
23+
case Foo extends MixedParams[List, collection.mutable.LinkedHashMap, Unit]
24+
25+
@main def Test: Unit =
26+
import Color._, Tag._, Expr._, ListLike._, TypeCtorsK._, MixedParams._
27+
import reflect.Selectable.reflectiveSelectable
28+
29+
extension [A](t: A) def show = runtime.ScalaRunTime.stringOf(t)
30+
31+
val colors: Array[Color] = Color.values
32+
val tags: Array[Tag[?]] = Tag.values
33+
val exprs: Array[Expr[? >: Null]] = Expr.values
34+
val listlikes: Array[ListLike[?]] = ListLike.values
35+
val typeCtorsK: Array[TypeCtorsK[?]] = TypeCtorsK.values
36+
37+
val mixedParams: Array[MixedParams[?, ? <: [X, Y] =>> collection.Map[X, Y], ?]] = MixedParams.values
38+
39+
def sameAs[T](arr: Array[T], compare: T*): Unit =
40+
assert(arr sameElements compare, s"${arr.show} does not correspond to ${compare.show}")
41+
42+
sameAs(colors, Red, Green, Blue)
43+
sameAs(tags, Int, String)
44+
sameAs(exprs, EmptyTree, AnyTree)
45+
sameAs(listlikes, EmptyListLike)
46+
sameAs(typeCtorsK, List, Option)
47+
sameAs(mixedParams, Foo)
48+
49+
def singleton[E <: AnyRef](value: E, name: String, companion: { def valueOf(s: String): E}) =
50+
val lookup = companion.valueOf(name)
51+
assert(value eq lookup, s"${value.show} is not identical to ${lookup.show}")
52+
53+
singleton(Green, "Green", Color)
54+
singleton(String, "String", Tag)
55+
singleton(AnyTree, "AnyTree", Expr)
56+
singleton(EmptyListLike, "EmptyListLike", ListLike)
57+
singleton(Option, "Option", TypeCtorsK)
58+
singleton(Foo, "Foo", MixedParams)

0 commit comments

Comments
 (0)