Skip to content

Commit 733b57c

Browse files
authored
Merge pull request #9628 from dotty-staging/topic/enum-lookup-methods
remove scala.runtime.EnumValues
2 parents 2300095 + dc2ae75 commit 733b57c

File tree

11 files changed

+121
-104
lines changed

11 files changed

+121
-104
lines changed

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

+83-64
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,17 @@ object DesugarEnums {
2020
val Simple, Object, Class: Value = Value
2121
}
2222

23+
final case class EnumConstraints(minKind: CaseKind.Value, maxKind: CaseKind.Value, enumCases: List[(Int, RefTree)]):
24+
require(minKind <= maxKind && !(cached && enumCases.isEmpty))
25+
def requiresCreator = minKind == CaseKind.Simple
26+
def isEnumeration = maxKind < CaseKind.Class
27+
def cached = minKind < CaseKind.Class
28+
end EnumConstraints
29+
2330
/** Attachment containing the number of enum cases, the smallest kind that was seen so far,
2431
* and a list of all the value cases with their ordinals.
2532
*/
26-
val EnumCaseCount: Property.Key[(Int, CaseKind.Value, List[(Int, TermName)])] = Property.Key()
33+
val EnumCaseCount: Property.Key[(Int, CaseKind.Value, CaseKind.Value, List[(Int, TermName)])] = Property.Key()
2734

2835
/** Attachment signalling that when this definition is desugared, it should add any additional
2936
* lookup methods for enums.
@@ -39,6 +46,11 @@ object DesugarEnums {
3946
if (cls.is(Module)) cls.linkedClass else cls
4047
}
4148

49+
def enumCompanion(using Context): Symbol = {
50+
val cls = ctx.owner
51+
if (cls.is(Module)) cls.sourceModule else cls.linkedClass.sourceModule
52+
}
53+
4254
/** Is `tree` an (untyped) enum case? */
4355
def isEnumCase(tree: Tree)(using Context): Boolean = tree match {
4456
case tree: MemberDef => tree.mods.isEnumCase
@@ -84,65 +96,73 @@ object DesugarEnums {
8496
private def valuesDot(name: PreName)(implicit src: SourceFile) =
8597
Select(Ident(nme.DOLLAR_VALUES), name.toTermName)
8698

87-
private def registerCall(using Context): Tree =
88-
Apply(valuesDot("register"), This(EmptyTypeIdent) :: Nil)
99+
private def ArrayLiteral(values: List[Tree], tpt: Tree)(using Context): Tree =
100+
val clazzOf = TypeApply(ref(defn.Predef_classOf.termRef), tpt :: Nil)
101+
val ctag = Apply(TypeApply(ref(defn.ClassTagModule_apply.termRef), tpt :: Nil), clazzOf :: Nil)
102+
val apply = Select(ref(defn.ArrayModule.termRef), nme.apply)
103+
Apply(Apply(TypeApply(apply, tpt :: Nil), values), ctag :: Nil)
89104

90-
/** The following lists of definitions for an enum type E:
105+
/** The following lists of definitions for an enum type E and known value cases e_0, ..., e_n:
91106
*
92-
* private val $values = new EnumValues[E]
93-
* def values = $values.values.toArray
94-
* def valueOf($name: String) =
95-
* try $values.fromName($name) catch
96-
* {
97-
* case ex$:NoSuchElementException =>
98-
* throw new IllegalArgumentException("key not found: ".concat(name))
99-
* }
107+
* private val $values = Array[E](e_0,...,e_n)(ClassTag[E](classOf[E]))
108+
* def values = $values.clone
109+
* def valueOf($name: String) = $name match {
110+
* case "e_0" => e_0
111+
* ...
112+
* case "e_n" => e_n
113+
* case _ => throw new IllegalArgumentException("case not found: " + $name)
114+
* }
100115
*/
101-
private def enumScaffolding(using Context): List[Tree] = {
116+
private def enumScaffolding(enumValues: List[RefTree])(using Context): List[Tree] = {
102117
val rawEnumClassRef = rawRef(enumClass.typeRef)
103118
extension (tpe: NamedType) def ofRawEnum = AppliedTypeTree(ref(tpe), rawEnumClassRef)
119+
120+
val lazyFlagOpt = if enumCompanion.owner.isStatic then EmptyFlags else Lazy
121+
val privateValuesDef = ValDef(nme.DOLLAR_VALUES, TypeTree(), ArrayLiteral(enumValues, rawEnumClassRef))
122+
.withFlags(Private | Synthetic | lazyFlagOpt)
123+
104124
val valuesDef =
105-
DefDef(nme.values, Nil, Nil, defn.ArrayType.ofRawEnum, Select(valuesDot(nme.values), nme.toArray))
125+
DefDef(nme.values, Nil, Nil, defn.ArrayType.ofRawEnum, valuesDot(nme.clone_))
106126
.withFlags(Synthetic)
107-
val privateValuesDef =
108-
ValDef(nme.DOLLAR_VALUES, TypeTree(), New(defn.EnumValuesClass.typeRef.ofRawEnum, ListOfNil))
109-
.withFlags(Private | Synthetic)
110-
111-
val valuesOfExnMessage = Apply(
112-
Select(Literal(Constant("key not found: ")), "concat".toTermName),
113-
Ident(nme.nameDollar) :: Nil)
114-
val valuesOfBody = Try(
115-
expr = Apply(valuesDot("fromName"), Ident(nme.nameDollar) :: Nil),
116-
cases = CaseDef(
117-
pat = Typed(Ident(nme.DEFAULT_EXCEPTION_NAME), TypeTree(defn.NoSuchElementExceptionType)),
118-
guard = EmptyTree,
119-
body = Throw(New(TypeTree(defn.IllegalArgumentExceptionType), List(valuesOfExnMessage :: Nil)))
120-
) :: Nil,
121-
finalizer = EmptyTree
122-
)
127+
128+
val valuesOfBody: Tree =
129+
val defaultCase =
130+
val msg = Apply(Select(Literal(Constant("enum case not found: ")), nme.PLUS), Ident(nme.nameDollar))
131+
CaseDef(Ident(nme.WILDCARD), EmptyTree,
132+
Throw(New(TypeTree(defn.IllegalArgumentExceptionType), List(msg :: Nil))))
133+
val stringCases = enumValues.map(enumValue =>
134+
CaseDef(Literal(Constant(enumValue.name.toString)), EmptyTree, enumValue)
135+
) ::: defaultCase :: Nil
136+
Match(Ident(nme.nameDollar), stringCases)
123137
val valueOfDef = DefDef(nme.valueOf, Nil, List(param(nme.nameDollar, defn.StringType) :: Nil),
124138
TypeTree(), valuesOfBody)
125139
.withFlags(Synthetic)
126140

127-
valuesDef ::
128141
privateValuesDef ::
142+
valuesDef ::
129143
valueOfDef :: Nil
130144
}
131145

132-
private def enumLookupMethods(cases: List[(Int, TermName)])(using Context): List[Tree] =
133-
if isJavaEnum || cases.isEmpty then Nil
134-
else
135-
val defaultCase =
136-
val ord = Ident(nme.ordinal)
137-
val err = Throw(New(TypeTree(defn.IndexOutOfBoundsException.typeRef), List(Select(ord, nme.toString_) :: Nil)))
138-
CaseDef(ord, EmptyTree, err)
139-
val valueCases = cases.map((i, name) =>
140-
CaseDef(Literal(Constant(i)), EmptyTree, Ident(name))
141-
) ::: defaultCase :: Nil
142-
val fromOrdinalDef = DefDef(nme.fromOrdinalDollar, Nil, List(param(nme.ordinalDollar_, defn.IntType) :: Nil),
143-
rawRef(enumClass.typeRef), Match(Ident(nme.ordinalDollar_), valueCases))
144-
.withFlags(Synthetic | Private)
145-
fromOrdinalDef :: Nil
146+
private def enumLookupMethods(constraints: EnumConstraints)(using Context): List[Tree] =
147+
def scaffolding: List[Tree] = if constraints.cached then enumScaffolding(constraints.enumCases.map(_._2)) else Nil
148+
def valueCtor: List[Tree] = if constraints.requiresCreator then enumValueCreator :: Nil else Nil
149+
def byOrdinal: List[Tree] =
150+
if isJavaEnum || !constraints.cached then Nil
151+
else
152+
val defaultCase =
153+
val ord = Ident(nme.ordinal)
154+
val err = Throw(New(TypeTree(defn.IndexOutOfBoundsException.typeRef), List(Select(ord, nme.toString_) :: Nil)))
155+
CaseDef(ord, EmptyTree, err)
156+
val valueCases = constraints.enumCases.map((i, enumValue) =>
157+
CaseDef(Literal(Constant(i)), EmptyTree, enumValue)
158+
) ::: defaultCase :: Nil
159+
val fromOrdinalDef = DefDef(nme.fromOrdinalDollar, Nil, List(param(nme.ordinalDollar_, defn.IntType) :: Nil),
160+
rawRef(enumClass.typeRef), Match(Ident(nme.ordinalDollar_), valueCases))
161+
.withFlags(Synthetic | Private)
162+
fromOrdinalDef :: Nil
163+
164+
scaffolding ::: valueCtor ::: byOrdinal
165+
end enumLookupMethods
146166

147167
/** A creation method for a value of enum type `E`, which is defined as follows:
148168
*
@@ -167,7 +187,7 @@ object DesugarEnums {
167187
parents = enumClassRef :: scalaRuntimeDot(tpnme.EnumValue) :: Nil,
168188
derived = Nil,
169189
self = EmptyValDef,
170-
body = fieldMethods ::: registerCall :: Nil
190+
body = fieldMethods
171191
).withAttachment(ExtendsSingletonMirror, ()))
172192
DefDef(nme.DOLLAR_NEW, Nil,
173193
List(List(param(nme.ordinalDollar_, defn.IntType), param(nme.nameDollar, defn.StringType))),
@@ -279,27 +299,26 @@ object DesugarEnums {
279299
* unless that scaffolding was already generated by a previous call to `nextEnumKind`.
280300
*/
281301
def nextOrdinal(name: Name, kind: CaseKind.Value, definesLookups: Boolean)(using Context): (Int, List[Tree]) = {
282-
val (ordinal, seenKind, seenCases) = ctx.tree.removeAttachment(EnumCaseCount).getOrElse((0, CaseKind.Class, Nil))
283-
val minKind = if kind < seenKind then kind else seenKind
302+
val (ordinal, seenMinKind, seenMaxKind, seenCases) =
303+
ctx.tree.removeAttachment(EnumCaseCount).getOrElse((0, CaseKind.Class, CaseKind.Simple, Nil))
304+
val minKind = if kind < seenMinKind then kind else seenMinKind
305+
val maxKind = if kind > seenMaxKind then kind else seenMaxKind
284306
val cases = name match
285307
case name: TermName => (ordinal, name) :: seenCases
286308
case _ => seenCases
287-
ctx.tree.pushAttachment(EnumCaseCount, (ordinal + 1, minKind, cases))
288-
val scaffolding0 =
289-
if (kind >= seenKind) Nil
290-
else if (kind == CaseKind.Object) enumScaffolding
291-
else if (seenKind == CaseKind.Object) enumValueCreator :: Nil
292-
else enumScaffolding :+ enumValueCreator
293-
val scaffolding =
294-
if definesLookups then scaffolding0 ::: enumLookupMethods(cases.reverse)
295-
else scaffolding0
296-
(ordinal, scaffolding)
309+
if definesLookups then
310+
val companionRef = ref(enumCompanion.termRef)
311+
val cachedValues = cases.reverse.map((i, name) => (i, Select(companionRef, name)))
312+
(ordinal, enumLookupMethods(EnumConstraints(minKind, maxKind, cachedValues)))
313+
else
314+
ctx.tree.pushAttachment(EnumCaseCount, (ordinal + 1, minKind, maxKind, cases))
315+
(ordinal, Nil)
297316
}
298317

299-
def param(name: TermName, typ: Type)(using Context) =
300-
ValDef(name, TypeTree(typ), EmptyTree).withFlags(Param)
318+
def param(name: TermName, typ: Type)(using Context): ValDef = param(name, TypeTree(typ))
319+
def param(name: TermName, tpt: Tree)(using Context): ValDef = ValDef(name, tpt, EmptyTree).withFlags(Param)
301320

302-
private def isJavaEnum(using Context): Boolean = ctx.owner.linkedClass.derivesFrom(defn.JavaEnumClass)
321+
private def isJavaEnum(using Context): Boolean = enumClass.derivesFrom(defn.JavaEnumClass)
303322

304323
def ordinalMeth(body: Tree)(using Context): DefDef =
305324
DefDef(nme.ordinal, Nil, Nil, TypeTree(defn.IntType), body)
@@ -325,10 +344,10 @@ object DesugarEnums {
325344
val enumLabelDef = enumLabelLit(name.toString)
326345
val impl1 = cpy.Template(impl)(
327346
parents = impl.parents :+ scalaRuntimeDot(tpnme.EnumValue),
328-
body = ordinalDef ::: enumLabelDef :: registerCall :: Nil
347+
body = ordinalDef ::: enumLabelDef :: Nil
329348
).withAttachment(ExtendsSingletonMirror, ())
330349
val vdef = ValDef(name, TypeTree(), New(impl1)).withMods(mods.withAddedFlags(EnumValue, span))
331-
flatTree(scaffolding ::: vdef :: Nil).withSpan(span)
350+
flatTree(vdef :: scaffolding).withSpan(span)
332351
}
333352
}
334353

@@ -344,6 +363,6 @@ object DesugarEnums {
344363
val (tag, scaffolding) = nextOrdinal(name, CaseKind.Simple, definesLookups)
345364
val creator = Apply(Ident(nme.DOLLAR_NEW), List(Literal(Constant(tag)), Literal(Constant(name.toString))))
346365
val vdef = ValDef(name, enumClassRef, creator).withMods(mods.withAddedFlags(EnumValue, span))
347-
flatTree(scaffolding ::: vdef :: Nil).withSpan(span)
366+
flatTree(vdef :: scaffolding).withSpan(span)
348367
}
349368
}

compiler/src/dotty/tools/dotc/core/Definitions.scala

-2
Original file line numberDiff line numberDiff line change
@@ -749,8 +749,6 @@ class Definitions {
749749

750750
@tu lazy val EnumClass: ClassSymbol = requiredClass("scala.Enum")
751751

752-
@tu lazy val EnumValuesClass: ClassSymbol = requiredClass("scala.runtime.EnumValues")
753-
754752
@tu lazy val EnumValueSerializationProxyClass: ClassSymbol = requiredClass("scala.runtime.EnumValueSerializationProxy")
755753
@tu lazy val EnumValueSerializationProxyConstructor: TermSymbol =
756754
EnumValueSerializationProxyClass.requiredMethod(nme.CONSTRUCTOR, List(ClassType(TypeBounds.empty), IntType))

compiler/src/dotty/tools/dotc/transform/CheckReentrant.scala

+1-4
Original file line numberDiff line numberDiff line change
@@ -46,12 +46,9 @@ class CheckReentrant extends MiniPhase {
4646
def isIgnored(sym: Symbol)(using Context): Boolean =
4747
sym.hasAnnotation(sharableAnnot()) ||
4848
sym.hasAnnotation(unsharedAnnot()) ||
49-
sym.topLevelClass.owner == scalaJSIRPackageClass() ||
49+
sym.topLevelClass.owner == scalaJSIRPackageClass()
5050
// We would add @sharable annotations on ScalaJSVersions and
5151
// VersionChecks but we do not have control over that code
52-
sym.owner == defn.EnumValuesClass
53-
// enum values are initialized eagerly before use
54-
// in the long run, we should make them vals
5552

5653
def scanning(sym: Symbol)(op: => Unit)(using Context): Unit = {
5754
report.log(i"${" " * indent}scanning $sym")

compiler/src/dotty/tools/dotc/transform/init/Env.scala

-1
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@ case class Env(ctx: Context) {
2525

2626
// Methods that should be ignored in the checking
2727
lazy val ignoredMethods: Set[Symbol] = Set(
28-
requiredClass("scala.runtime.EnumValues").requiredMethod("register"),
2928
defn.Any_getClass,
3029
defn.Any_isInstanceOf,
3130
defn.Object_eq,

docs/docs/reference/enums/desugarEnums.md

+3-7
Original file line numberDiff line numberDiff line change
@@ -121,12 +121,10 @@ map into `case class`es or `val`s.
121121
```
122122
expands to a value definition in `E`'s companion object:
123123
```scala
124-
val C = new <parents> { <body>; def ordinal = n; $values.register(this) }
124+
val C = new <parents> { <body>; def ordinal = n }
125125
```
126126
where `n` is the ordinal number of the case in the companion object,
127-
starting from 0. The statement `$values.register(this)` registers the value
128-
as one of the `values` of the enumeration (see below). `$values` is a
129-
compiler-defined private value in the companion object. The anonymous class also
127+
starting from 0. The anonymous class also
130128
implements the abstract `Product` methods that it inherits from `Enum`.
131129

132130

@@ -162,8 +160,7 @@ An enum `E` (possibly generic) that defines one or more singleton cases
162160
will define the following additional synthetic members in its companion object (where `E'` denotes `E` with
163161
any type parameters replaced by wildcards):
164162

165-
- A method `valueOf(name: String): E'`. It returns the singleton case value whose
166-
`toString` representation is `name`.
163+
- A method `valueOf(name: String): E'`. It returns the singleton case value whose `enumLabel` is `name`.
167164
- A method `values` which returns an `Array[E']` of all singleton case
168165
values defined by `E`, in the order of their definitions.
169166

@@ -178,7 +175,6 @@ If `E` contains at least one simple case, its companion object will define in ad
178175
def enumLabel = $name
179176
override def productPrefix = enumLabel // if not overridden in `E`
180177
override def toString = enumLabel // if not overridden in `E`
181-
$values.register(this) // register enum value so that `valueOf` and `values` can return it.
182178
}
183179
```
184180

docs/docs/reference/enums/enums.md

-1
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,6 @@ val Venus: Planet =
136136
def enumLabel: String = "Venus"
137137
override def productPrefix: String = enumLabel
138138
override def toString: String = enumLabel
139-
// internal code to register value
140139
}
141140
```
142141

library/src-bootstrapped/scala/runtime/EnumValues.scala

-21
This file was deleted.

tests/neg/enumsLabelDef.scala

+5-2
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,18 @@
11
enum Labelled {
22

33
case A // error overriding method enumLabel in class Labelled of type => String;
4-
case B(arg: Int) // error overriding method enumLabel in class Labelled of type => String;
54

65
def enumLabel: String = "nolabel"
76
}
87

98
trait Mixin { def enumLabel: String = "mixin" }
109

1110
enum Mixed extends Mixin {
12-
case C // error overriding method enumLabel in trait Mixin of type => String;
11+
case B // error overriding method enumLabel in trait Mixin of type => String;
12+
}
13+
14+
enum MixedAlso {
15+
case C extends MixedAlso with Mixin // error overriding method enumLabel in trait Mixin of type => String;
1316
}
1417

1518
trait HasEnumLabel { def enumLabel: String }

tests/run/enum-java.check

+1-1
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ MONDAY : 0
2626
TUESDAY : 1
2727
SATURDAY : 2
2828
By-name value: MONDAY
29-
Correctly failed to retrieve illegal name, message: key not found: stuff
29+
Correctly failed to retrieve illegal name, message: enum case not found: stuff
3030

3131
Collections Test
3232
Retrieving Monday: workday

tests/run/enums-java-compat.check

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,4 @@ TUESDAY : 1
66
SATURDAY : 2
77
Stuff : 3
88
By-name value: MONDAY
9-
Correctly failed to retrieve illegal name, message: key not found: stuff
9+
Correctly failed to retrieve illegal name, message: enum case not found: stuff

tests/run/enums-thunk.scala

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
class Outer {
2+
val thunk = { () =>
3+
enum E { case A1 }
4+
E.A1
5+
}
6+
val thunk2 = { () =>
7+
enum E { case A2 }
8+
E.values
9+
}
10+
}
11+
12+
object Outer2 {
13+
val thunk = { () =>
14+
enum E { case B1 }
15+
E.B1
16+
}
17+
val thunk2 = { () =>
18+
enum E { case B2 }
19+
E.values
20+
}
21+
}
22+
23+
@main def Test =
24+
assert(Outer().thunk().toString == "A1")
25+
assert(Outer().thunk2()(0).toString == "A2")
26+
assert(Outer2.thunk().toString == "B1")
27+
assert(Outer2.thunk2()(0).toString == "B2")

0 commit comments

Comments
 (0)