diff --git a/compiler/src/dotty/tools/dotc/ast/DesugarEnums.scala b/compiler/src/dotty/tools/dotc/ast/DesugarEnums.scala index 997caeec61a8..7531b0f872fa 100644 --- a/compiler/src/dotty/tools/dotc/ast/DesugarEnums.scala +++ b/compiler/src/dotty/tools/dotc/ast/DesugarEnums.scala @@ -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: @@ -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) diff --git a/compiler/src/dotty/tools/dotc/core/StdNames.scala b/compiler/src/dotty/tools/dotc/core/StdNames.scala index 9b0468437465..54ae8cf38566 100644 --- a/compiler/src/dotty/tools/dotc/core/StdNames.scala +++ b/compiler/src/dotty/tools/dotc/core/StdNames.scala @@ -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" diff --git a/compiler/src/dotty/tools/dotc/transform/SyntheticMembers.scala b/compiler/src/dotty/tools/dotc/transform/SyntheticMembers.scala index 4c4e15bfdeb0..b82abf369efc 100644 --- a/compiler/src/dotty/tools/dotc/transform/SyntheticMembers.scala +++ b/compiler/src/dotty/tools/dotc/transform/SyntheticMembers.scala @@ -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. */ @@ -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 diff --git a/tests/neg/i4470c.scala b/tests/neg/i4470c.scala index 79ae8b22f5af..f3f305980682 100644 --- a/tests/neg/i4470c.scala +++ b/tests/neg/i4470c.scala @@ -1,4 +1,5 @@ object DuplicatedEnum { + enum Maybe[+T] { // error case Some[T](x: T) extends Maybe[T] } @@ -6,4 +7,25 @@ object DuplicatedEnum { 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] + } + } diff --git a/tests/run/enum-values.scala b/tests/run/enum-values.scala index cb7f5c2d533e..26a01b7133b5 100644 --- a/tests/run/enum-values.scala +++ b/tests/run/enum-values.scala @@ -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] @@ -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 @@ -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}") diff --git a/tests/semanticdb/metac.expect b/tests/semanticdb/metac.expect index 0cdc0db3dce7..3bbb646c55d4 100644 --- a/tests/semanticdb/metac.expect +++ b/tests/semanticdb/metac.expect @@ -641,7 +641,7 @@ Schema => SemanticDB v4 Uri => Enums.scala Text => empty Language => Scala -Symbols => 181 entries +Symbols => 185 entries Occurrences => 203 entries Symbols: @@ -651,22 +651,20 @@ _empty_/Enums.Coin#``(). => primary ctor _empty_/Enums.Coin#``().(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#``(). => primary ctor _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 @@ -674,14 +672,14 @@ _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#``(). => primary ctor _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 @@ -690,6 +688,8 @@ _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 @@ -697,8 +697,6 @@ _empty_/Enums.Maybe# => abstract sealed enum class Maybe _empty_/Enums.Maybe#[A] => covariant typeparam A _empty_/Enums.Maybe#``(). => primary ctor _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 @@ -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 @@ -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#``(). => primary ctor _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 @@ -765,6 +765,8 @@ _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 @@ -772,19 +774,17 @@ _empty_/Enums.Tag# => abstract sealed enum class Tag _empty_/Enums.Tag#[A] => typeparam A _empty_/Enums.Tag#``(). => primary ctor _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#``(). => primary ctor _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 @@ -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 @@ -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