From b2570dc9d1e72aa8bb4dd9b07558d2293d6dbcd8 Mon Sep 17 00:00:00 2001 From: Jamie Thompson Date: Mon, 15 Nov 2021 09:57:02 +0100 Subject: [PATCH] move implementation of ordinal in enums to posttyper also add a check that ordinal is not implemented by the user, or mixed in by a trait - this is necessary as scala.deriving.Mirror.Sum delegates to ordinal method on an enum. Note that before this commit, as enum cases would previously declare ordinal methods at desugaring (without an override flag), refchecks would issue an override-without-override-modifier error anyway. Co-authored-by: Jamie Thompson Co-authored-by: Martin Odersky --- .../src/dotty/tools/dotc/ast/DesugarEnums.scala | 11 +++-------- .../src/dotty/tools/dotc/core/Definitions.scala | 1 + .../tools/dotc/transform/SyntheticMembers.scala | 14 +++++++++++++- .../src/dotty/tools/dotc/typer/Checking.scala | 13 +++++++++++++ tests/neg/enumsLabel-singleimpl.scala | 8 ++++---- tests/pos/i13554.scala | 6 ++++++ tests/pos/i13554a.scala | 15 +++++++++++++++ 7 files changed, 55 insertions(+), 13 deletions(-) create mode 100644 tests/pos/i13554.scala create mode 100644 tests/pos/i13554a.scala 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) + } +}