diff --git a/compiler/src/dotty/tools/dotc/ast/DesugarEnums.scala b/compiler/src/dotty/tools/dotc/ast/DesugarEnums.scala index d47fd93abaf4..009aba09f2f6 100644 --- a/compiler/src/dotty/tools/dotc/ast/DesugarEnums.scala +++ b/compiler/src/dotty/tools/dotc/ast/DesugarEnums.scala @@ -179,13 +179,12 @@ object DesugarEnums { * } */ private def enumValueCreator(using Context) = { - val fieldMethods = if isJavaEnum then Nil else ordinalMeth(Ident(nme.ordinalDollar_)) :: Nil val creator = New(Template( constr = emptyConstructor, parents = enumClassRef :: scalaRuntimeDot(tpnme.EnumValue) :: Nil, derived = Nil, self = EmptyValDef, - body = fieldMethods + body = Nil ).withAttachment(ExtendsSingletonMirror, ())) DefDef(nme.DOLLAR_NEW, List(List(param(nme.ordinalDollar_, defn.IntType), param(nme.nameDollar, defn.StringType))), @@ -270,8 +269,6 @@ object DesugarEnums { def param(name: TermName, typ: Type)(using Context): ValDef = param(name, TypeTree(typ)) def param(name: TermName, tpt: Tree)(using Context): ValDef = ValDef(name, tpt, EmptyTree).withFlags(Param) - private def isJavaEnum(using Context): Boolean = enumClass.derivesFrom(defn.JavaEnumClass) - def ordinalMeth(body: Tree)(using Context): DefDef = DefDef(nme.ordinal, Nil, TypeTree(defn.IntType), body).withAddedFlags(Synthetic) @@ -290,10 +287,8 @@ object DesugarEnums { expandSimpleEnumCase(name, mods, definesLookups, span) else { val (tag, scaffolding) = nextOrdinal(name, CaseKind.Object, definesLookups) - val impl1 = cpy.Template(impl)( - parents = impl.parents :+ scalaRuntimeDot(tpnme.EnumValue), - body = if isJavaEnum then Nil else ordinalMethLit(tag) :: Nil - ).withAttachment(ExtendsSingletonMirror, ()) + val impl1 = cpy.Template(impl)(parents = impl.parents :+ scalaRuntimeDot(tpnme.EnumValue), body = Nil) + .withAttachment(ExtendsSingletonMirror, ()) val vdef = ValDef(name, TypeTree(), New(impl1)).withMods(mods.withAddedFlags(EnumValue, span)) flatTree(vdef :: scaffolding).withSpan(span) } diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index 16ce0e381c75..d6c713039190 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -733,6 +733,7 @@ class Definitions { @tu lazy val NoneModule: Symbol = requiredModule("scala.None") @tu lazy val EnumClass: ClassSymbol = requiredClass("scala.reflect.Enum") + @tu lazy val Enum_ordinal: Symbol = EnumClass.requiredMethod(nme.ordinal) @tu lazy val EnumValueSerializationProxyClass: ClassSymbol = requiredClass("scala.runtime.EnumValueSerializationProxy") @tu lazy val EnumValueSerializationProxyConstructor: TermSymbol = diff --git a/compiler/src/dotty/tools/dotc/transform/SyntheticMembers.scala b/compiler/src/dotty/tools/dotc/transform/SyntheticMembers.scala index ac8d96afb604..3a94a78b3d8e 100644 --- a/compiler/src/dotty/tools/dotc/transform/SyntheticMembers.scala +++ b/compiler/src/dotty/tools/dotc/transform/SyntheticMembers.scala @@ -66,7 +66,7 @@ class SyntheticMembers(thisPhase: DenotTransformer) { myCaseSymbols = defn.caseClassSynthesized myCaseModuleSymbols = myCaseSymbols.filter(_ ne defn.Any_equals) myEnumValueSymbols = List(defn.Product_productPrefix) - myNonJavaEnumValueSymbols = myEnumValueSymbols :+ defn.Any_toString + myNonJavaEnumValueSymbols = myEnumValueSymbols :+ defn.Any_toString :+ defn.Enum_ordinal } def valueSymbols(using Context): List[Symbol] = { initSymbols; myValueSymbols } @@ -132,6 +132,17 @@ class SyntheticMembers(thisPhase: DenotTransformer) { else // assume owner is `val Foo = new MyEnum { def ordinal = 0 }` Literal(Constant(clazz.owner.name.toString)) + def ordinalRef: Tree = + if isSimpleEnumValue then // owner is `def $new(_$ordinal: Int, $name: String) = new MyEnum { ... }` + ref(clazz.owner.paramSymss.head.find(_.name == nme.ordinalDollar_).get) + else // val CaseN = new MyEnum { ... def ordinal: Int = n } + val vdef = clazz.owner + val parentEnum = vdef.owner.companionClass + val children = parentEnum.children.zipWithIndex + val candidate: Option[Int] = children.collectFirst { case (child, idx) if child == vdef => idx } + assert(candidate.isDefined, i"could not find child for $vdef") + Literal(Constant(candidate.get)) + def toStringBody(vrefss: List[List[Tree]]): Tree = if (clazz.is(ModuleClass)) ownName else if (isNonJavaEnumValue) identifierRef @@ -143,6 +154,7 @@ class SyntheticMembers(thisPhase: DenotTransformer) { case nme.toString_ => toStringBody(vrefss) case nme.equals_ => equalsBody(vrefss.head.head) case nme.canEqual_ => canEqualBody(vrefss.head.head) + case nme.ordinal => ordinalRef case nme.productArity => Literal(Constant(accessors.length)) case nme.productPrefix if isEnumValue => nameRef case nme.productPrefix => ownName diff --git a/compiler/src/dotty/tools/dotc/typer/Checking.scala b/compiler/src/dotty/tools/dotc/typer/Checking.scala index 185b0584a5a2..6343f0bc4716 100644 --- a/compiler/src/dotty/tools/dotc/typer/Checking.scala +++ b/compiler/src/dotty/tools/dotc/typer/Checking.scala @@ -1217,8 +1217,19 @@ trait Checking { /** 1. Check that all case classes that extend `scala.reflect.Enum` are `enum` cases * 2. Check that parameterised `enum` cases do not extend java.lang.Enum. * 3. Check that only a static `enum` base class can extend java.lang.Enum. + * 4. Check that user does not implement an `ordinal` method in the body of an enum class. */ def checkEnum(cdef: untpd.TypeDef, cls: Symbol, firstParent: Symbol)(using Context): Unit = { + def existingDef(sym: Symbol, clazz: ClassSymbol)(using Context): Symbol = // adapted from SyntheticMembers + val existing = sym.matchingMember(clazz.thisType) + if existing != sym && !existing.is(Deferred) then existing else NoSymbol + def checkExistingOrdinal(using Context) = + val decl = existingDef(defn.Enum_ordinal, cls.asClass) + if decl.exists then + if decl.owner == cls then + report.error(em"the ordinal method of enum $cls can not be defined by the user", decl.srcPos) + else + report.error(em"enum $cls can not inherit the concrete ordinal method of ${decl.owner}", cdef.srcPos) def isEnumAnonCls = cls.isAnonymousClass && cls.owner.isTerm @@ -1238,6 +1249,8 @@ trait Checking { // this test allows inheriting from `Enum` by hand; // see enum-List-control.scala. report.error(ClassCannotExtendEnum(cls, firstParent), cdef.srcPos) + if cls.isEnumClass && !isJavaEnum then + checkExistingOrdinal } /** Check that the firstParent for an enum case derives from the declaring enum class, if not, adds it as a parent diff --git a/tests/neg/enumsLabel-singleimpl.scala b/tests/neg/enumsLabel-singleimpl.scala index 8f72a6cc71f2..a246ac9e5461 100644 --- a/tests/neg/enumsLabel-singleimpl.scala +++ b/tests/neg/enumsLabel-singleimpl.scala @@ -1,13 +1,13 @@ enum Ordinalled { - case A // error: method ordinal of type => Int needs `override` modifier + case A - def ordinal: Int = -1 + def ordinal: Int = -1 // error: the ordinal method of enum class Ordinalled can not be defined by the user } trait HasOrdinal { def ordinal: Int = 23 } -enum MyEnum extends HasOrdinal { - case Foo // error: method ordinal of type => Int needs `override` modifier +enum MyEnum extends HasOrdinal { // error: enum class MyEnum can not inherit the concrete ordinal method of trait HasOrdinal + case Foo } diff --git a/tests/pos/i13554.scala b/tests/pos/i13554.scala new file mode 100644 index 000000000000..e30d3eb9bd9f --- /dev/null +++ b/tests/pos/i13554.scala @@ -0,0 +1,6 @@ +object StatusCode: + class Matcher + +enum StatusCode(m: StatusCode.Matcher): + case InternalServerError extends StatusCode(???) + diff --git a/tests/pos/i13554a.scala b/tests/pos/i13554a.scala new file mode 100644 index 000000000000..0c6f0c6c972f --- /dev/null +++ b/tests/pos/i13554a.scala @@ -0,0 +1,15 @@ +object StatusCode: + enum Matcher: + case ServerError extends Matcher + end Matcher +end StatusCode + +enum StatusCode(code: Int, m: StatusCode.Matcher): + case InternalServerError extends StatusCode(500, StatusCode.Matcher.ServerError) +end StatusCode + +object Main { + def main(args: Array[String]): Unit = { + println(StatusCode.InternalServerError) + } +}