Skip to content

Commit d2330c9

Browse files
committed
Always override enumLabel in each enum case.
As a rule, even class cases override it to be consistent with singleton cases which must have a value matching the identifier. this also moves runtime.EnumValues into src-bootstrapped so that fromName can directly call .enumLabel This commit also removes the forced override of productPrefix in EnumValue and individial cases, this allows the user to customise productPrefix as with typical case classes. An override of productPrefix will be generated in SyntheticMembers to forward to enumLabel if a class parent does not define one.
1 parent c11c43e commit d2330c9

File tree

12 files changed

+69
-61
lines changed

12 files changed

+69
-61
lines changed

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

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -586,7 +586,9 @@ object desugar {
586586
yield syntheticProperty(selName, caseParams(i).tpt,
587587
Select(This(EmptyTypeIdent), caseParams(i).name))
588588

589-
def ordinalMeths = if (isEnumCase) ordinalMethLit(nextOrdinal(CaseKind.Class)._1) :: Nil else Nil
589+
def enumMeths =
590+
if (isEnumCase) ordinalMethLit(nextOrdinal(CaseKind.Class)._1) :: enumLabelLit(className.toString) :: Nil
591+
else Nil
590592
def copyMeths = {
591593
val hasRepeatedParam = constrVparamss.exists(_.exists {
592594
case ValDef(_, tpt, _) => isRepeated(tpt)
@@ -605,7 +607,7 @@ object desugar {
605607
}
606608

607609
if (isCaseClass)
608-
copyMeths ::: ordinalMeths ::: productElemMeths
610+
copyMeths ::: enumMeths ::: productElemMeths
609611
else Nil
610612
}
611613

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

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -125,9 +125,9 @@ object DesugarEnums {
125125
/** A creation method for a value of enum type `E`, which is defined as follows:
126126
*
127127
* private def $new(_$ordinal: Int, $name: String) = new E with scala.runtime.EnumValue {
128-
* def ordinal = _$ordinal // if `E` does not derive from jl.Enum
129-
* override def productPrefix = $name // if `E` does not derive from `java.lang.Enum`
130-
* override def productPrefix = this.name // if `E` derives from `java.lang.Enum`
128+
* override def ordinal = _$ordinal // if `E` does not derive from `java.lang.Enum`
129+
* override def enumLabel = $name // if `E` does not derive from `java.lang.Enum`
130+
* override def enumLabel = this.name // if `E` derives from `java.lang.Enum`
131131
* $values.register(this)
132132
* }
133133
*/
@@ -145,7 +145,7 @@ object DesugarEnums {
145145
parents = enumClassRef :: scalaRuntimeDot(tpnme.EnumValue) :: Nil,
146146
derived = Nil,
147147
self = EmptyValDef,
148-
body = fieldMethods ::: productPrefixMeth :: registerCall :: Nil
148+
body = fieldMethods ::: registerCall :: Nil
149149
).withAttachment(ExtendsSingletonMirror, ()))
150150
DefDef(nme.DOLLAR_NEW, Nil,
151151
List(List(param(nme.ordinalDollar_, defn.IntType), param(nme.nameDollar, defn.StringType))),
@@ -274,16 +274,11 @@ object DesugarEnums {
274274
private def isJavaEnum(using Context): Boolean = ctx.owner.linkedClass.derivesFrom(defn.JavaEnumClass)
275275

276276
def ordinalMeth(body: Tree)(using Context): DefDef =
277-
DefDef(nme.ordinal, Nil, Nil, TypeTree(defn.IntType), body)
277+
DefDef(nme.ordinal, Nil, Nil, TypeTree(defn.IntType), body).withFlags(Override)
278278

279279
def enumLabelMeth(body: Tree)(using Context): DefDef =
280280
DefDef(nme.enumLabel, Nil, Nil, TypeTree(defn.StringType), body).withFlags(Override)
281281

282-
def productPrefixMeth(using Context): DefDef =
283-
// TODO: once `scala.Enum` is rebootstrapped and `.valueOf` is implemented in terms of `enumLabel` we can make
284-
// `productPrefix` overrideable in SyntheticMembers
285-
DefDef(nme.productPrefix, Nil, Nil, TypeTree(defn.StringType), Select(This(EmptyTypeIdent), nme.enumLabel)).withFlags(Override)
286-
287282
def ordinalMethLit(ord: Int)(using Context): DefDef =
288283
ordinalMeth(Literal(Constant(ord)))
289284

@@ -302,7 +297,7 @@ object DesugarEnums {
302297
val enumLabelDef = enumLabelLit(name.toString)
303298
val impl1 = cpy.Template(impl)(
304299
parents = impl.parents :+ scalaRuntimeDot(tpnme.EnumValue),
305-
body = ordinalDef ::: enumLabelDef :: productPrefixMeth :: registerCall :: Nil
300+
body = ordinalDef ::: enumLabelDef :: registerCall :: Nil
306301
).withAttachment(ExtendsSingletonMirror, ())
307302
val vdef = ValDef(name, TypeTree(), New(impl1)).withMods(mods.withAddedFlags(EnumValue, span))
308303
flatTree(scaffolding ::: vdef :: Nil).withSpan(span)

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

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -648,8 +648,6 @@ class Definitions {
648648
@tu lazy val NoneModule: Symbol = requiredModule("scala.None")
649649

650650
@tu lazy val EnumClass: ClassSymbol = requiredClass("scala.Enum")
651-
@tu lazy val Enum_ordinal: Symbol = EnumClass.requiredMethod(nme.ordinal)
652-
@tu lazy val Enum_enumLabel: Symbol = EnumClass.requiredMethod(nme.enumLabel)
653651

654652
@tu lazy val EnumValuesClass: ClassSymbol = requiredClass("scala.runtime.EnumValues")
655653

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

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ class SyntheticMembers(thisPhase: DenotTransformer) {
5757
private var myValueSymbols: List[Symbol] = Nil
5858
private var myCaseSymbols: List[Symbol] = Nil
5959
private var myCaseModuleSymbols: List[Symbol] = Nil
60-
private var myEnumCaseClassSymbols: List[Symbol] = Nil
60+
private var myEnumValueSymbols: List[Symbol] = Nil
6161
private var myNonJavaEnumValueSymbols: List[Symbol] = Nil
6262

6363
private def initSymbols(using Context) =
@@ -67,14 +67,14 @@ class SyntheticMembers(thisPhase: DenotTransformer) {
6767
defn.Product_productArity, defn.Product_productPrefix, defn.Product_productElement,
6868
defn.Product_productElementName)
6969
myCaseModuleSymbols = myCaseSymbols.filter(_ ne defn.Any_equals)
70-
myEnumCaseClassSymbols = List(defn.Enum_enumLabel)
71-
myNonJavaEnumValueSymbols = List(defn.Any_toString)
70+
myEnumValueSymbols = List(defn.Product_productPrefix)
71+
myNonJavaEnumValueSymbols = myEnumValueSymbols :+ defn.Any_toString
7272
}
7373

7474
def valueSymbols(using Context): List[Symbol] = { initSymbols; myValueSymbols }
7575
def caseSymbols(using Context): List[Symbol] = { initSymbols; myCaseSymbols }
7676
def caseModuleSymbols(using Context): List[Symbol] = { initSymbols; myCaseModuleSymbols }
77-
def enumCaseClassSymbols(using Context): List[Symbol] = { initSymbols; myEnumCaseClassSymbols }
77+
def enumValueSymbols(using Context): List[Symbol] = { initSymbols; myEnumValueSymbols }
7878
def nonJavaEnumValueSymbols(using Context): List[Symbol] = { initSymbols; myNonJavaEnumValueSymbols }
7979

8080
private def existingDef(sym: Symbol, clazz: ClassSymbol)(using Context): Symbol = {
@@ -101,9 +101,9 @@ class SyntheticMembers(thisPhase: DenotTransformer) {
101101
val symbolsToSynthesize: List[Symbol] =
102102
if (clazz.is(Case))
103103
if (clazz.is(Module)) caseModuleSymbols
104-
else if (isEnumCase) caseSymbols ++ enumCaseClassSymbols
105104
else caseSymbols
106105
else if (isNonJavaEnumValue) nonJavaEnumValueSymbols
106+
else if (isEnumValue) enumValueSymbols
107107
else if (isDerivedValueClass(clazz)) valueSymbols
108108
else Nil
109109

@@ -138,7 +138,8 @@ class SyntheticMembers(thisPhase: DenotTransformer) {
138138
case nme.equals_ => equalsBody(vrefss.head.head)
139139
case nme.canEqual_ => canEqualBody(vrefss.head.head)
140140
case nme.productArity => Literal(Constant(accessors.length))
141-
case nme.productPrefix | nme.enumLabel => ownName
141+
case nme.productPrefix if isEnumValue => callEnumLabel
142+
case nme.productPrefix => ownName
142143
case nme.productElement => productElementBody(accessors.length, vrefss.head.head)
143144
case nme.productElementName => productElementNameBody(accessors.length, vrefss.head.head)
144145
}

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1130,6 +1130,7 @@ trait Checking {
11301130

11311131
end checkEnumParent
11321132

1133+
11331134
/** Check that all references coming from enum cases in an enum companion object
11341135
* are legal.
11351136
* @param cdef the enum companion object class

docs/docs/reference/enums/desugarEnums.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -174,8 +174,8 @@ If `E` contains at least one simple case, its companion object will define in ad
174174
follows.
175175
```scala
176176
private def $new(_$ordinal: Int, $name: String) = new E with runtime.EnumValue {
177-
def ordinal = _$ordinal
178-
def enumLabel = $name
177+
override def ordinal = _$ordinal
178+
override def enumLabel = $name
179179
override def productPrefix = enumLabel // if not overridden in `E`
180180
override def toString = enumLabel // if not overridden in `E`
181181
$values.register(this) // register enum value so that `valueOf` and `values` can return it.

docs/docs/reference/enums/enums.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ For a more in-depth example of using Scala 3 enums from Java, see [this test](ht
110110
### Implementation
111111

112112
Enums are represented as `sealed` classes that extend the `scala.Enum` trait.
113-
This trait defines a two public methods, `ordinal` and `enumLabel`:
113+
This trait defines two public methods, `ordinal` and `enumLabel`:
114114

115115
```scala
116116
package scala
@@ -132,8 +132,8 @@ For instance, the `Venus` value above would be defined like this:
132132
```scala
133133
val Venus: Planet =
134134
new Planet(4.869E24, 6051800.0) {
135-
def ordinal: Int = 1
136-
def enumLabel: String = "Venus"
135+
override def ordinal: Int = 1
136+
override def enumLabel: String = "Venus"
137137
override def productPrefix: String = enumLabel
138138
override def toString: String = enumLabel
139139
// internal code to register value

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ class EnumValues[E <: Enum] {
1414

1515
def fromInt: Map[Int, E] = myMap
1616
def fromName: Map[String, E] = {
17-
if (fromNameCache == null) fromNameCache = myMap.values.map(v => v.productPrefix -> v).toMap
17+
if (fromNameCache == null) fromNameCache = myMap.values.map(v => v.enumLabel -> v).toMap
1818
fromNameCache
1919
}
2020
def values: Iterable[E] = myMap.values
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package scala.runtime
2+
3+
import scala.collection.immutable.TreeMap
4+
5+
class EnumValues[E <: Enum] {
6+
private[this] var myMap: Map[Int, E] = TreeMap.empty
7+
private[this] var fromNameCache: Map[String, E] = null
8+
9+
def register(v: E) = {
10+
require(!myMap.contains(v.ordinal))
11+
myMap = myMap.updated(v.ordinal, v)
12+
fromNameCache = null
13+
}
14+
15+
def fromInt: Map[Int, E] = myMap
16+
def fromName: Map[String, E] = {
17+
if (fromNameCache == null) fromNameCache = myMap.values.map(v => v.toString -> v).toMap
18+
fromNameCache
19+
}
20+
def values: Iterable[E] = myMap.values
21+
}

library/src/scala/runtime/EnumValue.scala

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ package scala.runtime
33
super trait EnumValue extends Product, Serializable:
44
override def canEqual(that: Any) = this eq that.asInstanceOf[AnyRef]
55
override def productArity: Int = 0
6-
override def productPrefix: String = toString
76
override def productElement(n: Int): Any =
87
throw IndexOutOfBoundsException(n.toString)
98
override def productElementName(n: Int): String =

tests/neg/enumsLabelDef.scala

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
enum Labelled {
2+
23
case A // error: cannot override final member method enumLabel in class Labelled
3-
case B(arg: Int) // ok: enumLabel has same behaviour here as productPrefix
4+
case B(arg: Int) // error: cannot override final member method enumLabel in class Labelled
45

5-
override final def enumLabel: String = "nolabel"
6+
final def enumLabel: String = "nolabel"
67
}

tests/run/enum-custom-toString.scala

Lines changed: 21 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@ enum EJ extends java.lang.Enum[EJ]:
66
case B
77
override def toString: String = "overridden"
88

9-
trait Mixin:
9+
trait Mixin extends Enum:
10+
override def enumLabel: String = "nolabel"
11+
override def productPrefix: String = "noprefix"
1012
override def toString: String = "overridden"
1113

1214
enum EM extends Mixin:
@@ -27,21 +29,13 @@ enum EC: // control case
2729
enum EO:
2830
case H
2931
case I(arg: Int)
30-
// TODO: allow `productPrefix` to be overridden in singleton enum values - until `scala.Enum` is bootstrapped with
31-
// `enumLabel`, `runtime.EnumValues` uses `productPrefix` for by-name lookup.
3232
override def productPrefix: String = "noprefix"
33+
override def toString: String = "overridden"
3334
end EO
3435

35-
enum EL {
36-
case J
37-
case K(arg: Int)
38-
override def enumLabel: String = "nolabel" // will always be overridden by simple cases
39-
}
40-
41-
enum EQ[T] {
42-
case L extends EQ[Int]
43-
override def enumLabel: String = "nolabel" // will always be overridden by value cases
44-
}
36+
enum EQ:
37+
case J extends EQ with Mixin
38+
case K(arg: Int) extends EQ with Mixin
4539

4640
abstract class Tag[T] extends Enum
4741
object Tag:
@@ -61,8 +55,8 @@ object Tag:
6155
assert(EJ.B.enumLabel == "B", s"EJ.B.enumLabel = ${EJ.B.enumLabel}")
6256
assert(EJ.valueOf("B") == EJ.B, s"EJ.valueOf(B) = ${EJ.valueOf("B")}")
6357
assert(EM.C.toString == "overridden", s"EM.C.toString = ${EM.C.toString}")
64-
assert(EM.C.productPrefix == "C", s"EM.C.productPrefix = ${EM.C.productPrefix}")
65-
assert(EM.C.enumLabel == "C", s"EM.C.enumLabel = ${EM.C.enumLabel}")
58+
assert(EM.C.productPrefix == "noprefix", s"EM.C.productPrefix = ${EM.C.productPrefix}")
59+
assert(EM.C.enumLabel == "C", s"EM.C.enumLabel = ${EM.C.enumLabel}") // enumLabel override is useless
6660
assert(EM.valueOf("C") == EM.C, s"EM.valueOf(C) = ${EM.valueOf("C")}")
6761
assert(ET.D.toString == "overridden", s"ET.D.toString = ${ET.D.toString}")
6862
assert(ET.D.productPrefix == "D", s"ET.D.productPrefix = ${ET.D.productPrefix}")
@@ -77,27 +71,23 @@ object Tag:
7771
assert(EC.G(0).toString == "G(0)", s"EC.G(0).toString = ${EC.G(0).toString}")
7872
assert(EC.G(0).productPrefix == "G", s"EC.G(0).productPrefix = ${EC.G(0).productPrefix}")
7973
assert(EC.G(0).enumLabel == "G", s"EC.G(0).enumLabel = ${EC.G(0).enumLabel}")
80-
assert(EO.H.toString == "H", s"EO.H.toString = ${EO.H.toString}")
81-
assert(EO.H.productPrefix == "H", s"EO.H.productPrefix = ${EO.H.productPrefix}") // TODO: enable override
74+
assert(EO.H.toString == "overridden", s"EO.H.toString = ${EO.H.toString}")
75+
assert(EO.H.productPrefix == "noprefix", s"EO.H.productPrefix = ${EO.H.productPrefix}")
8276
assert(EO.H.enumLabel == "H", s"EO.H.enumLabel = ${EO.H.enumLabel}")
8377
assert(EO.valueOf("H") == EO.H, s"EO.valueOf(H) = ${EO.valueOf("H")}")
84-
assert(EO.I(0).toString == "noprefix(0)", s"EO.I(0).toString = ${EO.I(0).toString}")
78+
assert(EO.I(0).toString == "overridden", s"EO.I(0).toString = ${EO.I(0).toString}")
8579
assert(EO.I(0).productPrefix == "noprefix", s"EO.I(0).productPrefix = ${EO.I(0).productPrefix}")
8680
assert(EO.I(0).enumLabel == "I", s"EO.I(0).enumLabel = ${EO.I(0).enumLabel}")
87-
assert(EL.J.toString == "J", s"EL.J.toString = ${EL.J.toString}")
88-
assert(EL.J.productPrefix == "J", s"EL.J.productPrefix = ${EL.J.productPrefix}")
89-
assert(EL.J.enumLabel == "J", s"EL.J.enumLabel = ${EL.J.enumLabel}") // can't override label in simple case
90-
assert(EL.valueOf("J") == EL.J, s"EL.valueOf(J) = ${EL.valueOf("J")}")
91-
assert(EL.K(0).toString == "K(0)", s"EL.K(0).toString = ${EL.K(0).toString}")
92-
assert(EL.K(0).productPrefix == "K", s"EL.K(0).productPrefix = ${EL.K(0).productPrefix}")
93-
assert(EL.K(0).enumLabel == "nolabel", s"EL.K(0).enumLabel = ${EL.K(0).enumLabel}") // enum label overridden in class case
94-
assert(EQ.L.toString == "L", s"EQ.L.toString = ${EQ.L.toString}")
95-
assert(EQ.L.productPrefix == "L", s"EQ.L.productPrefix = ${EQ.L.productPrefix}")
96-
assert(EQ.L.enumLabel == "L", s"EQ.L.enumLabel = ${EQ.L.enumLabel}") // can't override label in value case
97-
81+
assert(EQ.J.toString == "overridden", s"EQ.J.toString = ${EQ.J.toString}")
82+
assert(EQ.J.productPrefix == "noprefix", s"EQ.J.productPrefix = ${EQ.J.productPrefix}")
83+
assert(EQ.J.enumLabel == "J", s"EQ.J.enumLabel = ${EQ.J.enumLabel}") // enumLabel override is useless
84+
assert(EQ.valueOf("J") == EQ.J, s"EQ.valueOf(J) = ${EQ.valueOf("J")}")
85+
assert(EQ.K(0).toString == "overridden", s"EQ.K(0).toString = ${EQ.K(0).toString}")
86+
assert(EQ.K(0).productPrefix == "noprefix", s"EQ.K(0).productPrefix = ${EQ.K(0).productPrefix}")
87+
assert(EQ.K(0).enumLabel == "K", s"EQ.K(0).enumLabel = ${EQ.K(0).enumLabel}") // enumLabel override is useless
88+
assert(Tag.IntTag.productPrefix == "", s"Tag.IntTag.productPrefix = ${Tag.IntTag.productPrefix}")
89+
assert(Tag.IntTag.enumLabel == "IntTag", s"Tag.IntTag.enumLabel = ${Tag.IntTag.enumLabel}")
9890
assert(
9991
assertion = Tag.IntTag.toString == s"${Tag.IntTag.getClass.getName}@${Integer.toHexString(123)}",
10092
message = s"Tag.IntTag.toString = ${Tag.IntTag.toString}"
10193
)
102-
assert(Tag.IntTag.productPrefix == Tag.IntTag.toString, s"Tag.IntTag.productPrefix = ${Tag.IntTag.productPrefix}")
103-
assert(Tag.IntTag.enumLabel == "IntTag", s"Tag.IntTag.enumLabel = ${Tag.IntTag.enumLabel}")

0 commit comments

Comments
 (0)