diff --git a/compiler/src/dotty/tools/dotc/ast/Desugar.scala b/compiler/src/dotty/tools/dotc/ast/Desugar.scala index a55434c54bd0..0ad3c4a84375 100644 --- a/compiler/src/dotty/tools/dotc/ast/Desugar.scala +++ b/compiler/src/dotty/tools/dotc/ast/Desugar.scala @@ -33,11 +33,15 @@ object desugar { // ----- DerivedTypeTrees ----------------------------------- class SetterParamTree extends DerivedTypeTree { - def derivedType(sym: Symbol)(implicit ctx: Context) = sym.info.resultType + def derivedTree(sym: Symbol)(implicit ctx: Context) = tpd.TypeTree(sym.info.resultType) } class TypeRefTree extends DerivedTypeTree { - def derivedType(sym: Symbol)(implicit ctx: Context) = sym.typeRef + def derivedTree(sym: Symbol)(implicit ctx: Context) = tpd.TypeTree(sym.typeRef) + } + + class TermRefTree extends DerivedTypeTree { + def derivedTree(sym: Symbol)(implicit ctx: Context) = tpd.ref(sym) } /** A type tree that computes its type from an existing parameter. @@ -73,7 +77,7 @@ object desugar { * * parameter name == reference name ++ suffix */ - def derivedType(sym: Symbol)(implicit ctx: Context) = { + def derivedTree(sym: Symbol)(implicit ctx: Context) = { val relocate = new TypeMap { val originalOwner = sym.owner def apply(tp: Type) = tp match { @@ -91,7 +95,7 @@ object desugar { mapOver(tp) } } - relocate(sym.info) + tpd.TypeTree(relocate(sym.info)) } } @@ -301,34 +305,56 @@ object desugar { val isCaseObject = mods.is(Case) && mods.is(Module) val isImplicit = mods.is(Implicit) val isEnum = mods.hasMod[Mod.Enum] && !mods.is(Module) - val isEnumCase = isLegalEnumCase(cdef) + val isEnumCase = mods.hasMod[Mod.EnumCase] val isValueClass = parents.nonEmpty && isAnyVal(parents.head) - // This is not watertight, but `extends AnyVal` will be replaced by `inline` later. - + // This is not watertight, but `extends AnyVal` will be replaced by `inline` later. val originalTparams = constr1.tparams val originalVparamss = constr1.vparamss - val constrTparams = originalTparams.map(toDefParam) + lazy val derivedEnumParams = enumClass.typeParams.map(derivedTypeParam) + val impliedTparams = + if (isEnumCase && originalTparams.isEmpty) + derivedEnumParams.map(tdef => tdef.withFlags(tdef.mods.flags | PrivateLocal)) + else + originalTparams + val constrTparams = impliedTparams.map(toDefParam) val constrVparamss = if (originalVparamss.isEmpty) { // ensure parameter list is non-empty - if (isCaseClass) ctx.error(CaseClassMissingParamList(cdef), cdef.namePos) + if (isCaseClass && originalTparams.isEmpty) + ctx.error(CaseClassMissingParamList(cdef), cdef.namePos) ListOfNil } else originalVparamss.nestedMap(toDefParam) val constr = cpy.DefDef(constr1)(tparams = constrTparams, vparamss = constrVparamss) - // Add constructor type parameters and evidence implicit parameters - // to auxiliary constructors - val normalizedBody = impl.body map { - case ddef: DefDef if ddef.name.isConstructorName => - decompose( - defDef( - addEvidenceParams( - cpy.DefDef(ddef)(tparams = constrTparams), - evidenceParams(constr1).map(toDefParam)))) - case stat => - stat + val (normalizedBody, enumCases, enumCompanionRef) = { + // Add constructor type parameters and evidence implicit parameters + // to auxiliary constructors; set defaultGetters as a side effect. + def expandConstructor(tree: Tree) = tree match { + case ddef: DefDef if ddef.name.isConstructorName => + decompose( + defDef( + addEvidenceParams( + cpy.DefDef(ddef)(tparams = constrTparams), + evidenceParams(constr1).map(toDefParam)))) + case stat => + stat + } + // The Identifiers defined by a case + def caseIds(tree: Tree) = tree match { + case tree: MemberDef => Ident(tree.name.toTermName) :: Nil + case PatDef(_, ids, _, _) => ids + } + val stats = impl.body.map(expandConstructor) + if (isEnum) { + val (enumCases, enumStats) = stats.partition(DesugarEnums.isEnumCase) + val enumCompanionRef = new TermRefTree() + val enumImport = Import(enumCompanionRef, enumCases.flatMap(caseIds)) + (enumImport :: enumStats, enumCases, enumCompanionRef) + } + else (stats, Nil, EmptyTree) } + def anyRef = ref(defn.AnyRefAlias.typeRef) val derivedTparams = constrTparams.map(derivedTypeParam(_)) @@ -361,20 +387,16 @@ object desugar { val classTypeRef = appliedRef(classTycon) // a reference to `enumClass`, with type parameters coming from the case constructor - lazy val enumClassTypeRef = enumClass.primaryConstructor.info match { - case info: PolyType => - if (constrTparams.isEmpty) - interpolatedEnumParent(cdef.pos.startPos) - else if ((constrTparams.corresponds(info.paramNames))((param, name) => param.name == name)) - appliedRef(enumClassRef) - else { - ctx.error(i"explicit extends clause needed because type parameters of case and enum class differ" - , cdef.pos.startPos) - appliedTypeTree(enumClassRef, constrTparams map (_ => anyRef)) - } - case _ => + lazy val enumClassTypeRef = + if (enumClass.typeParams.isEmpty) enumClassRef - } + else if (originalTparams.isEmpty) + appliedRef(enumClassRef) + else { + ctx.error(i"explicit extends clause needed because both enum case and enum class have type parameters" + , cdef.pos.startPos) + appliedTypeTree(enumClassRef, constrTparams map (_ => anyRef)) + } // new C[Ts](paramss) lazy val creatorExpr = New(classTypeRef, constrVparamss nestedMap refOfDef) @@ -428,6 +450,7 @@ object desugar { } // Case classes and case objects get Product parents + // Enum cases get an inferred parent if no parents are given var parents1 = parents if (isEnumCase && parents.isEmpty) parents1 = enumClassTypeRef :: Nil @@ -473,7 +496,7 @@ object desugar { .withMods(companionMods | Synthetic)) .withPos(cdef.pos).toList - val companionMeths = defaultGetters ::: eqInstances + val companionMembers = defaultGetters ::: eqInstances ::: enumCases // The companion object definitions, if a companion is needed, Nil otherwise. // companion definitions include: @@ -486,18 +509,17 @@ object desugar { // For all other classes, the parent is AnyRef. val companions = if (isCaseClass) { - // The return type of the `apply` method + // The return type of the `apply` method, and an (empty or singleton) list + // of widening coercions val (applyResultTpt, widenDefs) = if (!isEnumCase) (TypeTree(), Nil) else if (parents.isEmpty || enumClass.typeParams.isEmpty) (enumClassTypeRef, Nil) - else { - val tparams = enumClass.typeParams.map(derivedTypeParam) - enumApplyResult(cdef, parents, tparams, appliedRef(enumClassRef, tparams)) - } + else + enumApplyResult(cdef, parents, derivedEnumParams, appliedRef(enumClassRef, derivedEnumParams)) - val parent = + val companionParent = if (constrTparams.nonEmpty || constrVparamss.length > 1 || mods.is(Abstract) || @@ -519,10 +541,10 @@ object desugar { DefDef(nme.unapply, derivedTparams, (unapplyParam :: Nil) :: Nil, TypeTree(), unapplyRHS) .withMods(synthetic) } - companionDefs(parent, applyMeths ::: unapplyMeth :: companionMeths) + companionDefs(companionParent, applyMeths ::: unapplyMeth :: companionMembers) } - else if (companionMeths.nonEmpty) - companionDefs(anyRef, companionMeths) + else if (companionMembers.nonEmpty) + companionDefs(anyRef, companionMembers) else if (isValueClass) { constr0.vparamss match { case (_ :: Nil) :: _ => companionDefs(anyRef, Nil) @@ -531,6 +553,13 @@ object desugar { } else Nil + enumCompanionRef match { + case ref: TermRefTree => // have the enum import watch the companion object + val (modVal: ValDef) :: _ = companions + ref.watching(modVal) + case _ => + } + // For an implicit class C[Ts](p11: T11, ..., p1N: T1N) ... (pM1: TM1, .., pMN: TMN), the method // synthetic implicit C[Ts](p11: T11, ..., p1N: T1N) ... (pM1: TM1, ..., pMN: TMN): C[Ts] = // new C[Ts](p11, ..., p1N) ... (pM1, ..., pMN) = @@ -563,7 +592,7 @@ object desugar { } val cdef1 = addEnumFlags { - val originalTparamsIt = originalTparams.toIterator + val originalTparamsIt = impliedTparams.toIterator val originalVparamsIt = originalVparamss.toIterator.flatten val tparamAccessors = derivedTparams.map(_.withMods(originalTparamsIt.next().mods)) val caseAccessor = if (isCaseClass) CaseAccessor else EmptyFlags @@ -603,7 +632,7 @@ object desugar { val moduleName = checkNotReservedName(mdef).asTermName val impl = mdef.impl val mods = mdef.mods - lazy val isEnumCase = isLegalEnumCase(mdef) + lazy val isEnumCase = mods.hasMod[Mod.EnumCase] if (mods is Package) PackageDef(Ident(moduleName), cpy.ModuleDef(mdef)(nme.PACKAGE, impl).withMods(mods &~ Package) :: Nil) else if (isEnumCase) @@ -650,7 +679,7 @@ object desugar { */ def patDef(pdef: PatDef)(implicit ctx: Context): Tree = flatTree { val PatDef(mods, pats, tpt, rhs) = pdef - if (mods.hasMod[Mod.EnumCase] && enumCaseIsLegal(pdef)) + if (mods.hasMod[Mod.EnumCase]) pats map { case id: Ident => expandSimpleEnumCase(id.name.asTermName, mods, diff --git a/compiler/src/dotty/tools/dotc/ast/DesugarEnums.scala b/compiler/src/dotty/tools/dotc/ast/DesugarEnums.scala index 4d1dda5b8930..e6e48f75ede5 100644 --- a/compiler/src/dotty/tools/dotc/ast/DesugarEnums.scala +++ b/compiler/src/dotty/tools/dotc/ast/DesugarEnums.scala @@ -6,7 +6,6 @@ import core._ import util.Positions._, Types._, Contexts._, Constants._, Names._, NameOps._, Flags._ import SymDenotations._, Symbols._, StdNames._, Annotations._, Trees._ import Decorators._ -import reporting.diagnostic.messages.EnumCaseDefinitionInNonEnumOwner import collection.mutable.ListBuffer import util.Property import typer.ErrorReporting._ @@ -23,20 +22,21 @@ object DesugarEnums { /** Attachment containing the number of enum cases and the smallest kind that was seen so far. */ val EnumCaseCount = new Property.Key[(Int, CaseKind.Value)] - /** the enumeration class that is a companion of the current object */ - def enumClass(implicit ctx: Context) = ctx.owner.linkedClass - - /** Is this an enum case that's situated in a companion object of an enum class? */ - def isLegalEnumCase(tree: MemberDef)(implicit ctx: Context): Boolean = - tree.mods.hasMod[Mod.EnumCase] && enumCaseIsLegal(tree) + /** The enumeration class that belongs to an enum case. This works no matter + * whether the case is still in the enum class or it has been transferred to the + * companion object. + */ + def enumClass(implicit ctx: Context): Symbol = { + val cls = ctx.owner + if (cls.is(Module)) cls.linkedClass else cls + } - /** Is enum case `tree` situated in a companion object of an enum class? */ - def enumCaseIsLegal(tree: Tree)(implicit ctx: Context): Boolean = ( - ctx.owner.is(ModuleClass) && enumClass.derivesFrom(defn.EnumClass) - || { ctx.error(EnumCaseDefinitionInNonEnumOwner(ctx.owner), tree.pos) - false - } - ) + /** Is `tree` an (untyped) enum case? */ + def isEnumCase(tree: Tree)(implicit ctx: Context): Boolean = tree match { + case tree: MemberDef => tree.mods.hasMod[Mod.EnumCase] + case PatDef(mods, _, _, _) => mods.hasMod[Mod.EnumCase] + case _ => false + } /** A reference to the enum class `E`, possibly followed by type arguments. * Each covariant type parameter is approximated by its lower bound. @@ -68,8 +68,8 @@ object DesugarEnums { /** Add implied flags to an enum class or an enum case */ def addEnumFlags(cdef: TypeDef)(implicit ctx: Context) = - if (cdef.mods.hasMod[Mod.Enum]) cdef.withFlags(cdef.mods.flags | Abstract | Sealed) - else if (isLegalEnumCase(cdef)) cdef.withFlags(cdef.mods.flags | Final) + if (cdef.mods.hasMod[Mod.Enum]) cdef.withMods(cdef.mods.withFlags(cdef.mods.flags | Abstract | Sealed)) + else if (isEnumCase(cdef)) cdef.withMods(cdef.mods.withFlags(cdef.mods.flags | Final)) else cdef private def valuesDot(name: String) = Select(Ident(nme.DOLLAR_VALUES), name.toTermName) @@ -193,24 +193,20 @@ object DesugarEnums { } /** Expand a module definition representing a parameterless enum case */ - def expandEnumModule(name: TermName, impl: Template, mods: Modifiers, pos: Position)(implicit ctx: Context): Tree = + def expandEnumModule(name: TermName, impl: Template, mods: Modifiers, pos: Position)(implicit ctx: Context): Tree = { + assert(impl.body.isEmpty) if (impl.parents.isEmpty) - if (impl.body.isEmpty) - expandSimpleEnumCase(name, mods, pos) - else { - val parent = interpolatedEnumParent(pos) - expandEnumModule(name, cpy.Template(impl)(parents = parent :: Nil), mods, pos) - } + expandSimpleEnumCase(name, mods, pos) 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 = - impl.body ++ List(tagMeth, toStringMeth) ++ registerCall) + val impl1 = cpy.Template(impl)(body = List(tagMeth, toStringMeth) ++ registerCall) val vdef = ValDef(name, TypeTree(), New(impl1)).withMods(mods | Final) flatTree(scaffolding ::: vdef :: Nil).withPos(pos) } + } /** Expand a simple enum case */ def expandSimpleEnumCase(name: TermName, mods: Modifiers, pos: Position)(implicit ctx: Context): Tree = diff --git a/compiler/src/dotty/tools/dotc/ast/tpd.scala b/compiler/src/dotty/tools/dotc/ast/tpd.scala index a0eb74be1422..e177d719dd6d 100644 --- a/compiler/src/dotty/tools/dotc/ast/tpd.scala +++ b/compiler/src/dotty/tools/dotc/ast/tpd.scala @@ -309,6 +309,7 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo { case NoPrefix => true case pre: ThisType => + tp.isType || pre.cls.isStaticOwner || tp.symbol.isParamOrAccessor && !pre.cls.is(Trait) && ctx.owner.enclosingClass == pre.cls // was ctx.owner.enclosingClass.derivesFrom(pre.cls) which was not tight enough diff --git a/compiler/src/dotty/tools/dotc/ast/untpd.scala b/compiler/src/dotty/tools/dotc/ast/untpd.scala index 22fd83688eaf..74f3f6438fd8 100644 --- a/compiler/src/dotty/tools/dotc/ast/untpd.scala +++ b/compiler/src/dotty/tools/dotc/ast/untpd.scala @@ -228,8 +228,8 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo { */ def ensureCompletions(implicit ctx: Context): Unit = () - /** The method that computes the type of this tree */ - def derivedType(originalSym: Symbol)(implicit ctx: Context): Type + /** The method that computes the tree with the derived type */ + def derivedTree(originalSym: Symbol)(implicit ctx: Context): tpd.Tree } /** Property key containing TypeTrees whose type is computed diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index 1870777ef588..3dd2d1e3f688 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -308,7 +308,6 @@ object Parsers { * class constructor */ private[this] var inClassConstrAnnots = false - private def fromWithinClassConstr[T](body: => T): T = { val saved = inClassConstrAnnots inClassConstrAnnots = true @@ -316,6 +315,14 @@ object Parsers { finally inClassConstrAnnots = saved } + private[this] var inEnum = false + private def withinEnum[T](isEnum: Boolean)(body: => T): T = { + val saved = inEnum + inEnum = isEnum + try body + finally inEnum = saved + } + def migrationWarningOrError(msg: String, offset: Int = in.offset) = if (in.isScala2Mode) ctx.migrationWarning(msg, source atPos Position(offset)) @@ -1975,7 +1982,7 @@ object Parsers { * | var ValDcl * | def DefDcl * | type {nl} TypeDcl - * EnumCase ::= `case' (EnumClassDef | ObjectDef) + * EnumCase ::= `case' (id ClassConstr [`extends' ConstrApps]] | ids) */ def defOrDcl(start: Int, mods: Modifiers): Tree = in.token match { case VAL => @@ -1990,7 +1997,7 @@ object Parsers { defDefOrDcl(start, posMods(start, mods)) case TYPE => typeDefOrDcl(start, posMods(start, mods)) - case CASE => + case CASE if inEnum => enumCase(start, mods) case _ => tmplDef(start, mods) @@ -2135,7 +2142,7 @@ object Parsers { } } - /** TmplDef ::= ([`case' | `enum]'] ‘class’ | trait’) ClassDef + /** TmplDef ::= ([`case'] ‘class’ | trait’) ClassDef * | [`case'] `object' ObjectDef * | `enum' EnumDef */ @@ -2152,9 +2159,7 @@ object Parsers { case CASEOBJECT => objectDef(start, posMods(start, mods | Case | Module)) case ENUM => - val enumMod = atPos(in.skipToken()) { Mod.Enum() } - if (in.token == CLASS) tmplDef(start, addMod(mods, enumMod)) - else enumDef(start, mods, enumMod) + enumDef(start, mods, atPos(in.skipToken()) { Mod.Enum() }) case _ => syntaxErrorOrIncomplete(ExpectedStartOfTopLevelDefinition()) EmptyTree @@ -2198,55 +2203,18 @@ object Parsers { ModuleDef(name, template).withMods(mods).setComment(in.getDocComment(start)) } - /** id ClassConstr [`extends' [ConstrApps]] - * [nl] ‘{’ EnumCaseStats ‘}’ + /** EnumDef ::= id ClassConstr [`extends' [ConstrApps]] EnumBody */ - def enumDef(start: Offset, mods: Modifiers, enumMod: Mod): Thicket = { - val point = nameStart + def enumDef(start: Offset, mods: Modifiers, enumMod: Mod): TypeDef = atPos(start, nameStart) { val modName = ident() val clsName = modName.toTypeName val constr = classConstr(clsName) - val parents = - if (in.token == EXTENDS) { - in.nextToken(); - newLineOptWhenFollowedBy(LBRACE) - if (in.token == LBRACE) Nil else tokenSeparated(WITH, constrApp) - } - else Nil - val clsDef = atPos(start, point) { - TypeDef(clsName, Template(constr, parents, EmptyValDef, Nil)) - .withMods(addMod(mods, enumMod)).setComment(in.getDocComment(start)) - } - newLineOptWhenFollowedBy(LBRACE) - val modDef = atPos(in.offset) { - val body = inBraces(enumCaseStats()) - ModuleDef(modName, Template(emptyConstructor, Nil, EmptyValDef, body)) - .withMods(mods) - } - Thicket(clsDef :: modDef :: Nil) - } - - /** EnumCaseStats = EnumCaseStat {semi EnumCaseStat} */ - def enumCaseStats(): List[DefTree] = { - val cases = new ListBuffer[DefTree] += enumCaseStat() - var exitOnError = false - while (!isStatSeqEnd && !exitOnError) { - acceptStatSep() - if (isCaseIntro) - cases += enumCaseStat() - else if (!isStatSep) { - exitOnError = mustStartStat - syntaxErrorOrIncomplete("illegal start of case") - } - } - cases.toList + val impl = templateOpt(constr, isEnum = true) + TypeDef(clsName, impl).withMods(addMod(mods, enumMod)).setComment(in.getDocComment(start)) } - /** EnumCaseStat = {Annotation [nl]} {Modifier} EnumCase */ - def enumCaseStat(): DefTree = - enumCase(in.offset, defAnnotsMods(modifierTokens)) - - /** EnumCase = `case' (EnumClassDef | ObjectDef) */ + /** EnumCase = `case' (id ClassConstr [`extends' ConstrApps] | ids) + */ def enumCase(start: Offset, mods: Modifiers): DefTree = { val mods1 = mods.withAddedMod(atPos(in.offset)(Mod.EnumCase())) | Case accept(CASE) @@ -2257,18 +2225,36 @@ object Parsers { atPos(start, nameStart) { val id = termIdent() - if (in.token == LBRACKET || in.token == LPAREN) - classDefRest(start, mods1, id.name.toTypeName) - else if (in.token == COMMA) { + if (in.token == COMMA) { in.nextToken() val ids = commaSeparated(() => termIdent()) PatDef(mods1, id :: ids, TypeTree(), EmptyTree) } - else - objectDefRest(start, mods1, id.name.asTermName) + else { + val caseDef = + if (in.token == LBRACKET || in.token == LPAREN || in.token == AT || isModifier) { + val clsName = id.name.toTypeName + val constr = classConstr(clsName, isCaseClass = true) + TypeDef(clsName, caseTemplate(constr)) + } + else + ModuleDef(id.name.toTermName, caseTemplate(emptyConstructor)) + caseDef.withMods(mods1).setComment(in.getDocComment(start)) + } } } + /** [`extends' ConstrApps] */ + def caseTemplate(constr: DefDef): Template = { + val parents = + if (in.token == EXTENDS) { + in.nextToken() + tokenSeparated(WITH, constrApp) + } + else Nil + Template(constr, parents, EmptyValDef, Nil) + } + /* -------- TEMPLATES ------------------------------------------- */ /** ConstrApp ::= SimpleType {ParArgumentExprs} @@ -2285,32 +2271,34 @@ object Parsers { * @return a pair consisting of the template, and a boolean which indicates * whether the template misses a body (i.e. no {...} part). */ - def template(constr: DefDef): (Template, Boolean) = { + def template(constr: DefDef, isEnum: Boolean = false): (Template, Boolean) = { newLineOptWhenFollowedBy(LBRACE) - if (in.token == LBRACE) (templateBodyOpt(constr, Nil), false) + if (in.token == LBRACE) (templateBodyOpt(constr, Nil, isEnum), false) else { val parents = tokenSeparated(WITH, constrApp) newLineOptWhenFollowedBy(LBRACE) + if (isEnum && in.token != LBRACE) + syntaxErrorOrIncomplete(ExpectedTokenButFound(LBRACE, in.token)) val missingBody = in.token != LBRACE - (templateBodyOpt(constr, parents), missingBody) + (templateBodyOpt(constr, parents, isEnum), missingBody) } } /** TemplateOpt = [`extends' Template | TemplateBody] */ - def templateOpt(constr: DefDef): Template = - if (in.token == EXTENDS) { in.nextToken(); template(constr)._1 } + def templateOpt(constr: DefDef, isEnum: Boolean = false): Template = + if (in.token == EXTENDS) { in.nextToken(); template(constr, isEnum)._1 } else { newLineOptWhenFollowedBy(LBRACE) - if (in.token == LBRACE) template(constr)._1 + if (in.token == LBRACE) template(constr, isEnum)._1 else Template(constr, Nil, EmptyValDef, Nil) } /** TemplateBody ::= [nl] `{' TemplateStatSeq `}' */ - def templateBodyOpt(constr: DefDef, parents: List[Tree]) = { + def templateBodyOpt(constr: DefDef, parents: List[Tree], isEnum: Boolean) = { val (self, stats) = - if (in.token == LBRACE) templateBody() else (EmptyValDef, Nil) + if (in.token == LBRACE) withinEnum(isEnum)(templateBody()) else (EmptyValDef, Nil) Template(constr, parents, self, stats) } @@ -2378,9 +2366,10 @@ object Parsers { * TemplateStat ::= Import * | Annotations Modifiers Def * | Annotations Modifiers Dcl - * | EnumCaseStat * | Expr1 * | + * EnumStat ::= TemplateStat + * | Annotations Modifiers EnumCase */ def templateStatSeq(): (ValDef, List[Tree]) = checkNoEscapingPlaceholders { var self: ValDef = EmptyValDef diff --git a/compiler/src/dotty/tools/dotc/reporting/diagnostic/messages.scala b/compiler/src/dotty/tools/dotc/reporting/diagnostic/messages.scala index 3c7720b65aa9..2c5425d99ec2 100644 --- a/compiler/src/dotty/tools/dotc/reporting/diagnostic/messages.scala +++ b/compiler/src/dotty/tools/dotc/reporting/diagnostic/messages.scala @@ -1783,16 +1783,6 @@ object messages { hl"""A class marked with the ${"final"} keyword cannot be extended""" } - case class EnumCaseDefinitionInNonEnumOwner(owner: Symbol)(implicit ctx: Context) - extends Message(EnumCaseDefinitionInNonEnumOwnerID) { - val kind = "Syntax" - val msg = em"case not allowed here, since owner ${owner} is not an ${"enum"} object" - val explanation = - hl"""${"enum"} cases are only allowed within the companion ${"object"} of an ${"enum class"}. - |If you want to create an ${"enum"} case, make sure the corresponding ${"enum class"} exists - |and has the ${"enum"} keyword.""" - } - case class ExpectedTypeBoundOrEquals(found: Token)(implicit ctx: Context) extends Message(ExpectedTypeBoundOrEqualsID) { val kind = "Syntax" diff --git a/compiler/src/dotty/tools/dotc/typer/Checking.scala b/compiler/src/dotty/tools/dotc/typer/Checking.scala index 4dd960e4fae3..030e50fdcc3d 100644 --- a/compiler/src/dotty/tools/dotc/typer/Checking.scala +++ b/compiler/src/dotty/tools/dotc/typer/Checking.scala @@ -28,6 +28,7 @@ import Decorators._ import Uniques._ import ErrorReporting.{err, errorType} import config.Printers.typr +import NameKinds.DefaultGetterName import collection.mutable import SymDenotations.NoCompleter @@ -741,9 +742,124 @@ trait Checking { tp.foreachPart(check, stopAtStatic = true) tp } + + /** Check that all non-synthetic references of the form `` or + * `this.` in `tree` that refer to a member of `badOwner` are + * `allowed`. Also check that there are no other explicit `this` references + * to `badOwner`. + */ + def checkRefsLegal(tree: tpd.Tree, badOwner: Symbol, allowed: (Name, Symbol) => Boolean, where: String)(implicit ctx: Context): Unit = { + val checker = new TreeTraverser { + def traverse(t: Tree)(implicit ctx: Context) = { + def check(owner: Symbol, checkedSym: Symbol) = + if (t.pos.isSourceDerived && owner == badOwner) + t match { + case t: RefTree if allowed(t.name, checkedSym) => + case _ => ctx.error(i"illegal reference to $checkedSym from $where", t.pos) + } + val sym = t.symbol + t match { + case Ident(_) | Select(This(_), _) => check(sym.maybeOwner, sym) + case This(_) => check(sym, sym) + case _ => traverseChildren(t) + } + } + } + checker.traverse(tree) + } + + /** Check that all case classes that extend `scala.Enum` are `enum` cases */ + def checkEnum(cdef: untpd.TypeDef, cls: Symbol)(implicit ctx: Context): Unit = { + import untpd.modsDeco + def isEnumAnonCls = + cls.isAnonymousClass && + cls.owner.isTerm && + (cls.owner.flagsUNSAFE.is(Case) || cls.owner.name == nme.DOLLAR_NEW) + if (!cdef.mods.hasMod[untpd.Mod.EnumCase] && !isEnumAnonCls) + ctx.error(em"normal case $cls in ${cls.owner} cannot extend an enum", cdef.pos) + } + + /** Check that all references coming from enum cases in an enum companion object + * are legal. + * @param cdef the enum companion object class + * @param enumCtx the context immediately enclosing the corresponding enum + */ + private def checkEnumCaseRefsLegal(cdef: TypeDef, enumCtx: Context)(implicit ctx: Context): Unit = { + + def checkCaseOrDefault(stat: Tree, caseCtx: Context) = { + + def check(tree: Tree) = { + // allow access to `sym` if a typedIdent just outside the enclosing enum + // would have produced the same symbol without errors + def allowAccess(name: Name, sym: Symbol): Boolean = { + val testCtx = caseCtx.fresh.setNewTyperState() + val ref = ctx.typer.typedIdent(untpd.Ident(name), WildcardType)(testCtx) + ref.symbol == sym && !testCtx.reporter.hasErrors + } + checkRefsLegal(tree, cdef.symbol, allowAccess, "enum case") + } + + if (stat.symbol.is(Case)) + stat match { + case TypeDef(_, Template(DefDef(_, tparams, vparamss, _, _), parents, _, _)) => + tparams.foreach(check) + vparamss.foreach(_.foreach(check)) + parents.foreach(check) + case vdef: ValDef => + vdef.rhs match { + case Block((clsDef @ TypeDef(_, impl: Template)) :: Nil, _) + if clsDef.symbol.isAnonymousClass => + impl.parents.foreach(check) + case _ => + } + case _ => + } + else if (stat.symbol.is(Module) && stat.symbol.linkedClass.is(Case)) + stat match { + case TypeDef(_, impl: Template) => + for ((defaultGetter @ + DefDef(DefaultGetterName(nme.CONSTRUCTOR, _), _, _, _, _)) <- impl.body) + check(defaultGetter.rhs) + case _ => + } + } + + cdef.rhs match { + case impl: Template => + def isCase(stat: Tree) = stat match { + case _: ValDef | _: TypeDef => stat.symbol.is(Case) + case _ => false + } + val cases = + for (stat <- impl.body if isCase(stat)) + yield untpd.Ident(stat.symbol.name.toTermName) + val caseImport: Import = Import(ref(cdef.symbol), cases) + val caseCtx = enumCtx.importContext(caseImport, caseImport.symbol) + for (stat <- impl.body) checkCaseOrDefault(stat, caseCtx) + case _ => + } + } + + /** Check all enum cases in all enum companions in `stats` for legal accesses. + * @param enumContexts a map from`enum` symbols to the contexts enclosing their definitions + */ + def checkEnumCompanions(stats: List[Tree], enumContexts: collection.Map[Symbol, Context])(implicit ctx: Context): List[Tree] = { + for (stat @ TypeDef(_, _) <- stats) + if (stat.symbol.is(Module)) + for (enumContext <- enumContexts.get(stat.symbol.linkedClass)) + checkEnumCaseRefsLegal(stat, enumContext) + stats + } +} + +trait ReChecking extends Checking { + import tpd._ + override def checkEnum(cdef: untpd.TypeDef, cls: Symbol)(implicit ctx: Context): Unit = () + override def checkRefsLegal(tree: tpd.Tree, badOwner: Symbol, allowed: (Name, Symbol) => Boolean, where: String)(implicit ctx: Context): Unit = () + override def checkEnumCompanions(stats: List[Tree], enumContexts: collection.Map[Symbol, Context])(implicit ctx: Context): List[Tree] = stats } -trait NoChecking extends Checking { +trait NoChecking extends ReChecking { import tpd._ override def checkNonCyclic(sym: Symbol, info: TypeBounds, reportErrors: Boolean)(implicit ctx: Context): Type = info override def checkValue(tree: Tree, proto: Type)(implicit ctx: Context): tree.type = tree diff --git a/compiler/src/dotty/tools/dotc/typer/Namer.scala b/compiler/src/dotty/tools/dotc/typer/Namer.scala index 16554609744b..af7bde9c2852 100644 --- a/compiler/src/dotty/tools/dotc/typer/Namer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Namer.scala @@ -526,6 +526,13 @@ class Namer { typer: Typer => mdef.putAttachment(ExpandedTree, Thicket(trees.filter(_ != tree))) } + /** Transfer all references to `from` to `to` */ + def transferReferences(from: ValDef, to: ValDef): Unit = { + val fromRefs = from.removeAttachment(References).getOrElse(Nil) + val toRefs = to.removeAttachment(References).getOrElse(Nil) + to.putAttachment(References, fromRefs ++ toRefs) + } + /** Merge the module class `modCls` in the expanded tree of `mdef` with the given stats */ def mergeModuleClass(mdef: Tree, modCls: TypeDef, stats: List[Tree]): TypeDef = { var res: TypeDef = null @@ -580,9 +587,12 @@ class Namer { typer: Typer => case vdef @ ValDef(name, _, _) if valid(vdef) => moduleValDef.get(name) match { case Some((stat1, vdef1)) => - if (vdef.mods.is(Synthetic) && !vdef1.mods.is(Synthetic)) + if (vdef.mods.is(Synthetic) && !vdef1.mods.is(Synthetic)) { + transferReferences(vdef, vdef1) removeInExpanded(stat, vdef) + } else if (!vdef.mods.is(Synthetic) && vdef1.mods.is(Synthetic)) { + transferReferences(vdef1, vdef) removeInExpanded(stat1, vdef1) moduleValDef(name) = (stat, vdef) } diff --git a/compiler/src/dotty/tools/dotc/typer/ReTyper.scala b/compiler/src/dotty/tools/dotc/typer/ReTyper.scala index be4218b72bf8..8de89f25aab6 100644 --- a/compiler/src/dotty/tools/dotc/typer/ReTyper.scala +++ b/compiler/src/dotty/tools/dotc/typer/ReTyper.scala @@ -22,7 +22,7 @@ import config.Printers.typr * * Otherwise, everything is as in Typer. */ -class ReTyper extends Typer { +class ReTyper extends Typer with ReChecking { import tpd._ private def assertTyped(tree: untpd.Tree)(implicit ctx: Context): Unit = diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index fc56b72eef12..09dfe59bd56d 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -1152,7 +1152,7 @@ class Typer extends Namer tree.ensureCompletions tree.getAttachment(untpd.OriginalSymbol) match { case Some(origSym) => - TypeTree(tree.derivedType(origSym)).withPos(tree.pos) + tree.derivedTree(origSym).withPos(tree.pos) // btw, no need to remove the attachment. The typed // tree is different from the untyped one, so the // untyped tree is no longer accessed after all @@ -1405,8 +1405,12 @@ class Typer extends Namer // Overwrite inline body to make sure it is not evaluated twice if (sym.isInlineMethod) Inliner.registerInlineInfo(sym, _ => rhs1) + if (sym.isConstructor && !sym.isPrimaryConstructor) + for (param <- tparams1 ::: vparamss1.flatten) + checkRefsLegal(param, sym.owner, (name, sym) => sym.is(TypeParam), "secondary constructor") + assignType(cpy.DefDef(ddef)(name, tparams1, vparamss1, tpt1, rhs1), sym) - //todo: make sure dependent method types do not depend on implicits or by-name params + //todo: make sure dependent method types do not depend on implicits or by-name params } def typedTypeDef(tdef: untpd.TypeDef, sym: Symbol)(implicit ctx: Context): Tree = track("typedTypeDef") { @@ -1506,6 +1510,7 @@ class Typer extends Namer checkVariance(impl1) if (!cls.is(AbstractOrTrait) && !ctx.isAfterTyper) checkRealizableBounds(cls, cdef.namePos) + if (cls.is(Case) && cls.derivesFrom(defn.EnumClass)) checkEnum(cdef, cls) val cdef1 = assignType(cpy.TypeDef(cdef)(name, impl1), cls) if (ctx.phase.isTyper && cdef1.tpe.derivesFrom(defn.DynamicClass) && !ctx.dynamicsEnabled) { val isRequired = parents1.exists(_.tpe.isRef(defn.DynamicClass)) @@ -1813,6 +1818,8 @@ class Typer extends Namer def typedStats(stats: List[untpd.Tree], exprOwner: Symbol)(implicit ctx: Context): List[tpd.Tree] = { val buf = new mutable.ListBuffer[Tree] + val enumContexts = new mutable.HashMap[Symbol, Context] + // A map from `enum` symbols to the contexts enclosing their definitions @tailrec def traverse(stats: List[untpd.Tree])(implicit ctx: Context): List[Tree] = stats match { case (imp: untpd.Import) :: rest => val imp1 = typed(imp) @@ -1827,6 +1834,12 @@ class Typer extends Namer case mdef1: DefDef if Inliner.hasBodyToInline(mdef1.symbol) => buf ++= inlineExpansion(mdef1) case mdef1 => + import untpd.modsDeco + mdef match { + case mdef: untpd.TypeDef if mdef.mods.hasMod[untpd.Mod.Enum] => + enumContexts(mdef1.symbol) = ctx + case _ => + } buf += mdef1 } traverse(rest) @@ -1846,7 +1859,7 @@ class Typer extends Namer val exprOwnerOpt = if (exprOwner == ctx.owner) None else Some(exprOwner) ctx.withProperty(ExprOwner, exprOwnerOpt) } - traverse(stats)(localCtx) + checkEnumCompanions(traverse(stats)(localCtx), enumContexts) } /** Given an inline method `mdef`, the method rewritten so that its body diff --git a/compiler/test/dotty/tools/dotc/reporting/ErrorMessagesTests.scala b/compiler/test/dotty/tools/dotc/reporting/ErrorMessagesTests.scala index 9d950953f550..6decf37b67ab 100644 --- a/compiler/test/dotty/tools/dotc/reporting/ErrorMessagesTests.scala +++ b/compiler/test/dotty/tools/dotc/reporting/ErrorMessagesTests.scala @@ -986,19 +986,6 @@ class ErrorMessagesTests extends ErrorMessagesTest { assertEquals(parent.show, "class A") } - @Test def enumCaseDefinitionInNonEnumOwner = - checkMessagesAfter("frontend") { - """object Qux { - | case Foo - |} - """.stripMargin - }.expect { (ictx, messages) => - implicit val ctx: Context = ictx - assertMessageCount(1, messages) - val EnumCaseDefinitionInNonEnumOwner(owner) :: Nil = messages - assertEquals("object Qux", owner.show) - } - @Test def tailrecNotApplicableNeitherPrivateNorFinal = checkMessagesAfter("tailrec") { """ diff --git a/docs/docs/internals/syntax.md b/docs/docs/internals/syntax.md index 40825d221589..f5084ce6d3dd 100644 --- a/docs/docs/internals/syntax.md +++ b/docs/docs/internals/syntax.md @@ -290,16 +290,6 @@ AccessQualifier ::= ‘[’ (id | ‘this’) ‘]’ Annotation ::= ‘@’ SimpleType {ParArgumentExprs} Apply(tpe, args) -TemplateBody ::= [nl] ‘{’ [SelfType] TemplateStat {semi TemplateStat} ‘}’ (self, stats) -TemplateStat ::= Import - | {Annotation [nl]} {Modifier} Def - | {Annotation [nl]} {Modifier} Dcl - | EnumCaseStat - | Expr1 - | -SelfType ::= id [‘:’ InfixType] ‘=>’ ValDef(_, name, tpt, _) - | ‘this’ ‘:’ InfixType ‘=>’ - Import ::= ‘import’ ImportExpr {‘,’ ImportExpr} ImportExpr ::= StableId ‘.’ (id | ‘_’ | ImportSelectors) Import(expr, sels) ImportSelectors ::= ‘{’ {ImportSelector ‘,’} (ImportSelector | ‘_’) ‘}’ @@ -335,19 +325,14 @@ DefDef ::= DefSig [‘:’ Type] ‘=’ Expr | ‘this’ DefParamClause DefParamClauses DefDef(_, , Nil, vparamss, EmptyTree, expr | Block) (‘=’ ConstrExpr | [nl] ConstrBlock) -TmplDef ::= ([‘case’ | `enum'] ‘class’ | trait’) ClassDef +TmplDef ::= ([‘case’] ‘class’ | trait’) ClassDef | [‘case’] ‘object’ ObjectDef | `enum' EnumDef ClassDef ::= id ClassConstr TemplateOpt ClassDef(mods, name, tparams, templ) ClassConstr ::= [ClsTypeParamClause] [ConstrMods] ClsParamClauses with DefDef(_, , Nil, vparamss, EmptyTree, EmptyTree) as first stat ConstrMods ::= {Annotation} [AccessModifier] ObjectDef ::= id TemplateOpt ModuleDef(mods, name, template) // no constructor -EnumDef ::= id ClassConstr [`extends' [ConstrApps]] EnumDef(mods, name, tparams, template) - [nl] ‘{’ EnumCaseStat {semi EnumCaseStat} ‘}’ -EnumCaseStat ::= {Annotation [nl]} {Modifier} EnumCase -EnumCase ::= `case' (EnumClassDef | ObjectDef | ids) -EnumClassDef ::= id [ClsTpeParamClause | ClsParamClause] ClassDef(mods, name, tparams, templ) - ClsParamClauses TemplateOpt +EnumDef ::= id ClassConstr [`extends' [ConstrApps]] EnumBody EnumDef(mods, name, tparams, template) TemplateOpt ::= [‘extends’ Template | [nl] TemplateBody] Template ::= ConstrApps [TemplateBody] | TemplateBody Template(constr, parents, self, stats) ConstrApps ::= ConstrApp {‘with’ ConstrApp} @@ -357,6 +342,20 @@ ConstrExpr ::= SelfInvocation SelfInvocation ::= ‘this’ ArgumentExprs {ArgumentExprs} ConstrBlock ::= ‘{’ SelfInvocation {semi BlockStat} ‘}’ +TemplateBody ::= [nl] ‘{’ [SelfType] TemplateStat {semi TemplateStat} ‘}’ (self, stats) +TemplateStat ::= Import + | {Annotation [nl]} {Modifier} Def + | {Annotation [nl]} {Modifier} Dcl + | Expr1 + | +SelfType ::= id [‘:’ InfixType] ‘=>’ ValDef(_, name, tpt, _) + | ‘this’ ‘:’ InfixType ‘=>’ + +EnumBody ::= [nl] ‘{’ [SelfType] EnumStat {semi EnumStat} ‘}’ +EnumStat ::= TemplateStat + | {Annotation [nl]} {Modifier} EnumCase +EnumCase ::= `case' (id ClassConstr [`extends' ConstrApps]] | ids) + TopStatSeq ::= TopStat {semi TopStat} TopStat ::= {Annotation [nl]} {Modifier} TmplDef | Import diff --git a/docs/docs/reference/enums/adts.md b/docs/docs/reference/enums/adts.md index 92851057fa5d..1d5c67d4f731 100644 --- a/docs/docs/reference/enums/adts.md +++ b/docs/docs/reference/enums/adts.md @@ -10,28 +10,28 @@ how an `Option` type can be represented as an ADT: ```scala enum Option[+T] { - case Some[+T](x: T) + case Some(x: T) case None } ``` -This example introduces `Option` enum class with a covariant type -parameter `T`, together with two cases, `Some` and `None`. `Some` is -parameterized with a type parameter `T` and a value parameter `x`. It -is a shorthand for writing a case class that extends `Option`. Since -`None` is not parameterized it is treated as a normal enum value. +This example introduces an `Option` enum with a covariant type +parameter `T` consisting of two cases, `Some` and `None`. `Some` is +parameterized with a value parameter `x`. It is a shorthand for writing a +case class that extends `Option`. Since `None` is not parameterized, it +is treated as a normal enum value. The `extends` clauses that were omitted in the example above can also be given explicitly: ```scala enum Option[+T] { - case Some[+T](x: T) extends Option[T] - case None extends Option[Nothing] + case Some(x: T) extends Option[T] + case None extends Option[Nothing] } ``` -Note that the parent type of `None` is inferred as +Note that the parent type of the `None` value is inferred as `Option[Nothing]`. Generally, all covariant type parameters of the enum class are minimized in a compiler-generated extends clause whereas all contravariant type parameters are maximized. If `Option` was non-variant, @@ -59,24 +59,22 @@ scala> new Option.Some(2) val res3: t2.Option.Some[Int] = Some(2) ``` -As all other enums, ADTs can have methods on both the enum class and -its companion object. For instance, here is `Option` again, with an -`isDefined` method and an `Option(...)` constructor. +As all other enums, ADTs can define methods. For instance, here is `Option` again, with an +`isDefined` method and an `Option(...)` constructor in its companion object. ```scala -enum class Option[+T] { - def isDefined: Boolean +enum Option[+T] { + case Some(x: T) extends Option[T] + case None + + def isDefined: Boolean = this match { + case None => false + case some => true + } } object Option { def apply[T >: Null](x: T): Option[T] = if (x == null) None else Some(x) - - case Some[+T](x: T) { - def isDefined = true - } - case None { - def isDefined = false - } } ``` @@ -98,23 +96,20 @@ enum Color(val rgb: Int) { ### Syntax of Enums -Changes to the syntax fall in two categories: enum classes and cases inside enums. +Changes to the syntax fall in two categories: enum definitions and cases inside enums. The changes are specified below as deltas with respect to the Scala syntax given [here](http://dotty.epfl.ch/docs/internals/syntax.html) - 1. Enum definitions and enum classes are defined as follows: + 1. Enum definitions are defined as follows: - TmplDef ::= `enum' `class’ ClassDef - | `enum' EnumDef - EnumDef ::= id ClassConstr [`extends' [ConstrApps]] - [nl] `{’ EnumCaseStat {semi EnumCaseStat} `}’ + TmplDef ::= `enum' EnumDef + EnumDef ::= id ClassConstr [`extends' [ConstrApps]] EnumBody + EnumBody ::= [nl] ‘{’ [SelfType] EnumStat {semi EnumStat} ‘}’ + EnumStat ::= TemplateStat + | {Annotation [nl]} {Modifier} EnumCase 2. Cases of enums are defined as follows: - EnumCaseStat ::= {Annotation [nl]} {Modifier} EnumCase - EnumCase ::= `case' (EnumClassDef | ObjectDef | ids) - EnumClassDef ::= id [ClsTpeParamClause | ClsParamClause] - ClsParamClauses TemplateOpt - TemplateStat ::= ... | EnumCaseStat + EnumCase ::= `case' (id ClassConstr [`extends' ConstrApps]] | ids) ### Reference diff --git a/docs/docs/reference/enums/desugarEnums.md b/docs/docs/reference/enums/desugarEnums.md index 1d3c1c448313..ac53adef5911 100644 --- a/docs/docs/reference/enums/desugarEnums.md +++ b/docs/docs/reference/enums/desugarEnums.md @@ -3,7 +3,7 @@ layout: doc-page title: "Translation of Enums and ADTs" --- -The compiler expands enum classes and cases to code that only uses +The compiler expands enums and their cases to code that only uses Scala's other language features. As such, enums in Scala are convenient _syntactic sugar_, but they are not essential to understand Scala's core. @@ -11,103 +11,103 @@ Scala's core. We now explain the expansion of enums in detail. First, some terminology and notational conventions: - - We use `E` as a name of an enum class, and `C` as a name of an enum case that appears in the companion object of `E`. - - We use `<...>` for syntactic constructs that in some circumstances might be empty. For instance `` represents either the body of a case between `{...}` or nothing at all. + - We use `E` as a name of an enum, and `C` as a name of a case that appears in `E`. + - We use `<...>` for syntactic constructs that in some circumstances might be empty. For instance, + `` represents one or more a parameter lists `(...)` or nothing at all. - Enum cases fall into three categories: - _Class cases_ are those cases that are parameterized, either with a type parameter section `[...]` or with one or more (possibly empty) parameter sections `(...)`. - - _Simple cases_ are cases of a non-generic enum class that have neither parameters nor an extends clause or body. That is, they consist of a name only. + - _Simple cases_ are cases of a non-generic enum that have neither parameters nor an extends clause or body. That is, they consist of a name only. - _Value cases_ are all cases that do not have a parameter section but that do have a (possibly generated) extends clause and/or a body. Simple cases and value cases are collectively called _singleton cases_. The desugaring rules imply that class cases are mapped to case classes, and singleton cases are mapped to `val` definitions. -There are eight desugaring rules. Rules (1) and (2) desugar enums and -enum classes. Rules (3) and (4) define extends clauses for cases that -are missing them. Rules (5 - 7) define how such expanded cases map -into case classes, case objects or vals. Finally, rule (8) expands -comma separated simple cases into a sequence of cases. +There are eight desugaring rules. Rule (1) desugar enum definitions. Rules +(2) and (3) desugar simple cases. Rules (4) to (6) define extends clauses for cases that +are missing them. Rules (7) and (8) define how such cases with extends clauses +map into case classes or vals. 1. An `enum` definition - enum E ... { } + enum E ... { } - expands to an enum class and a companion object + expands to a `sealed` `abstract` class that extends the `scala.Enum` trait and + an associated companion object that contains the defined cases, expanded according + to rules (2 - 8). The enum trait starts with a compiler-generated import that imports + the names `` of all cases so that they can be used without prefix in the trait. - enum class E ... + sealed abstract class E ... extends with scala.Enum { + import E.{ } + + } object E { } -2. An enum class definition +2. A simple case consisting of a comma-separated list of enum names - enum class E ... extends ... + case C_1, ..., C_n - expands to a `sealed` `abstract` class that extends the `scala.Enum` trait: + expands to - sealed abstract class E ... extends with scala.Enum ... + case C_1; ...; case C_n -3. If `E` is an enum class without type parameters, then a case in its companion object without an extends clause + Any modifiers or annotations on the original case extend to all expanded + cases. - case C +3. A simple case - expands to + case C - case C extends E + of an enum `E` that does not take type parameters expands to -4. If `E` is an enum class with type parameters `Ts`, then a case in its - companion object without an extends clause + val C = $new(n, "C") - case C + Here, `$new` is a private method that creates an instance of of `E` (see + below). - expands according to two alternatives, depending whether `C` has type - parameters or not. If `C` has type parameters, they must have the same - names and appear in the same order as the enum type parameters `Ts` - (variances may be different, however). In this case +4. If `E` is an enum with type parameters - case C [Ts] + V1 T1 > L1 <: U1 , ... , Vn Tn >: Ln <: Un (n > 0) - expands to + where each of the variances `Vi` is either `'+'` or `'-'`, then a simple case - case C[Ts] extends E[Ts] + case C - For the case where `C` does not have type parameters, assume `E`'s type - parameters are + expands to - V1 T1 > L1 <: U1 , ... , Vn Tn >: Ln <: Un (n > 0) + case C extends E[B1, ..., Bn] - where each of the variances `Vi` is either `'+'` or `'-'`. Then the case - expands to + where `Bi` is `Li` if `Vi = '+'` and `Ui` if `Vi = '-'`. This result is then further + rewritten with rule (7). Simple cases of enums with non-variant type + parameters are not permitted. - case C extends E[B1, ..., Bn] +5. A class case without an extends clause - where `Bi` is `Li` if `Vi = '+'` and `Ui` if `Vi = '-'`. It is an error if - `Bi` refers to some other type parameter `Tj (j = 0,..,n-1)`. It is also - an error if `E` has type parameters that are non-variant. + case C -5. A class case + of an enum `E` that does not take type parameters expands to - case C ... + case C extends E - expands analogous to a final case class: + This result is then further rewritten with rule (8). - final case class C ... +6. If `E` is an enum with type parameters `Ts`, a class case with neither type parameters nor an extends clause - 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 - the form + case C - def enumTag = n + expands to - where `n` is the ordinal number of the case in the companion object, - starting from 0. + case C[Ts] extends E[Ts] + + This result is then further rewritten with rule (8). For class cases that have type parameters themselves, an extends clause needs to be given explicitly. -6. A value case +7. A value case - case C extends + case C extends - expands to a value definition + expands to a value definition in `E`'s companion object: val C = new { ; def enumTag = n; $values.register(this) } @@ -116,27 +116,23 @@ comma separated simple cases into a sequence of cases. as one of the `enumValues` of the enumeration (see below). `$values` is a compiler-defined private value in the companion object. -7. A simple case +8. A class case - case C + case C extends - of an enum class `E` that does not take type parameters expands to + expands analogous to a final case class in `E`'s companion object: - val C = $new(n, "C") + final case class C extends - Here, `$new` is a private method that creates an instance of of `E` (see - below). - -8. A simple case consisting of a comma-separated list of enum names - - case C_1, ..., C_n - - expands to + 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 + the form - case C_1; ...; case C_n + def enumTag = n - Any modifiers or annotations on the original case extend to all expanded - cases. + where `n` is the ordinal number of the case in the companion object, + starting from 0. ### Equality @@ -149,7 +145,7 @@ be compared only to other values of the same enum type. Furtermore, generic ### Translation of Enumerations -Non-generic enum classes `E` that define one or more singleton cases +Non-generic enums `E` that define one or more singleton cases are called _enumerations_. Companion objects of enumerations define the following additional members. @@ -161,7 +157,7 @@ the following additional members. - A method `enumValues` which returns an `Iterable[E]` of all singleton case values in `E`, in the order of their definitions. -Companion objects that contain at least one simple case define in addition: +Companion objects of enumerations that contain at least one simple case define in addition: - A private method `$new` which defines a new simple case value with given ordinal number and name. This method can be thought as being defined as @@ -172,3 +168,17 @@ Companion objects that contain at least one simple case define in addition: def toString = name $values.register(this) // register enum value so that `valueOf` and `values` can return 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 +identifiers. + +Even though translated enum cases are located in the enum's companion object, referencing +this object or its members via `this` or a simple identifier is also illegal. The compiler typechecks enum cases in the scope of the enclosing companion object but flags any such illegal accesses as errors. + +### Other Rules + +A normal case class which is not produced from an enum case is not allowed to extend +`scala.Enum`. This ensures that the only cases of an enum are the ones that are +explictly declared in it. \ No newline at end of file diff --git a/docs/docs/reference/enums/enums.md b/docs/docs/reference/enums/enums.md index 48e9646542ef..3868961e3a1f 100644 --- a/docs/docs/reference/enums/enums.md +++ b/docs/docs/reference/enums/enums.md @@ -13,22 +13,11 @@ enum Color { This defines a new `sealed` class, `Color`, with three values, `Color.Red`, `Color.Green`, `Color.Blue`. The color values are members of `Color`s -companion object. The `Color` definition above is equivalent to the -following more explicit definition of an _enum class_ and a companion -object: - -```scala -enum class Color -object Color { - case Red - case Green - case Blue -} -``` +companion object. ### Parameterized enums -Enum classes can be parameterized. +Enums can be parameterized. ```scala enum Color(val rgb: Int) { @@ -53,7 +42,7 @@ scala> red.enumTag val res0: Int = 0 ``` -The companion object of an enum class also defines three utility methods. +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`. @@ -69,20 +58,14 @@ val res3: collection.Iterable[Color] = MapLike(Red, Green, Blue) ### User-defined members of enums -It is possible to add your own definitions to an enum class or its -companion object. To make clear what goes where you need to use the -longer syntax which defines an enum class alongside its companion -object explicitly. In the following example, we define some methods in -class `Planet` and a `main` method in its companion object. +It is possible to add your own definitions to an enum. Example: ```scala -enum class Planet(mass: Double, radius: Double) { +enum Planet(mass: Double, radius: Double) { private final val G = 6.67300E-11 def surfaceGravity = G * mass / (radius * radius) def surfaceWeight(otherMass: Double) = otherMass * surfaceGravity -} -object Planet { case MERCURY extends Planet(3.303e+23, 2.4397e6) case VENUS extends Planet(4.869e+24, 6.0518e6) case EARTH extends Planet(5.976e+24, 6.37814e6) @@ -91,7 +74,13 @@ object Planet { case SATURN extends Planet(5.688e+26, 6.0268e7) case URANUS extends Planet(8.686e+25, 2.5559e7) case NEPTUNE extends Planet(1.024e+26, 2.4746e7) +} +``` + +It is also possible to define an explicit companion object for an enum: +```scala +object Planet { def main(args: Array[String]) = { val earthWeight = args(0).toDouble val mass = earthWeight/EARTH.surfaceGravity @@ -103,7 +92,7 @@ object Planet { ### Implementation -Enum classes are represented as `sealed` classes that extend the `scala.Enum` trait. +Enums are represented as `sealed` classes that extend the `scala.Enum` trait. This trait defines a single method, `enumTag`: ```scala @@ -140,4 +129,5 @@ val Red: Color = $new(0, "Red") ### Reference -For more info, see [Issue #1970](https://github.com/lampepfl/dotty/issues/1970). +For more info, see [Issue #1970](https://github.com/lampepfl/dotty/issues/1970) and +[PR #4003](https://github.com/lampepfl/dotty/pull/4003). diff --git a/tests/neg/constrParams.scala b/tests/neg/constrParams.scala new file mode 100644 index 000000000000..38805a2e39e7 --- /dev/null +++ b/tests/neg/constrParams.scala @@ -0,0 +1,8 @@ + + +class C { + type INT = Int + class I + def this(x: INT) = this() // error: illegal access + def this(x: I) = this() // error: illegal access +} diff --git a/tests/run/enum-List3.scala b/tests/neg/enum-List3.scala similarity index 59% rename from tests/run/enum-List3.scala rename to tests/neg/enum-List3.scala index 8275e8bc3264..3229cdd82dec 100644 --- a/tests/run/enum-List3.scala +++ b/tests/neg/enum-List3.scala @@ -1,6 +1,6 @@ enum List[+T] { - case Cons[T](x: T, xs: List[T]) - case Nil + case Cons[T](x: T, xs: List[T]) // error: missing extends + case Nil extends List[Nothing] } object Test { import List._ diff --git a/tests/neg/enums.scala b/tests/neg/enums.scala index a7c641d11d12..1ff70ed9abcf 100644 --- a/tests/neg/enums.scala +++ b/tests/neg/enums.scala @@ -1,12 +1,8 @@ package enums enum List[+T] { - case Cons[T](x: T, xs: List[T]) // ok - case Snoc[U](xs: List[U], x: U) // error: different type parameters -} - -enum class X { - case Y // error: case not allowed here + case Cons[T](x: T, xs: List[T]) // error: missing extends + case Snoc[U](xs: List[U], x: U) // error: missing extends } enum E1[T] { @@ -21,8 +17,15 @@ enum E3[-T <: Ordered[T]] { case C // error: cannot determine type argument } +enum E4 { + case C +} + +case class C4() extends E4 // error: cannot extend enum +case object O4 extends E4 // error: cannot extend enum + enum Option[+T] { - case Some[T](x: T) + case Some(x: T) case None } diff --git a/tests/neg/enumsAccess.scala b/tests/neg/enumsAccess.scala new file mode 100644 index 000000000000..c35beba60e11 --- /dev/null +++ b/tests/neg/enumsAccess.scala @@ -0,0 +1,96 @@ +package enums + +object test1 { + + enum E4 { + case C1(x: INT) // error: illegal reference + case C2(x: Int = defaultX) // error: illegal reference + case C3[T <: INT] // error: illegal reference + } + + object E4 { + type INT = Integer + val defaultX = 2 + } +} + +object test2 { + import E5._ + object E5 { + type INT = Integer + val defaultX = 2 + } + + enum E5 { + case C1(x: INT) // ok + case C2(x: Int = defaultX) // ok + case C3[T <: INT] // ok + } +} + +object test3 { + object E5 { + type INT = Integer + val defaultX = 2 + } + + import E5._ + + enum E5 { + case C1(x: INT) // ok + case C2(x: Int = defaultX)// ok + case C3[T <: INT] // ok + } +} + +object test4 { + + enum E5 { + case C1(x: INT) // error: illegal reference + case C2(x: Int = defaultX) // error: illegal reference + case C3[T <: INT] // error: illegal reference + } + + import E5._ + + object E5 { + type INT = Integer + val defaultX = 2 + } +} + +object test5 { + enum E5[T](x: T) { + case C3() extends E5[INT](defaultX)// error: illegal reference // error: illegal reference + case C4 extends E5[INT](defaultX) // error: illegal reference // error: illegal reference + case C5 extends E5[E5[_]](E5.this) // error: type mismatch + } + + object E5 { + type INT = Integer + val defaultX = 2 + } +} + +object test6 { + import E5._ + enum E5[T](x: T) { + case C3() extends E5[INT](defaultX) // ok + case C4 extends E5[INT](defaultX) // ok + } + + object E5 { + type INT = Integer + val defaultX = 2 + } +} + +object test7 { + + trait Arg + + enum E(x: Arg) { + case C extends E(this) // error: illegal reference to `this` + } + object E extends Arg +} diff --git a/tests/patmat/enum-Tree.scala b/tests/patmat/enum-Tree.scala index 68e4fc012545..25993f459875 100644 --- a/tests/patmat/enum-Tree.scala +++ b/tests/patmat/enum-Tree.scala @@ -5,7 +5,7 @@ enum Tree[T] { case Succ(n: Tree[Int]) extends Tree[Int] case Pred(n: Tree[Int]) extends Tree[Int] case IsZero(n: Tree[Int]) extends Tree[Boolean] - case If[T](cond: Tree[Boolean], thenp: Tree[T], elsep: Tree[T]) + case If[X](cond: Tree[Boolean], thenp: Tree[X], elsep: Tree[X]) extends Tree[X] } object Test { diff --git a/tests/patmat/planets.scala b/tests/patmat/planets.scala index bcbfd7eeb3cc..22c731428f70 100644 --- a/tests/patmat/planets.scala +++ b/tests/patmat/planets.scala @@ -1,9 +1,8 @@ -enum class Planet(mass: Double, radius: Double) { +enum Planet(mass: Double, radius: Double) { private final val G = 6.67300E-11 def surfaceGravity = G * mass / (radius * radius) def surfaceWeight(otherMass: Double) = otherMass * surfaceGravity -} -object Planet { + case MERCURY extends Planet(3.303e+23, 2.4397e6) case VENUS extends Planet(4.869e+24, 6.0518e6) case EARTH extends Planet(5.976e+24, 6.37814e6) diff --git a/tests/pos/enum-List-control.scala b/tests/pos/enum-List-control.scala index d9df176d16f6..b269df43b242 100644 --- a/tests/pos/enum-List-control.scala +++ b/tests/pos/enum-List-control.scala @@ -1,11 +1,17 @@ abstract sealed class List[T] extends Enum object List { - final case class Cons[T](x: T, xs: List[T]) extends List[T] { + final class Cons[T](x: T, xs: List[T]) extends List[T] { def enumTag = 0 } - final case class Nil[T]() extends List[T] { + 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 } + object Nil { + def apply[T](): List[T] = new Nil() + } } object Test { import List._ diff --git a/tests/pos/i3460.scala b/tests/pos/i3460.scala index 5ba4e6021cdf..6f5c39155719 100644 --- a/tests/pos/i3460.scala +++ b/tests/pos/i3460.scala @@ -1,7 +1,7 @@ -enum class Ducks - -object Ducks { +enum Ducks { case Dewey +} +object Ducks { def wooohoo: Int = 1 } diff --git a/tests/pos/objXfun.scala b/tests/pos/objXfun.scala index c05a08d9ba41..8b1dfbc84390 100644 --- a/tests/pos/objXfun.scala +++ b/tests/pos/objXfun.scala @@ -2,7 +2,6 @@ object Foo extends (Int => Int) { // OK def apply(x: Int) = x } -enum class E(x: Int) // used to generate Int => new E(x) as the parent of object E --> crash -object E { +enum E(x: Int) { // used to generate Int => new E(x) as the parent of object E --> crash case C(x: Int) extends E(x) } diff --git a/tests/pos/patmat.scala b/tests/pos/patmat.scala index 87b5e2ef372b..94d4580c5267 100644 --- a/tests/pos/patmat.scala +++ b/tests/pos/patmat.scala @@ -1,5 +1,4 @@ object Test { - val xs = List(1, 2, 3) xs match { @@ -36,7 +35,7 @@ object Test { } enum Option[+T] { - case Some[+T](value: T) + case Some(value: T) case None } import Option._ diff --git a/tests/pos/reference/adts.scala b/tests/pos/reference/adts.scala index 6fc670ada71b..716211f8b10f 100644 --- a/tests/pos/reference/adts.scala +++ b/tests/pos/reference/adts.scala @@ -2,7 +2,7 @@ package adts object t1 { enum Option[+T] { - case Some[T](x: T) + case Some(x: T) case None } @@ -11,8 +11,8 @@ enum Option[+T] { object t2 { enum Option[+T] { - case Some[T](x: T) extends Option[T] - case None extends Option[Nothing] + case Some(x: T) extends Option[T] + case None extends Option[Nothing] } @@ -27,19 +27,18 @@ enum Color(val rgb: Int) { object t3 { -enum class Option[+T] { - def isDefined: Boolean +enum Option[+T] { + case Some(x: T) extends Option[T] + case None + + def isDefined: Boolean = this match { + case None => false + case some => true + } } object Option { def apply[T >: Null](x: T): Option[T] = if (x == null) None else Some(x) - - case Some[+T](x: T) { - def isDefined = true - } - case None { - def isDefined = false - } } } diff --git a/tests/pos/reference/enums.scala b/tests/pos/reference/enums.scala index 1db1b055d9b0..71179c7aed61 100644 --- a/tests/pos/reference/enums.scala +++ b/tests/pos/reference/enums.scala @@ -10,43 +10,29 @@ enum Color { object t2 { -enum class Color -object Color { +enum Color { case Red case Green case Blue } - } object t3 { -enum class Color(val rgb: Int) -object Color { - case Red extends Color(0xFF0000) - case Green extends Color(0x00FF00) - case Blue extends Color(0x0000FF) -} - -} - -object t4 { - enum Color(val rgb: Int) { case Red extends Color(0xFF0000) case Green extends Color(0x00FF00) case Blue extends Color(0x0000FF) } + } -enum class Planet(mass: Double, radius: Double) { +enum Planet(mass: Double, radius: Double) { private final val G = 6.67300E-11 def surfaceGravity = G * mass / (radius * radius) def surfaceWeight(otherMass: Double) = otherMass * surfaceGravity -} -object Planet { case MERCURY extends Planet(3.303e+23, 2.4397e6) case VENUS extends Planet(4.869e+24, 6.0518e6) case EARTH extends Planet(5.976e+24, 6.37814e6) @@ -55,7 +41,9 @@ object Planet { case SATURN extends Planet(5.688e+26, 6.0268e7) case URANUS extends Planet(8.686e+25, 2.5559e7) case NEPTUNE extends Planet(1.024e+26, 2.4746e7) +} +object Planet { def main(args: Array[String]) = { val earthWeight = args(0).toDouble val mass = earthWeight/EARTH.surfaceGravity diff --git a/tests/run/enum-Color.scala b/tests/run/enum-Color.scala index f4f6aaef84d5..31b850329407 100644 --- a/tests/run/enum-Color.scala +++ b/tests/run/enum-Color.scala @@ -1,5 +1,6 @@ enum Color { case Red, Green, Blue + class Color // Just to throw a spanner in the works } object Test { diff --git a/tests/run/enum-List1.scala b/tests/run/enum-List1.scala index 03a81bd991bd..f3e672e53571 100644 --- a/tests/run/enum-List1.scala +++ b/tests/run/enum-List1.scala @@ -1,7 +1,6 @@ -enum class List[T] -object List { - case Cons[T](x: T, xs: List[T]) - case Nil[T]() +enum List[T] { + case Cons(x: T, xs: List[T]) + case Nil() } object Test { import List._ diff --git a/tests/run/enum-List2.scala b/tests/run/enum-List2.scala index 6a1d8d894b78..7346a6294e4f 100644 --- a/tests/run/enum-List2.scala +++ b/tests/run/enum-List2.scala @@ -1,7 +1,6 @@ -enum class List[+T] -object List { - case Cons[+T](x: T, xs: List[T]) - case Nil extends List[Nothing] +enum List[+T] { + case Cons(x: T, xs: List[T]) + case Nil } object Test { import List._ diff --git a/tests/run/enum-List2a.scala b/tests/run/enum-List2a.scala index d4f47c2da4ef..7346a6294e4f 100644 --- a/tests/run/enum-List2a.scala +++ b/tests/run/enum-List2a.scala @@ -1,6 +1,5 @@ -enum class List[+T] -object List { - case Cons[+T](x: T, xs: List[T]) +enum List[+T] { + case Cons(x: T, xs: List[T]) case Nil } object Test { diff --git a/tests/run/enum-List3.check b/tests/run/enum-List3.check deleted file mode 100644 index 1d4812de1f64..000000000000 --- a/tests/run/enum-List3.check +++ /dev/null @@ -1 +0,0 @@ -Cons(1,Cons(2,Cons(3,Nil))) diff --git a/tests/run/enum-Option.scala b/tests/run/enum-Option.scala index 364c35fa0b6d..293b48a49e6c 100644 --- a/tests/run/enum-Option.scala +++ b/tests/run/enum-Option.scala @@ -1,18 +1,18 @@ -enum class Option[+T >: Null] extends Serializable { - def isDefined: Boolean +enum Option[+T] extends Serializable { + case Some(x: T) + case None + + def isDefined: Boolean = this match { + case None => false + case some => true + } } object Option { def apply[T >: Null](x: T): Option[T] = if (x == null) None else Some(x) - - case Some[+T >: Null](x: T) { - def isDefined = true - } - case None { - def isDefined = false - } } object Test { + import Option._ def main(args: Array[String]) = { assert(Some(None).isDefined) Option("22") match { case Option.Some(x) => assert(x == "22") } diff --git a/tests/run/enum-Option1.scala b/tests/run/enum-Option1.scala new file mode 100644 index 000000000000..a3d5767244f6 --- /dev/null +++ b/tests/run/enum-Option1.scala @@ -0,0 +1,21 @@ +enum Option1[+T] extends Serializable { + case Some1(x: T) + case None1 + + def isDefined: Boolean = this match { + case None1 => false + case some => true + } +} +object Option1 { + def apply[T >: Null](x: T): Option1[T] = if (x == null) None1 else Some1(x) +} + +object Test { + import Option1._ + def main(args: Array[String]) = { + assert(Some1(None1).isDefined) + Option1("22") match { case Option1.Some1(x) => assert(x == "22") } + assert(Some1(None1) != None1) + } +} diff --git a/tests/run/enum-Tree.scala b/tests/run/enum-Tree.scala index 68e4fc012545..ef5bd7a5714e 100644 --- a/tests/run/enum-Tree.scala +++ b/tests/run/enum-Tree.scala @@ -5,7 +5,7 @@ enum Tree[T] { case Succ(n: Tree[Int]) extends Tree[Int] case Pred(n: Tree[Int]) extends Tree[Int] case IsZero(n: Tree[Int]) extends Tree[Boolean] - case If[T](cond: Tree[Boolean], thenp: Tree[T], elsep: Tree[T]) + case If(cond: Tree[Boolean], thenp: Tree[T], elsep: Tree[T]) } object Test { diff --git a/tests/run/enum-approx.scala b/tests/run/enum-approx.scala index 7811b3909bf0..f18af97df40a 100644 --- a/tests/run/enum-approx.scala +++ b/tests/run/enum-approx.scala @@ -1,14 +1,14 @@ -enum class Fun[-T, +U >: Null] { - def f: T => U = null -} -object Fun { - case Identity[T, U >: Null](override val f: T => U) extends Fun[T, U] - case ConstNull { - override def f = x => null - } - case ConstNullClass() { - override def f = x => null +enum Fun[-T, +U >: Null] { + def f: T => U = this match { + case Identity(g) => g + case ConstNull => (_ => null) + case ConstNullClass() => (_ => null) + case ConstNullSimple => null } + + case Identity[T, U >: Null](g: T => U) extends Fun[T, U] + case ConstNull + case ConstNullClass() case ConstNullSimple } diff --git a/tests/run/enums.scala b/tests/run/enums.scala index 7630b174ef38..1a515db40bda 100644 --- a/tests/run/enums.scala +++ b/tests/run/enums.scala @@ -100,6 +100,14 @@ object Test5 { } } +object Test6 { + enum Color(val x: Int) { + case Green extends Color(3) + case Red extends Color(2) + case Violet extends Color(Green.x + Red.x) + } +} + object SerializationTest { object Types extends Enumeration { val X, Y = Value } class A extends java.io.Serializable { val types = Types.values } diff --git a/tests/run/generic/Enum.scala b/tests/run/generic/Enum.scala new file mode 100644 index 000000000000..ce64ac054933 --- /dev/null +++ b/tests/run/generic/Enum.scala @@ -0,0 +1,25 @@ +package generic + +trait Enum { + def enumTag: Int +} + +object runtime { + class EnumValues[E <: Enum] { + private[this] var myMap: Map[Int, E] = Map() + private[this] var fromNameCache: Map[String, E] = null + + def register(v: E) = { + require(!myMap.contains(v.enumTag)) + myMap = myMap.updated(v.enumTag, 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 + } +} \ No newline at end of file diff --git a/tests/run/planets.scala b/tests/run/planets.scala index bcbfd7eeb3cc..22c731428f70 100644 --- a/tests/run/planets.scala +++ b/tests/run/planets.scala @@ -1,9 +1,8 @@ -enum class Planet(mass: Double, radius: Double) { +enum Planet(mass: Double, radius: Double) { private final val G = 6.67300E-11 def surfaceGravity = G * mass / (radius * radius) def surfaceWeight(otherMass: Double) = otherMass * surfaceGravity -} -object Planet { + case MERCURY extends Planet(3.303e+23, 2.4397e6) case VENUS extends Planet(4.869e+24, 6.0518e6) case EARTH extends Planet(5.976e+24, 6.37814e6)