Skip to content

Commit

Permalink
Merge pull request #9987 from dotty-staging/topic/enum-fromOrdinal
Browse files Browse the repository at this point in the history
make fromOrdinal public and always available
  • Loading branch information
bishabosha authored Oct 15, 2020
2 parents 63771ac + 6d50fef commit 0525ba0
Show file tree
Hide file tree
Showing 6 changed files with 127 additions and 38 deletions.
41 changes: 26 additions & 15 deletions compiler/src/dotty/tools/dotc/ast/DesugarEnums.scala
Original file line number Diff line number Diff line change
Expand Up @@ -154,22 +154,29 @@ object DesugarEnums {
private def enumLookupMethods(constraints: EnumConstraints)(using Context): List[Tree] =
def scaffolding: List[Tree] = if constraints.cached then enumScaffolding(constraints.enumCases.map(_._2)) else Nil
def valueCtor: List[Tree] = if constraints.requiresCreator then enumValueCreator :: Nil else Nil
def byOrdinal: List[Tree] =
if isJavaEnum || !constraints.cached then Nil
def fromOrdinal: Tree =
def throwArg(ordinal: Tree) =
Throw(New(TypeTree(defn.NoSuchElementExceptionType), List(Select(ordinal, nme.toString_) :: Nil)))
if !constraints.cached then
fromOrdinalMeth(throwArg)
else
val defaultCase =
val ord = Ident(nme.ordinal)
val err = Throw(New(TypeTree(defn.IndexOutOfBoundsException.typeRef), List(Select(ord, nme.toString_) :: Nil)))
CaseDef(ord, EmptyTree, err)
val valueCases = constraints.enumCases.map((i, enumValue) =>
CaseDef(Literal(Constant(i)), EmptyTree, enumValue)
) ::: defaultCase :: Nil
val fromOrdinalDef = DefDef(nme.fromOrdinalDollar, Nil, List(param(nme.ordinalDollar_, defn.IntType) :: Nil),
rawRef(enumClass.typeRef), Match(Ident(nme.ordinalDollar_), valueCases))
.withFlags(Synthetic | Private)
fromOrdinalDef :: Nil

scaffolding ::: valueCtor ::: byOrdinal
def default(ordinal: Tree) =
CaseDef(Ident(nme.WILDCARD), EmptyTree, throwArg(ordinal))
if constraints.isEnumeration then
fromOrdinalMeth(ordinal =>
Try(Apply(valuesDot(nme.apply), ordinal), default(ordinal) :: Nil, EmptyTree))
else
fromOrdinalMeth(ordinal =>
Match(ordinal,
constraints.enumCases.map((i, enumValue) => CaseDef(Literal(Constant(i)), EmptyTree, enumValue))
:+ default(ordinal)))

if !enumClass.exists then
// in the case of a double definition of an enum that only defines class cases (see tests/neg/i4470c.scala)
// it seems `enumClass` might be `NoSymbol`; in this case we provide no scaffolding.
Nil
else
scaffolding ::: valueCtor ::: fromOrdinal :: Nil
end enumLookupMethods

/** A creation method for a value of enum type `E`, which is defined as follows:
Expand Down Expand Up @@ -278,6 +285,10 @@ object DesugarEnums {
def ordinalMethLit(ord: Int)(using Context): DefDef =
ordinalMeth(Literal(Constant(ord)))

def fromOrdinalMeth(body: Tree => Tree)(using Context): DefDef =
DefDef(nme.fromOrdinal, Nil, (param(nme.ordinal, defn.IntType) :: Nil) :: Nil,
rawRef(enumClass.typeRef), body(Ident(nme.ordinal))).withFlags(Synthetic)

/** Expand a module definition representing a parameterless enum case */
def expandEnumModule(name: TermName, impl: Template, mods: Modifiers, definesLookups: Boolean, span: Span)(using Context): Tree = {
assert(impl.body.isEmpty)
Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/core/StdNames.scala
Original file line number Diff line number Diff line change
Expand Up @@ -617,7 +617,7 @@ object StdNames {
val using: N = "using"
val value: N = "value"
val valueOf : N = "valueOf"
val fromOrdinalDollar: N = "$fromOrdinal"
val fromOrdinal: N = "fromOrdinal"
val values: N = "values"
val view_ : N = "view"
val wait_ : N = "wait"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -429,7 +429,7 @@ class SyntheticMembers(thisPhase: DenotTransformer) {
* and not deriving from `java.lang.Enum` add the method:
*
* private def readResolve(): AnyRef =
* MyEnum.$fromOrdinal(this.ordinal)
* MyEnum.fromOrdinal(this.ordinal)
*
* unless an implementation already exists, otherwise do nothing.
*/
Expand All @@ -443,7 +443,7 @@ class SyntheticMembers(thisPhase: DenotTransformer) {
List(
DefDef(readResolveDef(clazz),
_ => ref(clazz.owner.owner.sourceModule)
.select(nme.fromOrdinalDollar)
.select(nme.fromOrdinal)
.appliedTo(This(clazz).select(nme.ordinal).ensureApplied))
.withSpan(ctx.owner.span.focus))
else
Expand Down
22 changes: 22 additions & 0 deletions tests/neg/i4470c.scala
Original file line number Diff line number Diff line change
@@ -1,9 +1,31 @@
object DuplicatedEnum {

enum Maybe[+T] { // error
case Some[T](x: T) extends Maybe[T]
}

enum Maybe[+T] { // error
case Some[T](x: T) extends Maybe[T]
}

enum Color { // error
case Red, Green, Blue
}

enum Color { // error
case Red, Green, Blue
}

enum Tag[T] { // error
case Int extends Tag[Int]
case OfClass[T]()(using val tag: reflect.ClassTag[T]) extends Tag[T] // mix order of class and value
case String extends Tag[String]
}

enum Tag[T] { // error
case Int extends Tag[Int]
case OfClass[T]()(using val tag: reflect.ClassTag[T]) extends Tag[T] // mix order of class and value
case String extends Tag[String]
}

}
62 changes: 57 additions & 5 deletions tests/run/enum-values.scala
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
import reflect.Selectable.reflectiveSelectable
import deriving.Mirror

enum Color:
case Red, Green, Blue

enum Suits extends java.lang.Enum[Suits]:
case Clubs, Spades, Diamonds, Hearts

enum Tag[T]:
case Int extends Tag[Int]
case OfClass[T]()(using val tag: reflect.ClassTag[T]) extends Tag[T] // mix order of class and value
case String extends Tag[String]
case OfClass[T]()(using val tag: reflect.ClassTag[T]) extends Tag[T]

enum Expr[-T >: Null]:
case EmptyTree extends Expr[Null]
Expand All @@ -16,18 +22,64 @@ enum ListLike[+T]:

enum TypeCtorsK[F[_]]:
case List extends TypeCtorsK[List]
case Const[T]() extends TypeCtorsK[[U] =>> T] // mix order of class and value
case Option extends TypeCtorsK[Option]
case Const[T]() extends TypeCtorsK[[U] =>> T]

enum MixedParams[F[_], G[X,Y] <: collection.Map[X,Y], T]:
case Foo extends MixedParams[List, collection.mutable.LinkedHashMap, Unit]

enum ClassOnly: // this should still generate the `ordinal` and `fromOrdinal` companion methods
case BranchProd(i: Int)

@main def Test: Unit =
import Color._, Tag._, Expr._, ListLike._, TypeCtorsK._, MixedParams._
import reflect.Selectable.reflectiveSelectable
import Color._, Suits._, Tag._, Expr._, ListLike._, TypeCtorsK._, MixedParams._, ClassOnly._

type FromOrdinal[T] = {
def fromOrdinal(ordinal: Int): T
}

type ValueOf[T] = {
def valueOf(s: String): T
}

extension [A](t: A) def show = runtime.ScalaRunTime.stringOf(t)

def fetchFromOrdinal[T <: AnyRef & reflect.Enum](companion: FromOrdinal[T], compare: T*): Unit =
for c <- compare do
assert(companion.fromOrdinal(c.ordinal) eq c,
s"$c does not `eq` companion.fromOrdinal(${c.ordinal}), got ${companion.fromOrdinal(c.ordinal)}")

def notFromOrdinal[T <: AnyRef & reflect.Enum](companion: FromOrdinal[T], compare: T): Unit =
cantFind(companion, compare.ordinal)

def cantFind[T](companion: FromOrdinal[T], ordinal: Int): Unit =
try
companion.fromOrdinal(ordinal)
assertFail(s"$companion.fromOrdinal(${ordinal}) did not fail")
catch
case e: java.lang.reflect.InvocationTargetException => // TODO: maybe reflect.Selectable should catch this?
assert(e.getCause.isInstanceOf[java.util.NoSuchElementException]
&& e.getCause.getMessage == ordinal.toString)

fetchFromOrdinal(companion = Color, compare = Red, Green, Blue)
fetchFromOrdinal(companion = Suits, compare = Clubs, Spades, Diamonds, Hearts)
fetchFromOrdinal(companion = Tag, compare = Int, String)
fetchFromOrdinal(companion = Expr, compare = EmptyTree, AnyTree)
fetchFromOrdinal(companion = ListLike, compare = EmptyListLike)
fetchFromOrdinal(companion = TypeCtorsK, compare = List, Option)
fetchFromOrdinal(companion = MixedParams, compare = Foo)

notFromOrdinal(companion = Tag, compare = OfClass[String]())
notFromOrdinal(companion = TypeCtorsK, compare = Const[String]())
notFromOrdinal(companion = ClassOnly, compare = BranchProd(1)) // ClassOnly has the `fromOrdinal` method

cantFind(companion = Color, ordinal = 500) // test default case for enumeration
cantFind(companion = Suits, ordinal = 500) // test default case for Java style enumeration
cantFind(companion = Tag, ordinal = 500) // test default case for mixed adt with non-simple values
cantFind(companion = ClassOnly, ordinal = 500) // should always throw

assert(summon[Mirror.SumOf[ClassOnly]].ordinal(BranchProd(1)) == 0)

val colors: Array[Color] = Color.values
val tags: Array[Tag[?]] = Tag.values
val exprs: Array[Expr[? >: Null]] = Expr.values
Expand All @@ -46,7 +98,7 @@ enum MixedParams[F[_], G[X,Y] <: collection.Map[X,Y], T]:
sameAs(typeCtorsK, List, Option)
sameAs(mixedParams, Foo)

def singleton[E <: AnyRef](value: E, name: String, companion: { def valueOf(s: String): E}) =
def singleton[E <: AnyRef](value: E, name: String, companion: ValueOf[E]) =
val lookup = companion.valueOf(name)
assert(value eq lookup, s"${value.show} is not identical to ${lookup.show}")

Expand Down
34 changes: 19 additions & 15 deletions tests/semanticdb/metac.expect
Original file line number Diff line number Diff line change
Expand Up @@ -641,7 +641,7 @@ Schema => SemanticDB v4
Uri => Enums.scala
Text => empty
Language => Scala
Symbols => 181 entries
Symbols => 185 entries
Occurrences => 203 entries

Symbols:
Expand All @@ -651,37 +651,35 @@ _empty_/Enums.Coin#`<init>`(). => primary ctor <init>
_empty_/Enums.Coin#`<init>`().(value) => param value
_empty_/Enums.Coin#value. => val method value
_empty_/Enums.Coin. => final object Coin
_empty_/Enums.Coin.$fromOrdinal(). => method $fromOrdinal
_empty_/Enums.Coin.$fromOrdinal().(_$ordinal) => param _$ordinal
_empty_/Enums.Coin.$values. => val method $values
_empty_/Enums.Coin.Dime. => case val static enum method Dime
_empty_/Enums.Coin.Dollar. => case val static enum method Dollar
_empty_/Enums.Coin.Nickel. => case val static enum method Nickel
_empty_/Enums.Coin.Penny. => case val static enum method Penny
_empty_/Enums.Coin.Quarter. => case val static enum method Quarter
_empty_/Enums.Coin.fromOrdinal(). => method fromOrdinal
_empty_/Enums.Coin.fromOrdinal().(ordinal) => param ordinal
_empty_/Enums.Coin.valueOf(). => method valueOf
_empty_/Enums.Coin.valueOf().($name) => param $name
_empty_/Enums.Coin.values(). => method values
_empty_/Enums.Colour# => abstract sealed enum class Colour
_empty_/Enums.Colour#`<init>`(). => primary ctor <init>
_empty_/Enums.Colour. => final object Colour
_empty_/Enums.Colour.$fromOrdinal(). => method $fromOrdinal
_empty_/Enums.Colour.$fromOrdinal().(_$ordinal) => param _$ordinal
_empty_/Enums.Colour.$new(). => method $new
_empty_/Enums.Colour.$new().($name) => param $name
_empty_/Enums.Colour.$new().(_$ordinal) => param _$ordinal
_empty_/Enums.Colour.$values. => val method $values
_empty_/Enums.Colour.Blue. => case val static enum method Blue
_empty_/Enums.Colour.Green. => case val static enum method Green
_empty_/Enums.Colour.Red. => case val static enum method Red
_empty_/Enums.Colour.fromOrdinal(). => method fromOrdinal
_empty_/Enums.Colour.fromOrdinal().(ordinal) => param ordinal
_empty_/Enums.Colour.valueOf(). => method valueOf
_empty_/Enums.Colour.valueOf().($name) => param $name
_empty_/Enums.Colour.values(). => method values
_empty_/Enums.Directions# => abstract sealed enum class Directions
_empty_/Enums.Directions#`<init>`(). => primary ctor <init>
_empty_/Enums.Directions. => final object Directions
_empty_/Enums.Directions.$fromOrdinal(). => method $fromOrdinal
_empty_/Enums.Directions.$fromOrdinal().(_$ordinal) => param _$ordinal
_empty_/Enums.Directions.$new(). => method $new
_empty_/Enums.Directions.$new().($name) => param $name
_empty_/Enums.Directions.$new().(_$ordinal) => param _$ordinal
Expand All @@ -690,15 +688,15 @@ _empty_/Enums.Directions.East. => case val static enum method East
_empty_/Enums.Directions.North. => case val static enum method North
_empty_/Enums.Directions.South. => case val static enum method South
_empty_/Enums.Directions.West. => case val static enum method West
_empty_/Enums.Directions.fromOrdinal(). => method fromOrdinal
_empty_/Enums.Directions.fromOrdinal().(ordinal) => param ordinal
_empty_/Enums.Directions.valueOf(). => method valueOf
_empty_/Enums.Directions.valueOf().($name) => param $name
_empty_/Enums.Directions.values(). => method values
_empty_/Enums.Maybe# => abstract sealed enum class Maybe
_empty_/Enums.Maybe#[A] => covariant typeparam A
_empty_/Enums.Maybe#`<init>`(). => primary ctor <init>
_empty_/Enums.Maybe. => final object Maybe
_empty_/Enums.Maybe.$fromOrdinal(). => method $fromOrdinal
_empty_/Enums.Maybe.$fromOrdinal().(_$ordinal) => param _$ordinal
_empty_/Enums.Maybe.$values. => val method $values
_empty_/Enums.Maybe.Just# => final case enum class Just
_empty_/Enums.Maybe.Just#[A] => covariant typeparam A
Expand All @@ -721,6 +719,8 @@ _empty_/Enums.Maybe.Just.unapply(). => method unapply
_empty_/Enums.Maybe.Just.unapply().(x$1) => param x$1
_empty_/Enums.Maybe.Just.unapply().[A] => typeparam A
_empty_/Enums.Maybe.None. => case val static enum method None
_empty_/Enums.Maybe.fromOrdinal(). => method fromOrdinal
_empty_/Enums.Maybe.fromOrdinal().(ordinal) => param ordinal
_empty_/Enums.Maybe.valueOf(). => method valueOf
_empty_/Enums.Maybe.valueOf().($name) => param $name
_empty_/Enums.Maybe.values(). => method values
Expand All @@ -744,14 +744,14 @@ _empty_/Enums.Planet.Neptune. => case val static enum method Neptune
_empty_/Enums.Planet.Saturn. => case val static enum method Saturn
_empty_/Enums.Planet.Uranus. => case val static enum method Uranus
_empty_/Enums.Planet.Venus. => case val static enum method Venus
_empty_/Enums.Planet.fromOrdinal(). => method fromOrdinal
_empty_/Enums.Planet.fromOrdinal().(ordinal) => param ordinal
_empty_/Enums.Planet.valueOf(). => method valueOf
_empty_/Enums.Planet.valueOf().($name) => param $name
_empty_/Enums.Planet.values(). => method values
_empty_/Enums.Suits# => abstract sealed enum class Suits
_empty_/Enums.Suits#`<init>`(). => primary ctor <init>
_empty_/Enums.Suits. => final object Suits
_empty_/Enums.Suits.$fromOrdinal(). => method $fromOrdinal
_empty_/Enums.Suits.$fromOrdinal().(_$ordinal) => param _$ordinal
_empty_/Enums.Suits.$new(). => method $new
_empty_/Enums.Suits.$new().($name) => param $name
_empty_/Enums.Suits.$new().(_$ordinal) => param _$ordinal
Expand All @@ -765,26 +765,26 @@ _empty_/Enums.Suits.extension_isBlack(). => method extension_isBlack
_empty_/Enums.Suits.extension_isBlack().(suit) => param suit
_empty_/Enums.Suits.extension_isRed(). => method extension_isRed
_empty_/Enums.Suits.extension_isRed().(suit) => param suit
_empty_/Enums.Suits.fromOrdinal(). => method fromOrdinal
_empty_/Enums.Suits.fromOrdinal().(ordinal) => param ordinal
_empty_/Enums.Suits.valueOf(). => method valueOf
_empty_/Enums.Suits.valueOf().($name) => param $name
_empty_/Enums.Suits.values(). => method values
_empty_/Enums.Tag# => abstract sealed enum class Tag
_empty_/Enums.Tag#[A] => typeparam A
_empty_/Enums.Tag#`<init>`(). => primary ctor <init>
_empty_/Enums.Tag. => final object Tag
_empty_/Enums.Tag.$fromOrdinal(). => method $fromOrdinal
_empty_/Enums.Tag.$fromOrdinal().(_$ordinal) => param _$ordinal
_empty_/Enums.Tag.$values. => val method $values
_empty_/Enums.Tag.BooleanTag. => case val static enum method BooleanTag
_empty_/Enums.Tag.IntTag. => case val static enum method IntTag
_empty_/Enums.Tag.fromOrdinal(). => method fromOrdinal
_empty_/Enums.Tag.fromOrdinal().(ordinal) => param ordinal
_empty_/Enums.Tag.valueOf(). => method valueOf
_empty_/Enums.Tag.valueOf().($name) => param $name
_empty_/Enums.Tag.values(). => method values
_empty_/Enums.WeekDays# => abstract sealed enum class WeekDays
_empty_/Enums.WeekDays#`<init>`(). => primary ctor <init>
_empty_/Enums.WeekDays. => final object WeekDays
_empty_/Enums.WeekDays.$fromOrdinal(). => method $fromOrdinal
_empty_/Enums.WeekDays.$fromOrdinal().(_$ordinal) => param _$ordinal
_empty_/Enums.WeekDays.$new(). => method $new
_empty_/Enums.WeekDays.$new().($name) => param $name
_empty_/Enums.WeekDays.$new().(_$ordinal) => param _$ordinal
Expand All @@ -796,6 +796,8 @@ _empty_/Enums.WeekDays.Sunday. => case val static enum method Sunday
_empty_/Enums.WeekDays.Thursday. => case val static enum method Thursday
_empty_/Enums.WeekDays.Tuesday. => case val static enum method Tuesday
_empty_/Enums.WeekDays.Wednesday. => case val static enum method Wednesday
_empty_/Enums.WeekDays.fromOrdinal(). => method fromOrdinal
_empty_/Enums.WeekDays.fromOrdinal().(ordinal) => param ordinal
_empty_/Enums.WeekDays.valueOf(). => method valueOf
_empty_/Enums.WeekDays.valueOf().($name) => param $name
_empty_/Enums.WeekDays.values(). => method values
Expand All @@ -817,6 +819,8 @@ _empty_/Enums.`<:<`.Refl.toString(). => method toString
_empty_/Enums.`<:<`.Refl.unapply(). => method unapply
_empty_/Enums.`<:<`.Refl.unapply().(x$1) => param x$1
_empty_/Enums.`<:<`.Refl.unapply().[C] => typeparam C
_empty_/Enums.`<:<`.fromOrdinal(). => method fromOrdinal
_empty_/Enums.`<:<`.fromOrdinal().(ordinal) => param ordinal
_empty_/Enums.`<:<`.given_T(). => final implicit method given_T
_empty_/Enums.`<:<`.given_T().[T] => typeparam T
_empty_/Enums.extension_unwrap(). => method extension_unwrap
Expand Down

0 comments on commit 0525ba0

Please sign in to comment.