diff --git a/compiler/src/dotty/tools/backend/jvm/BTypesFromSymbols.scala b/compiler/src/dotty/tools/backend/jvm/BTypesFromSymbols.scala index 897537df047d..7ba33941eb5a 100644 --- a/compiler/src/dotty/tools/backend/jvm/BTypesFromSymbols.scala +++ b/compiler/src/dotty/tools/backend/jvm/BTypesFromSymbols.scala @@ -223,7 +223,8 @@ class BTypesFromSymbols[I <: BackendInterface](val int: I) extends BTypes { if (sym.hasEnumFlag) ACC_ENUM else 0, if (sym.isVarargsMethod) ACC_VARARGS else 0, if (sym.isSynchronized) ACC_SYNCHRONIZED else 0, - if (sym.isDeprecated) asm.Opcodes.ACC_DEPRECATED else 0 + if (sym.isDeprecated) asm.Opcodes.ACC_DEPRECATED else 0, + if (sym.isJavaEnum) asm.Opcodes.ACC_ENUM else 0 ) } diff --git a/compiler/src/dotty/tools/backend/jvm/BackendInterface.scala b/compiler/src/dotty/tools/backend/jvm/BackendInterface.scala index 66fda62641c6..0ffb9af33156 100644 --- a/compiler/src/dotty/tools/backend/jvm/BackendInterface.scala +++ b/compiler/src/dotty/tools/backend/jvm/BackendInterface.scala @@ -512,6 +512,7 @@ abstract class BackendInterface extends BackendInterfaceDefinitions { def isJavaDefaultMethod: Boolean def isClassConstructor: Boolean def isSerializable: Boolean + def isJavaEnum: Boolean /** * True for module classes of modules that are top-level or owned only by objects. Module classes diff --git a/compiler/src/dotty/tools/backend/jvm/DottyBackendInterface.scala b/compiler/src/dotty/tools/backend/jvm/DottyBackendInterface.scala index 3cd668e1fc38..bd7ead685a77 100644 --- a/compiler/src/dotty/tools/backend/jvm/DottyBackendInterface.scala +++ b/compiler/src/dotty/tools/backend/jvm/DottyBackendInterface.scala @@ -703,6 +703,7 @@ class DottyBackendInterface(outputDirectory: AbstractFile, val superCallsMap: Ma def shouldEmitForwarders: Boolean = (sym is Flags.Module) && sym.isStatic def isJavaEntryPoint: Boolean = CollectEntryPoints.isJavaEntryPoint(sym) + def isJavaEnum = sym.derivesFromJavaEnum def isClassConstructor: Boolean = toDenot(sym).isClassConstructor def isSerializable: Boolean = toDenot(sym).isSerializable diff --git a/compiler/src/dotty/tools/dotc/ast/Desugar.scala b/compiler/src/dotty/tools/dotc/ast/Desugar.scala index daf9aa7e5c3b..406daf864cff 100644 --- a/compiler/src/dotty/tools/dotc/ast/Desugar.scala +++ b/compiler/src/dotty/tools/dotc/ast/Desugar.scala @@ -545,7 +545,7 @@ object desugar { yield syntheticProperty(nme.selectorName(i), caseParams(i).tpt, Select(This(EmptyTypeIdent), caseParams(i).name)) } - def enumTagMeths = if (isEnumCase) enumTagMeth(CaseKind.Class)._1 :: Nil else Nil + def ordinalMeths = if (isEnumCase) ordinalMethLit(nextOrdinal(CaseKind.Class)._1) :: Nil else Nil def copyMeths = { val hasRepeatedParam = constrVparamss.exists(_.exists { case ValDef(_, tpt, _) => isRepeated(tpt) @@ -582,7 +582,7 @@ object desugar { } if (isCaseClass) - productElemNameMeth :: copyMeths ::: enumTagMeths ::: productElemMeths + productElemNameMeth :: copyMeths ::: ordinalMeths ::: productElemMeths else Nil } diff --git a/compiler/src/dotty/tools/dotc/ast/DesugarEnums.scala b/compiler/src/dotty/tools/dotc/ast/DesugarEnums.scala index 2fcf7a107fc7..afc295409142 100644 --- a/compiler/src/dotty/tools/dotc/ast/DesugarEnums.scala +++ b/compiler/src/dotty/tools/dotc/ast/DesugarEnums.scala @@ -74,8 +74,9 @@ object DesugarEnums { else if (isEnumCase(cdef)) cdef.withMods(cdef.mods.withFlags(cdef.mods.flags | Final)) else cdef - private def valuesDot(name: String)(implicit src: SourceFile) = + private def valuesDot(name: PreName)(implicit src: SourceFile) = Select(Ident(nme.DOLLAR_VALUES), name.toTermName) + private def registerCall(implicit ctx: Context): List[Tree] = if (enumClass.typeParams.nonEmpty) Nil else Apply(valuesDot("register"), This(EmptyTypeIdent) :: Nil) :: Nil @@ -83,48 +84,62 @@ object DesugarEnums { /** The following lists of definitions for an enum type E: * * private val $values = new EnumValues[E] - * def enumValue = $values.fromInt - * def enumValueNamed = $values.fromName - * def enumValues = $values.values + * def valueOf($name: String) = + * try $values.fromName($name) catch + * { + * case ex$:NoSuchElementException => + * throw new IllegalArgumentException("key not found: ".concat(name)) + * } + * def values = $values.values.toArray */ private def enumScaffolding(implicit ctx: Context): List[Tree] = { - def enumDefDef(name: String, select: String) = - DefDef(name.toTermName, Nil, Nil, TypeTree(), valuesDot(select)) + val valuesDef = + DefDef(nme.values, Nil, Nil, TypeTree(), Select(valuesDot(nme.values), nme.toArray)) val privateValuesDef = ValDef(nme.DOLLAR_VALUES, TypeTree(), New(TypeTree(defn.EnumValuesType.appliedTo(enumClass.typeRef :: Nil)), ListOfNil)) .withFlags(Private) - val valueOfDef = enumDefDef("enumValue", "fromInt") - val withNameDef = enumDefDef("enumValueNamed", "fromName") - val valuesDef = enumDefDef("enumValues", "values") - List(privateValuesDef, valueOfDef, withNameDef, valuesDef) + + val valuesOfExnMessage = Apply( + Select(Literal(Constant("key not found: ")), "concat".toTermName), + Ident(nme.nameDollar) :: Nil) + val valuesOfBody = Try( + expr = Apply(valuesDot("fromName"), Ident(nme.nameDollar) :: Nil), + cases = CaseDef( + pat = Typed(Ident(nme.DEFAULT_EXCEPTION_NAME), TypeTree(defn.NoSuchElementExceptionType)), + guard = EmptyTree, + body = Throw(New(TypeTree(defn.IllegalArgumentExceptionType), List(valuesOfExnMessage :: Nil))) + ) :: Nil, + finalizer = EmptyTree + ) + val valueOfDef = DefDef(nme.valueOf, Nil, List(param(nme.nameDollar, defn.StringType) :: Nil), + TypeTree(), valuesOfBody) + + valuesDef :: + privateValuesDef :: + valueOfDef :: Nil } /** A creation method for a value of enum type `E`, which is defined as follows: * - * private def $new(tag: Int, name: String) = new E { - * def enumTag = tag - * override def toString = name + * private def $new(_$ordinal: Int, $name: String) = new E { + * def $ordinal = $tag + * override def toString = $name * $values.register(this) * } */ private def enumValueCreator(implicit ctx: Context) = { - def param(name: TermName, typ: Type) = - ValDef(name, TypeTree(typ), EmptyTree).withFlags(Param) - val enumTagDef = - DefDef(nme.enumTag, Nil, Nil, TypeTree(), Ident(nme.tag)) - val toStringDef = - DefDef(nme.toString_, Nil, Nil, TypeTree(), Ident(nme.name)) - .withFlags(Override) + val ordinalDef = ordinalMeth(Ident(nme.ordinalDollar_)) + val toStringDef = toStringMeth(Ident(nme.nameDollar)) val creator = New(Template( constr = emptyConstructor, parents = enumClassRef :: Nil, derived = Nil, self = EmptyValDef, - body = List(enumTagDef, toStringDef) ++ registerCall + body = List(ordinalDef, toStringDef) ++ registerCall ).withAttachment(ExtendsSingletonMirror, ())) DefDef(nme.DOLLAR_NEW, Nil, - List(List(param(nme.tag, defn.IntType), param(nme.name, defn.StringType))), + List(List(param(nme.ordinalDollar_, defn.IntType), param(nme.nameDollar, defn.StringType))), TypeTree(), creator).withFlags(Private | Synthetic) } @@ -232,7 +247,7 @@ object DesugarEnums { * - scaffolding containing the necessary definitions for singleton enum cases * unless that scaffolding was already generated by a previous call to `nextEnumKind`. */ - def nextEnumTag(kind: CaseKind.Value)(implicit ctx: Context): (Int, List[Tree]) = { + def nextOrdinal(kind: CaseKind.Value)(implicit ctx: Context): (Int, List[Tree]) = { val (count, seenKind) = ctx.tree.removeAttachment(EnumCaseCount).getOrElse((0, CaseKind.Class)) val minKind = if (kind < seenKind) kind else seenKind ctx.tree.pushAttachment(EnumCaseCount, (count + 1, minKind)) @@ -244,14 +259,20 @@ object DesugarEnums { (count, scaffolding) } - /** A pair consisting of - * - a method returning the next enum tag - * - scaffolding as defined in `nextEnumTag` - */ - def enumTagMeth(kind: CaseKind.Value)(implicit ctx: Context): (DefDef, List[Tree]) = { - val (tag, scaffolding) = nextEnumTag(kind) - (DefDef(nme.enumTag, Nil, Nil, TypeTree(), Literal(Constant(tag))), scaffolding) - } + def param(name: TermName, typ: Type)(implicit ctx: Context) = + ValDef(name, TypeTree(typ), EmptyTree).withFlags(Param) + + def ordinalMeth(body: Tree)(implicit ctx: Context): DefDef = + DefDef(nme.ordinalDollar, Nil, Nil, TypeTree(defn.IntType), body) + + def toStringMeth(body: Tree)(implicit ctx: Context): DefDef = + DefDef(nme.toString_, Nil, Nil, TypeTree(defn.StringType), body).withFlags(Override) + + def ordinalMethLit(ord: Int)(implicit ctx: Context): DefDef = + ordinalMeth(Literal(Constant(ord))) + + def toStringMethLit(name: String)(implicit ctx: Context): DefDef = + toStringMeth(Literal(Constant(name))) /** Expand a module definition representing a parameterless enum case */ def expandEnumModule(name: TermName, impl: Template, mods: Modifiers, span: Span)(implicit ctx: Context): Tree = { @@ -260,11 +281,10 @@ object DesugarEnums { else if (impl.parents.isEmpty) expandSimpleEnumCase(name, mods, span) else { - def toStringMeth = - DefDef(nme.toString_, Nil, Nil, TypeTree(defn.StringType), Literal(Constant(name.toString))) - .withFlags(Override) - val (tagMeth, scaffolding) = enumTagMeth(CaseKind.Object) - val impl1 = cpy.Template(impl)(body = List(tagMeth, toStringMeth) ++ registerCall) + val (tag, scaffolding) = nextOrdinal(CaseKind.Object) + val ordinalDef = ordinalMethLit(tag) + val toStringDef = toStringMethLit(name.toString) + val impl1 = cpy.Template(impl)(body = List(ordinalDef, toStringDef) ++ registerCall) .withAttachment(ExtendsSingletonMirror, ()) val vdef = ValDef(name, TypeTree(), New(impl1)).withMods(mods | Final) flatTree(scaffolding ::: vdef :: Nil).withSpan(span) @@ -280,7 +300,7 @@ object DesugarEnums { expandEnumModule(name, impl, mods, span) } else { - val (tag, scaffolding) = nextEnumTag(CaseKind.Simple) + val (tag, scaffolding) = nextOrdinal(CaseKind.Simple) val creator = Apply(Ident(nme.DOLLAR_NEW), List(Literal(Constant(tag)), Literal(Constant(name.toString)))) val vdef = ValDef(name, enumClassRef, creator).withMods(mods | Final) flatTree(scaffolding ::: vdef :: Nil).withSpan(span) diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index b074193c2c05..2e046eb783c8 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -605,6 +605,11 @@ class Definitions { @threadUnsafe lazy val SystemClass: ClassSymbol = ctx.requiredClass("java.lang.System") @threadUnsafe lazy val SystemModule: Symbol = SystemClass.linkedClass + @threadUnsafe lazy val NoSuchElementExceptionClass = ctx.requiredClass("java.util.NoSuchElementException") + def NoSuchElementExceptionType = NoSuchElementExceptionClass.typeRef + @threadUnsafe lazy val IllegalArgumentExceptionClass = ctx.requiredClass("java.lang.IllegalArgumentException") + def IllegalArgumentExceptionType = IllegalArgumentExceptionClass.typeRef + // in scalac modified to have Any as parent @threadUnsafe lazy val ThrowableType: TypeRef = ctx.requiredClassRef("java.lang.Throwable") @@ -701,6 +706,8 @@ class Definitions { def NoneClass(implicit ctx: Context): ClassSymbol = NoneModuleRef.symbol.moduleClass.asClass @threadUnsafe lazy val EnumType: TypeRef = ctx.requiredClassRef("scala.Enum") def EnumClass(implicit ctx: Context): ClassSymbol = EnumType.symbol.asClass + @threadUnsafe lazy val Enum_ordinalR: TermRef = EnumClass.requiredMethodRef(nme.ordinal) + def Enum_ordinal(implicit ctx: Context): Symbol = Enum_ordinalR.symbol @threadUnsafe lazy val EnumValuesType: TypeRef = ctx.requiredClassRef("scala.runtime.EnumValues") def EnumValuesClass(implicit ctx: Context): ClassSymbol = EnumValuesType.symbol.asClass @threadUnsafe lazy val ProductType: TypeRef = ctx.requiredClassRef("scala.Product") diff --git a/compiler/src/dotty/tools/dotc/core/StdNames.scala b/compiler/src/dotty/tools/dotc/core/StdNames.scala index d0f359d1d9b8..420e9190ad15 100644 --- a/compiler/src/dotty/tools/dotc/core/StdNames.scala +++ b/compiler/src/dotty/tools/dotc/core/StdNames.scala @@ -420,7 +420,6 @@ object StdNames { val elems: N = "elems" val emptyValDef: N = "emptyValDef" val ensureAccessible : N = "ensureAccessible" - val enumTag: N = "enumTag" val eq: N = "eq" val eqInstance: N = "eqInstance" val equalsNumChar : N = "equalsNumChar" @@ -485,6 +484,7 @@ object StdNames { val mirror : N = "mirror" val moduleClass : N = "moduleClass" val name: N = "name" + val nameDollar: N = "$name" val ne: N = "ne" val newFreeTerm: N = "newFreeTerm" val newFreeType: N = "newFreeType" @@ -500,6 +500,8 @@ object StdNames { val ofDim: N = "ofDim" val opaque: N = "opaque" val ordinal: N = "ordinal" + val ordinalDollar: N = "$ordinal" + val ordinalDollar_ : N = "_$ordinal" val origin: N = "origin" val prefix : N = "prefix" val productArity: N = "productArity" diff --git a/compiler/src/dotty/tools/dotc/transform/CompleteJavaEnums.scala b/compiler/src/dotty/tools/dotc/transform/CompleteJavaEnums.scala index b42fb3b14b24..5aa006ebdaca 100644 --- a/compiler/src/dotty/tools/dotc/transform/CompleteJavaEnums.scala +++ b/compiler/src/dotty/tools/dotc/transform/CompleteJavaEnums.scala @@ -13,12 +13,13 @@ import Constants._ import Decorators._ import DenotTransformers._ import dotty.tools.dotc.ast.Trees._ +import SymUtils._ object CompleteJavaEnums { val name: String = "completeJavaEnums" - private val nameParamName: TermName = "$name".toTermName - private val ordinalParamName: TermName = "$ordinal".toTermName + private val nameParamName: TermName = "_$name".toTermName + private val ordinalParamName: TermName = "_$ordinal".toTermName } /** For Scala enums that inherit from java.lang.Enum: @@ -37,16 +38,10 @@ class CompleteJavaEnums extends MiniPhase with InfoTransformer { thisPhase => def transformInfo(tp: Type, sym: Symbol)(implicit ctx: Context): Type = if (sym.isConstructor && ( sym == defn.JavaEnumClass.primaryConstructor || - derivesFromJavaEnum(sym.owner))) + sym.owner.derivesFromJavaEnum)) addConstrParams(sym.info) else tp - /** Is `sym` a Scala enum class that derives (directly) from `java.lang.Enum`? - */ - private def derivesFromJavaEnum(sym: Symbol)(implicit ctx: Context) = - sym.is(Enum, butNot = Case) && - sym.info.parents.exists(p => p.typeSymbol == defn.JavaEnumClass) - /** Add constructor parameters `$name: String` and `$ordinal: Int` to the end of * the last parameter list of (method- or poly-) type `tp`. */ @@ -87,24 +82,24 @@ class CompleteJavaEnums extends MiniPhase with InfoTransformer { thisPhase => * 2. If this is a $new method that creates simple cases, pass $name and $ordinal parameters * to the enum superclass. The $new method looks like this: * - * def $new(..., enumTag: Int, name: String) = { + * def $new(..., ordinal: Int, name: String) = { * class $anon extends E(...) { ... } * new $anon * } * * After the transform it is expanded to * - * def $new(..., enumTag: Int, name: String) = { - * class $anon extends E(..., name, enumTag) { ... } + * def $new(..., ordinal: Int, name: String) = { + * class $anon extends E(..., name, ordinal) { ... } * new $anon * } */ override def transformDefDef(tree: DefDef)(implicit ctx: Context): DefDef = { val sym = tree.symbol - if (sym.isConstructor && derivesFromJavaEnum(sym.owner)) + if (sym.isConstructor && sym.owner.derivesFromJavaEnum) cpy.DefDef(tree)( vparamss = tree.vparamss.init :+ (tree.vparamss.last ++ addedParams(sym, Param))) - else if (sym.name == nme.DOLLAR_NEW && derivesFromJavaEnum(sym.owner.linkedClass)) { + else if (sym.name == nme.DOLLAR_NEW && sym.owner.linkedClass.derivesFromJavaEnum) { val Block((tdef @ TypeDef(tpnme.ANON_CLASS, templ: Template)) :: Nil, call) = tree.rhs val args = tree.vparamss.last.takeRight(2).map(param => ref(param.symbol)).reverse val templ1 = cpy.Template(templ)( @@ -124,7 +119,7 @@ class CompleteJavaEnums extends MiniPhase with InfoTransformer { thisPhase => * * class $anon extends E(...) { * ... - * def enumTag = N + * def ordinal = N * def toString = S * ... * } @@ -137,7 +132,7 @@ class CompleteJavaEnums extends MiniPhase with InfoTransformer { thisPhase => */ override def transformTemplate(templ: Template)(implicit ctx: Context): Template = { val cls = templ.symbol.owner - if (derivesFromJavaEnum(cls)) { + if (cls.derivesFromJavaEnum) { val (params, rest) = decomposeTemplateBody(templ.body) val addedDefs = addedParams(cls, ParamAccessor) val addedSyms = addedDefs.map(_.symbol.entered) @@ -145,12 +140,12 @@ class CompleteJavaEnums extends MiniPhase with InfoTransformer { thisPhase => parents = addEnumConstrArgs(defn.JavaEnumClass, templ.parents, addedSyms.map(ref)), body = params ++ addedDefs ++ rest) } - else if (cls.isAnonymousClass && cls.owner.is(EnumCase) && derivesFromJavaEnum(cls.owner.owner.linkedClass)) { + else if (cls.isAnonymousClass && cls.owner.is(EnumCase) && cls.owner.owner.linkedClass.derivesFromJavaEnum) { def rhsOf(name: TermName) = templ.body.collect { case mdef: DefDef if mdef.name == name => mdef.rhs }.head - val args = List(rhsOf(nme.toString_), rhsOf(nme.enumTag)) + val args = List(rhsOf(nme.toString_), rhsOf(nme.ordinalDollar)) cpy.Template(templ)( parents = addEnumConstrArgs(cls.owner.owner.linkedClass, templ.parents, args)) } diff --git a/compiler/src/dotty/tools/dotc/transform/SymUtils.scala b/compiler/src/dotty/tools/dotc/transform/SymUtils.scala index 5c25530b247d..2695f1486487 100644 --- a/compiler/src/dotty/tools/dotc/transform/SymUtils.scala +++ b/compiler/src/dotty/tools/dotc/transform/SymUtils.scala @@ -67,6 +67,10 @@ class SymUtils(val self: Symbol) extends AnyVal { def isParamOrAccessor(implicit ctx: Context): Boolean = self.is(Param) || self.is(ParamAccessor) + def derivesFromJavaEnum(implicit ctx: Context) = + self.is(Enum, butNot = Case) && + self.info.parents.exists(p => p.typeSymbol == defn.JavaEnumClass) + /** Is this a case class for which a product mirror is generated? * Excluded are value classes, abstract classes and case classes with more than one * parameter section. diff --git a/compiler/src/dotty/tools/dotc/transform/SyntheticMembers.scala b/compiler/src/dotty/tools/dotc/transform/SyntheticMembers.scala index 60d98a74aaa8..318f16277066 100644 --- a/compiler/src/dotty/tools/dotc/transform/SyntheticMembers.scala +++ b/compiler/src/dotty/tools/dotc/transform/SyntheticMembers.scala @@ -56,6 +56,7 @@ class SyntheticMembers(thisPhase: DenotTransformer) { private[this] var myValueSymbols: List[Symbol] = Nil private[this] var myCaseSymbols: List[Symbol] = Nil private[this] var myCaseModuleSymbols: List[Symbol] = Nil + private[this] var myEnumCaseSymbols: List[Symbol] = Nil private def initSymbols(implicit ctx: Context) = if (myValueSymbols.isEmpty) { @@ -63,11 +64,13 @@ class SyntheticMembers(thisPhase: DenotTransformer) { myCaseSymbols = myValueSymbols ++ List(defn.Any_toString, defn.Product_canEqual, defn.Product_productArity, defn.Product_productPrefix, defn.Product_productElement) myCaseModuleSymbols = myCaseSymbols.filter(_ ne defn.Any_equals) + myEnumCaseSymbols = List(defn.Enum_ordinal) } def valueSymbols(implicit ctx: Context): List[Symbol] = { initSymbols; myValueSymbols } def caseSymbols(implicit ctx: Context): List[Symbol] = { initSymbols; myCaseSymbols } def caseModuleSymbols(implicit ctx: Context): List[Symbol] = { initSymbols; myCaseModuleSymbols } + def enumCaseSymbols(implicit ctx: Context): List[Symbol] = { initSymbols; myEnumCaseSymbols } private def existingDef(sym: Symbol, clazz: ClassSymbol)(implicit ctx: Context): Symbol = { val existing = sym.matchingMember(clazz.thisType) @@ -86,12 +89,15 @@ class SyntheticMembers(thisPhase: DenotTransformer) { lazy val accessors = if (isDerivedValueClass(clazz)) clazz.paramAccessors.take(1) // Tail parameters can only be `erased` else clazz.caseAccessors + val isEnumCase = clazz.derivesFrom(defn.EnumClass) && clazz != defn.EnumClass val symbolsToSynthesize: List[Symbol] = if (clazz.is(Case)) { if (clazz.is(Module)) caseModuleSymbols + else if (isEnumCase) caseSymbols ++ enumCaseSymbols else caseSymbols } + else if (isEnumCase) enumCaseSymbols else if (isDerivedValueClass(clazz)) valueSymbols else Nil @@ -120,6 +126,7 @@ class SyntheticMembers(thisPhase: DenotTransformer) { case nme.productArity => Literal(Constant(accessors.length)) case nme.productPrefix => ownName case nme.productElement => productElementBody(accessors.length, vrefss.head.head) + case nme.ordinal => Select(This(clazz), nme.ordinalDollar) } ctx.log(s"adding $synthetic to $clazz at ${ctx.phase}") synthesizeDef(synthetic, treess => ctx => syntheticRHS(treess)(ctx)) @@ -362,7 +369,7 @@ class SyntheticMembers(thisPhase: DenotTransformer) { /** For an enum T: * - * def ordinal(x: MirroredMonoType) = x.enumTag + * def ordinal(x: MirroredMonoType) = x.ordinal * * For sealed trait with children of normalized types C_1, ..., C_n: * @@ -377,7 +384,7 @@ class SyntheticMembers(thisPhase: DenotTransformer) { * O is O.type. */ def ordinalBody(cls: Symbol, param: Tree)(implicit ctx: Context): Tree = - if (cls.is(Enum)) param.select(nme.enumTag) + if (cls.is(Enum)) param.select(nme.ordinal).ensureApplied else { val cases = for ((child, idx) <- cls.children.zipWithIndex) yield { diff --git a/docs/docs/reference/enums/desugarEnums.md b/docs/docs/reference/enums/desugarEnums.md index 38b18fb3cb51..7bf8bf0615ae 100644 --- a/docs/docs/reference/enums/desugarEnums.md +++ b/docs/docs/reference/enums/desugarEnums.md @@ -120,11 +120,11 @@ map into case classes or vals. expands to a value definition in `E`'s companion object: - val C = new { ; def enumTag = n; $values.register(this) } + val C = new { ; def ordinal = n; $values.register(this) } where `n` is the ordinal number of the case in the companion object, starting from 0. The statement `$values.register(this)` registers the value - as one of the `enumValues` of the enumeration (see below). `$values` is a + as one of the `values` of the enumeration (see below). `$values` is a compiler-defined private value in the companion object. It is an error if a value case refers to a type parameter of the enclosing `enum` @@ -140,10 +140,10 @@ map into case classes or vals. However, unlike for a regular case class, the return type of the associated `apply` method is a fully parameterized type instance of the enum class `E` - itself instead of `C`. Also the enum case defines an `enumTag` method of + itself instead of `C`. Also the enum case defines an `ordinal` method of the form - def enumTag = n + def ordinal = n where `n` is the ordinal number of the case in the companion object, starting from 0. @@ -159,12 +159,9 @@ Non-generic enums `E` that define one or more singleton cases are called _enumerations_. Companion objects of enumerations define the following additional members. - - A method `enumValue` of type `scala.collection.immutable.Map[Int, E]`. - `enumValue(n)` returns the singleton case value with ordinal number `n`. - - A method `enumValueNamed` of type `scala.collection.immutable.Map[String, E]`. - `enumValueNamed(s)` returns the singleton case value whose `toString` - representation is `s`. - - A method `enumValues` which returns an `Iterable[E]` of all singleton case + - A method `valueOf(name: String): E`. It returns the singleton case value whose + `toString` representation is `name`. + - A method `values` which returns an `Array[E]` of all singleton case values in `E`, in the order of their definitions. Companion objects of enumerations that contain at least one simple case define in addition: @@ -173,12 +170,14 @@ Companion objects of enumerations that contain at least one simple case define i ordinal number and name. This method can be thought as being defined as follows. - def $new(tag: Int, name: String): ET = new E { - def enumTag = tag - def toString = name - $values.register(this) // register enum value so that `valueOf` and `values` can return it. + private def $new(\_$ordinal: Int, $name: String) = new E { + def $ordinal = $tag + override def toString = $name + $values.register(this) // register enum value so that `valueOf` and `values` can return it. } +The `$ordinal` method above is used to generate the `ordinal` method if the enum does not extend a `java.lang.Enum` (as Scala enums do not extend `java.lang.Enum`s unless explicitly specified). In case it does, there is no need to generate `ordinal` as `java.lang.Enum` defines it. + ### Scopes for Enum Cases A case in an `enum` is treated similarly to a secondary constructor. It can access neither the enclosing `enum` using `this`, nor its value parameters or instance members using simple diff --git a/docs/docs/reference/enums/enums.md b/docs/docs/reference/enums/enums.md index 50599b66fba0..5af13b211262 100644 --- a/docs/docs/reference/enums/enums.md +++ b/docs/docs/reference/enums/enums.md @@ -33,27 +33,25 @@ explicit extends clause. ### Methods defined for enums The values of an enum correspond to unique integers. The integer -associated with an enum value is returned by its `enumTag` method: +associated with an enum value is returned by its `ordinal` method: ```scala scala> val red = Color.Red val red: Color = Red -scala> red.enumTag +scala> red.ordinal val res0: Int = 0 ``` -The companion object of an enum also defines three utility methods. -The `enumValue` and `enumValueNamed` methods obtain an enum value -by its tag or its name. The `enumValues` method returns all enum values -defined in an enumeration in an `Iterable`. +The companion object of an enum also defines two utility methods. +The `valueOf` method obtains an enum value +by its name. The `values` method returns all enum values +defined in an enumeration in an `Array`. ```scala -scala> Color.enumValue(1) -val res1: Color = Green -scala> Color.enumValueNamed("Blue") -val res2: Color = Blue -scala> Color.enumValues -val res3: collection.Iterable[Color] = MapLike(Red, Green, Blue) +scala> Color.valueOf("Blue") +val res0: Color = Blue +scala> Color.values +val res1: Array[Color] = Array(Red, Green, Blue) ``` ### User-defined members of enums @@ -84,16 +82,32 @@ object Planet { def main(args: Array[String]) = { val earthWeight = args(0).toDouble val mass = earthWeight / Earth.surfaceGravity - for (p <- enumValues) + for (p <- values) println(s"Your weight on $p is ${p.surfaceWeight(mass)}") } } ``` +### Compatibility with Java Enums +If you want to use the Scala-defined enums as Java enums, you can do so by extending `compat.JEnum` class as follows: + +```scala +enum Color extends compat.JEnum[Color] { case Red, Green, Blue } +``` + +The type parameter comes from the Java enum [definition](https://docs.oracle.com/javase/8/docs/api/index.html?java/lang/Enum.html) and should me the same as the type of the enum. The compiler will transform the definition above so that `Color` extends `java.lang.Enum`. + +After defining `Color` like that, you can use like you would a Java enum: + +```scala +scala> Color.Red.compareTo(Color.Green) +val res15: Int = -1 +``` + ### Implementation Enums are represented as `sealed` classes that extend the `scala.Enum` trait. -This trait defines a single method, `enumTag`: +This trait defines a single public method, `ordinal`: ```scala package scala @@ -102,7 +116,7 @@ package scala trait Enum { /** A number uniquely identifying a case of an enum */ - def enumTag: Int + def ordinal: Int } ``` @@ -112,7 +126,7 @@ For instance, the `Venus` value above would be defined like this: ```scala val Venus: Planet = new Planet(4.869E24, 6051800.0) { - def enumTag: Int = 1 + def ordinal: Int = 1 override def toString: String = "Venus" // internal code to register value } diff --git a/library/src/scala/Enum.scala b/library/src/scala/Enum.scala index 7d2eefb3df9c..69f1daa27ab8 100644 --- a/library/src/scala/Enum.scala +++ b/library/src/scala/Enum.scala @@ -4,5 +4,6 @@ package scala trait Enum { /** A number uniquely identifying a case of an enum */ - def enumTag: Int + def ordinal: Int + protected def $ordinal: Int } diff --git a/library/src/scala/runtime/EnumValues.scala b/library/src/scala/runtime/EnumValues.scala index 64901512cad1..3f59faed5d56 100644 --- a/library/src/scala/runtime/EnumValues.scala +++ b/library/src/scala/runtime/EnumValues.scala @@ -7,8 +7,8 @@ class EnumValues[E <: Enum] { private[this] var fromNameCache: Map[String, E] = null def register(v: E) = { - require(!myMap.contains(v.enumTag)) - myMap = myMap.updated(v.enumTag, v) + require(!myMap.contains(v.ordinal)) + myMap = myMap.updated(v.ordinal, v) fromNameCache = null } diff --git a/tests/neg/i5008.scala b/tests/neg/i5008.scala index 25e19f423982..c70f2e291eaa 100644 --- a/tests/neg/i5008.scala +++ b/tests/neg/i5008.scala @@ -5,4 +5,4 @@ enum Baz extends Foo { case Z } // error enum Quux extends Foo with Bar { case Z } // error class Quuw extends Foo // error -class Quuz extends Foo { val enumTag = 1 } // error +class Quuz extends Foo { val ordinal = 1 } // error diff --git a/tests/neg/i5019.scala b/tests/neg/i5019.scala index 0651aac8edac..ecb852bf052a 100644 --- a/tests/neg/i5019.scala +++ b/tests/neg/i5019.scala @@ -1,3 +1,3 @@ enum A { // error - def newA(tag: Int) = new A { val enumTag = tag } // error + def newA(tag: Int) = new A { val ordinal = tag } // error } diff --git a/tests/patmat/planets.scala b/tests/patmat/planets.scala index 22c731428f70..2c4f31b17ca3 100644 --- a/tests/patmat/planets.scala +++ b/tests/patmat/planets.scala @@ -15,11 +15,10 @@ enum Planet(mass: Double, radius: Double) { object Test { def main(args: Array[String]) = { import Planet._ - assert(enumValueNamed("SATURN") == SATURN) - assert(enumValue(2) == EARTH) + assert(valueOf("SATURN") == SATURN) val earthWeight = 100 val mass = earthWeight/EARTH.surfaceGravity - for (p <- enumValues) + for (p <- values) println(s"Your weight on $p is ${p.surfaceWeight(mass)}") } } diff --git a/tests/pos/enum-List-control.scala b/tests/pos/enum-List-control.scala index b269df43b242..931406214122 100644 --- a/tests/pos/enum-List-control.scala +++ b/tests/pos/enum-List-control.scala @@ -1,13 +1,13 @@ abstract sealed class List[T] extends Enum object List { final class Cons[T](x: T, xs: List[T]) extends List[T] { - def enumTag = 0 + def $ordinal = 0 } object Cons { def apply[T](x: T, xs: List[T]): List[T] = new Cons(x, xs) } final class Nil[T]() extends List[T] { - def enumTag = 1 + def $ordinal = 1 } object Nil { def apply[T](): List[T] = new Nil() diff --git a/tests/pos/reference/enums.scala b/tests/pos/reference/enums.scala index 71179c7aed61..fa684d735aeb 100644 --- a/tests/pos/reference/enums.scala +++ b/tests/pos/reference/enums.scala @@ -47,7 +47,7 @@ object Planet { def main(args: Array[String]) = { val earthWeight = args(0).toDouble val mass = earthWeight/EARTH.surfaceGravity - for (p <- enumValues) + for (p <- values) println(s"Your weight on $p is ${p.surfaceWeight(mass)}") } } diff --git a/tests/run/enum-Color.check b/tests/run/enum-Color.check index 865c47e495d1..43edee1968a7 100644 --- a/tests/run/enum-Color.check +++ b/tests/run/enum-Color.check @@ -1,3 +1,3 @@ -Red: 0 -Green: 1 -Blue: 2 +Red: Red +Green: Green +Blue: Blue diff --git a/tests/run/enum-Color.scala b/tests/run/enum-Color.scala index 31b850329407..b00493dc00b9 100644 --- a/tests/run/enum-Color.scala +++ b/tests/run/enum-Color.scala @@ -5,9 +5,9 @@ enum Color { object Test { def main(args: Array[String]) = - for (color <- Color.enumValues) { - println(s"$color: ${color.enumTag}") - assert(Color.enumValue(color.enumTag) eq color) + for (color <- Color.values) { + println(s"$color: ${color.toString}") + assert(Color.valueOf(color.toString) eq color) import Color._ color match { case Red | Green | Blue => diff --git a/tests/run/enum-efficient-collections.check b/tests/run/enum-efficient-collections.check new file mode 100644 index 000000000000..3dff8bb9a658 --- /dev/null +++ b/tests/run/enum-efficient-collections.check @@ -0,0 +1,6 @@ +MONDAY +TUESDAY +SATURDAY +WEDNESDAY +workday +weekend diff --git a/tests/run/enum-efficient-collections.scala b/tests/run/enum-efficient-collections.scala new file mode 100644 index 000000000000..01aeb97be014 --- /dev/null +++ b/tests/run/enum-efficient-collections.scala @@ -0,0 +1,16 @@ +enum Day extends java.lang.Enum[Day] { + case MONDAY, TUESDAY, SATURDAY, WEDNESDAY +} + +object Test { + def main(args: Array[String]): Unit = { + val allDays = java.util.EnumSet.allOf(classOf[Day]) + val dayMap = new java.util.EnumMap[Day, String](classOf[Day]) + dayMap.put(Day.MONDAY, "workday") + dayMap.put(Day.SATURDAY, "weekend") + + allDays.toArray.foreach(println) + println(dayMap.get(Day.MONDAY)) + println(dayMap.get(Day.SATURDAY)) + } +} diff --git a/tests/run/enums-java-compat.check b/tests/run/enums-java-compat.check new file mode 100644 index 000000000000..04468ba9b44d --- /dev/null +++ b/tests/run/enums-java-compat.check @@ -0,0 +1,9 @@ +ordinal: 0 +toString: EARTH +Values class: class [LA; +MONDAY : 0 +TUESDAY : 1 +SATURDAY : 2 +Stuff : 3 +By-name value: MONDAY +Correctly failed to retrieve illegal name, message: key not found: stuff diff --git a/tests/run/enums-java-compat.scala b/tests/run/enums-java-compat.scala new file mode 100644 index 000000000000..592e38c8d603 --- /dev/null +++ b/tests/run/enums-java-compat.scala @@ -0,0 +1,40 @@ +class JEnum { + def name: String = "Foo" + def action = "fofofo" +} + +enum A extends JEnum { + case MONDAY, TUESDAY, SATURDAY + case Stuff + case Someday(x: String) + def report = "Reported" +} + +trait Foo1 +trait Bar + +enum B(val gravity: Double)(val isItGood: Boolean) extends Foo1 { + case EARTH extends B(9.8)(true) + case JUPITER extends B(100)(true) + case MOON extends B(4.3)(true) + case Foo extends B(10)(true) with Bar +} + +object Test { + def main(args: Array[String]): Unit = { + val t1 = B.EARTH + val t2 = B.JUPITER + + println("ordinal: " + t1.ordinal) + println("toString: " + t1.toString) + + val values: Array[A] = A.values + println("Values class: " + values.getClass) + values.foreach(v => println(v.toString + " : " + v.ordinal)) + println("By-name value: " + A.valueOf("MONDAY")) + try A.valueOf("stuff") + catch { case e: IllegalArgumentException => + println("Correctly failed to retrieve illegal name, message: " + e.getMessage) + } + } +} \ No newline at end of file diff --git a/tests/run/generic/Color.scala b/tests/run/generic/Color.scala index 7f2a8818c0fc..3db940cdc94a 100644 --- a/tests/run/generic/Color.scala +++ b/tests/run/generic/Color.scala @@ -18,7 +18,7 @@ object Color { def values = $values.values private def $new(tag: Int, name: String) = new Color { - def enumTag = tag + def ordinal = tag override def toString = name $values.register(this) } @@ -29,7 +29,7 @@ object Color { implicit val ColorShape: Color `shaped` EnumValue[Color] = new (Color `shaped` EnumValue[Color]) { - def toShape(x: Color) = EnumValue(x.enumTag) + def toShape(x: Color) = EnumValue(x.ordinal) def fromShape(x: EnumValue[Color]) = Color.valueOf(x.tag) } } diff --git a/tests/run/generic/Enum.scala b/tests/run/generic/Enum.scala index ce64ac054933..3e04786de07d 100644 --- a/tests/run/generic/Enum.scala +++ b/tests/run/generic/Enum.scala @@ -1,7 +1,7 @@ package generic trait Enum { - def enumTag: Int + def ordinal: Int } object runtime { @@ -10,8 +10,8 @@ object runtime { private[this] var fromNameCache: Map[String, E] = null def register(v: E) = { - require(!myMap.contains(v.enumTag)) - myMap = myMap.updated(v.enumTag, v) + require(!myMap.contains(v.ordinal)) + myMap = myMap.updated(v.ordinal, v) fromNameCache = null } diff --git a/tests/run/generic/List.scala b/tests/run/generic/List.scala index bc01ce63fc14..07ed98295ba3 100644 --- a/tests/run/generic/List.scala +++ b/tests/run/generic/List.scala @@ -10,7 +10,7 @@ import Shapes._ sealed trait List0[T] extends Enum object List0 { abstract case class Cons[T](hd: T, tl: List0[T]) extends List0[T] { - def enumTag = 0 + def ordinal = 0 } object Cons { def apply[T](x: T, xs: List0[T]): List0[T] = new Cons(x, xs) {} @@ -22,7 +22,7 @@ object List0 { } abstract case class Nil[T]() extends List0[T] { - def enumTag = 1 + def ordinal = 1 } object Nil { def apply[T](): List0[T] = new Nil[T]() {} @@ -54,7 +54,7 @@ object List0 { sealed trait List[+T] extends Enum object List { abstract case class Cons[T](hd: T, tl: List[T]) extends List[T] { - def enumTag = 0 + def ordinal = 0 } object Cons { def apply[T](x: T, xs: List[T]): List[T] = new Cons(x, xs) {} @@ -67,7 +67,7 @@ object List { } val Nil = new List[Nothing] { - def enumTag = 1 + def ordinal = 1 override def toString = "Nil" } diff --git a/tests/run/generic/SearchResult.scala b/tests/run/generic/SearchResult.scala index 96ec7e3f2536..bc1b4a0dd901 100644 --- a/tests/run/generic/SearchResult.scala +++ b/tests/run/generic/SearchResult.scala @@ -19,13 +19,13 @@ object SearchResult { def values = $values.values private def $new(tag: Int, name: String) = new SearchResult { - def enumTag = tag + def ordinal = tag override def toString = name $values.register(this) } abstract case class Success(result: Color) extends SearchResult { - def enumTag = 0 + def ordinal = 0 } object Success { def apply(result: Color): SearchResult = new Success(result) {} @@ -40,7 +40,7 @@ object SearchResult { val NoMatch: SearchResult = $new(2, "NoMatch") abstract case class Ambiguous(alt1: SearchResult, alt2: SearchResult) extends SearchResult { - def enumTag = 3 + def ordinal = 3 } object Ambiguous { def apply(alt1: SearchResult, alt2: SearchResult): SearchResult = new Ambiguous(alt1, alt2) {} @@ -58,7 +58,7 @@ object SearchResult { def toShape(x: SearchResult) = x match { case x: Success => Fst(x) case x: Ambiguous => Snd(Fst(x)) - case x => Snd(Snd(EnumValue(x.enumTag))) + case x => Snd(Snd(EnumValue(x.ordinal))) } def fromShape(x: Sum[Success, Sum[Ambiguous, EnumValue[SearchResult]]]): SearchResult = x match { case Fst(s) => s diff --git a/tests/run/generic/Tree.scala b/tests/run/generic/Tree.scala index 673506b07a0b..32996685fc90 100644 --- a/tests/run/generic/Tree.scala +++ b/tests/run/generic/Tree.scala @@ -17,25 +17,25 @@ sealed trait Tree[TR] extends Enum object Tree { val True: Tree[Boolean] = new Tree[Boolean] { - def enumTag = 0 + def ordinal = 0 override def toString = "True" } implicit def TrueSingleton: Singleton[True.type] = new Singleton[True.type](True) val False: Tree[Boolean] = new Tree[Boolean] { - def enumTag = 1 + def ordinal = 1 override def toString = "False" } implicit def FalseSingleton: Singleton[False.type] = new Singleton[False.type](False) val Zero: Tree[Int] = new Tree[Int] { - def enumTag = 2 + def ordinal = 2 override def toString = "Zero" } implicit def ZeroSingleton: Singleton[Zero.type] = new Singleton[Zero.type](Zero) abstract case class Succ(n: Tree[Int]) extends Tree[Int] { - def enumTag = 3 + def ordinal = 3 } object Succ { def apply(x: Tree[Int]): Tree[Int] = new Succ(x) {} @@ -46,7 +46,7 @@ object Tree { } abstract case class Pred(n: Tree[Int]) extends Tree[Int] { - def enumTag = 4 + def ordinal = 4 } object Pred { def apply(x: Tree[Int]): Tree[Int] = new Pred(x) {} @@ -57,7 +57,7 @@ object Tree { } abstract case class IsZero(n: Tree[Int]) extends Tree[Boolean] { - def enumTag = 5 + def ordinal = 5 } object IsZero { def apply(x: Tree[Int]): Tree[Boolean] = new IsZero(x) {} @@ -68,7 +68,7 @@ object Tree { } abstract case class If[T](cond: Tree[Boolean], thenp: Tree[T], elsep: Tree[T]) extends Tree[T] { - def enumTag = 6 + def ordinal = 6 } object If { def apply[T](cond: Tree[Boolean], thenp: Tree[T], elsep: Tree[T]): Tree[T] = new If(cond, thenp, elsep) {} diff --git a/tests/run/i4961.scala b/tests/run/i4961.scala index c89469c0c3df..c49056e88925 100644 --- a/tests/run/i4961.scala +++ b/tests/run/i4961.scala @@ -1,7 +1,6 @@ trait Animal object Animal { - def enumValues: Iterable[Animal] = Dog.enumValues ++ Cat.enumValues - def enumValueNamed = Dog.enumValueNamed ++ Cat.enumValueNamed + def enumValues: Iterable[Animal] = Dog.values ++ Cat.values } enum Dog extends Animal { @@ -27,7 +26,5 @@ object Test { ) assert(Animal.enumValues == values) - assert(Animal.enumValueNamed("Boxer") == Boxer) - assert(Animal.enumValueNamed("Ragdoll") == Ragdoll) } } diff --git a/tests/run/planets.scala b/tests/run/planets.scala index 22c731428f70..2c4f31b17ca3 100644 --- a/tests/run/planets.scala +++ b/tests/run/planets.scala @@ -15,11 +15,10 @@ enum Planet(mass: Double, radius: Double) { object Test { def main(args: Array[String]) = { import Planet._ - assert(enumValueNamed("SATURN") == SATURN) - assert(enumValue(2) == EARTH) + assert(valueOf("SATURN") == SATURN) val earthWeight = 100 val mass = earthWeight/EARTH.surfaceGravity - for (p <- enumValues) + for (p <- values) println(s"Your weight on $p is ${p.surfaceWeight(mass)}") } }