From 8392705309e65896e680600b2ae03f94ac35754e Mon Sep 17 00:00:00 2001 From: Jamie Thompson Date: Fri, 18 Sep 2020 16:50:26 +0200 Subject: [PATCH 01/14] fix #9873: deprecate scala.Enum, no longer use it as parent of enums this commit introduces the concept that some desugaring expansion can occur after parents of the current class are known, i.e. methods that contain no references like ordinal and enumLabel. --- .../src/dotty/tools/dotc/ast/Desugar.scala | 11 +++--- .../dotty/tools/dotc/ast/DesugarEnums.scala | 7 ++++ compiler/src/dotty/tools/dotc/ast/untpd.scala | 4 +++ .../dotty/tools/dotc/transform/SymUtils.scala | 2 ++ .../dotc/transform/SyntheticMembers.scala | 2 +- .../src/dotty/tools/dotc/typer/Namer.scala | 24 +++++++++---- .../src/dotty/tools/dotc/typer/Typer.scala | 5 ++- library/src-bootstrapped/scala/Enum.scala | 3 +- library/src-non-bootstrapped/scala/Enum.scala | 3 ++ .../runtime/EnumValueSerializationProxy.java | 36 ------------------- .../scala/runtime/EnumValues.scala | 21 ----------- tests/run/generic/Enum.scala | 2 +- 12 files changed, 48 insertions(+), 72 deletions(-) delete mode 100644 library/src-non-bootstrapped/scala/runtime/EnumValueSerializationProxy.java delete mode 100644 library/src-non-bootstrapped/scala/runtime/EnumValues.scala diff --git a/compiler/src/dotty/tools/dotc/ast/Desugar.scala b/compiler/src/dotty/tools/dotc/ast/Desugar.scala index 03b879699957..5853f6a764e6 100644 --- a/compiler/src/dotty/tools/dotc/ast/Desugar.scala +++ b/compiler/src/dotty/tools/dotc/ast/Desugar.scala @@ -403,6 +403,7 @@ object desugar { val isCaseObject = mods.is(Case) && isObject val isEnum = mods.isEnumClass && !mods.is(Module) def isEnumCase = mods.isEnumCase + def isNonEnumCase = !isEnumCase && (isCaseClass || isCaseObject) val isValueClass = parents.nonEmpty && isAnyVal(parents.head) // This is not watertight, but `extends AnyVal` will be replaced by `inline` later. @@ -483,7 +484,8 @@ object desugar { val enumCompanionRef = TermRefTree() val enumImport = Import(enumCompanionRef, enumCases.flatMap(caseIds).map(ImportSelector(_))) - (enumImport :: enumStats, enumCases, enumCompanionRef) + val enumSpecMethods = EnumGetters() + (enumImport :: enumSpecMethods :: enumStats, enumCases, enumCompanionRef) } else (stats, Nil, EmptyTree) } @@ -621,10 +623,8 @@ object desugar { var parents1 = parents if (isEnumCase && parents.isEmpty) parents1 = enumClassTypeRef :: Nil - if (isCaseClass | isCaseObject) + if (isNonEnumCase || isEnum) parents1 = parents1 :+ scalaDot(str.Product.toTypeName) :+ scalaDot(nme.Serializable.toTypeName) - if (isEnum) - parents1 = parents1 :+ ref(defn.EnumClass.typeRef) // derived type classes of non-module classes go to their companions val (clsDerived, companionDerived) = @@ -890,6 +890,9 @@ object desugar { } } + def enumGetters(getters: EnumGetters)(using Context): Tree = + flatTree(DesugarEnums.enumBaseMeths).withSpan(getters.span) + /** Transform extension construct to list of extension methods */ def extMethods(ext: ExtMethods)(using Context): Tree = flatTree { for mdef <- ext.methods yield diff --git a/compiler/src/dotty/tools/dotc/ast/DesugarEnums.scala b/compiler/src/dotty/tools/dotc/ast/DesugarEnums.scala index 4ed857678b26..42f23cb1da3d 100644 --- a/compiler/src/dotty/tools/dotc/ast/DesugarEnums.scala +++ b/compiler/src/dotty/tools/dotc/ast/DesugarEnums.scala @@ -322,6 +322,13 @@ object DesugarEnums { (ordinal, Nil) } + def enumBaseMeths(using Context): List[Tree] = + val ordinalDef = DefDef(nme.ordinal, Nil, Nil, ref(defn.IntType), EmptyTree) + val enumLabelDef = DefDef(nme.enumLabel, Nil, Nil, ref(defn.StringClass.typeRef), EmptyTree) + val base = enumLabelDef :: Nil + if isJavaEnum then base + else ordinalDef :: base + 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) diff --git a/compiler/src/dotty/tools/dotc/ast/untpd.scala b/compiler/src/dotty/tools/dotc/ast/untpd.scala index 5e87ef6aeeed..6417d949e41d 100644 --- a/compiler/src/dotty/tools/dotc/ast/untpd.scala +++ b/compiler/src/dotty/tools/dotc/ast/untpd.scala @@ -116,6 +116,7 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo { case class Export(expr: Tree, selectors: List[ImportSelector])(implicit @constructorOnly src: SourceFile) extends Tree case class ExtMethods(tparams: List[TypeDef], vparamss: List[List[ValDef]], methods: List[DefDef])(implicit @constructorOnly src: SourceFile) extends Tree case class MacroTree(expr: Tree)(implicit @constructorOnly src: SourceFile) extends Tree + case class EnumGetters()(implicit @constructorOnly src: SourceFile) extends Tree case class ImportSelector(imported: Ident, renamed: Tree = EmptyTree, bound: Tree = EmptyTree)(implicit @constructorOnly src: SourceFile) extends Tree { // TODO: Make bound a typed tree? @@ -700,6 +701,7 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo { cpy.Export(tree)(transform(expr), selectors) case ExtMethods(tparams, vparamss, methods) => cpy.ExtMethods(tree)(transformSub(tparams), vparamss.mapConserve(transformSub(_)), transformSub(methods)) + case enums: EnumGetters => enums case ImportSelector(imported, renamed, bound) => cpy.ImportSelector(tree)(transformSub(imported), transform(renamed), transform(bound)) case Number(_, _) | TypedSplice(_) => @@ -761,6 +763,8 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo { this(x, expr) case ExtMethods(tparams, vparamss, methods) => this(vparamss.foldLeft(this(x, tparams))(apply), methods) + case EnumGetters() => + x case ImportSelector(imported, renamed, bound) => this(this(this(x, imported), renamed), bound) case Number(_, _) => diff --git a/compiler/src/dotty/tools/dotc/transform/SymUtils.scala b/compiler/src/dotty/tools/dotc/transform/SymUtils.scala index 62500b758612..2a6ee1898597 100644 --- a/compiler/src/dotty/tools/dotc/transform/SymUtils.scala +++ b/compiler/src/dotty/tools/dotc/transform/SymUtils.scala @@ -168,6 +168,8 @@ object SymUtils { self } + def isScalaEnum(using Context): Boolean = self.is(Enum, butNot=JavaDefined) + /** Does this symbol refer to anonymous classes synthesized by enum desugaring? */ def isEnumAnonymClass(using Context): Boolean = self.isAnonymousClass && (self.owner.name.eq(nme.DOLLAR_NEW) || self.owner.is(CaseVal)) diff --git a/compiler/src/dotty/tools/dotc/transform/SyntheticMembers.scala b/compiler/src/dotty/tools/dotc/transform/SyntheticMembers.scala index bbd2f13395a0..6cab230d5abf 100644 --- a/compiler/src/dotty/tools/dotc/transform/SyntheticMembers.scala +++ b/compiler/src/dotty/tools/dotc/transform/SyntheticMembers.scala @@ -94,7 +94,7 @@ 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 isEnumCase = clazz.classParents.exists(_.typeSymbol.isScalaEnum) val isEnumValue = isEnumCase && clazz.isAnonymousClass && clazz.classParents.head.classSymbol.is(Enum) val isNonJavaEnumValue = isEnumValue && !clazz.derivesFrom(defn.JavaEnumClass) diff --git a/compiler/src/dotty/tools/dotc/typer/Namer.scala b/compiler/src/dotty/tools/dotc/typer/Namer.scala index 02607393580d..ab0360f6b707 100644 --- a/compiler/src/dotty/tools/dotc/typer/Namer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Namer.scala @@ -343,17 +343,20 @@ class Namer { typer: Typer => tree.pushAttachment(ExpandedTree, expanded) } tree match { - case tree: DefTree => record(desugar.defTree(tree)) - case tree: PackageDef => record(desugar.packageDef(tree)) - case tree: ExtMethods => record(desugar.extMethods(tree)) - case _ => + case tree: DefTree => record(desugar.defTree(tree)) + case tree: PackageDef => record(desugar.packageDef(tree)) + case tree: ExtMethods => record(desugar.extMethods(tree)) + case tree: EnumGetters => record(desugar.enumGetters(tree)) + case _ => } } /** The expanded version of this tree, or tree itself if not expanded */ def expanded(tree: Tree)(using Context): Tree = tree match { - case _: DefTree | _: PackageDef | _: ExtMethods => tree.attachmentOrElse(ExpandedTree, tree) - case _ => tree + case _: DefTree | _: PackageDef | _: ExtMethods | _: EnumGetters => + tree.attachmentOrElse(ExpandedTree, tree) + case _ => + tree } /** For all class definitions `stat` in `xstats`: If the companion class is @@ -925,11 +928,17 @@ class Namer { typer: Typer => val TypeDef(name, impl @ Template(constr, _, self, _)) = original - private val (params, rest): (List[Tree], List[Tree]) = impl.body.span { + private val (params, restOfBody): (List[Tree], List[Tree]) = impl.body.span { case td: TypeDef => td.mods.is(Param) case vd: ValDef => vd.mods.is(ParamAccessor) case _ => false } + private val (restAfterParents, rest): (List[Tree], List[Tree]) = + if original.mods.isEnumClass then + val (imports :: getters :: Nil, stats): @unchecked = restOfBody.splitAt(2) + (getters :: Nil, imports :: stats) // enum getters desugaring needs to test if a parent is java.lang.Enum + else + (Nil, restOfBody) def init(): Context = index(params) @@ -1196,6 +1205,7 @@ class Namer { typer: Typer => cls.setNoInitsFlags(parentsKind(parents), untpd.bodyKind(rest)) if (cls.isNoInitsClass) cls.primaryConstructor.setFlag(StableRealizable) processExports(using localCtx) + index(restAfterParents)(using localCtx) } } diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index f414c0aadf3a..4c01e3f12bf3 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -2122,7 +2122,7 @@ class Typer extends Namer .withType(dummy.termRef) if (!cls.isOneOf(AbstractOrTrait) && !ctx.isAfterTyper) checkRealizableBounds(cls, cdef.sourcePos.withSpan(cdef.nameSpan)) - if cls.derivesFrom(defn.EnumClass) then + if cls.isScalaEnum || firstParent.isScalaEnum then checkEnum(cdef, cls, firstParent) val cdef1 = assignType(cpy.TypeDef(cdef)(name, impl1), cls) @@ -2635,6 +2635,9 @@ class Typer extends Namer case (stat: untpd.ExtMethods) :: rest => val xtree = stat.removeAttachment(ExpandedTree).get traverse(xtree :: rest) + case (stat: untpd.EnumGetters) :: rest => + val xtree = stat.removeAttachment(ExpandedTree).get + traverse(xtree :: rest) case stat :: rest => val stat1 = typed(stat)(using ctx.exprContext(stat, exprOwner)) checkStatementPurity(stat1)(stat, exprOwner) diff --git a/library/src-bootstrapped/scala/Enum.scala b/library/src-bootstrapped/scala/Enum.scala index ff713f90fe11..b0b2b95ace66 100644 --- a/library/src-bootstrapped/scala/Enum.scala +++ b/library/src-bootstrapped/scala/Enum.scala @@ -1,6 +1,7 @@ package scala -/** A base trait of all enum classes */ +/** A Product that also describes a label and ordinal */ +@deprecated("scala.Enum is no longer supported", "3.0.0-M1") trait Enum extends Product, Serializable: /** A string uniquely identifying a case of an enum */ diff --git a/library/src-non-bootstrapped/scala/Enum.scala b/library/src-non-bootstrapped/scala/Enum.scala index ce21eb12cd08..ff713f90fe11 100644 --- a/library/src-non-bootstrapped/scala/Enum.scala +++ b/library/src-non-bootstrapped/scala/Enum.scala @@ -3,5 +3,8 @@ package scala /** A base trait of all enum classes */ trait Enum extends Product, Serializable: + /** A string uniquely identifying a case of an enum */ + def enumLabel: String + /** A number uniquely identifying a case of an enum */ def ordinal: Int diff --git a/library/src-non-bootstrapped/scala/runtime/EnumValueSerializationProxy.java b/library/src-non-bootstrapped/scala/runtime/EnumValueSerializationProxy.java deleted file mode 100644 index 206a48f2b770..000000000000 --- a/library/src-non-bootstrapped/scala/runtime/EnumValueSerializationProxy.java +++ /dev/null @@ -1,36 +0,0 @@ -package scala.runtime; - -import java.io.Serializable; -import java.security.AccessController; -import java.security.PrivilegedActionException; -import java.security.PrivilegedExceptionAction; - -/** A serialization proxy for singleton enum values, based on `scala.runtime.ModuleSerializationProxy` */ -public final class EnumValueSerializationProxy implements Serializable { - private static final long serialVersionUID = 1L; - private final Class enumClass; - private final int ordinal; - private static final ClassValue enumValues = new ClassValue() { - @Override - protected Object[] computeValue(Class type) { - try { - return AccessController.doPrivileged((PrivilegedExceptionAction) () -> - (Object[])type.getMethod("values").invoke(null)); - } catch (PrivilegedActionException e) { - Throwable cause = e.getCause(); - if (cause instanceof RuntimeException) throw (RuntimeException) cause; - else throw new RuntimeException(cause); - } - } - }; - - public EnumValueSerializationProxy(Class enumClass, int ordinal) { - this.enumClass = enumClass; - this.ordinal = ordinal; - } - - @SuppressWarnings("unused") - private Object readResolve() { - return enumValues.get(enumClass)[ordinal]; - } -} diff --git a/library/src-non-bootstrapped/scala/runtime/EnumValues.scala b/library/src-non-bootstrapped/scala/runtime/EnumValues.scala deleted file mode 100644 index b3f960b7f604..000000000000 --- a/library/src-non-bootstrapped/scala/runtime/EnumValues.scala +++ /dev/null @@ -1,21 +0,0 @@ -package scala.runtime - -import scala.collection.immutable.TreeMap - -class EnumValues[E <: Enum] { - private[this] var myMap: Map[Int, E] = TreeMap.empty - private[this] var fromNameCache: Map[String, E] = null - - def register(v: E) = { - require(!myMap.contains(v.ordinal)) - myMap = myMap.updated(v.ordinal, v) - fromNameCache = null - } - - def fromInt: Map[Int, E] = myMap - def fromName: Map[String, E] = { - if (fromNameCache == null) fromNameCache = myMap.values.map(v => v.toString -> v).toMap - fromNameCache - } - def values: Iterable[E] = myMap.values -} diff --git a/tests/run/generic/Enum.scala b/tests/run/generic/Enum.scala index 3e04786de07d..40ce2983730b 100644 --- a/tests/run/generic/Enum.scala +++ b/tests/run/generic/Enum.scala @@ -22,4 +22,4 @@ object runtime { } def values: Iterable[E] = myMap.values } -} \ No newline at end of file +} From 913d8319a0095b951fa2d1ed898b3c2ec0fb6fb3 Mon Sep 17 00:00:00 2001 From: bishabosha Date: Fri, 25 Sep 2020 02:17:05 +0200 Subject: [PATCH 02/14] fix tests --- tests/patmat/i7186.scala | 6 ++++-- tests/semanticdb/metac.expect | 19 ++++++++++++++++++- 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/tests/patmat/i7186.scala b/tests/patmat/i7186.scala index 95c7d4db02c4..d34945bed756 100644 --- a/tests/patmat/i7186.scala +++ b/tests/patmat/i7186.scala @@ -1,5 +1,7 @@ import MIPS._ +import deriving.Mirror.SumOf + object MIPS { type Labels = Label | ControlLabel type Src = Register | Constant @@ -233,8 +235,8 @@ object printMips { def getScopedLabel(s: Scoped): String = "L" + getScopedId(s) - def printEnum[E](e: String => Enum, t: E, code: String) = { - val num = e(t.toString).ordinal + def printEnum[E: SumOf](e: String => E, t: E, code: String) = { + val num = summon[SumOf[E]].ordinal(e(t.toString)) s"$code$num" } } diff --git a/tests/semanticdb/metac.expect b/tests/semanticdb/metac.expect index 41a2ac13737b..a075c2342092 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 => 183 entries +Symbols => 200 entries Occurrences => 203 entries Symbols: @@ -649,6 +649,8 @@ _empty_/Enums. => final object Enums _empty_/Enums.Coin# => abstract sealed enum class Coin _empty_/Enums.Coin#``(). => primary ctor _empty_/Enums.Coin#``().(value) => param value +_empty_/Enums.Coin#enumLabel(). => abstract method enumLabel +_empty_/Enums.Coin#ordinal(). => abstract method ordinal _empty_/Enums.Coin#value. => val method value _empty_/Enums.Coin. => final object Coin _empty_/Enums.Coin.$fromOrdinal(). => method $fromOrdinal @@ -664,6 +666,8 @@ _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#enumLabel(). => abstract method enumLabel +_empty_/Enums.Colour#ordinal(). => abstract method ordinal _empty_/Enums.Colour. => final object Colour _empty_/Enums.Colour.$fromOrdinal(). => method $fromOrdinal _empty_/Enums.Colour.$fromOrdinal().(_$ordinal) => param _$ordinal @@ -679,6 +683,8 @@ _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#enumLabel(). => abstract method enumLabel +_empty_/Enums.Directions#ordinal(). => abstract method ordinal _empty_/Enums.Directions. => final object Directions _empty_/Enums.Directions.$fromOrdinal(). => method $fromOrdinal _empty_/Enums.Directions.$fromOrdinal().(_$ordinal) => param _$ordinal @@ -696,6 +702,8 @@ _empty_/Enums.Directions.values(). => method values _empty_/Enums.Maybe# => abstract sealed enum class Maybe _empty_/Enums.Maybe#[A] => covariant typeparam A _empty_/Enums.Maybe#``(). => primary ctor +_empty_/Enums.Maybe#enumLabel(). => abstract method enumLabel +_empty_/Enums.Maybe#ordinal(). => abstract method ordinal _empty_/Enums.Maybe. => final object Maybe _empty_/Enums.Maybe.$fromOrdinal(). => method $fromOrdinal _empty_/Enums.Maybe.$fromOrdinal().(_$ordinal) => param _$ordinal @@ -730,6 +738,7 @@ _empty_/Enums.Planet#G. => final val method G _empty_/Enums.Planet#``(). => primary ctor _empty_/Enums.Planet#``().(mass) => param mass _empty_/Enums.Planet#``().(radius) => param radius +_empty_/Enums.Planet#enumLabel(). => abstract method enumLabel _empty_/Enums.Planet#mass. => val method mass _empty_/Enums.Planet#radius. => val method radius _empty_/Enums.Planet#surfaceGravity(). => method surfaceGravity @@ -750,6 +759,8 @@ _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#enumLabel(). => abstract method enumLabel +_empty_/Enums.Suits#ordinal(). => abstract method ordinal _empty_/Enums.Suits. => final object Suits _empty_/Enums.Suits.$fromOrdinal(). => method $fromOrdinal _empty_/Enums.Suits.$fromOrdinal().(_$ordinal) => param _$ordinal @@ -772,6 +783,8 @@ _empty_/Enums.Suits.values(). => method values _empty_/Enums.Tag# => abstract sealed enum class Tag _empty_/Enums.Tag#[A] => typeparam A _empty_/Enums.Tag#``(). => primary ctor +_empty_/Enums.Tag#enumLabel(). => abstract method enumLabel +_empty_/Enums.Tag#ordinal(). => abstract method ordinal _empty_/Enums.Tag. => final object Tag _empty_/Enums.Tag.$fromOrdinal(). => method $fromOrdinal _empty_/Enums.Tag.$fromOrdinal().(_$ordinal) => param _$ordinal @@ -783,6 +796,8 @@ _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#enumLabel(). => abstract method enumLabel +_empty_/Enums.WeekDays#ordinal(). => abstract method ordinal _empty_/Enums.WeekDays. => final object WeekDays _empty_/Enums.WeekDays.$fromOrdinal(). => method $fromOrdinal _empty_/Enums.WeekDays.$fromOrdinal().(_$ordinal) => param _$ordinal @@ -804,6 +819,8 @@ _empty_/Enums.`<:<`# => abstract sealed enum class <:< _empty_/Enums.`<:<`#[A] => contravariant typeparam A _empty_/Enums.`<:<`#[B] => typeparam B _empty_/Enums.`<:<`#``(). => primary ctor +_empty_/Enums.`<:<`#enumLabel(). => abstract method enumLabel +_empty_/Enums.`<:<`#ordinal(). => abstract method ordinal _empty_/Enums.`<:<`. => final object <:< _empty_/Enums.`<:<`.Refl# => final case enum class Refl _empty_/Enums.`<:<`.Refl#[C] => typeparam C From 4e91dbe97e03cb91962546530d410876dc14269b Mon Sep 17 00:00:00 2001 From: bishabosha Date: Fri, 25 Sep 2020 14:43:20 +0200 Subject: [PATCH 03/14] error on custom definition of enum getters --- .../tools/dotc/reporting/ErrorMessageID.scala | 3 ++- .../dotty/tools/dotc/reporting/messages.scala | 4 ++++ .../src/dotty/tools/dotc/typer/Checking.scala | 7 +++++- tests/neg/enumsLabel-overrides.scala | 15 +++++++++++++ tests/neg/enumsLabel-singleimpl.scala | 13 +++++++++++ tests/neg/enumsLabelDef.scala | 22 ------------------- 6 files changed, 40 insertions(+), 24 deletions(-) create mode 100644 tests/neg/enumsLabel-overrides.scala create mode 100644 tests/neg/enumsLabel-singleimpl.scala delete mode 100644 tests/neg/enumsLabelDef.scala diff --git a/compiler/src/dotty/tools/dotc/reporting/ErrorMessageID.scala b/compiler/src/dotty/tools/dotc/reporting/ErrorMessageID.scala index 37a675f6e35b..6f9de0027ea3 100644 --- a/compiler/src/dotty/tools/dotc/reporting/ErrorMessageID.scala +++ b/compiler/src/dotty/tools/dotc/reporting/ErrorMessageID.scala @@ -167,7 +167,8 @@ enum ErrorMessageID extends java.lang.Enum[ErrorMessageID] { ModifierNotAllowedForDefinitionID, CannotExtendJavaEnumID, InvalidReferenceInImplicitNotFoundAnnotationID, - TraitMayNotDefineNativeMethodID + TraitMayNotDefineNativeMethodID, + EnumGettersRedefinitionID def errorNumber = ordinal - 2 } diff --git a/compiler/src/dotty/tools/dotc/reporting/messages.scala b/compiler/src/dotty/tools/dotc/reporting/messages.scala index f4c46317ab98..30d916fd0864 100644 --- a/compiler/src/dotty/tools/dotc/reporting/messages.scala +++ b/compiler/src/dotty/tools/dotc/reporting/messages.scala @@ -2031,6 +2031,10 @@ import ast.tpd def explain = "" } + class EnumGettersRedefinition(decl: Symbol)(using Context) extends NamingMsg(EnumGettersRedefinitionID): + def msg = em"redefinition of $decl: ${decl.info} in an ${hl("enum")}" + def explain = em"users may not may not supply their own definition for $decl when inside an ${hl("enum")}" + class DoubleDefinition(decl: Symbol, previousDecl: Symbol, base: Symbol)(using Context) extends NamingMsg(DoubleDefinitionID) { def msg = { def nameAnd = if (decl.name != previousDecl.name) " name and" else "" diff --git a/compiler/src/dotty/tools/dotc/typer/Checking.scala b/compiler/src/dotty/tools/dotc/typer/Checking.scala index ad0d7406369c..df58864d7e41 100644 --- a/compiler/src/dotty/tools/dotc/typer/Checking.scala +++ b/compiler/src/dotty/tools/dotc/typer/Checking.scala @@ -889,7 +889,12 @@ trait Checking { if (decl.matches(other) && !javaFieldMethodPair) { def doubleDefError(decl: Symbol, other: Symbol): Unit = if (!decl.info.isErroneous && !other.info.isErroneous) - report.error(DoubleDefinition(decl, other, cls), decl.srcPos) + if decl.owner.is(Enum, butNot=JavaDefined|Case) && decl.span.isSynthetic && ( + decl.name == nme.ordinal || decl.name == nme.enumLabel) + then + report.error(EnumGettersRedefinition(decl), other.srcPos) + else + report.error(DoubleDefinition(decl, other, cls), decl.srcPos) if (decl is Synthetic) doubleDefError(other, decl) else doubleDefError(decl, other) } diff --git a/tests/neg/enumsLabel-overrides.scala b/tests/neg/enumsLabel-overrides.scala new file mode 100644 index 000000000000..4b7036b4785f --- /dev/null +++ b/tests/neg/enumsLabel-overrides.scala @@ -0,0 +1,15 @@ +trait Mixin { def enumLabel: String = "mixin" } + +enum Mixed extends Mixin { + case B // error: overriding method enumLabel in trait Mixin of type => String; +} + +enum MixedAlso { + case C extends MixedAlso with Mixin // error: overriding method enumLabel in trait Mixin of type => String; +} + +trait HasEnumLabel { def enumLabel: String } + +enum MyEnum extends HasEnumLabel { + case D // ok +} diff --git a/tests/neg/enumsLabel-singleimpl.scala b/tests/neg/enumsLabel-singleimpl.scala new file mode 100644 index 000000000000..654661a04fcf --- /dev/null +++ b/tests/neg/enumsLabel-singleimpl.scala @@ -0,0 +1,13 @@ +enum Labelled { + + case A + + def enumLabel: String = "nolabel" // error: redefinition of method enumLabel: => String in an enum +} + +enum Ordinalled { + + case A + + def ordinal: Int = -1 // error: redefinition of method ordinal: => Int in an enum +} diff --git a/tests/neg/enumsLabelDef.scala b/tests/neg/enumsLabelDef.scala deleted file mode 100644 index e7bc10108bb6..000000000000 --- a/tests/neg/enumsLabelDef.scala +++ /dev/null @@ -1,22 +0,0 @@ -enum Labelled { - - case A // error overriding method enumLabel in class Labelled of type => String; - - def enumLabel: String = "nolabel" -} - -trait Mixin { def enumLabel: String = "mixin" } - -enum Mixed extends Mixin { - case B // error overriding method enumLabel in trait Mixin of type => String; -} - -enum MixedAlso { - case C extends MixedAlso with Mixin // error overriding method enumLabel in trait Mixin of type => String; -} - -trait HasEnumLabel { def enumLabel: String } - -enum MyEnum extends HasEnumLabel { - case D // ok -} From 235158701ca3a8cc1db0633a10068f561680c5dc Mon Sep 17 00:00:00 2001 From: bishabosha Date: Fri, 25 Sep 2020 17:36:51 +0200 Subject: [PATCH 04/14] add enumLabel to Mirror.Sum --- .../dotc/transform/SyntheticMembers.scala | 30 ++++++++ library/src-bootstrapped/scala/deriving.scala | 76 +++++++++++++++++++ .../scala/deriving.scala | 0 tests/run/enum-mirror-sumOf.scala | 27 +++++++ 4 files changed, 133 insertions(+) create mode 100644 library/src-bootstrapped/scala/deriving.scala rename library/{src => src-non-bootstrapped}/scala/deriving.scala (100%) create mode 100644 tests/run/enum-mirror-sumOf.scala diff --git a/compiler/src/dotty/tools/dotc/transform/SyntheticMembers.scala b/compiler/src/dotty/tools/dotc/transform/SyntheticMembers.scala index 6cab230d5abf..179bdce7b959 100644 --- a/compiler/src/dotty/tools/dotc/transform/SyntheticMembers.scala +++ b/compiler/src/dotty/tools/dotc/transform/SyntheticMembers.scala @@ -513,6 +513,34 @@ class SyntheticMembers(thisPhase: DenotTransformer) { Match(param, cases) } + /** For an enum T: + * + * def enumLabel(x: MirroredMonoType) = x.enumLabel + * + * For sealed trait with children of normalized types C_1, ..., C_n: + * + * def enumLabel(x: MirroredMonoType) = x match { + * case _: C_1 => "C_1" + * ... + * case _: C_n => "C_n" + * } + * + * Here, the normalized type of a class C is C[?, ...., ?] with + * a wildcard for each type parameter. The normalized type of an object + * O is O.type. + */ + def enumLabelBody(cls: Symbol, param: Tree)(using Context): Tree = + if (cls.is(Enum)) param.select(nme.enumLabel).ensureApplied + else { + val cases = + for ((child, idx) <- cls.children.zipWithIndex) yield { + val patType = if (child.isTerm) child.termRef else child.rawTypeRef + val pat = Typed(untpd.Ident(nme.WILDCARD).withType(patType), TypeTree(patType)) + CaseDef(pat, EmptyTree, Literal(Constant(child.name.toString))) + } + Match(param, cases) + } + /** - If `impl` is the companion of a generic sum, add `deriving.Mirror.Sum` parent * and `MirroredMonoType` and `ordinal` members. * - If `impl` is the companion of a generic product, add `deriving.Mirror.Product` parent @@ -564,6 +592,8 @@ class SyntheticMembers(thisPhase: DenotTransformer) { addParent(defn.Mirror_SumClass.typeRef) addMethod(nme.ordinal, MethodType(monoType.typeRef :: Nil, defn.IntType), cls, ordinalBody(_, _)) + addMethod(nme.enumLabel, MethodType(monoType.typeRef :: Nil, defn.StringType), cls, + enumLabelBody(_, _)) } if (clazz.is(Module)) { diff --git a/library/src-bootstrapped/scala/deriving.scala b/library/src-bootstrapped/scala/deriving.scala new file mode 100644 index 000000000000..48189187458b --- /dev/null +++ b/library/src-bootstrapped/scala/deriving.scala @@ -0,0 +1,76 @@ +package scala + +import quoted._ + +object deriving { + + /** Mirrors allows typelevel access to enums, case classes and objects, and their sealed parents. + */ + sealed trait Mirror { + + /** The mirrored *-type */ + type MirroredMonoType + + /** The name of the type */ + type MirroredLabel <: String + + /** The names of the product elements */ + type MirroredElemLabels <: Tuple + } + + object Mirror { + + /** The Mirror for a sum type */ + trait Sum extends Mirror { self => + /** The ordinal number of the case class of `x`. For enums, `ordinal(x) == x.ordinal` */ + def ordinal(x: MirroredMonoType): Int + /** The case label of the case class of `x`. For enums, `enumLabel(x) == x.enumLabel` */ + def enumLabel(x: MirroredMonoType): String + } + + /** The Mirror for a product type */ + trait Product extends Mirror { + + /** Create a new instance of type `T` with elements taken from product `p`. */ + def fromProduct(p: scala.Product): MirroredMonoType + } + + trait Singleton extends Product { + type MirroredMonoType = this.type + type MirroredType = this.type + type MirroredElemTypes = EmptyTuple + type MirroredElemLabels = EmptyTuple + def fromProduct(p: scala.Product) = this + } + + /** A proxy for Scala 2 singletons, which do not inherit `Singleton` directly */ + class SingletonProxy(val value: AnyRef) extends Product { + type MirroredMonoType = value.type + type MirroredType = value.type + type MirroredElemTypes = EmptyTuple + type MirroredElemLabels = EmptyTuple + def fromProduct(p: scala.Product) = value + } + + type Of[T] = Mirror { type MirroredType = T; type MirroredMonoType = T ; type MirroredElemTypes <: Tuple } + type ProductOf[T] = Mirror.Product { type MirroredType = T; type MirroredMonoType = T ; type MirroredElemTypes <: Tuple } + type SumOf[T] = Mirror.Sum { type MirroredType = T; type MirroredMonoType = T; type MirroredElemTypes <: Tuple } + } + + /** Helper class to turn arrays into products */ + class ArrayProduct(val elems: Array[AnyRef]) extends Product { + def this(size: Int) = this(new Array[AnyRef](size)) + def canEqual(that: Any): Boolean = true + def productElement(n: Int) = elems(n) + def productArity = elems.length + override def productIterator: Iterator[Any] = elems.iterator + def update(n: Int, x: Any) = elems(n) = x.asInstanceOf[AnyRef] + } + + /** The empty product */ + object EmptyProduct extends ArrayProduct(Array.emptyObjectArray) + + /** Helper method to select a product element */ + def productElement[T](x: Any, idx: Int) = + x.asInstanceOf[Product].productElement(idx).asInstanceOf[T] +} diff --git a/library/src/scala/deriving.scala b/library/src-non-bootstrapped/scala/deriving.scala similarity index 100% rename from library/src/scala/deriving.scala rename to library/src-non-bootstrapped/scala/deriving.scala diff --git a/tests/run/enum-mirror-sumOf.scala b/tests/run/enum-mirror-sumOf.scala new file mode 100644 index 000000000000..ee00e89f940d --- /dev/null +++ b/tests/run/enum-mirror-sumOf.scala @@ -0,0 +1,27 @@ +import Nat._, Lst._ + +def enumLabelOf[E](e: E)(using s: deriving.Mirror.SumOf[E]): String = s.enumLabel(e) +def ordinalOf[E](e: E)(using s: deriving.Mirror.SumOf[E]): Int = s.ordinal(e) + +enum Nat: + case S(pred: Nat) + case Z + +sealed abstract class Lst[+T] +object Lst: + final case class Cdr[+T](t: T, ts: Lst[T]) extends Lst[T] + case object NIL extends Lst[Nothing] + +@main def Test = + + // labels + assert(enumLabelOf(S(Z)) == "S") + assert(enumLabelOf(Z) == "Z") + assert(enumLabelOf(Cdr(1, NIL): Lst[Int]) == "Cdr") + assert(enumLabelOf(NIL: Lst[Int]) == "NIL") + + // ordinals + assert(ordinalOf(S(Z)) == 0) + assert(ordinalOf(Z) == 1) + assert(ordinalOf(Cdr(1, NIL): Lst[Int]) == 0) + assert(ordinalOf(NIL: Lst[Int]) == 1) From 489a1afc4332de4ff7d8d73fc503e5aa81d0bb02 Mon Sep 17 00:00:00 2001 From: bishabosha Date: Mon, 28 Sep 2020 17:33:20 +0200 Subject: [PATCH 05/14] gen ordinal and enumLabel more lazily --- compiler/src/dotty/tools/dotc/ast/DesugarEnums.scala | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/ast/DesugarEnums.scala b/compiler/src/dotty/tools/dotc/ast/DesugarEnums.scala index 42f23cb1da3d..d25bfa7c95b6 100644 --- a/compiler/src/dotty/tools/dotc/ast/DesugarEnums.scala +++ b/compiler/src/dotty/tools/dotc/ast/DesugarEnums.scala @@ -323,11 +323,10 @@ object DesugarEnums { } def enumBaseMeths(using Context): List[Tree] = - val ordinalDef = DefDef(nme.ordinal, Nil, Nil, ref(defn.IntType), EmptyTree) - val enumLabelDef = DefDef(nme.enumLabel, Nil, Nil, ref(defn.StringClass.typeRef), EmptyTree) - val base = enumLabelDef :: Nil - if isJavaEnum then base - else ordinalDef :: base + if isJavaEnum then + enumLabelMeth(EmptyTree) :: Nil + else + ordinalMeth(EmptyTree) :: enumLabelMeth(EmptyTree) :: Nil 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) From 5a75bab075bdb7ef78a205ab5ee323a6935db078 Mon Sep 17 00:00:00 2001 From: bishabosha Date: Mon, 28 Sep 2020 17:42:35 +0200 Subject: [PATCH 06/14] adjust message for error --- compiler/src/dotty/tools/dotc/reporting/messages.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/reporting/messages.scala b/compiler/src/dotty/tools/dotc/reporting/messages.scala index 30d916fd0864..09bc55810b48 100644 --- a/compiler/src/dotty/tools/dotc/reporting/messages.scala +++ b/compiler/src/dotty/tools/dotc/reporting/messages.scala @@ -2033,7 +2033,7 @@ import ast.tpd class EnumGettersRedefinition(decl: Symbol)(using Context) extends NamingMsg(EnumGettersRedefinitionID): def msg = em"redefinition of $decl: ${decl.info} in an ${hl("enum")}" - def explain = em"users may not may not supply their own definition for $decl when inside an ${hl("enum")}" + def explain = em"users may not supply their own definition for $decl when inside an ${hl("enum")}" class DoubleDefinition(decl: Symbol, previousDecl: Symbol, base: Symbol)(using Context) extends NamingMsg(DoubleDefinitionID) { def msg = { From 5a4666759cccb11b35e6c6fcc4c5124b9f2c115d Mon Sep 17 00:00:00 2001 From: Jamie Thompson Date: Wed, 30 Sep 2020 16:54:05 +0200 Subject: [PATCH 07/14] only ordinal method is optional --- compiler/src/dotty/tools/dotc/ast/Desugar.scala | 7 ++++--- compiler/src/dotty/tools/dotc/ast/DesugarEnums.scala | 8 +++----- compiler/src/dotty/tools/dotc/typer/Namer.scala | 5 ++++- 3 files changed, 11 insertions(+), 9 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/ast/Desugar.scala b/compiler/src/dotty/tools/dotc/ast/Desugar.scala index 5853f6a764e6..af24aa54b663 100644 --- a/compiler/src/dotty/tools/dotc/ast/Desugar.scala +++ b/compiler/src/dotty/tools/dotc/ast/Desugar.scala @@ -484,8 +484,9 @@ object desugar { val enumCompanionRef = TermRefTree() val enumImport = Import(enumCompanionRef, enumCases.flatMap(caseIds).map(ImportSelector(_))) - val enumSpecMethods = EnumGetters() - (enumImport :: enumSpecMethods :: enumStats, enumCases, enumCompanionRef) + val enumLabelDef = DesugarEnums.enumLabelMeth(EmptyTree) + val enumGetters = EnumGetters() // optionally generate ordinal method + (enumImport :: enumGetters :: enumLabelDef :: enumStats, enumCases, enumCompanionRef) } else (stats, Nil, EmptyTree) } @@ -891,7 +892,7 @@ object desugar { } def enumGetters(getters: EnumGetters)(using Context): Tree = - flatTree(DesugarEnums.enumBaseMeths).withSpan(getters.span) + flatTree(DesugarEnums.optionalOrdinalMethod).withSpan(getters.span) /** Transform extension construct to list of extension methods */ def extMethods(ext: ExtMethods)(using Context): Tree = flatTree { diff --git a/compiler/src/dotty/tools/dotc/ast/DesugarEnums.scala b/compiler/src/dotty/tools/dotc/ast/DesugarEnums.scala index d25bfa7c95b6..f75b7a4df217 100644 --- a/compiler/src/dotty/tools/dotc/ast/DesugarEnums.scala +++ b/compiler/src/dotty/tools/dotc/ast/DesugarEnums.scala @@ -322,11 +322,9 @@ object DesugarEnums { (ordinal, Nil) } - def enumBaseMeths(using Context): List[Tree] = - if isJavaEnum then - enumLabelMeth(EmptyTree) :: Nil - else - ordinalMeth(EmptyTree) :: enumLabelMeth(EmptyTree) :: Nil + def optionalOrdinalMethod(using Context): List[Tree] = + if isJavaEnum then Nil + else ordinalMeth(EmptyTree) :: Nil 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) diff --git a/compiler/src/dotty/tools/dotc/typer/Namer.scala b/compiler/src/dotty/tools/dotc/typer/Namer.scala index ab0360f6b707..9644171dabec 100644 --- a/compiler/src/dotty/tools/dotc/typer/Namer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Namer.scala @@ -935,8 +935,11 @@ class Namer { typer: Typer => } private val (restAfterParents, rest): (List[Tree], List[Tree]) = if original.mods.isEnumClass then + // in Desugar.scala, desugaring an enum class definition fixes the + // first and second statements in the body to be `imports` and `getters`. + // `getters` needs to be indexed after parents are resolved because it checks for a java.lang.Enum parent val (imports :: getters :: Nil, stats): @unchecked = restOfBody.splitAt(2) - (getters :: Nil, imports :: stats) // enum getters desugaring needs to test if a parent is java.lang.Enum + (getters :: Nil, imports :: stats) else (Nil, restOfBody) From faa69acaaa4b74135d8b47139fd9a2b66de1bafb Mon Sep 17 00:00:00 2001 From: Jamie Thompson Date: Wed, 30 Sep 2020 17:04:05 +0200 Subject: [PATCH 08/14] remove special case error --- .../src/dotty/tools/dotc/reporting/ErrorMessageID.scala | 3 +-- compiler/src/dotty/tools/dotc/reporting/messages.scala | 4 ---- compiler/src/dotty/tools/dotc/typer/Checking.scala | 7 +------ tests/neg/enumsLabel-singleimpl.scala | 8 +++++--- 4 files changed, 7 insertions(+), 15 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/reporting/ErrorMessageID.scala b/compiler/src/dotty/tools/dotc/reporting/ErrorMessageID.scala index 6f9de0027ea3..37a675f6e35b 100644 --- a/compiler/src/dotty/tools/dotc/reporting/ErrorMessageID.scala +++ b/compiler/src/dotty/tools/dotc/reporting/ErrorMessageID.scala @@ -167,8 +167,7 @@ enum ErrorMessageID extends java.lang.Enum[ErrorMessageID] { ModifierNotAllowedForDefinitionID, CannotExtendJavaEnumID, InvalidReferenceInImplicitNotFoundAnnotationID, - TraitMayNotDefineNativeMethodID, - EnumGettersRedefinitionID + TraitMayNotDefineNativeMethodID def errorNumber = ordinal - 2 } diff --git a/compiler/src/dotty/tools/dotc/reporting/messages.scala b/compiler/src/dotty/tools/dotc/reporting/messages.scala index 09bc55810b48..f4c46317ab98 100644 --- a/compiler/src/dotty/tools/dotc/reporting/messages.scala +++ b/compiler/src/dotty/tools/dotc/reporting/messages.scala @@ -2031,10 +2031,6 @@ import ast.tpd def explain = "" } - class EnumGettersRedefinition(decl: Symbol)(using Context) extends NamingMsg(EnumGettersRedefinitionID): - def msg = em"redefinition of $decl: ${decl.info} in an ${hl("enum")}" - def explain = em"users may not supply their own definition for $decl when inside an ${hl("enum")}" - class DoubleDefinition(decl: Symbol, previousDecl: Symbol, base: Symbol)(using Context) extends NamingMsg(DoubleDefinitionID) { def msg = { def nameAnd = if (decl.name != previousDecl.name) " name and" else "" diff --git a/compiler/src/dotty/tools/dotc/typer/Checking.scala b/compiler/src/dotty/tools/dotc/typer/Checking.scala index df58864d7e41..ad0d7406369c 100644 --- a/compiler/src/dotty/tools/dotc/typer/Checking.scala +++ b/compiler/src/dotty/tools/dotc/typer/Checking.scala @@ -889,12 +889,7 @@ trait Checking { if (decl.matches(other) && !javaFieldMethodPair) { def doubleDefError(decl: Symbol, other: Symbol): Unit = if (!decl.info.isErroneous && !other.info.isErroneous) - if decl.owner.is(Enum, butNot=JavaDefined|Case) && decl.span.isSynthetic && ( - decl.name == nme.ordinal || decl.name == nme.enumLabel) - then - report.error(EnumGettersRedefinition(decl), other.srcPos) - else - report.error(DoubleDefinition(decl, other, cls), decl.srcPos) + report.error(DoubleDefinition(decl, other, cls), decl.srcPos) if (decl is Synthetic) doubleDefError(other, decl) else doubleDefError(decl, other) } diff --git a/tests/neg/enumsLabel-singleimpl.scala b/tests/neg/enumsLabel-singleimpl.scala index 654661a04fcf..4a975c72c714 100644 --- a/tests/neg/enumsLabel-singleimpl.scala +++ b/tests/neg/enumsLabel-singleimpl.scala @@ -2,12 +2,14 @@ enum Labelled { case A - def enumLabel: String = "nolabel" // error: redefinition of method enumLabel: => String in an enum + def enumLabel: String = "nolabel" // error: double definition of method enumLabel: => String + } enum Ordinalled { - case A + case A // error: double definition of method ordinal: => Int + + def ordinal: Int = -1 - def ordinal: Int = -1 // error: redefinition of method ordinal: => Int in an enum } From 96a8af9d75c534cd8186ab6cc6c29f90f81e6f30 Mon Sep 17 00:00:00 2001 From: Jamie Thompson Date: Wed, 30 Sep 2020 17:09:11 +0200 Subject: [PATCH 09/14] revert add enumLabel to Mirror --- .../dotc/transform/SyntheticMembers.scala | 30 -------- library/src-bootstrapped/scala/deriving.scala | 76 ------------------- .../scala/deriving.scala | 0 tests/run/enum-mirror-sumOf.scala | 27 ------- 4 files changed, 133 deletions(-) delete mode 100644 library/src-bootstrapped/scala/deriving.scala rename library/{src-non-bootstrapped => src}/scala/deriving.scala (100%) delete mode 100644 tests/run/enum-mirror-sumOf.scala diff --git a/compiler/src/dotty/tools/dotc/transform/SyntheticMembers.scala b/compiler/src/dotty/tools/dotc/transform/SyntheticMembers.scala index 179bdce7b959..6cab230d5abf 100644 --- a/compiler/src/dotty/tools/dotc/transform/SyntheticMembers.scala +++ b/compiler/src/dotty/tools/dotc/transform/SyntheticMembers.scala @@ -513,34 +513,6 @@ class SyntheticMembers(thisPhase: DenotTransformer) { Match(param, cases) } - /** For an enum T: - * - * def enumLabel(x: MirroredMonoType) = x.enumLabel - * - * For sealed trait with children of normalized types C_1, ..., C_n: - * - * def enumLabel(x: MirroredMonoType) = x match { - * case _: C_1 => "C_1" - * ... - * case _: C_n => "C_n" - * } - * - * Here, the normalized type of a class C is C[?, ...., ?] with - * a wildcard for each type parameter. The normalized type of an object - * O is O.type. - */ - def enumLabelBody(cls: Symbol, param: Tree)(using Context): Tree = - if (cls.is(Enum)) param.select(nme.enumLabel).ensureApplied - else { - val cases = - for ((child, idx) <- cls.children.zipWithIndex) yield { - val patType = if (child.isTerm) child.termRef else child.rawTypeRef - val pat = Typed(untpd.Ident(nme.WILDCARD).withType(patType), TypeTree(patType)) - CaseDef(pat, EmptyTree, Literal(Constant(child.name.toString))) - } - Match(param, cases) - } - /** - If `impl` is the companion of a generic sum, add `deriving.Mirror.Sum` parent * and `MirroredMonoType` and `ordinal` members. * - If `impl` is the companion of a generic product, add `deriving.Mirror.Product` parent @@ -592,8 +564,6 @@ class SyntheticMembers(thisPhase: DenotTransformer) { addParent(defn.Mirror_SumClass.typeRef) addMethod(nme.ordinal, MethodType(monoType.typeRef :: Nil, defn.IntType), cls, ordinalBody(_, _)) - addMethod(nme.enumLabel, MethodType(monoType.typeRef :: Nil, defn.StringType), cls, - enumLabelBody(_, _)) } if (clazz.is(Module)) { diff --git a/library/src-bootstrapped/scala/deriving.scala b/library/src-bootstrapped/scala/deriving.scala deleted file mode 100644 index 48189187458b..000000000000 --- a/library/src-bootstrapped/scala/deriving.scala +++ /dev/null @@ -1,76 +0,0 @@ -package scala - -import quoted._ - -object deriving { - - /** Mirrors allows typelevel access to enums, case classes and objects, and their sealed parents. - */ - sealed trait Mirror { - - /** The mirrored *-type */ - type MirroredMonoType - - /** The name of the type */ - type MirroredLabel <: String - - /** The names of the product elements */ - type MirroredElemLabels <: Tuple - } - - object Mirror { - - /** The Mirror for a sum type */ - trait Sum extends Mirror { self => - /** The ordinal number of the case class of `x`. For enums, `ordinal(x) == x.ordinal` */ - def ordinal(x: MirroredMonoType): Int - /** The case label of the case class of `x`. For enums, `enumLabel(x) == x.enumLabel` */ - def enumLabel(x: MirroredMonoType): String - } - - /** The Mirror for a product type */ - trait Product extends Mirror { - - /** Create a new instance of type `T` with elements taken from product `p`. */ - def fromProduct(p: scala.Product): MirroredMonoType - } - - trait Singleton extends Product { - type MirroredMonoType = this.type - type MirroredType = this.type - type MirroredElemTypes = EmptyTuple - type MirroredElemLabels = EmptyTuple - def fromProduct(p: scala.Product) = this - } - - /** A proxy for Scala 2 singletons, which do not inherit `Singleton` directly */ - class SingletonProxy(val value: AnyRef) extends Product { - type MirroredMonoType = value.type - type MirroredType = value.type - type MirroredElemTypes = EmptyTuple - type MirroredElemLabels = EmptyTuple - def fromProduct(p: scala.Product) = value - } - - type Of[T] = Mirror { type MirroredType = T; type MirroredMonoType = T ; type MirroredElemTypes <: Tuple } - type ProductOf[T] = Mirror.Product { type MirroredType = T; type MirroredMonoType = T ; type MirroredElemTypes <: Tuple } - type SumOf[T] = Mirror.Sum { type MirroredType = T; type MirroredMonoType = T; type MirroredElemTypes <: Tuple } - } - - /** Helper class to turn arrays into products */ - class ArrayProduct(val elems: Array[AnyRef]) extends Product { - def this(size: Int) = this(new Array[AnyRef](size)) - def canEqual(that: Any): Boolean = true - def productElement(n: Int) = elems(n) - def productArity = elems.length - override def productIterator: Iterator[Any] = elems.iterator - def update(n: Int, x: Any) = elems(n) = x.asInstanceOf[AnyRef] - } - - /** The empty product */ - object EmptyProduct extends ArrayProduct(Array.emptyObjectArray) - - /** Helper method to select a product element */ - def productElement[T](x: Any, idx: Int) = - x.asInstanceOf[Product].productElement(idx).asInstanceOf[T] -} diff --git a/library/src-non-bootstrapped/scala/deriving.scala b/library/src/scala/deriving.scala similarity index 100% rename from library/src-non-bootstrapped/scala/deriving.scala rename to library/src/scala/deriving.scala diff --git a/tests/run/enum-mirror-sumOf.scala b/tests/run/enum-mirror-sumOf.scala deleted file mode 100644 index ee00e89f940d..000000000000 --- a/tests/run/enum-mirror-sumOf.scala +++ /dev/null @@ -1,27 +0,0 @@ -import Nat._, Lst._ - -def enumLabelOf[E](e: E)(using s: deriving.Mirror.SumOf[E]): String = s.enumLabel(e) -def ordinalOf[E](e: E)(using s: deriving.Mirror.SumOf[E]): Int = s.ordinal(e) - -enum Nat: - case S(pred: Nat) - case Z - -sealed abstract class Lst[+T] -object Lst: - final case class Cdr[+T](t: T, ts: Lst[T]) extends Lst[T] - case object NIL extends Lst[Nothing] - -@main def Test = - - // labels - assert(enumLabelOf(S(Z)) == "S") - assert(enumLabelOf(Z) == "Z") - assert(enumLabelOf(Cdr(1, NIL): Lst[Int]) == "Cdr") - assert(enumLabelOf(NIL: Lst[Int]) == "NIL") - - // ordinals - assert(ordinalOf(S(Z)) == 0) - assert(ordinalOf(Z) == 1) - assert(ordinalOf(Cdr(1, NIL): Lst[Int]) == 0) - assert(ordinalOf(NIL: Lst[Int]) == 1) From 4d04804d42d0632e9fe879a6b540a852de39ceea Mon Sep 17 00:00:00 2001 From: Jamie Thompson Date: Wed, 30 Sep 2020 17:18:49 +0200 Subject: [PATCH 10/14] use classSymbol --- .../src/dotty/tools/dotc/transform/SyntheticMembers.scala | 2 +- compiler/src/dotty/tools/dotc/typer/Typer.scala | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/SyntheticMembers.scala b/compiler/src/dotty/tools/dotc/transform/SyntheticMembers.scala index 6cab230d5abf..b309906828e1 100644 --- a/compiler/src/dotty/tools/dotc/transform/SyntheticMembers.scala +++ b/compiler/src/dotty/tools/dotc/transform/SyntheticMembers.scala @@ -94,7 +94,7 @@ 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.classParents.exists(_.typeSymbol.isScalaEnum) + val isEnumCase = clazz.classParents.exists(_.classSymbol.isScalaEnum) val isEnumValue = isEnumCase && clazz.isAnonymousClass && clazz.classParents.head.classSymbol.is(Enum) val isNonJavaEnumValue = isEnumValue && !clazz.derivesFrom(defn.JavaEnumClass) diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 4c01e3f12bf3..2217fb790e6b 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -2105,7 +2105,8 @@ class Typer extends Namer val constr1 = typed(constr).asInstanceOf[DefDef] val parentsWithClass = ensureFirstTreeIsClass(parents.mapconserve(typedParent).filterConserve(!_.isEmpty), cdef.nameSpan) val parents1 = ensureConstrCall(cls, parentsWithClass)(using superCtx) - val firstParent = parents1.head.tpe.dealias.typeSymbol + val firstParentTpe = parents1.head.tpe.dealias + val firstParent = firstParentTpe.typeSymbol checkEnumParent(cls, firstParent) @@ -2122,7 +2123,7 @@ class Typer extends Namer .withType(dummy.termRef) if (!cls.isOneOf(AbstractOrTrait) && !ctx.isAfterTyper) checkRealizableBounds(cls, cdef.sourcePos.withSpan(cdef.nameSpan)) - if cls.isScalaEnum || firstParent.isScalaEnum then + if cls.isScalaEnum || firstParentTpe.classSymbol.isScalaEnum then checkEnum(cdef, cls, firstParent) val cdef1 = assignType(cpy.TypeDef(cdef)(name, impl1), cls) From 7adfa3f8201293364d405d6e5211d8643249784f Mon Sep 17 00:00:00 2001 From: Jamie Thompson Date: Wed, 30 Sep 2020 18:04:58 +0200 Subject: [PATCH 11/14] adjust comment --- compiler/src/dotty/tools/dotc/typer/Namer.scala | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Namer.scala b/compiler/src/dotty/tools/dotc/typer/Namer.scala index 9644171dabec..5e9397a2018f 100644 --- a/compiler/src/dotty/tools/dotc/typer/Namer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Namer.scala @@ -937,7 +937,8 @@ class Namer { typer: Typer => if original.mods.isEnumClass then // in Desugar.scala, desugaring an enum class definition fixes the // first and second statements in the body to be `imports` and `getters`. - // `getters` needs to be indexed after parents are resolved because it checks for a java.lang.Enum parent + // `imports` is an import list of the enum cases from the companion of `cls` + // `getters` will expand to `def ordinal: Int` if the parents of `cls` are not java.lang.Enum val (imports :: getters :: Nil, stats): @unchecked = restOfBody.splitAt(2) (getters :: Nil, imports :: stats) else From 4e3d6e4e97c588c6a61195c9a848b78aaa7032b7 Mon Sep 17 00:00:00 2001 From: bishabosha Date: Thu, 1 Oct 2020 13:21:28 +0200 Subject: [PATCH 12/14] Remove untpd.EnumGetters no longer special case some definitions for indexing after the parents of a class are discovered. Extend invalidateIfClashingSynthetic to cover ordinal override from java.lang.Enum --- .../src/dotty/tools/dotc/ast/Desugar.scala | 10 ++-- .../dotty/tools/dotc/ast/DesugarEnums.scala | 4 -- compiler/src/dotty/tools/dotc/ast/untpd.scala | 4 -- .../dotty/tools/dotc/transform/SymUtils.scala | 3 +- .../dotc/transform/SyntheticMembers.scala | 4 +- .../src/dotty/tools/dotc/typer/Namer.scala | 55 +++++++++---------- .../src/dotty/tools/dotc/typer/Typer.scala | 10 +--- tests/neg/enumsLabel-singleimpl.scala | 4 +- 8 files changed, 41 insertions(+), 53 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/ast/Desugar.scala b/compiler/src/dotty/tools/dotc/ast/Desugar.scala index af24aa54b663..989b7b787b2a 100644 --- a/compiler/src/dotty/tools/dotc/ast/Desugar.scala +++ b/compiler/src/dotty/tools/dotc/ast/Desugar.scala @@ -58,6 +58,9 @@ object desugar { case _ => false } + def isRetractableCaseClassOrEnumMethodName(name: Name)(using Context): Boolean = + isRetractableCaseClassMethodName(name) || name == nme.ordinal + /** Is `name` the name of a method that is added unconditionally to case classes? */ def isDesugaredCaseClassMethodName(name: Name)(using Context): Boolean = isRetractableCaseClassMethodName(name) || name.isSelectorName @@ -485,8 +488,8 @@ object desugar { val enumImport = Import(enumCompanionRef, enumCases.flatMap(caseIds).map(ImportSelector(_))) val enumLabelDef = DesugarEnums.enumLabelMeth(EmptyTree) - val enumGetters = EnumGetters() // optionally generate ordinal method - (enumImport :: enumGetters :: enumLabelDef :: enumStats, enumCases, enumCompanionRef) + val ordinalDef = DesugarEnums.ordinalMeth(EmptyTree).withAddedFlags(Synthetic) + (enumImport :: ordinalDef :: enumLabelDef :: enumStats, enumCases, enumCompanionRef) } else (stats, Nil, EmptyTree) } @@ -891,9 +894,6 @@ object desugar { } } - def enumGetters(getters: EnumGetters)(using Context): Tree = - flatTree(DesugarEnums.optionalOrdinalMethod).withSpan(getters.span) - /** Transform extension construct to list of extension methods */ def extMethods(ext: ExtMethods)(using Context): Tree = flatTree { for mdef <- ext.methods yield diff --git a/compiler/src/dotty/tools/dotc/ast/DesugarEnums.scala b/compiler/src/dotty/tools/dotc/ast/DesugarEnums.scala index f75b7a4df217..4ed857678b26 100644 --- a/compiler/src/dotty/tools/dotc/ast/DesugarEnums.scala +++ b/compiler/src/dotty/tools/dotc/ast/DesugarEnums.scala @@ -322,10 +322,6 @@ object DesugarEnums { (ordinal, Nil) } - def optionalOrdinalMethod(using Context): List[Tree] = - if isJavaEnum then Nil - else ordinalMeth(EmptyTree) :: Nil - 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) diff --git a/compiler/src/dotty/tools/dotc/ast/untpd.scala b/compiler/src/dotty/tools/dotc/ast/untpd.scala index 6417d949e41d..5e87ef6aeeed 100644 --- a/compiler/src/dotty/tools/dotc/ast/untpd.scala +++ b/compiler/src/dotty/tools/dotc/ast/untpd.scala @@ -116,7 +116,6 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo { case class Export(expr: Tree, selectors: List[ImportSelector])(implicit @constructorOnly src: SourceFile) extends Tree case class ExtMethods(tparams: List[TypeDef], vparamss: List[List[ValDef]], methods: List[DefDef])(implicit @constructorOnly src: SourceFile) extends Tree case class MacroTree(expr: Tree)(implicit @constructorOnly src: SourceFile) extends Tree - case class EnumGetters()(implicit @constructorOnly src: SourceFile) extends Tree case class ImportSelector(imported: Ident, renamed: Tree = EmptyTree, bound: Tree = EmptyTree)(implicit @constructorOnly src: SourceFile) extends Tree { // TODO: Make bound a typed tree? @@ -701,7 +700,6 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo { cpy.Export(tree)(transform(expr), selectors) case ExtMethods(tparams, vparamss, methods) => cpy.ExtMethods(tree)(transformSub(tparams), vparamss.mapConserve(transformSub(_)), transformSub(methods)) - case enums: EnumGetters => enums case ImportSelector(imported, renamed, bound) => cpy.ImportSelector(tree)(transformSub(imported), transform(renamed), transform(bound)) case Number(_, _) | TypedSplice(_) => @@ -763,8 +761,6 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo { this(x, expr) case ExtMethods(tparams, vparamss, methods) => this(vparamss.foldLeft(this(x, tparams))(apply), methods) - case EnumGetters() => - x case ImportSelector(imported, renamed, bound) => this(this(this(x, imported), renamed), bound) case Number(_, _) => diff --git a/compiler/src/dotty/tools/dotc/transform/SymUtils.scala b/compiler/src/dotty/tools/dotc/transform/SymUtils.scala index 2a6ee1898597..5a577b0de28a 100644 --- a/compiler/src/dotty/tools/dotc/transform/SymUtils.scala +++ b/compiler/src/dotty/tools/dotc/transform/SymUtils.scala @@ -168,7 +168,8 @@ object SymUtils { self } - def isScalaEnum(using Context): Boolean = self.is(Enum, butNot=JavaDefined) + def isEnum(using Context): Boolean = self.is(Enum, butNot=JavaDefined) + def isEnumClass(using Context): Boolean = isEnum && !self.is(Case) /** Does this symbol refer to anonymous classes synthesized by enum desugaring? */ def isEnumAnonymClass(using Context): Boolean = diff --git a/compiler/src/dotty/tools/dotc/transform/SyntheticMembers.scala b/compiler/src/dotty/tools/dotc/transform/SyntheticMembers.scala index b309906828e1..4429dd431422 100644 --- a/compiler/src/dotty/tools/dotc/transform/SyntheticMembers.scala +++ b/compiler/src/dotty/tools/dotc/transform/SyntheticMembers.scala @@ -94,8 +94,8 @@ 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.classParents.exists(_.classSymbol.isScalaEnum) - val isEnumValue = isEnumCase && clazz.isAnonymousClass && clazz.classParents.head.classSymbol.is(Enum) + val isEnumCase = clazz.isEnum || clazz.classParents.exists(_.classSymbol.isEnum) + val isEnumValue = isEnumCase && clazz.isAnonymousClass && clazz.classParents.head.classSymbol.isEnum val isNonJavaEnumValue = isEnumValue && !clazz.derivesFrom(defn.JavaEnumClass) val symbolsToSynthesize: List[Symbol] = diff --git a/compiler/src/dotty/tools/dotc/typer/Namer.scala b/compiler/src/dotty/tools/dotc/typer/Namer.scala index 5e9397a2018f..a880d79fa7fe 100644 --- a/compiler/src/dotty/tools/dotc/typer/Namer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Namer.scala @@ -343,20 +343,17 @@ class Namer { typer: Typer => tree.pushAttachment(ExpandedTree, expanded) } tree match { - case tree: DefTree => record(desugar.defTree(tree)) - case tree: PackageDef => record(desugar.packageDef(tree)) - case tree: ExtMethods => record(desugar.extMethods(tree)) - case tree: EnumGetters => record(desugar.enumGetters(tree)) - case _ => + case tree: DefTree => record(desugar.defTree(tree)) + case tree: PackageDef => record(desugar.packageDef(tree)) + case tree: ExtMethods => record(desugar.extMethods(tree)) + case _ => } } /** The expanded version of this tree, or tree itself if not expanded */ def expanded(tree: Tree)(using Context): Tree = tree match { - case _: DefTree | _: PackageDef | _: ExtMethods | _: EnumGetters => - tree.attachmentOrElse(ExpandedTree, tree) - case _ => - tree + case _: DefTree | _: PackageDef | _: ExtMethods => tree.attachmentOrElse(ExpandedTree, tree) + case _ => tree } /** For all class definitions `stat` in `xstats`: If the companion class is @@ -746,8 +743,11 @@ class Namer { typer: Typer => } /** Invalidate `denot` by overwriting its info with `NoType` if - * `denot` is a compiler generated case class method that clashes - * with a user-defined method in the same scope with a matching type. + * one of the following holds: + * - `denot` is a compiler generated case class method that clashes + * with a user-defined method in the same scope with a matching type. + * - `denot` is a compiler generated `ordinal` method that would override + * `ordinal` declared in `java.lang.Enum` */ private def invalidateIfClashingSynthetic(denot: SymDenotation): Unit = { def isCaseClass(owner: Symbol) = @@ -755,12 +755,22 @@ class Namer { typer: Typer => if (owner.is(Module)) owner.linkedClass.is(CaseClass) else owner.is(CaseClass) } - val isClashingSynthetic = - denot.is(Synthetic) && - desugar.isRetractableCaseClassMethodName(denot.name) && - isCaseClass(denot.owner) && - denot.owner.info.decls.lookupAll(denot.name).exists(alt => + def isJavaEnumBaseClass(owner: Symbol) = + owner.isClass && owner.isEnumClass && owner.derivesFrom(defn.JavaEnumClass) + def firstParentCls(owner: Symbol) = + owner.asClass.classParents.head.classSymbol + def findMatch(owner: Symbol) = + owner.info.decls.lookupAll(denot.name).exists(alt => alt != denot.symbol && alt.info.matchesLoosely(denot.info)) + def clashingCaseClassMethod = + desugar.isRetractableCaseClassMethodName(denot.name) + && isCaseClass(denot.owner) + && findMatch(denot.owner) + def clashingEnumMethod = + denot.name == nme.ordinal + && isJavaEnumBaseClass(denot.owner) + && findMatch(firstParentCls(denot.owner)) + val isClashingSynthetic = denot.is(Synthetic) && (clashingCaseClassMethod || clashingEnumMethod) if (isClashingSynthetic) { typr.println(i"invalidating clashing $denot in ${denot.owner}") denot.markAbsent() @@ -928,21 +938,11 @@ class Namer { typer: Typer => val TypeDef(name, impl @ Template(constr, _, self, _)) = original - private val (params, restOfBody): (List[Tree], List[Tree]) = impl.body.span { + private val (params, rest): (List[Tree], List[Tree]) = impl.body.span { case td: TypeDef => td.mods.is(Param) case vd: ValDef => vd.mods.is(ParamAccessor) case _ => false } - private val (restAfterParents, rest): (List[Tree], List[Tree]) = - if original.mods.isEnumClass then - // in Desugar.scala, desugaring an enum class definition fixes the - // first and second statements in the body to be `imports` and `getters`. - // `imports` is an import list of the enum cases from the companion of `cls` - // `getters` will expand to `def ordinal: Int` if the parents of `cls` are not java.lang.Enum - val (imports :: getters :: Nil, stats): @unchecked = restOfBody.splitAt(2) - (getters :: Nil, imports :: stats) - else - (Nil, restOfBody) def init(): Context = index(params) @@ -1209,7 +1209,6 @@ class Namer { typer: Typer => cls.setNoInitsFlags(parentsKind(parents), untpd.bodyKind(rest)) if (cls.isNoInitsClass) cls.primaryConstructor.setFlag(StableRealizable) processExports(using localCtx) - index(restAfterParents)(using localCtx) } } diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 2217fb790e6b..f0139a05648a 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -1941,11 +1941,10 @@ class Typer extends Namer } def typedDefDef(ddef: untpd.DefDef, sym: Symbol)(using Context): Tree = { - if (!sym.info.exists) { // it's a discarded synthetic case class method, drop it - assert(sym.is(Synthetic) && desugar.isRetractableCaseClassMethodName(sym.name)) + if !sym.info.exists then // it's a discarded synthetic case class method, or ordinal method drop it + assert(sym.is(Synthetic) && desugar.isRetractableCaseClassOrEnumMethodName(sym.name)) sym.owner.info.decls.openForMutations.unlink(sym) return EmptyTree - } val DefDef(name, tparams, vparamss, tpt, _) = ddef completeAnnotations(ddef, sym) val tparams1 = tparams.mapconserve(typed(_).asInstanceOf[TypeDef]) @@ -2123,7 +2122,7 @@ class Typer extends Namer .withType(dummy.termRef) if (!cls.isOneOf(AbstractOrTrait) && !ctx.isAfterTyper) checkRealizableBounds(cls, cdef.sourcePos.withSpan(cdef.nameSpan)) - if cls.isScalaEnum || firstParentTpe.classSymbol.isScalaEnum then + if cls.isEnum || firstParentTpe.classSymbol.isEnum then checkEnum(cdef, cls, firstParent) val cdef1 = assignType(cpy.TypeDef(cdef)(name, impl1), cls) @@ -2636,9 +2635,6 @@ class Typer extends Namer case (stat: untpd.ExtMethods) :: rest => val xtree = stat.removeAttachment(ExpandedTree).get traverse(xtree :: rest) - case (stat: untpd.EnumGetters) :: rest => - val xtree = stat.removeAttachment(ExpandedTree).get - traverse(xtree :: rest) case stat :: rest => val stat1 = typed(stat)(using ctx.exprContext(stat, exprOwner)) checkStatementPurity(stat1)(stat, exprOwner) diff --git a/tests/neg/enumsLabel-singleimpl.scala b/tests/neg/enumsLabel-singleimpl.scala index 4a975c72c714..4dd489ccd337 100644 --- a/tests/neg/enumsLabel-singleimpl.scala +++ b/tests/neg/enumsLabel-singleimpl.scala @@ -8,8 +8,8 @@ enum Labelled { enum Ordinalled { - case A // error: double definition of method ordinal: => Int + case A - def ordinal: Int = -1 + def ordinal: Int = -1 // error: double definition of method ordinal: => Int } From be1125318e41859dd306744637ffd746e156dbd2 Mon Sep 17 00:00:00 2001 From: Jamie Thompson Date: Fri, 2 Oct 2020 16:45:44 +0200 Subject: [PATCH 13/14] delay enter ordinal symbol --- .../src/dotty/tools/dotc/typer/Namer.scala | 78 +++++++++++++------ 1 file changed, 56 insertions(+), 22 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Namer.scala b/compiler/src/dotty/tools/dotc/typer/Namer.scala index a880d79fa7fe..205f446030b7 100644 --- a/compiler/src/dotty/tools/dotc/typer/Namer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Namer.scala @@ -377,13 +377,13 @@ class Namer { typer: Typer => /** Expand tree and create top-level symbols for statement and enter them into symbol table */ def index(stat: Tree)(using Context): Context = { expand(stat) - indexExpanded(stat) + indexExpanded(stat, mutable.Buffer.empty) // here we do not expect to populate the buffer } /** Create top-level symbols for all statements in the expansion of this statement and * enter them into symbol table */ - def indexExpanded(origStat: Tree)(using Context): Context = { + def indexExpanded(origStat: Tree, delayedEntry: mutable.Buffer[Context ?=> Context])(using Context): Context = { def recur(stat: Tree): Context = stat match { case pcl: PackageDef => val pkg = createPackageSymbol(pcl.pid) @@ -395,10 +395,23 @@ class Namer { typer: Typer => ctx.importContext(imp, createSymbol(imp)) case mdef: DefTree => val sym = createSymbol(mdef) - enterSymbol(sym) - setDocstring(sym, origStat) - addEnumConstants(mdef, sym) - ctx + def enterDefTree(sym: Symbol): Context = + enterSymbol(sym) + setDocstring(sym, origStat) + addEnumConstants(mdef, sym) + ctx + if sym.is(Synthetic) && isEnumOrdinal(sym) then + delayedEntry += { ctx ?=> + // Here we delay entering into scope of an enum ordinal method until after + // the parents of `sym.owner` are known. This is because it is illegal to override ordinal + // in java.lang.Enum + if !clashingJavaEnumOrdinal(sym) then + enterDefTree(sym) + ctx + } + ctx + else + enterDefTree(sym) case stats: Thicket => stats.toList.foreach(recur) ctx @@ -456,7 +469,10 @@ class Namer { typer: Typer => /** Create top-level symbols for statements and enter them into symbol table * @return A context that reflects all imports in `stats`. */ - def index(stats: List[Tree])(using Context): Context = { + def index(stats: List[Tree])(using Context): Context = + index(stats, mutable.Buffer.empty) // here we do not expect to fill the buffer + + def index(stats: List[Tree], delayedEntry: mutable.Buffer[Context ?=> Context])(using Context): Context = { // module name -> (stat, moduleCls | moduleVal) val moduleClsDef = mutable.Map[TypeName, (Tree, TypeDef)]() @@ -631,7 +647,7 @@ class Namer { typer: Typer => stats.foreach(expand) mergeCompanionDefs() - val ctxWithStats = stats.foldLeft(ctx)((ctx, stat) => indexExpanded(stat)(using ctx)) + val ctxWithStats = stats.foldLeft(ctx)((ctx, stat) => indexExpanded(stat, delayedEntry)(using ctx)) createCompanionLinks(using ctxWithStats) ctxWithStats } @@ -663,6 +679,23 @@ class Namer { typer: Typer => sym.resetFlag(GivenOrImplicit) } + private def findMatch(denot: SymDenotation, owner: Symbol)(using Context) = + owner.info.decls.lookupAll(denot.name).exists(alt => + alt != denot.symbol && alt.info.matchesLoosely(denot.info)) + + private def isEnumOrdinal(denot: SymDenotation)(using Context) = + denot.name == nme.ordinal + && denot.owner.isClass && denot.owner.isEnumClass + + private def clashingJavaEnumOrdinal(denot: SymDenotation)(using Context) = + def firstParentCls(owner: Symbol) = + owner.asClass.classParents.head.classSymbol + def isJavaEnumBaseClass(owner: Symbol) = + owner.isClass && owner.isEnumClass && owner.derivesFrom(defn.JavaEnumClass) + denot.name == nme.ordinal + && isJavaEnumBaseClass(denot.owner) + && findMatch(denot, firstParentCls(denot.owner)) + /** The completer of a symbol defined by a member def or import (except ClassSymbols) */ class Completer(val original: Tree)(ictx: Context) extends LazyType with SymbolLoaders.SecondCompleter { @@ -755,22 +788,11 @@ class Namer { typer: Typer => if (owner.is(Module)) owner.linkedClass.is(CaseClass) else owner.is(CaseClass) } - def isJavaEnumBaseClass(owner: Symbol) = - owner.isClass && owner.isEnumClass && owner.derivesFrom(defn.JavaEnumClass) - def firstParentCls(owner: Symbol) = - owner.asClass.classParents.head.classSymbol - def findMatch(owner: Symbol) = - owner.info.decls.lookupAll(denot.name).exists(alt => - alt != denot.symbol && alt.info.matchesLoosely(denot.info)) def clashingCaseClassMethod = desugar.isRetractableCaseClassMethodName(denot.name) && isCaseClass(denot.owner) - && findMatch(denot.owner) - def clashingEnumMethod = - denot.name == nme.ordinal - && isJavaEnumBaseClass(denot.owner) - && findMatch(firstParentCls(denot.owner)) - val isClashingSynthetic = denot.is(Synthetic) && (clashingCaseClassMethod || clashingEnumMethod) + && findMatch(denot, denot.owner) + val isClashingSynthetic = denot.is(Synthetic) && (clashingCaseClassMethod || clashingJavaEnumOrdinal(denot)) if (isClashingSynthetic) { typr.println(i"invalidating clashing $denot in ${denot.owner}") denot.markAbsent() @@ -936,6 +958,11 @@ class Namer { typer: Typer => /** info to be used temporarily while completing the class, to avoid cyclic references. */ private var tempInfo: TempClassInfo = _ + /** we delay entry of the following symbols (already created) until after parents are known: + * - def ordinal: Int (if parent is not java.lang.Enum) + */ + private var delayedEntry: List[Context ?=> Context] = _ + val TypeDef(name, impl @ Template(constr, _, self, _)) = original private val (params, rest): (List[Tree], List[Tree]) = impl.body.span { @@ -1096,8 +1123,11 @@ class Namer { typer: Typer => localCtx = completerCtx.inClassContext(selfInfo) + val delayedEntryBuf = collection.mutable.ListBuffer.empty[Context ?=> Context] + index(constr) - index(rest)(using localCtx) + index(rest, delayedEntryBuf)(using localCtx) + delayedEntry = delayedEntryBuf.toList symbolOfTree(constr).info.stripPoly match // Completes constr symbol as a side effect case mt: MethodType if cls.is(Case) && mt.isParamDependent => @@ -1202,6 +1232,10 @@ class Namer { typer: Typer => denot.info = tempInfo.finalized(parentTypes) tempInfo = null // The temporary info can now be garbage-collected + // now we know the parents we can decide if we enter the symbols or not + delayedEntry.foldLeft(localCtx) { (localCtx, op) => op(using localCtx) } + delayedEntry = null + Checking.checkWellFormed(cls) if (isDerivedValueClass(cls)) cls.setFlag(Final) cls.info = avoidPrivateLeaks(cls) From 4951a755cfa51f720e8765ba9b8a8a90eb9f0e6a Mon Sep 17 00:00:00 2001 From: Jamie Thompson Date: Fri, 2 Oct 2020 17:11:44 +0200 Subject: [PATCH 14/14] refactor synthetic flag for ordinal + enumlabel --- compiler/src/dotty/tools/dotc/ast/Desugar.scala | 2 +- compiler/src/dotty/tools/dotc/ast/DesugarEnums.scala | 4 ++-- .../src/dotty/tools/dotc/transform/SyntheticMembers.scala | 3 +-- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/ast/Desugar.scala b/compiler/src/dotty/tools/dotc/ast/Desugar.scala index 989b7b787b2a..54e3d26d61ac 100644 --- a/compiler/src/dotty/tools/dotc/ast/Desugar.scala +++ b/compiler/src/dotty/tools/dotc/ast/Desugar.scala @@ -488,7 +488,7 @@ object desugar { val enumImport = Import(enumCompanionRef, enumCases.flatMap(caseIds).map(ImportSelector(_))) val enumLabelDef = DesugarEnums.enumLabelMeth(EmptyTree) - val ordinalDef = DesugarEnums.ordinalMeth(EmptyTree).withAddedFlags(Synthetic) + val ordinalDef = DesugarEnums.ordinalMeth(EmptyTree) (enumImport :: ordinalDef :: enumLabelDef :: enumStats, enumCases, enumCompanionRef) } else (stats, Nil, EmptyTree) diff --git a/compiler/src/dotty/tools/dotc/ast/DesugarEnums.scala b/compiler/src/dotty/tools/dotc/ast/DesugarEnums.scala index 4ed857678b26..868552102262 100644 --- a/compiler/src/dotty/tools/dotc/ast/DesugarEnums.scala +++ b/compiler/src/dotty/tools/dotc/ast/DesugarEnums.scala @@ -328,10 +328,10 @@ object DesugarEnums { private def isJavaEnum(using Context): Boolean = enumClass.derivesFrom(defn.JavaEnumClass) def ordinalMeth(body: Tree)(using Context): DefDef = - DefDef(nme.ordinal, Nil, Nil, TypeTree(defn.IntType), body) + DefDef(nme.ordinal, Nil, Nil, TypeTree(defn.IntType), body).withAddedFlags(Synthetic) def enumLabelMeth(body: Tree)(using Context): DefDef = - DefDef(nme.enumLabel, Nil, Nil, TypeTree(defn.StringType), body) + DefDef(nme.enumLabel, Nil, Nil, TypeTree(defn.StringType), body).withAddedFlags(Synthetic) def ordinalMethLit(ord: Int)(using Context): DefDef = ordinalMeth(Literal(Constant(ord))) diff --git a/compiler/src/dotty/tools/dotc/transform/SyntheticMembers.scala b/compiler/src/dotty/tools/dotc/transform/SyntheticMembers.scala index 4429dd431422..982ba4c00b22 100644 --- a/compiler/src/dotty/tools/dotc/transform/SyntheticMembers.scala +++ b/compiler/src/dotty/tools/dotc/transform/SyntheticMembers.scala @@ -94,8 +94,7 @@ 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.isEnum || clazz.classParents.exists(_.classSymbol.isEnum) - val isEnumValue = isEnumCase && clazz.isAnonymousClass && clazz.classParents.head.classSymbol.isEnum + val isEnumValue = clazz.isAnonymousClass && clazz.classParents.head.classSymbol.is(Enum) val isNonJavaEnumValue = isEnumValue && !clazz.derivesFrom(defn.JavaEnumClass) val symbolsToSynthesize: List[Symbol] =