Skip to content

make fromOrdinal public and always available #9987

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

Merged
merged 4 commits into from
Oct 15, 2020
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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