From 0a4891153f65315473cac4294b0427a071efa70a Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 8 Feb 2017 21:08:49 +1100 Subject: [PATCH 01/37] Fix mal-formatting. Insert an empty line before "where" in an explanation. --- compiler/src/dotty/tools/dotc/core/Decorators.scala | 2 +- compiler/src/dotty/tools/dotc/printing/Formatting.scala | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Decorators.scala b/compiler/src/dotty/tools/dotc/core/Decorators.scala index 0e8ae196a8a2..c65dc5b974dd 100644 --- a/compiler/src/dotty/tools/dotc/core/Decorators.scala +++ b/compiler/src/dotty/tools/dotc/core/Decorators.scala @@ -185,7 +185,7 @@ object Decorators { * give more info about type variables and to disambiguate where needed. */ def ex(args: Any*)(implicit ctx: Context): String = - explained2(implicit ctx => em(args: _*)) + explained(implicit ctx => em(args: _*)) /** Formatter that adds syntax highlighting to all interpolated values */ def hl(args: Any*)(implicit ctx: Context): String = diff --git a/compiler/src/dotty/tools/dotc/printing/Formatting.scala b/compiler/src/dotty/tools/dotc/printing/Formatting.scala index 05f1af9d7a1e..760b2268910a 100644 --- a/compiler/src/dotty/tools/dotc/printing/Formatting.scala +++ b/compiler/src/dotty/tools/dotc/printing/Formatting.scala @@ -213,9 +213,11 @@ object Formatting { * ex"disambiguate $tpe1 and $tpe2" * ``` */ - def explained2(op: Context => String)(implicit ctx: Context): String = { + def explained(op: Context => String)(implicit ctx: Context): String = { val seen = new Seen - op(explainCtx(seen)) ++ explanations(seen) + val msg = op(explainCtx(seen)) + val addendum = explanations(seen) + if (addendum.isEmpty) msg else msg ++ "\n\n" ++ addendum } /** When getting a type mismatch it is useful to disambiguate placeholders like: From 669c5a8ed52f77942ee1ed7cf5813451d3762579 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 5 Feb 2017 15:55:44 +1100 Subject: [PATCH 02/37] Add enum syntax Modify syntax.md and Tokens/Parser/untpd to support enums. --- compiler/src/dotty/tools/dotc/ast/untpd.scala | 7 ++ .../dotty/tools/dotc/parsing/Parsers.scala | 116 ++++++++++++++---- .../src/dotty/tools/dotc/parsing/Tokens.scala | 4 + docs/docs/internals/syntax.md | 17 ++- 4 files changed, 115 insertions(+), 29 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/ast/untpd.scala b/compiler/src/dotty/tools/dotc/ast/untpd.scala index 9b55720b8817..e14c6714b59f 100644 --- a/compiler/src/dotty/tools/dotc/ast/untpd.scala +++ b/compiler/src/dotty/tools/dotc/ast/untpd.scala @@ -39,6 +39,9 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo { def withName(name: Name)(implicit ctx: Context) = cpy.ModuleDef(this)(name.toTermName, impl) } + /** mods case name impl */ + case class EnumDef(name: TypeName, impl: Template) extends MemberDef + case class ParsedTry(expr: Tree, handler: Tree, finalizer: Tree) extends TermTree case class SymbolLit(str: String) extends TermTree @@ -132,6 +135,10 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo { case class Inline() extends Mod(Flags.Inline) case class Type() extends Mod(Flags.EmptyFlags) + + case class Enum() extends Mod(Flags.EmptyFlags) + + case class EnumCase() extends Mod(Flags.EmptyFlags) } /** Modifiers and annotations for definitions diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index b644c94cc0e4..d3c10040682c 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -1873,15 +1873,16 @@ object Parsers { mods1 } - /** Def ::= val PatDef - * | var VarDef - * | def DefDef - * | type {nl} TypeDcl - * | TmplDef - * Dcl ::= val ValDcl - * | var ValDcl - * | def DefDcl - * | type {nl} TypeDcl + /** Def ::= val PatDef + * | var VarDef + * | def DefDef + * | type {nl} TypeDcl + * | TmplDef + * Dcl ::= val ValDcl + * | var ValDcl + * | def DefDcl + * | type {nl} TypeDcl + * EnumCase ::= `case' (EnumClassDef | ObjectDef) */ def defOrDcl(start: Int, mods: Modifiers): Tree = in.token match { case VAL => @@ -1896,6 +1897,8 @@ object Parsers { defDefOrDcl(start, posMods(start, mods), in.getDocComment(start)) case TYPE => typeDefOrDcl(start, posMods(start, mods), in.getDocComment(start)) + case CASE => + enumCase(start, mods, in.getDocComment(start)) case _ => tmplDef(start, mods) } @@ -2041,8 +2044,9 @@ object Parsers { } } - /** TmplDef ::= ([`case'] `class' | `trait') ClassDef - * | [`case'] `object' ObjectDef + /** TmplDef ::= ([`case' | `enum]'] ‘class’ | [`enum'] trait’) ClassDef + * | [`case' | `enum'] `object' ObjectDef + * | `enum' EnumDef */ def tmplDef(start: Int, mods: Modifiers): Tree = { val docstring = in.getDocComment(start) @@ -2057,29 +2061,39 @@ object Parsers { objectDef(start, posMods(start, mods | Module), docstring) case CASEOBJECT => objectDef(start, posMods(start, mods | Case | Module), docstring) + case ENUM => + val mods1 = addMod(mods, atPos(in.skipToken()) { Mod.Enum() }) + in.token match { + case CLASS | TRAIT | OBJECT => tmplDef(start, mods1) + case _ => enumDef(start, mods, docstring) + } case _ => syntaxErrorOrIncomplete("expected start of definition") EmptyTree } } - /** ClassDef ::= id [ClsTypeParamClause] - * [ConstrMods] ClsParamClauses TemplateOpt + /** ClassDef ::= id ClassConstr TemplateOpt */ def classDef(start: Offset, mods: Modifiers, docstring: Option[Comment]): TypeDef = atPos(start, nameStart) { - val name = ident().toTypeName - val constr = atPos(in.lastOffset) { - val tparams = typeParamClauseOpt(ParamOwner.Class) - val cmods = constrModsOpt(name) - val vparamss = paramClauses(name, mods is Case) + classDefRest(start, mods, docstring, ident().toTypeName) + } - makeConstructor(tparams, vparamss).withMods(cmods) - } + def classDefRest(start: Offset, mods: Modifiers, docstring: Option[Comment], name: TypeName): TypeDef = { + val constr = classConstr(name, isCaseClass = mods is Case) val templ = templateOpt(constr) - TypeDef(name, templ).withMods(mods).setComment(docstring) } + /** ClassConstr ::= [ClsTypeParamClause] [ConstrMods] ClsParamClauses + */ + def classConstr(owner: Name, isCaseClass: Boolean = false): DefDef = atPos(in.lastOffset) { + val tparams = typeParamClauseOpt(ParamOwner.Class) + val cmods = constrModsOpt(owner) + val vparamss = paramClauses(owner, isCaseClass) + makeConstructor(tparams, vparamss).withMods(cmods) + } + /** ConstrMods ::= AccessModifier * | Annotation {Annotation} (AccessModifier | `this') */ @@ -2094,12 +2108,64 @@ object Parsers { /** ObjectDef ::= id TemplateOpt */ def objectDef(start: Offset, mods: Modifiers, docstring: Option[Comment] = None): ModuleDef = atPos(start, nameStart) { - val name = ident() - val template = templateOpt(emptyConstructor) + objectDefRest(start, mods, docstring, ident()) + } + def objectDefRest(start: Offset, mods: Modifiers, docstring: Option[Comment] = None, name: TermName): ModuleDef = { + val template = templateOpt(emptyConstructor) ModuleDef(name, template).withMods(mods).setComment(docstring) } + /** id ClassConstr [`extends' [ConstrApps]] + * [nl] ‘{’ EnumCaseStats ‘}’ + */ + def enumDef(start: Offset, mods: Modifiers, docstring: Option[Comment] = None): EnumDef = atPos(start, nameStart) { + val name = ident().toTypeName + val constr = classConstr(name) + val parents = + if (in.token == EXTENDS) { + in.nextToken(); + newLineOptWhenFollowedBy(LBRACE) + if (in.token == LBRACE) Nil else tokenSeparated(WITH, constrApp) + } + else Nil + newLineOptWhenFollowedBy(LBRACE) + val body = inBraces(enumCaseStats) + EnumDef(name, Template(constr, Nil, EmptyValDef, body)) + .withMods(mods).setComment(in.getDocComment(start)).asInstanceOf[EnumDef] + } + + /** EnumCaseStats = EnumCaseStat {semi EnumCaseStat */ + def enumCaseStats(): List[MemberDef] = { + val cases = new ListBuffer[MemberDef] += enumCaseStat() + while (in.token != RBRACE) { + acceptStatSep() + cases += enumCaseStat() + } + cases.toList + } + + /** EnumCaseStat = {Annotation [nl]} {Modifier} EnumCase */ + def enumCaseStat(): MemberDef = { + val start = in.offset + val docstring = in.getDocComment(start) + val mods = defAnnotsMods(modifierTokens) + enumCase(start, mods, docstring) + } + + /** EnumCase = `case' (EnumClassDef | ObjectDef) */ + def enumCase(start: Offset, mods: Modifiers, docstring: Option[Comment]): MemberDef = { + val mods1 = mods.withAddedMod(atPos(in.offset)(Mod.EnumCase())) | Case + accept(CASE) + atPos(start, nameStart) { + val name = ident() + if (in.token == LBRACKET || in.token == LPAREN) + classDefRest(start, mods1, docstring, name.toTypeName) + else + objectDefRest(start, mods1, docstring, name) + } + } + /* -------- TEMPLATES ------------------------------------------- */ /** ConstrApp ::= SimpleType {ParArgumentExprs} @@ -2209,8 +2275,8 @@ object Parsers { * TemplateStat ::= Import * | Annotations Modifiers Def * | Annotations Modifiers Dcl + * | EnumCaseStat * | Expr1 - * | super ArgumentExprs {ArgumentExprs} * | */ def templateStatSeq(): (ValDef, List[Tree]) = checkNoEscapingPlaceholders { @@ -2240,7 +2306,7 @@ object Parsers { stats ++= importClause() else if (isExprIntro) stats += expr1() - else if (isDefIntro(modifierTokens)) + else if (isDefIntro(modifierTokensOrCase)) stats += defOrDcl(in.offset, defAnnotsMods(modifierTokens)) else if (!isStatSep) { exitOnError = mustStartStat diff --git a/compiler/src/dotty/tools/dotc/parsing/Tokens.scala b/compiler/src/dotty/tools/dotc/parsing/Tokens.scala index 8d42e525a27f..6109dda2a10e 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Tokens.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Tokens.scala @@ -92,6 +92,7 @@ abstract class TokensCommon { //final val THEN = 60; enter(THEN, "then") //final val FORSOME = 61; enter(FORSOME, "forSome") // TODO: deprecate //final val INLINE = 62; enter(INLINE, "inline") + //final val ENUM = 63; enter(ENUM, "enum") /** special symbols */ final val COMMA = 70; enter(COMMA, "','") @@ -175,6 +176,7 @@ object Tokens extends TokensCommon { final val THEN = 60; enter(THEN, "then") final val FORSOME = 61; enter(FORSOME, "forSome") // TODO: deprecate final val INLINE = 62; enter(INLINE, "inline") + final val ENUM = 63; enter(ENUM, "enum") /** special symbols */ final val NEWLINE = 78; enter(NEWLINE, "end of statement", "new line") @@ -228,6 +230,8 @@ object Tokens extends TokensCommon { final val modifierTokens = localModifierTokens | accessModifierTokens | BitSet( OVERRIDE) + final val modifierTokensOrCase = modifierTokens | BitSet(CASE) + /** Is token only legal as start of statement (eof also included)? */ final val mustStartStatTokens = defIntroTokens | modifierTokens | BitSet( IMPORT, PACKAGE) diff --git a/docs/docs/internals/syntax.md b/docs/docs/internals/syntax.md index 7f06cdc2a514..804a0f57abc3 100644 --- a/docs/docs/internals/syntax.md +++ b/docs/docs/internals/syntax.md @@ -289,7 +289,9 @@ TemplateBody ::= [nl] ‘{’ [SelfType] TemplateStat {semi TemplateStat} TemplateStat ::= Import | {Annotation [nl]} {Modifier} Def | {Annotation [nl]} {Modifier} Dcl + | EnumCaseStat | Expr1 + | SelfType ::= id [‘:’ InfixType] ‘=>’ ValDef(_, name, tpt, _) | ‘this’ ‘:’ InfixType ‘=>’ @@ -328,13 +330,20 @@ DefDef ::= DefSig [‘:’ Type] ‘=’ Expr | ‘this’ DefParamClause DefParamClauses DefDef(_, , Nil, vparamss, EmptyTree, expr | Block) (‘=’ ConstrExpr | [nl] ConstrBlock) -TmplDef ::= ([‘case’] ‘class’ | ‘trait’) ClassDef - | [‘case’] ‘object’ ObjectDef -ClassDef ::= id [ClsTypeParamClause] ClassDef(mods, name, tparams, templ) - [ConstrMods] ClsParamClauses TemplateOpt with DefDef(_, , Nil, vparamss, EmptyTree, EmptyTree) as first stat +TmplDef ::= ([‘case’ | `enum'] ‘class’ | [`enum'] trait’) ClassDef + | [‘case’ | `enum'] ‘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 ::= AccessModifier | Annotation {Annotation} (AccessModifier | ‘this’) 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) +EnumClassDef ::= id [ClsTpeParamClause | ClsParamClause] ClassDef(mods, name, tparams, templ) + ClsParamClauses TemplateOpt TemplateOpt ::= [‘extends’ Template | [nl] TemplateBody] Template ::= ConstrApps [TemplateBody] | TemplateBody Template(constr, parents, self, stats) ConstrApps ::= ConstrApp {‘with’ ConstrApp} From ca039bae393a1d32720d6517baae434b91eefefd Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 5 Feb 2017 16:16:54 +1100 Subject: [PATCH 03/37] Don't pass docstring as a parameter. It's completely redundant, docstring is just the comment found at the `start` offset, which is passed anyway. --- .../dotty/tools/dotc/parsing/Parsers.scala | 72 +++++++++---------- 1 file changed, 33 insertions(+), 39 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index d3c10040682c..1084b5784789 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -1888,17 +1888,17 @@ object Parsers { case VAL => val mod = atPos(in.skipToken()) { Mod.Val() } val mods1 = mods.withAddedMod(mod) - patDefOrDcl(start, mods1, in.getDocComment(start)) + patDefOrDcl(start, mods1) case VAR => val mod = atPos(in.skipToken()) { Mod.Var() } val mod1 = addMod(mods, mod) - patDefOrDcl(start, mod1, in.getDocComment(start)) + patDefOrDcl(start, mod1) case DEF => - defDefOrDcl(start, posMods(start, mods), in.getDocComment(start)) + defDefOrDcl(start, posMods(start, mods)) case TYPE => - typeDefOrDcl(start, posMods(start, mods), in.getDocComment(start)) + typeDefOrDcl(start, posMods(start, mods)) case CASE => - enumCase(start, mods, in.getDocComment(start)) + enumCase(start, mods) case _ => tmplDef(start, mods) } @@ -1908,7 +1908,7 @@ object Parsers { * ValDcl ::= id {`,' id} `:' Type * VarDcl ::= id {`,' id} `:' Type */ - def patDefOrDcl(start: Offset, mods: Modifiers, docstring: Option[Comment] = None): Tree = atPos(start, nameStart) { + def patDefOrDcl(start: Offset, mods: Modifiers): Tree = atPos(start, nameStart) { val lhs = commaSeparated(pattern2) val tpt = typedOpt() val rhs = @@ -1923,7 +1923,7 @@ object Parsers { } else EmptyTree lhs match { case (id @ Ident(name: TermName)) :: Nil => { - ValDef(name, tpt, rhs).withMods(mods).setComment(docstring) + ValDef(name, tpt, rhs).withMods(mods).setComment(in.getDocComment(start)) } case _ => PatDef(mods, lhs, tpt, rhs) } @@ -1949,7 +1949,7 @@ object Parsers { * DefDcl ::= DefSig `:' Type * DefSig ::= id [DefTypeParamClause] ParamClauses */ - def defDefOrDcl(start: Offset, mods: Modifiers, docstring: Option[Comment] = None): Tree = atPos(start, nameStart) { + def defDefOrDcl(start: Offset, mods: Modifiers): Tree = atPos(start, nameStart) { def scala2ProcedureSyntax(resultTypeStr: String) = { val toInsert = if (in.token == LBRACE) s"$resultTypeStr =" @@ -1992,7 +1992,7 @@ object Parsers { accept(EQUALS) expr() } - DefDef(name, tparams, vparamss, tpt, rhs).withMods(mods1).setComment(docstring) + DefDef(name, tparams, vparamss, tpt, rhs).withMods(mods1).setComment(in.getDocComment(start)) } } @@ -2026,7 +2026,7 @@ object Parsers { /** TypeDef ::= type id [TypeParamClause] `=' Type * TypeDcl ::= type id [TypeParamClause] TypeBounds */ - def typeDefOrDcl(start: Offset, mods: Modifiers, docstring: Option[Comment] = None): Tree = { + def typeDefOrDcl(start: Offset, mods: Modifiers): Tree = { newLinesOpt() atPos(start, nameStart) { val name = ident().toTypeName @@ -2034,9 +2034,9 @@ object Parsers { in.token match { case EQUALS => in.nextToken() - TypeDef(name, lambdaAbstract(tparams, typ())).withMods(mods).setComment(docstring) + TypeDef(name, lambdaAbstract(tparams, typ())).withMods(mods).setComment(in.getDocComment(start)) case SUPERTYPE | SUBTYPE | SEMI | NEWLINE | NEWLINES | COMMA | RBRACE | EOF => - TypeDef(name, lambdaAbstract(tparams, typeBounds())).withMods(mods).setComment(docstring) + TypeDef(name, lambdaAbstract(tparams, typeBounds())).withMods(mods).setComment(in.getDocComment(start)) case _ => syntaxErrorOrIncomplete("`=', `>:', or `<:' expected") EmptyTree @@ -2049,23 +2049,22 @@ object Parsers { * | `enum' EnumDef */ def tmplDef(start: Int, mods: Modifiers): Tree = { - val docstring = in.getDocComment(start) in.token match { case TRAIT => - classDef(start, posMods(start, addFlag(mods, Trait)), docstring) + classDef(start, posMods(start, addFlag(mods, Trait))) case CLASS => - classDef(start, posMods(start, mods), docstring) + classDef(start, posMods(start, mods)) case CASECLASS => - classDef(start, posMods(start, mods | Case), docstring) + classDef(start, posMods(start, mods | Case)) case OBJECT => - objectDef(start, posMods(start, mods | Module), docstring) + objectDef(start, posMods(start, mods | Module)) case CASEOBJECT => - objectDef(start, posMods(start, mods | Case | Module), docstring) + objectDef(start, posMods(start, mods | Case | Module)) case ENUM => val mods1 = addMod(mods, atPos(in.skipToken()) { Mod.Enum() }) in.token match { case CLASS | TRAIT | OBJECT => tmplDef(start, mods1) - case _ => enumDef(start, mods, docstring) + case _ => enumDef(start, mods) } case _ => syntaxErrorOrIncomplete("expected start of definition") @@ -2075,14 +2074,14 @@ object Parsers { /** ClassDef ::= id ClassConstr TemplateOpt */ - def classDef(start: Offset, mods: Modifiers, docstring: Option[Comment]): TypeDef = atPos(start, nameStart) { - classDefRest(start, mods, docstring, ident().toTypeName) + def classDef(start: Offset, mods: Modifiers): TypeDef = atPos(start, nameStart) { + classDefRest(start, mods, ident().toTypeName) } - def classDefRest(start: Offset, mods: Modifiers, docstring: Option[Comment], name: TypeName): TypeDef = { + def classDefRest(start: Offset, mods: Modifiers, name: TypeName): TypeDef = { val constr = classConstr(name, isCaseClass = mods is Case) val templ = templateOpt(constr) - TypeDef(name, templ).withMods(mods).setComment(docstring) + TypeDef(name, templ).withMods(mods).setComment(in.getDocComment(start)) } /** ClassConstr ::= [ClsTypeParamClause] [ConstrMods] ClsParamClauses @@ -2107,19 +2106,19 @@ object Parsers { /** ObjectDef ::= id TemplateOpt */ - def objectDef(start: Offset, mods: Modifiers, docstring: Option[Comment] = None): ModuleDef = atPos(start, nameStart) { - objectDefRest(start, mods, docstring, ident()) + def objectDef(start: Offset, mods: Modifiers): ModuleDef = atPos(start, nameStart) { + objectDefRest(start, mods, ident()) } - def objectDefRest(start: Offset, mods: Modifiers, docstring: Option[Comment] = None, name: TermName): ModuleDef = { + def objectDefRest(start: Offset, mods: Modifiers, name: TermName): ModuleDef = { val template = templateOpt(emptyConstructor) - ModuleDef(name, template).withMods(mods).setComment(docstring) + ModuleDef(name, template).withMods(mods).setComment(in.getDocComment(start)) } /** id ClassConstr [`extends' [ConstrApps]] * [nl] ‘{’ EnumCaseStats ‘}’ */ - def enumDef(start: Offset, mods: Modifiers, docstring: Option[Comment] = None): EnumDef = atPos(start, nameStart) { + def enumDef(start: Offset, mods: Modifiers): EnumDef = atPos(start, nameStart) { val name = ident().toTypeName val constr = classConstr(name) val parents = @@ -2146,23 +2145,19 @@ object Parsers { } /** EnumCaseStat = {Annotation [nl]} {Modifier} EnumCase */ - def enumCaseStat(): MemberDef = { - val start = in.offset - val docstring = in.getDocComment(start) - val mods = defAnnotsMods(modifierTokens) - enumCase(start, mods, docstring) - } + def enumCaseStat(): MemberDef = + enumCase(in.offset, defAnnotsMods(modifierTokens)) /** EnumCase = `case' (EnumClassDef | ObjectDef) */ - def enumCase(start: Offset, mods: Modifiers, docstring: Option[Comment]): MemberDef = { + def enumCase(start: Offset, mods: Modifiers): MemberDef = { val mods1 = mods.withAddedMod(atPos(in.offset)(Mod.EnumCase())) | Case accept(CASE) atPos(start, nameStart) { val name = ident() if (in.token == LBRACKET || in.token == LPAREN) - classDefRest(start, mods1, docstring, name.toTypeName) + classDefRest(start, mods1, name.toTypeName) else - objectDefRest(start, mods1, docstring, name) + objectDefRest(start, mods1, name) } } @@ -2389,8 +2384,7 @@ object Parsers { if (in.token == PACKAGE) { in.nextToken() if (in.token == OBJECT) { - val docstring = in.getDocComment(start) - ts += objectDef(start, atPos(start, in.skipToken()) { Modifiers(Package) }, docstring) + ts += objectDef(start, atPos(start, in.skipToken()) { Modifiers(Package) }) if (in.token != EOF) { acceptStatSep() ts ++= topStatSeq() From fe14afb67ecfeef0c7d6bc6588a1ec1c04b21fb8 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 8 Feb 2017 21:10:58 +1100 Subject: [PATCH 04/37] Simplify syntax `enum' only allowed as a prefix of classes, dropped from traits and objects. --- compiler/src/dotty/tools/dotc/ast/Desugar.scala | 3 +-- compiler/src/dotty/tools/dotc/parsing/JavaParsers.scala | 4 ++-- compiler/src/dotty/tools/dotc/parsing/Parsers.scala | 9 +++------ compiler/src/dotty/tools/dotc/parsing/Scanners.scala | 8 ++++++-- compiler/src/dotty/tools/dotc/parsing/Tokens.scala | 4 ++-- docs/docs/internals/syntax.md | 6 +++--- 6 files changed, 17 insertions(+), 17 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/ast/Desugar.scala b/compiler/src/dotty/tools/dotc/ast/Desugar.scala index 75c7078a1885..87994a87bef3 100644 --- a/compiler/src/dotty/tools/dotc/ast/Desugar.scala +++ b/compiler/src/dotty/tools/dotc/ast/Desugar.scala @@ -923,7 +923,7 @@ object desugar { case (gen: GenFrom) :: (rest @ (GenFrom(_, _) :: _)) => val cont = makeFor(mapName, flatMapName, rest, body) Apply(rhsSelect(gen, flatMapName), makeLambda(gen.pat, cont)) - case (enum @ GenFrom(pat, rhs)) :: (rest @ GenAlias(_, _) :: _) => + case (GenFrom(pat, rhs)) :: (rest @ GenAlias(_, _) :: _) => val (valeqs, rest1) = rest.span(_.isInstanceOf[GenAlias]) val pats = valeqs map { case GenAlias(pat, _) => pat } val rhss = valeqs map { case GenAlias(_, rhs) => rhs } @@ -1024,7 +1024,6 @@ object desugar { List(CaseDef(Ident(nme.DEFAULT_EXCEPTION_NAME), EmptyTree, Apply(handler, Ident(nme.DEFAULT_EXCEPTION_NAME)))), finalizer) } - } }.withPos(tree.pos) diff --git a/compiler/src/dotty/tools/dotc/parsing/JavaParsers.scala b/compiler/src/dotty/tools/dotc/parsing/JavaParsers.scala index 6b73a9456c14..47f201a09a0d 100644 --- a/compiler/src/dotty/tools/dotc/parsing/JavaParsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/JavaParsers.scala @@ -828,11 +828,11 @@ object JavaParsers { val superclazz = Apply(TypeApply( Select(New(javaLangDot(tpnme.Enum)), nme.CONSTRUCTOR), List(enumType)), List(Literal(Constant(null)),Literal(Constant(0)))) - val enum = atPos(start, nameOffset) { + val enumclazz = atPos(start, nameOffset) { TypeDef(name, makeTemplate(superclazz :: interfaces, body, List(), true)).withMods(mods | Flags.Enum) } - addCompanionObject(consts ::: statics ::: predefs, enum) + addCompanionObject(consts ::: statics ::: predefs, enumclazz) } def enumConst(enumType: Tree) = { diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index 1084b5784789..7ec44e342104 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -2044,8 +2044,8 @@ object Parsers { } } - /** TmplDef ::= ([`case' | `enum]'] ‘class’ | [`enum'] trait’) ClassDef - * | [`case' | `enum'] `object' ObjectDef + /** TmplDef ::= ([`case' | `enum]'] ‘class’ | trait’) ClassDef + * | [`case'] `object' ObjectDef * | `enum' EnumDef */ def tmplDef(start: Int, mods: Modifiers): Tree = { @@ -2062,10 +2062,7 @@ object Parsers { objectDef(start, posMods(start, mods | Case | Module)) case ENUM => val mods1 = addMod(mods, atPos(in.skipToken()) { Mod.Enum() }) - in.token match { - case CLASS | TRAIT | OBJECT => tmplDef(start, mods1) - case _ => enumDef(start, mods) - } + if (in.token == CLASS) tmplDef(start, mods1) else enumDef(start, mods) case _ => syntaxErrorOrIncomplete("expected start of definition") EmptyTree diff --git a/compiler/src/dotty/tools/dotc/parsing/Scanners.scala b/compiler/src/dotty/tools/dotc/parsing/Scanners.scala index 847f600c09a7..4b1d02093fb2 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Scanners.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Scanners.scala @@ -294,7 +294,10 @@ object Scanners { if (!sepRegions.isEmpty && sepRegions.head == lastToken) sepRegions = sepRegions.tail case ARROW => - if (!sepRegions.isEmpty && sepRegions.head == lastToken) + if (!sepRegions.isEmpty && sepRegions.head == ARROW) + sepRegions = sepRegions.tail + case EXTENDS => + if (!sepRegions.isEmpty && sepRegions.head == ARROW) sepRegions = sepRegions.tail case STRINGLIT => if (inMultiLineInterpolation) @@ -330,7 +333,8 @@ object Scanners { if (isAfterLineEnd() && (canEndStatTokens contains lastToken) && (canStartStatTokens contains token) && - (sepRegions.isEmpty || sepRegions.head == RBRACE)) { + (sepRegions.isEmpty || sepRegions.head == RBRACE || + sepRegions.head == ARROW && token == CASE)) { next copyFrom this // todo: make offset line-end of previous line? offset = if (lineStartOffset <= offset) lineStartOffset else lastLineStartOffset diff --git a/compiler/src/dotty/tools/dotc/parsing/Tokens.scala b/compiler/src/dotty/tools/dotc/parsing/Tokens.scala index 6109dda2a10e..d2ea9240c504 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Tokens.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Tokens.scala @@ -194,7 +194,7 @@ object Tokens extends TokensCommon { /** XML mode */ final val XMLSTART = 96; enter(XMLSTART, "$XMLSTART$<") // TODO: deprecate - final val alphaKeywords = tokenRange(IF, INLINE) + final val alphaKeywords = tokenRange(IF, ENUM) final val symbolicKeywords = tokenRange(USCORE, VIEWBOUND) final val symbolicTokens = tokenRange(COMMA, VIEWBOUND) final val keywords = alphaKeywords | symbolicKeywords @@ -215,7 +215,7 @@ object Tokens extends TokensCommon { final val canStartBindingTokens = identifierTokens | BitSet(USCORE, LPAREN) - final val templateIntroTokens = BitSet(CLASS, TRAIT, OBJECT, CASECLASS, CASEOBJECT) + final val templateIntroTokens = BitSet(CLASS, TRAIT, OBJECT, ENUM, CASECLASS, CASEOBJECT) final val dclIntroTokens = BitSet(DEF, VAL, VAR, TYPE) diff --git a/docs/docs/internals/syntax.md b/docs/docs/internals/syntax.md index 804a0f57abc3..f89a0f490dd9 100644 --- a/docs/docs/internals/syntax.md +++ b/docs/docs/internals/syntax.md @@ -330,15 +330,15 @@ DefDef ::= DefSig [‘:’ Type] ‘=’ Expr | ‘this’ DefParamClause DefParamClauses DefDef(_, , Nil, vparamss, EmptyTree, expr | Block) (‘=’ ConstrExpr | [nl] ConstrBlock) -TmplDef ::= ([‘case’ | `enum'] ‘class’ | [`enum'] trait’) ClassDef - | [‘case’ | `enum'] ‘object’ ObjectDef +TmplDef ::= ([‘case’ | `enum'] ‘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 ::= AccessModifier | Annotation {Annotation} (AccessModifier | ‘this’) ObjectDef ::= id TemplateOpt ModuleDef(mods, name, template) // no constructor -EnumDef ::= id ClassConstr [`extends' [ConstrApps]] EnumDef(mods, name, tparams, template) +EnumDef ::= id ClassConstr [`extends' [ConstrApps]] EnumDef(mods, name, tparams, template) [nl] ‘{’ EnumCaseStat {semi EnumCaseStat ‘}’ EnumCaseStat ::= {Annotation [nl]} {Modifier} EnumCase EnumCase ::= `case' (EnumClassDef | ObjectDef) From 5c53f532b96f256f0b84b4408e2e59836c13212a Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 8 Feb 2017 11:44:10 +1100 Subject: [PATCH 05/37] Allow value expansion of modules in mergeCompanionDefs --- compiler/src/dotty/tools/dotc/typer/Namer.scala | 1 - 1 file changed, 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Namer.scala b/compiler/src/dotty/tools/dotc/typer/Namer.scala index 94506f318ec1..ed580c631a1b 100644 --- a/compiler/src/dotty/tools/dotc/typer/Namer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Namer.scala @@ -564,7 +564,6 @@ class Namer { typer: Typer => case _ => } case _ => - } } From 69fd9dc80e78feb35ee601a9aaac813eef331e6a Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 8 Feb 2017 21:17:36 +1100 Subject: [PATCH 06/37] Adapt generic tests to model modified enum values scheme --- tests/run/generic/Color.scala | 12 ++++++++---- tests/run/generic/Enum.scala | 20 +++++++++++++------- tests/run/generic/List.scala | 2 +- tests/run/generic/SearchResult.scala | 16 ++++++++++------ tests/run/generic/Tree.scala | 6 +++--- 5 files changed, 35 insertions(+), 21 deletions(-) diff --git a/tests/run/generic/Color.scala b/tests/run/generic/Color.scala index ed248295d834..0a0f3f1e5317 100644 --- a/tests/run/generic/Color.scala +++ b/tests/run/generic/Color.scala @@ -10,12 +10,16 @@ import Shapes._ */ sealed trait Color extends Enum -object Color extends EnumValues[Color](3) { +object Color { + + private val $values = new EnumValues[Color] + def valueOf: Int => Color = $values + def values = $values.values private def $new(tag: Int, name: String) = new Color { def enumTag = tag override def toString = name - registerEnumValue(this) + $values.register(this) } val Red: Color = $new(0, "Red") @@ -25,6 +29,6 @@ object Color extends EnumValues[Color](3) { implicit val ColorShape: Color `shaped` EnumValue[Color] = new (Color `shaped` EnumValue[Color]) { def toShape(x: Color) = EnumValue(x.enumTag) - def fromShape(x: EnumValue[Color]) = Color.value(x.tag) + def fromShape(x: EnumValue[Color]) = Color.valueOf(x.tag) } -} \ No newline at end of file +} diff --git a/tests/run/generic/Enum.scala b/tests/run/generic/Enum.scala index dbdbfe8ebe02..38c9022d5c2b 100644 --- a/tests/run/generic/Enum.scala +++ b/tests/run/generic/Enum.scala @@ -1,6 +1,8 @@ package generic import Shapes.Singleton +import scala.collection.mutable.ResizableArray +import scala.collection.immutable.Seq trait Enum { def enumTag: Int @@ -8,11 +10,15 @@ trait Enum { trait FiniteEnum extends Enum -abstract class EnumValues[E <: Enum](numVals: Int) { - private var myValues = new Array[AnyRef](numVals) - - def registerEnumValue(v: E) = - myValues(v.enumTag) = v - - def value: IndexedSeq[E] = (myValues: IndexedSeq[AnyRef]).asInstanceOf[IndexedSeq[E]] +class EnumValues[E <: Enum] extends ResizableArray[E] { + private var valuesCache: Seq[E] = Nil + def register(v: E) = { + ensureSize(v.enumTag + 1) + array(v.enumTag) = v + valuesCache = null + } + def values: Seq[E] = { + if (valuesCache == null) valuesCache = array.filter(_ != null).toList.asInstanceOf[scala.List[E]] + valuesCache + } } diff --git a/tests/run/generic/List.scala b/tests/run/generic/List.scala index 3f365765698c..bc01ce63fc14 100644 --- a/tests/run/generic/List.scala +++ b/tests/run/generic/List.scala @@ -46,7 +46,7 @@ object List0 { } } -/** enum List[T] { +/** enum List[+T] { * case Cons(x: T, xs: List[T]) * case Nil extends List[Nothing] * } diff --git a/tests/run/generic/SearchResult.scala b/tests/run/generic/SearchResult.scala index 1c86d1b4f0eb..9a747fc4849d 100644 --- a/tests/run/generic/SearchResult.scala +++ b/tests/run/generic/SearchResult.scala @@ -11,12 +11,16 @@ import Shapes._ */ sealed trait SearchResult extends Enum -object SearchResult extends EnumValues[SearchResult](3) { +object SearchResult extends { + + private val $values = new EnumValues[SearchResult] + def valueOf: Int => SearchResult = $values + def values = $values.values private def $new(tag: Int, name: String) = new SearchResult { def enumTag = tag override def toString = name - registerEnumValue(this) + $values.register(this) } abstract case class Success(result: Color) extends SearchResult { @@ -31,8 +35,8 @@ object SearchResult extends EnumValues[SearchResult](3) { } } - val Diverging = $new(1, "Diverging") - val NoMatch = $new(2, "NoMatch") + val Diverging: SearchResult = $new(1, "Diverging") + val NoMatch: SearchResult = $new(2, "NoMatch") abstract case class Ambiguous(alt1: SearchResult, alt2: SearchResult) extends SearchResult { def enumTag = 3 @@ -58,7 +62,7 @@ object SearchResult extends EnumValues[SearchResult](3) { def fromShape(x: Sum[Success, Sum[Ambiguous, EnumValue[SearchResult]]]): SearchResult = x match { case Fst(s) => s case Snd(Fst(a)) => a - case Snd(Snd(ev)) => value(ev.tag) + case Snd(Snd(ev)) => valueOf(ev.tag) } } -} \ No newline at end of file +} diff --git a/tests/run/generic/Tree.scala b/tests/run/generic/Tree.scala index f4e7069448a3..673506b07a0b 100644 --- a/tests/run/generic/Tree.scala +++ b/tests/run/generic/Tree.scala @@ -2,14 +2,14 @@ package generic import Shapes._ -/** enum Tree[TS] { +/** enum Tree[T] { * case True extends Tree[Boolean] * case False extends Tree[Boolean] * case Zero extends Tree[Int] * 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(cond: Boolean, thenp: Tree[T], elsep: Tree[T]) extends Tree[T] + * case If(cond: Boolean, thenp: Tree[T], elsep: Tree[T]) * } */ sealed trait Tree[TR] extends Enum @@ -110,4 +110,4 @@ object Tree { case Snd(Snd(_if)) => _if } } -} \ No newline at end of file +} From 41d83d42650d0c0b54c47c1a9043d0b92315aa4e Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 8 Feb 2017 21:19:16 +1100 Subject: [PATCH 07/37] Change handling of enum defs. The previous scheme did not work because desugaring cannot deal with repeated expansions. We now sidestep the issue by doing the expansion in the parser. Luckily, positions work out perfectly, so that one can reconstruct the source precisely from the parsed untyped trees. --- compiler/src/dotty/tools/dotc/ast/untpd.scala | 8 ++-- .../dotty/tools/dotc/parsing/Parsers.scala | 41 +++++++++++++------ 2 files changed, 34 insertions(+), 15 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/ast/untpd.scala b/compiler/src/dotty/tools/dotc/ast/untpd.scala index e14c6714b59f..7020e4dac5cf 100644 --- a/compiler/src/dotty/tools/dotc/ast/untpd.scala +++ b/compiler/src/dotty/tools/dotc/ast/untpd.scala @@ -9,6 +9,7 @@ import Decorators._ import util.Property import language.higherKinds import collection.mutable.ListBuffer +import reflect.ClassTag object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo { @@ -39,9 +40,6 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo { def withName(name: Name)(implicit ctx: Context) = cpy.ModuleDef(this)(name.toTermName, impl) } - /** mods case name impl */ - case class EnumDef(name: TypeName, impl: Template) extends MemberDef - case class ParsedTry(expr: Tree, handler: Tree, finalizer: Tree) extends TermTree case class SymbolLit(str: String) extends TermTree @@ -192,6 +190,10 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo { def hasFlags = flags != EmptyFlags def hasAnnotations = annotations.nonEmpty def hasPrivateWithin = privateWithin != tpnme.EMPTY + def hasMod[T: ClassTag] = { + val cls = implicitly[ClassTag[T]].runtimeClass + mods.exists(mod => cls.isAssignableFrom(mod.getClass)) + } } @sharable val EmptyModifiers: Modifiers = new Modifiers() diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index 7ec44e342104..ee736179a5e8 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -49,6 +49,13 @@ object Parsers { val Class, Type, TypeParam, Def = Value } + private implicit class AddDeco(val buf: ListBuffer[Tree]) extends AnyVal { + def +++=(x: Tree) = x match { + case x: Thicket => buf ++= x.trees + case x => buf += x + } + } + /** The parse starting point depends on whether the source file is self-contained: * if not, the AST will be supplemented. */ @@ -2061,8 +2068,9 @@ object Parsers { case CASEOBJECT => objectDef(start, posMods(start, mods | Case | Module)) case ENUM => - val mods1 = addMod(mods, atPos(in.skipToken()) { Mod.Enum() }) - if (in.token == CLASS) tmplDef(start, mods1) else enumDef(start, mods) + val enumMod = atPos(in.skipToken()) { Mod.Enum() } + if (in.token == CLASS) tmplDef(start, addMod(mods, enumMod)) + else enumDef(start, mods, enumMod) case _ => syntaxErrorOrIncomplete("expected start of definition") EmptyTree @@ -2115,9 +2123,11 @@ object Parsers { /** id ClassConstr [`extends' [ConstrApps]] * [nl] ‘{’ EnumCaseStats ‘}’ */ - def enumDef(start: Offset, mods: Modifiers): EnumDef = atPos(start, nameStart) { - val name = ident().toTypeName - val constr = classConstr(name) + def enumDef(start: Offset, mods: Modifiers, enumMod: Mod): Thicket = { + val point = nameStart + val modName = ident() + val clsName = modName.toTypeName + val constr = classConstr(clsName) val parents = if (in.token == EXTENDS) { in.nextToken(); @@ -2125,10 +2135,17 @@ object Parsers { 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 body = inBraces(enumCaseStats) - EnumDef(name, Template(constr, Nil, EmptyValDef, body)) - .withMods(mods).setComment(in.getDocComment(start)).asInstanceOf[EnumDef] + 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 */ @@ -2249,7 +2266,7 @@ object Parsers { else if (in.token == IMPORT) stats ++= importClause() else if (in.token == AT || isTemplateIntro || isModifier) - stats += tmplDef(in.offset, defAnnotsMods(modifierTokens)) + stats +++= tmplDef(in.offset, defAnnotsMods(modifierTokens)) else if (!isStatSep) { if (in.token == CASE) syntaxErrorOrIncomplete("only `case class` or `case object` allowed") @@ -2299,7 +2316,7 @@ object Parsers { else if (isExprIntro) stats += expr1() else if (isDefIntro(modifierTokensOrCase)) - stats += defOrDcl(in.offset, defAnnotsMods(modifierTokens)) + stats +++= defOrDcl(in.offset, defAnnotsMods(modifierTokens)) else if (!isStatSep) { exitOnError = mustStartStat syntaxErrorOrIncomplete("illegal start of definition") @@ -2357,9 +2374,9 @@ object Parsers { val start = in.offset val imods = implicitMods() if (isBindingIntro) stats += implicitClosure(start, Location.InBlock, imods) - else stats += localDef(start, imods) + else stats +++= localDef(start, imods) } else { - stats += localDef(in.offset) + stats +++= localDef(in.offset) } else if (!isStatSep && (in.token != CASE)) { exitOnError = mustStartStat From cea243a4fc38dcc8831000d1066e10362df37576 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 8 Feb 2017 21:23:15 +1100 Subject: [PATCH 08/37] Implement enum desugaring --- .../src/dotty/tools/dotc/ast/Desugar.scala | 90 ++++++++----- .../dotty/tools/dotc/ast/DesugarEnums.scala | 124 ++++++++++++++++++ .../dotty/tools/dotc/core/Definitions.scala | 4 + .../src/dotty/tools/dotc/core/StdNames.scala | 7 +- library/src/scala/Enum.scala | 8 ++ library/src/scala/runtime/EnumValues.scala | 18 +++ 6 files changed, 215 insertions(+), 36 deletions(-) create mode 100644 compiler/src/dotty/tools/dotc/ast/DesugarEnums.scala create mode 100644 library/src/scala/Enum.scala create mode 100644 library/src/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 87994a87bef3..863c10cb07c3 100644 --- a/compiler/src/dotty/tools/dotc/ast/Desugar.scala +++ b/compiler/src/dotty/tools/dotc/ast/Desugar.scala @@ -14,6 +14,7 @@ import reporting.diagnostic.messages._ object desugar { import untpd._ + import DesugarEnums._ /** Tags a .withFilter call generated by desugaring a for expression. * Such calls can alternatively be rewritten to use filter. @@ -263,7 +264,9 @@ object desugar { val className = checkNotReservedName(cdef).asTypeName val impl @ Template(constr0, parents, self, _) = cdef.rhs val mods = cdef.mods - val companionMods = mods.withFlags((mods.flags & AccessFlags).toCommonFlags) + val companionMods = mods + .withFlags((mods.flags & AccessFlags).toCommonFlags) + .withMods(mods.mods.filter(!_.isInstanceOf[Mod.EnumCase])) val (constr1, defaultGetters) = defDef(constr0, isPrimaryConstructor = true) match { case meth: DefDef => (meth, Nil) @@ -288,17 +291,22 @@ object desugar { } val isCaseClass = mods.is(Case) && !mods.is(Module) + val isEnum = mods.hasMod[Mod.Enum] + val isEnumCase = isLegalEnumCase(cdef) val isValueClass = parents.nonEmpty && isAnyVal(parents.head) // This is not watertight, but `extends AnyVal` will be replaced by `inline` later. - val constrTparams = constr1.tparams map toDefParam + val originalTparams = + if (isEnumCase && parents.isEmpty) reconstitutedEnumTypeParams(cdef.pos.startPos) + else constr1.tparams + val originalVparamss = constr1.vparamss + val constrTparams = originalTparams.map(toDefParam) val constrVparamss = - if (constr1.vparamss.isEmpty) { // ensure parameter list is non-empty - if (isCaseClass) - ctx.error(CaseClassMissingParamList(cdef), cdef.namePos) + if (originalVparamss.isEmpty) { // ensure parameter list is non-empty + if (isCaseClass) ctx.error(CaseClassMissingParamList(cdef), cdef.namePos) ListOfNil } - else constr1.vparamss.nestedMap(toDefParam) + else originalVparamss.nestedMap(toDefParam) val constr = cpy.DefDef(constr1)(tparams = constrTparams, vparamss = constrVparamss) // Add constructor type parameters and evidence implicit parameters @@ -312,21 +320,22 @@ object desugar { stat } - val derivedTparams = constrTparams map derivedTypeParam + val derivedTparams = + if (isEnumCase) constrTparams else constrTparams map derivedTypeParam val derivedVparamss = constrVparamss nestedMap derivedTermParam val arity = constrVparamss.head.length - var classTycon: Tree = EmptyTree + val classTycon: Tree = new TypeRefTree // watching is set at end of method - // a reference to the class type, with all parameters given. - val classTypeRef/*: Tree*/ = { - // -language:keepUnions difference: classTypeRef needs type annotation, otherwise - // infers Ident | AppliedTypeTree, which - // renders the :\ in companions below untypable. - classTycon = (new TypeRefTree) withPos cdef.pos.startPos // watching is set at end of method - val tparams = impl.constr.tparams - if (tparams.isEmpty) classTycon else AppliedTypeTree(classTycon, tparams map refOfDef) - } + def appliedRef(tycon: Tree) = + (if (constrTparams.isEmpty) tycon + else AppliedTypeTree(tycon, constrTparams map refOfDef)) + .withPos(cdef.pos.startPos) + + // a reference to the class type bound by `cdef`, with type parameters coming from the constructor + val classTypeRef = appliedRef(classTycon) + // a refereence to `enumClass`, with type parameters coming from the constructor + lazy val enumClassTypeRef = appliedRef(enumClassRef) // new C[Ts](paramss) lazy val creatorExpr = New(classTypeRef, constrVparamss nestedMap refOfDef) @@ -374,7 +383,9 @@ object desugar { DefDef(nme.copy, derivedTparams, copyFirstParams :: copyRestParamss, TypeTree(), creatorExpr) .withMods(synthetic) :: Nil } - copyMeths ::: productElemMeths.toList + + val enumTagMeths = if (isEnumCase) enumTagMeth :: Nil else Nil + copyMeths ::: enumTagMeths ::: productElemMeths.toList } else Nil @@ -387,8 +398,12 @@ object desugar { // Case classes and case objects get a ProductN parent var parents1 = parents + if (isEnumCase && parents.isEmpty) + parents1 = enumClassTypeRef :: Nil if (mods.is(Case) && arity <= Definitions.MaxTupleArity) - parents1 = parents1 :+ productConstr(arity) + parents1 = parents1 :+ productConstr(arity) // TODO: This also adds Product0 to caes objects. Do we want that? + if (isEnum) + parents1 = parents1 :+ ref(defn.EnumType) // The thicket which is the desugared version of the companion object // synthetic object C extends parentTpt { defs } @@ -419,9 +434,11 @@ object desugar { else (constrVparamss :\ classTypeRef) ((vparams, restpe) => Function(vparams map (_.tpt), restpe)) val applyMeths = if (mods is Abstract) Nil - else - DefDef(nme.apply, derivedTparams, derivedVparamss, TypeTree(), creatorExpr) + else { + val restpe = if (isEnumCase) enumClassTypeRef else TypeTree() + DefDef(nme.apply, derivedTparams, derivedVparamss, restpe, creatorExpr) .withFlags(Synthetic | (constr1.mods.flags & DefaultParameterized)) :: Nil + } val unapplyMeth = { val unapplyParam = makeSyntheticParameter(tpt = classTypeRef) val unapplyRHS = if (arity == 0) Literal(Constant(true)) else Ident(unapplyParam.name) @@ -464,15 +481,15 @@ object desugar { else cpy.ValDef(self)(tpt = selfType).withMods(self.mods | SelfName) } - val cdef1 = { - val originalTparams = constr1.tparams.toIterator - val originalVparams = constr1.vparamss.toIterator.flatten - val tparamAccessors = derivedTparams.map(_.withMods(originalTparams.next.mods)) + val cdef1 = addEnumFlags { + val originalTparamsIt = originalTparams.toIterator + val originalVparamsIt = originalVparamss.toIterator.flatten + val tparamAccessors = derivedTparams.map(_.withMods(originalTparamsIt.next.mods)) val caseAccessor = if (isCaseClass) CaseAccessor else EmptyFlags val vparamAccessors = derivedVparamss match { case first :: rest => - first.map(_.withMods(originalVparams.next.mods | caseAccessor)) ++ - rest.flatten.map(_.withMods(originalVparams.next.mods)) + first.map(_.withMods(originalVparamsIt.next.mods | caseAccessor)) ++ + rest.flatten.map(_.withMods(originalVparamsIt.next.mods)) case _ => Nil } @@ -503,23 +520,26 @@ object desugar { */ def moduleDef(mdef: ModuleDef)(implicit ctx: Context): Tree = { val moduleName = checkNotReservedName(mdef).asTermName - val tmpl = mdef.impl + val impl = mdef.impl val mods = mdef.mods + lazy val isEnumCase = isLegalEnumCase(mdef) if (mods is Package) - PackageDef(Ident(moduleName), cpy.ModuleDef(mdef)(nme.PACKAGE, tmpl).withMods(mods &~ Package) :: Nil) + PackageDef(Ident(moduleName), cpy.ModuleDef(mdef)(nme.PACKAGE, impl).withMods(mods &~ Package) :: Nil) + else if (isEnumCase) + expandEnumModule(moduleName, impl, mods, mdef.pos) else { val clsName = moduleName.moduleClassName val clsRef = Ident(clsName) val modul = ValDef(moduleName, clsRef, New(clsRef, Nil)) .withMods(mods | ModuleCreationFlags | mods.flags & AccessFlags) .withPos(mdef.pos) - val ValDef(selfName, selfTpt, _) = tmpl.self - val selfMods = tmpl.self.mods - if (!selfTpt.isEmpty) ctx.error(ObjectMayNotHaveSelfType(mdef), tmpl.self.pos) - val clsSelf = ValDef(selfName, SingletonTypeTree(Ident(moduleName)), tmpl.self.rhs) + val ValDef(selfName, selfTpt, _) = impl.self + val selfMods = impl.self.mods + if (!selfTpt.isEmpty) ctx.error(ObjectMayNotHaveSelfType(mdef), impl.self.pos) + val clsSelf = ValDef(selfName, SingletonTypeTree(Ident(moduleName)), impl.self.rhs) .withMods(selfMods) - .withPos(tmpl.self.pos orElse tmpl.pos.startPos) - val clsTmpl = cpy.Template(tmpl)(self = clsSelf, body = tmpl.body) + .withPos(impl.self.pos orElse impl.pos.startPos) + val clsTmpl = cpy.Template(impl)(self = clsSelf, body = impl.body) val cls = TypeDef(clsName, clsTmpl) .withMods(mods.toTypeFlags & RetainedModuleClassFlags | ModuleClassCreationFlags) Thicket(modul, classDef(cls).withPos(mdef.pos)) diff --git a/compiler/src/dotty/tools/dotc/ast/DesugarEnums.scala b/compiler/src/dotty/tools/dotc/ast/DesugarEnums.scala new file mode 100644 index 000000000000..4317c818397e --- /dev/null +++ b/compiler/src/dotty/tools/dotc/ast/DesugarEnums.scala @@ -0,0 +1,124 @@ +package dotty.tools +package dotc +package ast + +import core._ +import util.Positions._, Types._, Contexts._, Constants._, Names._, NameOps._, Flags._ +import SymDenotations._, Symbols._, StdNames._, Annotations._, Trees._ +import Decorators._ +import collection.mutable.ListBuffer +import util.Property +import reporting.diagnostic.messages._ + +object DesugarEnums { + import untpd._ + import desugar.DerivedFromParamTree + + val EnumCaseCount = new Property.Key[Int] + + def enumClass(implicit ctx: Context) = ctx.owner.linkedClass + + def nextEnumTag(implicit ctx: Context): Int = { + val result = ctx.tree.removeAttachment(EnumCaseCount).getOrElse(0) + ctx.tree.pushAttachment(EnumCaseCount, result + 1) + result + } + + def isLegalEnumCase(tree: MemberDef)(implicit ctx: Context): Boolean = { + tree.mods.hasMod[Mod.EnumCase] && + ( ctx.owner.is(ModuleClass) && enumClass.derivesFrom(defn.EnumClass) + || { ctx.error(em"case not allowed here, since owner ${ctx.owner} is not an `enum' object", tree.pos) + false + } + ) + } + + /** Type parameters reconstituted from the constructor + * of the `enum' class corresponding to an enum case + */ + def reconstitutedEnumTypeParams(pos: Position)(implicit ctx: Context) = { + val tparams = enumClass.primaryConstructor.info match { + case info: PolyType => + ctx.newTypeParams(ctx.newLocalDummy(enumClass), info.paramNames, EmptyFlags, info.instantiateBounds) + case _ => + Nil + } + for (tparam <- tparams) yield { + val tbounds = new DerivedFromParamTree + tbounds.pushAttachment(OriginalSymbol, tparam) + TypeDef(tparam.name, tbounds) + .withFlags(Param | PrivateLocal).withPos(pos) + } + } + + def enumTagMeth(implicit ctx: Context) = + DefDef(nme.enumTag, Nil, Nil, TypeTree(), Literal(Constant(nextEnumTag))) + + def enumClassRef(implicit ctx: Context) = TypeTree(enumClass.typeRef) + + 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) + else cdef + + /** The following lists of definitions for an enum type E: + * + * private val $values = new EnumValues[E] + * def valueOf: Int => E = $values + * def values = $values.values + * + * private def $new(tag: Int, name: String) = new E { + * def enumTag = tag + * override def toString = name + * $values.register(this) + * } + */ + private def enumScaffolding(implicit ctx: Context): List[Tree] = { + val valsRef = Ident(nme.DOLLAR_VALUES) + def param(name: TermName, typ: Type) = + ValDef(name, TypeTree(typ), EmptyTree).withFlags(Param) + val privateValuesDef = + ValDef(nme.DOLLAR_VALUES, TypeTree(), + New(TypeTree(defn.EnumValuesType.appliedTo(enumClass.typeRef :: Nil)), ListOfNil)) + .withFlags(Private) + val valueOfDef = + DefDef(nme.valueOf, Nil, Nil, + TypeTree(defn.FunctionOf(defn.IntType :: Nil, enumClass.typeRef)), valsRef) + val valuesDef = + DefDef(nme.values, Nil, Nil, TypeTree(), Select(valsRef, nme.values)) + val enumTagDef = + DefDef(nme.enumTag, Nil, Nil, TypeTree(), Ident(nme.tag)) + val toStringDef = + DefDef(nme.toString_, Nil, Nil, TypeTree(), Ident(nme.name)) + .withFlags(Override) + val registerStat = + Apply(Select(valsRef, nme.register), This(EmptyTypeIdent) :: Nil) + def creator = New(Template(emptyConstructor, enumClassRef :: Nil, EmptyValDef, + List(enumTagDef, toStringDef, registerStat))) + val newDef = + DefDef(nme.DOLLAR_NEW, Nil, + List(List(param(nme.tag, defn.IntType), param(nme.name, defn.StringType))), + TypeTree(), creator) + List(privateValuesDef, valueOfDef, valuesDef, newDef) + } + + def expandEnumModule(name: TermName, impl: Template, mods: Modifiers, pos: Position)(implicit ctx: Context): Tree = { + def nameLit = Literal(Constant(name.toString)) + if (impl.parents.isEmpty) { + if (reconstitutedEnumTypeParams(pos).nonEmpty) + ctx.error(i"illegal enum value of generic $enumClass: an explicit `extends' clause is needed", pos) + val tag = nextEnumTag + val prefix = if (tag == 0) enumScaffolding else Nil + val creator = Apply(Ident(nme.DOLLAR_NEW), List(Literal(Constant(tag)), nameLit)) + val vdef = ValDef(name, enumClassRef, creator).withMods(mods | Final).withPos(pos) + flatTree(prefix ::: vdef :: Nil).withPos(pos.startPos) + } else { + def toStringMeth = + DefDef(nme.toString_, Nil, Nil, TypeTree(defn.StringType), nameLit) + .withFlags(Override) + val impl1 = cpy.Template(impl)(body = + impl.body ++ List(enumTagMeth, toStringMeth)) + ValDef(name, TypeTree(), New(impl1)).withMods(mods | Final).withPos(pos) + } + } +} diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index 4d4350f98651..39b46cbfef65 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -512,6 +512,10 @@ class Definitions { def DynamicClass(implicit ctx: Context) = DynamicType.symbol.asClass lazy val OptionType: TypeRef = ctx.requiredClassRef("scala.Option") def OptionClass(implicit ctx: Context) = OptionType.symbol.asClass + lazy val EnumType: TypeRef = ctx.requiredClassRef("scala.Enum") + def EnumClass(implicit ctx: Context) = EnumType.symbol.asClass + lazy val EnumValuesType: TypeRef = ctx.requiredClassRef("scala.runtime.EnumValues") + def EnumValuesClass(implicit ctx: Context) = EnumValuesType.symbol.asClass lazy val ProductType: TypeRef = ctx.requiredClassRef("scala.Product") def ProductClass(implicit ctx: Context) = ProductType.symbol.asClass lazy val Product_canEqualR = ProductClass.requiredMethodRef(nme.canEqual_) diff --git a/compiler/src/dotty/tools/dotc/core/StdNames.scala b/compiler/src/dotty/tools/dotc/core/StdNames.scala index 5b7dc3d1d5b2..ff3ddbad725a 100644 --- a/compiler/src/dotty/tools/dotc/core/StdNames.scala +++ b/compiler/src/dotty/tools/dotc/core/StdNames.scala @@ -132,6 +132,8 @@ object StdNames { val TRAIT_SETTER_SEPARATOR: N = "$_setter_$" val DIRECT_SUFFIX: N = "$direct" val LAZY_IMPLICIT_PREFIX: N = "$lazy_implicit$" + val DOLLAR_VALUES: N = "$values" + val DOLLAR_NEW: N = "$new" // value types (and AnyRef) are all used as terms as well // as (at least) arguments to the @specialize annotation. @@ -395,6 +397,7 @@ object StdNames { val elem: N = "elem" val emptyValDef: N = "emptyValDef" val ensureAccessible : N = "ensureAccessible" + val enumTag: N = "enumTag" val eq: N = "eq" val equalsNumChar : N = "equalsNumChar" val equalsNumNum : N = "equalsNumNum" @@ -474,6 +477,7 @@ object StdNames { val productPrefix: N = "productPrefix" val readResolve: N = "readResolve" val reflect : N = "reflect" + val register: N = "register" val reify : N = "reify" val rootMirror : N = "rootMirror" val runOrElse: N = "runOrElse" @@ -499,6 +503,7 @@ object StdNames { val staticModule : N = "staticModule" val staticPackage : N = "staticPackage" val synchronized_ : N = "synchronized" + val tag: N = "tag" val tail: N = "tail" val `then` : N = "then" val this_ : N = "this" @@ -523,7 +528,7 @@ object StdNames { val updateDynamic: N = "updateDynamic" val value: N = "value" val valueOf : N = "valueOf" - val values : N = "values" + val values: N = "values" val view_ : N = "view" val wait_ : N = "wait" val withFilter: N = "withFilter" diff --git a/library/src/scala/Enum.scala b/library/src/scala/Enum.scala new file mode 100644 index 000000000000..7d2eefb3df9c --- /dev/null +++ b/library/src/scala/Enum.scala @@ -0,0 +1,8 @@ +package scala + +/** A base trait of all enum classes */ +trait Enum { + + /** A number uniquely identifying a case of an enum */ + def enumTag: Int +} diff --git a/library/src/scala/runtime/EnumValues.scala b/library/src/scala/runtime/EnumValues.scala new file mode 100644 index 000000000000..6d2e56cf3963 --- /dev/null +++ b/library/src/scala/runtime/EnumValues.scala @@ -0,0 +1,18 @@ +package scala.runtime + +import scala.collection.immutable.Seq +import scala.collection.mutable.ResizableArray + +class EnumValues[E <: Enum] extends ResizableArray[E] { + private var valuesCache: List[E] = Nil + def register(v: E) = { + ensureSize(v.enumTag + 1) + size0 = size0 max (v.enumTag + 1) + array(v.enumTag) = v + valuesCache = null + } + def values: Seq[E] = { + if (valuesCache == null) valuesCache = array.filter(_ != null).toList.asInstanceOf[scala.List[E]] + valuesCache + } +} From c2790577d64b039792618c92e6ab7cff7a7ed824 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 8 Feb 2017 21:23:47 +1100 Subject: [PATCH 09/37] Add tests --- tests/neg/enums.scala | 8 ++++++++ tests/run/enum-Color.check | 3 +++ tests/run/enum-Color.scala | 13 +++++++++++++ tests/run/enum-List1.check | 1 + tests/run/enum-List1.scala | 10 ++++++++++ tests/run/enum-List2.check | 1 + tests/run/enum-List2.scala | 11 +++++++++++ 7 files changed, 47 insertions(+) create mode 100644 tests/neg/enums.scala create mode 100644 tests/run/enum-Color.check create mode 100644 tests/run/enum-Color.scala create mode 100644 tests/run/enum-List1.check create mode 100644 tests/run/enum-List1.scala create mode 100644 tests/run/enum-List2.check create mode 100644 tests/run/enum-List2.scala diff --git a/tests/neg/enums.scala b/tests/neg/enums.scala new file mode 100644 index 000000000000..83311f37c8e2 --- /dev/null +++ b/tests/neg/enums.scala @@ -0,0 +1,8 @@ +enum List[+T] { + case Cons(x: T, xs: List[T]) + case Nil // error: illegal enum value +} + +enum class X { + case Y // error: case not allowed here +} diff --git a/tests/run/enum-Color.check b/tests/run/enum-Color.check new file mode 100644 index 000000000000..865c47e495d1 --- /dev/null +++ b/tests/run/enum-Color.check @@ -0,0 +1,3 @@ +Red: 0 +Green: 1 +Blue: 2 diff --git a/tests/run/enum-Color.scala b/tests/run/enum-Color.scala new file mode 100644 index 000000000000..24916abeeb9a --- /dev/null +++ b/tests/run/enum-Color.scala @@ -0,0 +1,13 @@ +enum Color { + case Red + case Green + case Blue +} + +object Test { + def main(args: Array[String]) = + for (color <- Color.values) { + println(s"$color: ${color.enumTag}") + assert(Color.valueOf(color.enumTag) eq color) + } +} diff --git a/tests/run/enum-List1.check b/tests/run/enum-List1.check new file mode 100644 index 000000000000..3ed5061b4035 --- /dev/null +++ b/tests/run/enum-List1.check @@ -0,0 +1 @@ +Cons(1,Cons(2,Cons(3,Nil()))) diff --git a/tests/run/enum-List1.scala b/tests/run/enum-List1.scala new file mode 100644 index 000000000000..bb75bec4a1ec --- /dev/null +++ b/tests/run/enum-List1.scala @@ -0,0 +1,10 @@ +enum class List[T] +object List { + case Cons(x: T, xs: List[T]) + case Nil() +} +object Test { + import List._ + val xs = Cons(1, Cons(2, Cons(3, Nil()))) + def main(args: Array[String]) = println(xs) +} diff --git a/tests/run/enum-List2.check b/tests/run/enum-List2.check new file mode 100644 index 000000000000..1d4812de1f64 --- /dev/null +++ b/tests/run/enum-List2.check @@ -0,0 +1 @@ +Cons(1,Cons(2,Cons(3,Nil))) diff --git a/tests/run/enum-List2.scala b/tests/run/enum-List2.scala new file mode 100644 index 000000000000..030de0f849aa --- /dev/null +++ b/tests/run/enum-List2.scala @@ -0,0 +1,11 @@ +enum class List[+T] +object List { + case Cons(x: T, xs: List[T]) + case Nil extends List[Nothing] +} +object Test { + import List._ + val xs = Cons(1, Cons(2, Cons(3, Nil))) + def main(args: Array[String]) = println(xs) +} + From b4ece2a235cc335d3afc58112652f6c98b25435f Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 8 Feb 2017 21:24:07 +1100 Subject: [PATCH 10/37] Comment out unused method in Context --- compiler/src/dotty/tools/dotc/core/Contexts.scala | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Contexts.scala b/compiler/src/dotty/tools/dotc/core/Contexts.scala index 6b7be12be871..8707b66f9228 100644 --- a/compiler/src/dotty/tools/dotc/core/Contexts.scala +++ b/compiler/src/dotty/tools/dotc/core/Contexts.scala @@ -306,13 +306,14 @@ object Contexts { sb.append(str) } - /** The next outer context whose tree is a template or package definition */ + /** The next outer context whose tree is a template or package definition + * Note: Currently unused def enclTemplate: Context = { var c = this while (c != NoContext && !c.tree.isInstanceOf[Template[_]] && !c.tree.isInstanceOf[PackageDef[_]]) c = c.outer c - } + }*/ /** The context for a supercall. This context is used for elaborating * the parents of a class and their arguments. From db387a292eebdd834729323bdf6f182cbd137b2e Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 8 Feb 2017 18:34:16 +1100 Subject: [PATCH 11/37] Improvement to REPL test In case of difference, dump transcript to file for easier comparisons. --- compiler/test/dotty/tools/dotc/repl/TestREPL.scala | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/compiler/test/dotty/tools/dotc/repl/TestREPL.scala b/compiler/test/dotty/tools/dotc/repl/TestREPL.scala index 131a88ab19ef..44523aae9de6 100644 --- a/compiler/test/dotty/tools/dotc/repl/TestREPL.scala +++ b/compiler/test/dotty/tools/dotc/repl/TestREPL.scala @@ -58,8 +58,11 @@ class TestREPL(script: String) extends REPL { val printed = out.toString val transcript = printed.drop(printed.indexOf(config.prompt)) if (transcript.toString.lines.toList != script.lines.toList) { - println("input differs from transcript:") + println("input differs from transcript (copy is repl.transcript):") println(transcript) + val s = new PrintStream("repl.transcript") + s.print(transcript) + s.close() assert(false) } } From 3ea983198029d4a31611957d8d1c01dc73654a7f Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 9 Feb 2017 00:13:41 +1100 Subject: [PATCH 12/37] Fix "closest" computation for docstrings --- compiler/src/dotty/tools/dotc/parsing/Scanners.scala | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/parsing/Scanners.scala b/compiler/src/dotty/tools/dotc/parsing/Scanners.scala index 4b1d02093fb2..65d3fb66c8f2 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Scanners.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Scanners.scala @@ -192,7 +192,9 @@ object Scanners { /** Returns the closest docstring preceding the position supplied */ def getDocComment(pos: Int): Option[Comment] = { def closest(c: Comment, docstrings: List[Comment]): Comment = docstrings match { - case x :: xs if (c.pos.end < x.pos.end && x.pos.end <= pos) => closest(x, xs) + case x :: xs => + if (c.pos.end < x.pos.end && x.pos.end <= pos) closest(x, xs) + else closest(c, xs) case Nil => c } From adf90094dce4bff8f6c8b7be92e7602b7bbf3085 Mon Sep 17 00:00:00 2001 From: Felix Mulder Date: Thu, 9 Feb 2017 10:17:54 +0100 Subject: [PATCH 13/37] Make `getDocComment` referentially transparent --- .../dotty/tools/dotc/parsing/Scanners.scala | 48 +++++-------------- 1 file changed, 12 insertions(+), 36 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/parsing/Scanners.scala b/compiler/src/dotty/tools/dotc/parsing/Scanners.scala index 65d3fb66c8f2..a6f3936f81f6 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Scanners.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Scanners.scala @@ -10,6 +10,7 @@ import util.Chars._ import Tokens._ import scala.annotation.{ switch, tailrec } import scala.collection.mutable +import scala.collection.immutable.SortedMap import mutable.ListBuffer import Utility.isNameStart import rewrite.Rewrites.patch @@ -174,39 +175,15 @@ object Scanners { class Scanner(source: SourceFile, override val startFrom: Offset = 0)(implicit ctx: Context) extends ScannerCommon(source)(ctx) { val keepComments = ctx.settings.YkeepComments.value - /** All doc comments as encountered, each list contains doc comments from - * the same block level. Starting with the deepest level and going upward - */ - private[this] var docsPerBlockStack: List[List[Comment]] = List(Nil) - - /** Adds level of nesting to docstrings */ - def enterBlock(): Unit = - docsPerBlockStack = List(Nil) ::: docsPerBlockStack + /** All doc comments kept by their end position in a `Map` */ + private[this] var docstringMap: SortedMap[Int, Comment] = SortedMap.empty - /** Removes level of nesting for docstrings */ - def exitBlock(): Unit = docsPerBlockStack = docsPerBlockStack match { - case x :: Nil => List(Nil) - case _ => docsPerBlockStack.tail - } + private[this] def addComment(comment: Comment): Unit = + docstringMap = docstringMap + (comment.pos.end -> comment) /** Returns the closest docstring preceding the position supplied */ - def getDocComment(pos: Int): Option[Comment] = { - def closest(c: Comment, docstrings: List[Comment]): Comment = docstrings match { - case x :: xs => - if (c.pos.end < x.pos.end && x.pos.end <= pos) closest(x, xs) - else closest(c, xs) - case Nil => c - } - - docsPerBlockStack match { - case (list @ (x :: xs)) :: _ => { - val c = closest(x, xs) - docsPerBlockStack = list.dropWhile(_ != c).tail :: docsPerBlockStack.tail - Some(c) - } - case _ => None - } - } + def getDocComment(pos: Int): Option[Comment] = + docstringMap.to(pos).lastOption.map(_._2) /** A buffer for comments */ val commentBuf = new StringBuilder @@ -541,13 +518,13 @@ object Scanners { case ',' => nextChar(); token = COMMA case '(' => - enterBlock(); nextChar(); token = LPAREN + nextChar(); token = LPAREN case '{' => - enterBlock(); nextChar(); token = LBRACE + nextChar(); token = LBRACE case ')' => - exitBlock(); nextChar(); token = RPAREN + nextChar(); token = RPAREN case '}' => - exitBlock(); nextChar(); token = RBRACE + nextChar(); token = RBRACE case '[' => nextChar(); token = LBRACKET case ']' => @@ -615,8 +592,7 @@ object Scanners { val pos = Position(start, charOffset, start) val comment = Comment(pos, flushBuf(commentBuf)) - if (comment.isDocComment) - docsPerBlockStack = (docsPerBlockStack.head :+ comment) :: docsPerBlockStack.tail + if (comment.isDocComment) addComment(comment) } true From 2be70e6019cff01d8a4bc40b90614c64d5d638ff Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 9 Feb 2017 22:49:19 +1100 Subject: [PATCH 14/37] Another test --- tests/run/enum-List3.check | 1 + tests/run/enum-List3.scala | 10 ++++++++++ 2 files changed, 11 insertions(+) create mode 100644 tests/run/enum-List3.check create mode 100644 tests/run/enum-List3.scala diff --git a/tests/run/enum-List3.check b/tests/run/enum-List3.check new file mode 100644 index 000000000000..1d4812de1f64 --- /dev/null +++ b/tests/run/enum-List3.check @@ -0,0 +1 @@ +Cons(1,Cons(2,Cons(3,Nil))) diff --git a/tests/run/enum-List3.scala b/tests/run/enum-List3.scala new file mode 100644 index 000000000000..e5ffe1a28ce1 --- /dev/null +++ b/tests/run/enum-List3.scala @@ -0,0 +1,10 @@ +enum List[+T] { + case Cons(x: T, xs: List[T]) + case Nil extends List[Nothing] +} +object Test { + import List._ + val xs = Cons(1, Cons(2, Cons(3, Nil))) + def main(args: Array[String]) = println(xs) +} + From 7a7db73ae9738f6ed63772851fa5e9820628b015 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 9 Feb 2017 23:16:59 +1100 Subject: [PATCH 15/37] Link generic test to actual implementations of Enum and EnumValues These are now implemented in scala.runtime. --- tests/run/generic/Color.scala | 2 +- tests/run/generic/Enum.scala | 24 ------------------------ tests/run/generic/SearchResult.scala | 2 +- 3 files changed, 2 insertions(+), 26 deletions(-) delete mode 100644 tests/run/generic/Enum.scala diff --git a/tests/run/generic/Color.scala b/tests/run/generic/Color.scala index 0a0f3f1e5317..183f183496cc 100644 --- a/tests/run/generic/Color.scala +++ b/tests/run/generic/Color.scala @@ -12,7 +12,7 @@ sealed trait Color extends Enum object Color { - private val $values = new EnumValues[Color] + private val $values = new runtime.EnumValues[Color] def valueOf: Int => Color = $values def values = $values.values diff --git a/tests/run/generic/Enum.scala b/tests/run/generic/Enum.scala deleted file mode 100644 index 38c9022d5c2b..000000000000 --- a/tests/run/generic/Enum.scala +++ /dev/null @@ -1,24 +0,0 @@ -package generic - -import Shapes.Singleton -import scala.collection.mutable.ResizableArray -import scala.collection.immutable.Seq - -trait Enum { - def enumTag: Int -} - -trait FiniteEnum extends Enum - -class EnumValues[E <: Enum] extends ResizableArray[E] { - private var valuesCache: Seq[E] = Nil - def register(v: E) = { - ensureSize(v.enumTag + 1) - array(v.enumTag) = v - valuesCache = null - } - def values: Seq[E] = { - if (valuesCache == null) valuesCache = array.filter(_ != null).toList.asInstanceOf[scala.List[E]] - valuesCache - } -} diff --git a/tests/run/generic/SearchResult.scala b/tests/run/generic/SearchResult.scala index 9a747fc4849d..d39ee89a04b7 100644 --- a/tests/run/generic/SearchResult.scala +++ b/tests/run/generic/SearchResult.scala @@ -13,7 +13,7 @@ sealed trait SearchResult extends Enum object SearchResult extends { - private val $values = new EnumValues[SearchResult] + private val $values = new runtime.EnumValues[SearchResult] def valueOf: Int => SearchResult = $values def values = $values.values From a5345a38c151472f80e56eaaa2ce764edde3ad79 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 10 Feb 2017 11:36:06 +1100 Subject: [PATCH 16/37] Be more systematic about result of apply method The same type needs to be used as a result type for the copy method, and the function type implemented by the companion module. --- .../src/dotty/tools/dotc/ast/Desugar.scala | 26 +++++++++++++------ 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/ast/Desugar.scala b/compiler/src/dotty/tools/dotc/ast/Desugar.scala index 863c10cb07c3..83a23edf0b90 100644 --- a/compiler/src/dotty/tools/dotc/ast/Desugar.scala +++ b/compiler/src/dotty/tools/dotc/ast/Desugar.scala @@ -334,12 +334,19 @@ object desugar { // a reference to the class type bound by `cdef`, with type parameters coming from the constructor val classTypeRef = appliedRef(classTycon) - // a refereence to `enumClass`, with type parameters coming from the constructor + // a reference to `enumClass`, with type parameters coming from the constructor lazy val enumClassTypeRef = appliedRef(enumClassRef) // new C[Ts](paramss) lazy val creatorExpr = New(classTypeRef, constrVparamss nestedMap refOfDef) + // The return type of the `apply` and `copy` methods + val applyResultTpt = + if (isEnumCase) + if (parents.isEmpty) enumClassTypeRef + else parents.head + else TypeTree() + // Methods to add to a case class C[..](p1: T1, ..., pN: Tn)(moreParams) // def isDefined = true // def productArity = N @@ -380,7 +387,7 @@ object desugar { cpy.ValDef(vparam)(rhs = copyDefault(vparam))) val copyRestParamss = derivedVparamss.tail.nestedMap(vparam => cpy.ValDef(vparam)(rhs = EmptyTree)) - DefDef(nme.copy, derivedTparams, copyFirstParams :: copyRestParamss, TypeTree(), creatorExpr) + DefDef(nme.copy, derivedTparams, copyFirstParams :: copyRestParamss, applyResultTpt, creatorExpr) .withMods(synthetic) :: Nil } @@ -430,15 +437,15 @@ object desugar { constrVparamss.length > 1 || mods.is(Abstract) || constr.mods.is(Private)) anyRef + else // todo: also use anyRef if constructor has a dependent method type (or rule that out)! - else (constrVparamss :\ classTypeRef) ((vparams, restpe) => Function(vparams map (_.tpt), restpe)) + (constrVparamss :\ (if (isEnumCase) applyResultTpt else classTypeRef)) ( + (vparams, restpe) => Function(vparams map (_.tpt), restpe)) val applyMeths = if (mods is Abstract) Nil - else { - val restpe = if (isEnumCase) enumClassTypeRef else TypeTree() - DefDef(nme.apply, derivedTparams, derivedVparamss, restpe, creatorExpr) + else + DefDef(nme.apply, derivedTparams, derivedVparamss, applyResultTpt, creatorExpr) .withFlags(Synthetic | (constr1.mods.flags & DefaultParameterized)) :: Nil - } val unapplyMeth = { val unapplyParam = makeSyntheticParameter(tpt = classTypeRef) val unapplyRHS = if (arity == 0) Literal(Constant(true)) else Ident(unapplyParam.name) @@ -505,7 +512,10 @@ object desugar { case _ => } - flatTree(cdef1 :: companions ::: implicitWrappers) + val result = val flatTree(cdef1 :: companions ::: implicitWrappers) + //if (isEnum) println(i"enum $cdef\n --->\n$result") + //if (isEnumCase) println(i"enum case $cdef\n --->\n$result") + result } val AccessOrSynthetic = AccessFlags | Synthetic From d3b2c37b9fb4211ade0660c3985f9f14a0ffa505 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 10 Feb 2017 11:36:25 +1100 Subject: [PATCH 17/37] New test case --- tests/pos/enum-List-control.scala | 14 ++++++++++++++ tests/run/enum-Tree.check | 1 + tests/run/enum-Tree.scala | 29 +++++++++++++++++++++++++++++ 3 files changed, 44 insertions(+) create mode 100644 tests/pos/enum-List-control.scala create mode 100644 tests/run/enum-Tree.check create mode 100644 tests/run/enum-Tree.scala diff --git a/tests/pos/enum-List-control.scala b/tests/pos/enum-List-control.scala new file mode 100644 index 000000000000..d9df176d16f6 --- /dev/null +++ b/tests/pos/enum-List-control.scala @@ -0,0 +1,14 @@ +abstract sealed class List[T] extends Enum +object List { + final case class Cons[T](x: T, xs: List[T]) extends List[T] { + def enumTag = 0 + } + final case class Nil[T]() extends List[T] { + def enumTag = 1 + } +} +object Test { + import List._ + val xs = Cons(1, Cons(2, Cons(3, Nil()))) + def main(args: Array[String]) = println(xs) +} diff --git a/tests/run/enum-Tree.check b/tests/run/enum-Tree.check new file mode 100644 index 000000000000..02f5151bec97 --- /dev/null +++ b/tests/run/enum-Tree.check @@ -0,0 +1 @@ +If(IsZero(Pred(Succ(Zero))),Succ(Succ(Zero)),Pred(Pred(Zero))) --> 2 diff --git a/tests/run/enum-Tree.scala b/tests/run/enum-Tree.scala new file mode 100644 index 000000000000..ef5bd7a5714e --- /dev/null +++ b/tests/run/enum-Tree.scala @@ -0,0 +1,29 @@ +enum Tree[T] { + case True extends Tree[Boolean] + case False extends Tree[Boolean] + case Zero extends Tree[Int] + 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(cond: Tree[Boolean], thenp: Tree[T], elsep: Tree[T]) +} + +object Test { + import Tree._ + + def eval[T](e: Tree[T]): T = e match { + case True => true + case False => false + case Zero => 0 + case Succ(f) => eval(f) + 1 + case Pred(f) => eval(f) - 1 + case IsZero(f) => eval(f) == 0 + case If(cond, thenp, elsep) => if (eval(cond)) eval(thenp) else eval(elsep) + } + + val data = If(IsZero(Pred(Succ(Zero))), Succ(Succ(Zero)), Pred(Pred(Zero))) + + def main(args: Array[String]) = { + println(s"$data --> ${eval(data)}") + } +} From 8f3c9a80cd464a962abeede37087ed52a6d9970a Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 10 Feb 2017 13:07:25 +1100 Subject: [PATCH 18/37] Fix typo --- compiler/src/dotty/tools/dotc/ast/Desugar.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/ast/Desugar.scala b/compiler/src/dotty/tools/dotc/ast/Desugar.scala index 83a23edf0b90..3a3acb06cc9b 100644 --- a/compiler/src/dotty/tools/dotc/ast/Desugar.scala +++ b/compiler/src/dotty/tools/dotc/ast/Desugar.scala @@ -512,7 +512,7 @@ object desugar { case _ => } - val result = val flatTree(cdef1 :: companions ::: implicitWrappers) + val result = flatTree(cdef1 :: companions ::: implicitWrappers) //if (isEnum) println(i"enum $cdef\n --->\n$result") //if (isEnumCase) println(i"enum case $cdef\n --->\n$result") result From c245600ed4bfd4af6f2b45e8cae2cf5a63ddeaf0 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 11 Feb 2017 00:11:25 +1100 Subject: [PATCH 19/37] More fine-grained distinctions when flags are defined. Flags like Trait are in fact not always defined when a symbol is created. For symbols loaded from class files, this flag, and some other is defined only once the classfile has been loaded. But this happens in general before the symbol is completed. We model this distinction by separating from the `FromStartFlags` set a new set `AfterLoadFlags` and distinguishing between the two sets in `SymDenotations#is`. Test case is enum-Option.scala. This erroneously complained before that `Enum` was not a trait. --- compiler/src/dotty/tools/dotc/core/Flags.scala | 17 +++++++++++++---- .../dotty/tools/dotc/core/SymDenotations.scala | 13 +++++++++---- .../dotc/reporting/diagnostic/messages.scala | 2 +- tests/run/enum-Option.scala | 16 ++++++++++++++++ 4 files changed, 39 insertions(+), 9 deletions(-) create mode 100644 tests/run/enum-Option.scala diff --git a/compiler/src/dotty/tools/dotc/core/Flags.scala b/compiler/src/dotty/tools/dotc/core/Flags.scala index c1267d8a2373..29f1078a2f9d 100644 --- a/compiler/src/dotty/tools/dotc/core/Flags.scala +++ b/compiler/src/dotty/tools/dotc/core/Flags.scala @@ -450,13 +450,22 @@ object Flags { /** Flags guaranteed to be set upon symbol creation */ final val FromStartFlags = - AccessFlags | Module | Package | Deferred | Final | MethodOrHKCommon | Param | ParamAccessor | Scala2ExistentialCommon | - Mutable.toCommonFlags | InSuperCall | Touched | JavaStatic | CovariantOrOuter | ContravariantOrLabel | ExpandedName | AccessorOrSealed | - CaseAccessorOrBaseTypeArg | Fresh | Frozen | Erroneous | ImplicitCommon | Permanent | Synthetic | - Inline | LazyOrTrait | SuperAccessorOrScala2x | SelfNameOrImplClass + Module | Package | Deferred | MethodOrHKCommon | Param | ParamAccessor | + Scala2ExistentialCommon | Mutable.toCommonFlags | InSuperCall | Touched | JavaStatic | + CovariantOrOuter | ContravariantOrLabel | ExpandedName | CaseAccessorOrBaseTypeArg | + Fresh | Frozen | Erroneous | ImplicitCommon | Permanent | Synthetic | + SuperAccessorOrScala2x | Inline + + /** Flags guaranteed to be set upon symbol creation, or, if symbol is a top-level + * class or object, when the class file defining the symbol is loaded (which + * is generally before the symbol is completed + */ + final val AfterLoadFlags = + FromStartFlags | AccessFlags | Final | AccessorOrSealed | LazyOrTrait | SelfNameOrImplClass assert(FromStartFlags.isTermFlags && FromStartFlags.isTypeFlags) // TODO: Should check that FromStartFlags do not change in completion + assert(AfterLoadFlags.isTermFlags && AfterLoadFlags.isTypeFlags) /** A value that's unstable unless complemented with a Stable flag */ final val UnstableValue = Mutable | Method diff --git a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala index db96463e0813..27782698d7cc 100644 --- a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala +++ b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala @@ -163,26 +163,31 @@ object SymDenotations { setFlag(flags & mask) } + private def isCurrent(fs: FlagSet) = + fs <= ( + if (myInfo.isInstanceOf[SymbolLoader]) FromStartFlags + else AfterLoadFlags) + /** Has this denotation one of the flags in `fs` set? */ final def is(fs: FlagSet)(implicit ctx: Context) = { - (if (fs <= FromStartFlags) myFlags else flags) is fs + (if (isCurrent(fs)) myFlags else flags) is fs } /** Has this denotation one of the flags in `fs` set, whereas none of the flags * in `butNot` are set? */ final def is(fs: FlagSet, butNot: FlagSet)(implicit ctx: Context) = - (if (fs <= FromStartFlags && butNot <= FromStartFlags) myFlags else flags) is (fs, butNot) + (if (isCurrent(fs) && isCurrent(butNot)) myFlags else flags) is (fs, butNot) /** Has this denotation all of the flags in `fs` set? */ final def is(fs: FlagConjunction)(implicit ctx: Context) = - (if (fs <= FromStartFlags) myFlags else flags) is fs + (if (isCurrent(fs)) myFlags else flags) is fs /** Has this denotation all of the flags in `fs` set, whereas none of the flags * in `butNot` are set? */ final def is(fs: FlagConjunction, butNot: FlagSet)(implicit ctx: Context) = - (if (fs <= FromStartFlags && butNot <= FromStartFlags) myFlags else flags) is (fs, butNot) + (if (isCurrent(fs) && isCurrent(butNot)) myFlags else flags) is (fs, butNot) /** The type info. * The info is an instance of TypeType iff this is a type denotation diff --git a/compiler/src/dotty/tools/dotc/reporting/diagnostic/messages.scala b/compiler/src/dotty/tools/dotc/reporting/diagnostic/messages.scala index 57365658e023..b0f5f8ab1140 100644 --- a/compiler/src/dotty/tools/dotc/reporting/diagnostic/messages.scala +++ b/compiler/src/dotty/tools/dotc/reporting/diagnostic/messages.scala @@ -238,7 +238,7 @@ object messages { import core.Flags._ val maxDist = 3 val decls = site.decls.flatMap { sym => - if (sym.is(Synthetic | PrivateOrLocal) || sym.isConstructor) Nil + if (sym.flagsUNSAFE.is(Synthetic | PrivateOrLocal) || sym.isConstructor) Nil else List((sym.name.show, sym)) } diff --git a/tests/run/enum-Option.scala b/tests/run/enum-Option.scala new file mode 100644 index 000000000000..74e449dafabb --- /dev/null +++ b/tests/run/enum-Option.scala @@ -0,0 +1,16 @@ +enum class Option[+T] extends Serializable { + def isDefined: Boolean +} +object Option { + case Some(x: T) { + def isDefined = true + } + case None extends Option[Nothing] { + def isDefined = false + } +} + +object Test { + def main(args: Array[String]) = + assert(Some(None).isDefined) +} From cb3e536f954d7e9a3eea528b07ffb768537f1383 Mon Sep 17 00:00:00 2001 From: Felix Mulder Date: Fri, 10 Feb 2017 15:29:35 +0100 Subject: [PATCH 20/37] Fix cheeky comment in nested scope --- .../dotty/tools/dotc/parsing/Scanners.scala | 23 ++++++++++---- .../tools/dotc/parsing/DocstringTests.scala | 30 +++++++++++++++++++ 2 files changed, 47 insertions(+), 6 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/parsing/Scanners.scala b/compiler/src/dotty/tools/dotc/parsing/Scanners.scala index a6f3936f81f6..ff5019dc9871 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Scanners.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Scanners.scala @@ -178,12 +178,21 @@ object Scanners { /** All doc comments kept by their end position in a `Map` */ private[this] var docstringMap: SortedMap[Int, Comment] = SortedMap.empty - private[this] def addComment(comment: Comment): Unit = - docstringMap = docstringMap + (comment.pos.end -> comment) + private[this] def addComment(comment: Comment): Unit = { + val lookahead = lookaheadReader + def nextPos: Int = (lookahead.getc(): @switch) match { + case ' ' | '\t' => nextPos + case CR | LF | FF => + // if we encounter line delimitng whitespace we don't count it, since + // it seems not to affect positions in source + nextPos - 1 + case _ => lookahead.charOffset - 1 + } + docstringMap = docstringMap + (nextPos -> comment) + } /** Returns the closest docstring preceding the position supplied */ - def getDocComment(pos: Int): Option[Comment] = - docstringMap.to(pos).lastOption.map(_._2) + def getDocComment(pos: Int): Option[Comment] = docstringMap.get(pos) /** A buffer for comments */ val commentBuf = new StringBuilder @@ -589,10 +598,12 @@ object Scanners { val start = lastCharOffset def finishComment(): Boolean = { if (keepComments) { - val pos = Position(start, charOffset, start) + val pos = Position(start, charOffset - 1, start) val comment = Comment(pos, flushBuf(commentBuf)) - if (comment.isDocComment) addComment(comment) + if (comment.isDocComment) { + addComment(comment) + } } true diff --git a/compiler/test/dotty/tools/dotc/parsing/DocstringTests.scala b/compiler/test/dotty/tools/dotc/parsing/DocstringTests.scala index 930ec117a6b8..81ac77761bfc 100644 --- a/compiler/test/dotty/tools/dotc/parsing/DocstringTests.scala +++ b/compiler/test/dotty/tools/dotc/parsing/DocstringTests.scala @@ -488,4 +488,34 @@ class DocstringTests extends DocstringTest { checkDocString(c.rawComment.map(_.raw), "/** Class1 */") } } + + @Test def nestedComment = { + val source = + """ + |trait T { + | /** Cheeky comment */ + |} + |class C + """.stripMargin + + import dotty.tools.dotc.ast.untpd._ + checkFrontend(source) { + case p @ PackageDef(_, Seq(_, c: TypeDef)) => + assert(c.rawComment == None, s"class C is not supposed to have a docstring (${c.rawComment.get}) in:$source") + } + } + + @Test def eofComment = { + val source = + """ + |class C + |/** Cheeky comment */ + """.stripMargin + + import dotty.tools.dotc.ast.untpd._ + checkFrontend(source) { + case p @ PackageDef(_, Seq(c: TypeDef)) => + assert(c.rawComment == None, s"class C is not supposed to have a docstring (${c.rawComment.get}) in:$source") + } + } } /* End class */ From 11965330882472e2976639c7c98b8ac44a819681 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 11 Feb 2017 17:22:35 +0100 Subject: [PATCH 21/37] Fix typo in syntax --- docs/docs/internals/syntax.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/docs/internals/syntax.md b/docs/docs/internals/syntax.md index f89a0f490dd9..c7f282dc195f 100644 --- a/docs/docs/internals/syntax.md +++ b/docs/docs/internals/syntax.md @@ -339,7 +339,7 @@ ConstrMods ::= AccessModifier | Annotation {Annotation} (AccessModifier | ‘this’) ObjectDef ::= id TemplateOpt ModuleDef(mods, name, template) // no constructor EnumDef ::= id ClassConstr [`extends' [ConstrApps]] EnumDef(mods, name, tparams, template) - [nl] ‘{’ EnumCaseStat {semi EnumCaseStat ‘}’ + [nl] ‘{’ EnumCaseStat {semi EnumCaseStat} ‘}’ EnumCaseStat ::= {Annotation [nl]} {Modifier} EnumCase EnumCase ::= `case' (EnumClassDef | ObjectDef) EnumClassDef ::= id [ClsTpeParamClause | ClsParamClause] ClassDef(mods, name, tparams, templ) From a30e7ecabc84583fa93bc215b1d1b5186caec07b Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 13 Feb 2017 13:09:11 +0100 Subject: [PATCH 22/37] Check that cases with type parameters also have an extends clause --- compiler/src/dotty/tools/dotc/ast/Desugar.scala | 6 +++++- tests/neg/enums.scala | 1 + 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/ast/Desugar.scala b/compiler/src/dotty/tools/dotc/ast/Desugar.scala index 3a3acb06cc9b..cf128c26ef3d 100644 --- a/compiler/src/dotty/tools/dotc/ast/Desugar.scala +++ b/compiler/src/dotty/tools/dotc/ast/Desugar.scala @@ -297,7 +297,11 @@ object desugar { // This is not watertight, but `extends AnyVal` will be replaced by `inline` later. val originalTparams = - if (isEnumCase && parents.isEmpty) reconstitutedEnumTypeParams(cdef.pos.startPos) + if (isEnumCase && parents.isEmpty) { + if (constr1.tparams.nonEmpty) + ctx.error(em"case with type parameters needs extends clause", constr1.tparams.head.pos) + reconstitutedEnumTypeParams(cdef.pos.startPos) + } else constr1.tparams val originalVparamss = constr1.vparamss val constrTparams = originalTparams.map(toDefParam) diff --git a/tests/neg/enums.scala b/tests/neg/enums.scala index 83311f37c8e2..2dc8999fae6c 100644 --- a/tests/neg/enums.scala +++ b/tests/neg/enums.scala @@ -1,6 +1,7 @@ enum List[+T] { case Cons(x: T, xs: List[T]) case Nil // error: illegal enum value + case Snoc[U](xs: List[U], x: U) // error: case with type parameters needs extends clause // error // error // error } enum class X { From c58555e434d886e079104b0311ecb523f4ee1b40 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 14 Feb 2017 09:44:07 +0100 Subject: [PATCH 23/37] Don't change the return type of the `copy` method `copy` should always return the type of it's rhs. The discussion of #1970 concluded that no special treatment for enums is needed. --- compiler/src/dotty/tools/dotc/ast/Desugar.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/ast/Desugar.scala b/compiler/src/dotty/tools/dotc/ast/Desugar.scala index cf128c26ef3d..aa073429d77f 100644 --- a/compiler/src/dotty/tools/dotc/ast/Desugar.scala +++ b/compiler/src/dotty/tools/dotc/ast/Desugar.scala @@ -391,7 +391,7 @@ object desugar { cpy.ValDef(vparam)(rhs = copyDefault(vparam))) val copyRestParamss = derivedVparamss.tail.nestedMap(vparam => cpy.ValDef(vparam)(rhs = EmptyTree)) - DefDef(nme.copy, derivedTparams, copyFirstParams :: copyRestParamss, applyResultTpt, creatorExpr) + DefDef(nme.copy, derivedTparams, copyFirstParams :: copyRestParamss, TypeTree(), creatorExpr) .withMods(synthetic) :: Nil } From 4bdad3c21a1461bed6e91ef69dd767fa5211f60d Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 14 Feb 2017 09:45:44 +0100 Subject: [PATCH 24/37] Change return type of `apply`. In an enum case like case C() extends P1 with ... with Pn ... apply now returns `P1 & ... & Pn`, where before it was just P1. Also, add to Option test. --- compiler/src/dotty/tools/dotc/ast/Desugar.scala | 14 +++++++------- tests/run/enum-Option.scala | 5 ++++- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/ast/Desugar.scala b/compiler/src/dotty/tools/dotc/ast/Desugar.scala index aa073429d77f..575af97f412c 100644 --- a/compiler/src/dotty/tools/dotc/ast/Desugar.scala +++ b/compiler/src/dotty/tools/dotc/ast/Desugar.scala @@ -344,13 +344,6 @@ object desugar { // new C[Ts](paramss) lazy val creatorExpr = New(classTypeRef, constrVparamss nestedMap refOfDef) - // The return type of the `apply` and `copy` methods - val applyResultTpt = - if (isEnumCase) - if (parents.isEmpty) enumClassTypeRef - else parents.head - else TypeTree() - // Methods to add to a case class C[..](p1: T1, ..., pN: Tn)(moreParams) // def isDefined = true // def productArity = N @@ -436,6 +429,13 @@ object desugar { // For all other classes, the parent is AnyRef. val companions = if (isCaseClass) { + // The return type of the `apply` method + val applyResultTpt = + if (isEnumCase) + if (parents.isEmpty) enumClassTypeRef + else parents.reduceLeft(AndTypeTree) + else TypeTree() + val parent = if (constrTparams.nonEmpty || constrVparamss.length > 1 || diff --git a/tests/run/enum-Option.scala b/tests/run/enum-Option.scala index 74e449dafabb..b9efadf0d4d9 100644 --- a/tests/run/enum-Option.scala +++ b/tests/run/enum-Option.scala @@ -2,6 +2,7 @@ enum class Option[+T] extends Serializable { def isDefined: Boolean } object Option { + def apply[T](x: T): Option[T] = if (x == null) None else Some(x) case Some(x: T) { def isDefined = true } @@ -11,6 +12,8 @@ object Option { } object Test { - def main(args: Array[String]) = + def main(args: Array[String]) = { assert(Some(None).isDefined) + Option(22) match { case Option.Some(x) => assert(x == 22) } + } } From cf10e28a54aaeab124f9a939f71da7e09d299bcb Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 14 Feb 2017 11:17:53 +0100 Subject: [PATCH 25/37] Change enumeration members. Based on the discussion in #1970, enumeration objects now have three public members: - valueOf: Map[Int, E] - withName: Map[String, E] - values: Iterable[E] Also, the variance of case type parameters is now the same as in the corresponding type parameter of the enum class. --- .../dotty/tools/dotc/ast/DesugarEnums.scala | 26 ++++++++++--------- .../src/dotty/tools/dotc/core/StdNames.scala | 1 - library/src/scala/runtime/EnumValues.scala | 25 ++++++++++-------- tests/run/generic/Color.scala | 3 ++- tests/run/generic/SearchResult.scala | 3 ++- 5 files changed, 32 insertions(+), 26 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/ast/DesugarEnums.scala b/compiler/src/dotty/tools/dotc/ast/DesugarEnums.scala index 4317c818397e..8bd3c8580861 100644 --- a/compiler/src/dotty/tools/dotc/ast/DesugarEnums.scala +++ b/compiler/src/dotty/tools/dotc/ast/DesugarEnums.scala @@ -34,7 +34,8 @@ object DesugarEnums { } /** Type parameters reconstituted from the constructor - * of the `enum' class corresponding to an enum case + * of the `enum' class corresponding to an enum case. + * The variance is the same as the corresponding type parameter of the enum class. */ def reconstitutedEnumTypeParams(pos: Position)(implicit ctx: Context) = { val tparams = enumClass.primaryConstructor.info match { @@ -43,11 +44,11 @@ object DesugarEnums { case _ => Nil } - for (tparam <- tparams) yield { + (tparams, enumClass.typeParams).zipped.map { (tparam, ecTparam) => val tbounds = new DerivedFromParamTree tbounds.pushAttachment(OriginalSymbol, tparam) TypeDef(tparam.name, tbounds) - .withFlags(Param | PrivateLocal).withPos(pos) + .withFlags(Param | PrivateLocal | ecTparam.flags & VarianceFlags).withPos(pos) } } @@ -64,7 +65,8 @@ object DesugarEnums { /** The following lists of definitions for an enum type E: * * private val $values = new EnumValues[E] - * def valueOf: Int => E = $values + * def valueOf = $values.fromInt + * def withName = $values.fromName * def values = $values.values * * private def $new(tag: Int, name: String) = new E { @@ -74,32 +76,32 @@ object DesugarEnums { * } */ private def enumScaffolding(implicit ctx: Context): List[Tree] = { - val valsRef = Ident(nme.DOLLAR_VALUES) + def valuesDot(name: String) = Select(Ident(nme.DOLLAR_VALUES), name.toTermName) + def enumDefDef(name: String, select: String) = + DefDef(name.toTermName, Nil, Nil, TypeTree(), valuesDot(select)) def param(name: TermName, typ: Type) = ValDef(name, TypeTree(typ), EmptyTree).withFlags(Param) val privateValuesDef = ValDef(nme.DOLLAR_VALUES, TypeTree(), New(TypeTree(defn.EnumValuesType.appliedTo(enumClass.typeRef :: Nil)), ListOfNil)) .withFlags(Private) - val valueOfDef = - DefDef(nme.valueOf, Nil, Nil, - TypeTree(defn.FunctionOf(defn.IntType :: Nil, enumClass.typeRef)), valsRef) - val valuesDef = - DefDef(nme.values, Nil, Nil, TypeTree(), Select(valsRef, nme.values)) + val valueOfDef = enumDefDef("valueOf", "fromInt") + val withNameDef = enumDefDef("withName", "fromName") + val valuesDef = enumDefDef("values", "values") val enumTagDef = DefDef(nme.enumTag, Nil, Nil, TypeTree(), Ident(nme.tag)) val toStringDef = DefDef(nme.toString_, Nil, Nil, TypeTree(), Ident(nme.name)) .withFlags(Override) val registerStat = - Apply(Select(valsRef, nme.register), This(EmptyTypeIdent) :: Nil) + Apply(valuesDot("register"), This(EmptyTypeIdent) :: Nil) def creator = New(Template(emptyConstructor, enumClassRef :: Nil, EmptyValDef, List(enumTagDef, toStringDef, registerStat))) val newDef = DefDef(nme.DOLLAR_NEW, Nil, List(List(param(nme.tag, defn.IntType), param(nme.name, defn.StringType))), TypeTree(), creator) - List(privateValuesDef, valueOfDef, valuesDef, newDef) + List(privateValuesDef, valueOfDef, withNameDef, valuesDef, newDef) } def expandEnumModule(name: TermName, impl: Template, mods: Modifiers, pos: Position)(implicit ctx: Context): Tree = { diff --git a/compiler/src/dotty/tools/dotc/core/StdNames.scala b/compiler/src/dotty/tools/dotc/core/StdNames.scala index ff3ddbad725a..bc3f96d91d51 100644 --- a/compiler/src/dotty/tools/dotc/core/StdNames.scala +++ b/compiler/src/dotty/tools/dotc/core/StdNames.scala @@ -477,7 +477,6 @@ object StdNames { val productPrefix: N = "productPrefix" val readResolve: N = "readResolve" val reflect : N = "reflect" - val register: N = "register" val reify : N = "reify" val rootMirror : N = "rootMirror" val runOrElse: N = "runOrElse" diff --git a/library/src/scala/runtime/EnumValues.scala b/library/src/scala/runtime/EnumValues.scala index 6d2e56cf3963..6f9d907b314f 100644 --- a/library/src/scala/runtime/EnumValues.scala +++ b/library/src/scala/runtime/EnumValues.scala @@ -1,18 +1,21 @@ package scala.runtime -import scala.collection.immutable.Seq -import scala.collection.mutable.ResizableArray +import scala.collection.immutable.Map + +class EnumValues[E <: Enum] { + private var myMap: Map[Int, E] = Map() + private var fromNameCache: Map[String, E] = null -class EnumValues[E <: Enum] extends ResizableArray[E] { - private var valuesCache: List[E] = Nil def register(v: E) = { - ensureSize(v.enumTag + 1) - size0 = size0 max (v.enumTag + 1) - array(v.enumTag) = v - valuesCache = null + require(!myMap.contains(v.enumTag)) + myMap = myMap.updated(v.enumTag, v) + fromNameCache = null } - def values: Seq[E] = { - if (valuesCache == null) valuesCache = array.filter(_ != null).toList.asInstanceOf[scala.List[E]] - valuesCache + + 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/Color.scala b/tests/run/generic/Color.scala index 183f183496cc..7f2a8818c0fc 100644 --- a/tests/run/generic/Color.scala +++ b/tests/run/generic/Color.scala @@ -13,7 +13,8 @@ sealed trait Color extends Enum object Color { private val $values = new runtime.EnumValues[Color] - def valueOf: Int => Color = $values + def valueOf = $values.fromInt + def withName = $values.fromName def values = $values.values private def $new(tag: Int, name: String) = new Color { diff --git a/tests/run/generic/SearchResult.scala b/tests/run/generic/SearchResult.scala index d39ee89a04b7..d4380a072b9f 100644 --- a/tests/run/generic/SearchResult.scala +++ b/tests/run/generic/SearchResult.scala @@ -14,7 +14,8 @@ sealed trait SearchResult extends Enum object SearchResult extends { private val $values = new runtime.EnumValues[SearchResult] - def valueOf: Int => SearchResult = $values + def valueOf = $values.fromInt + def withName = $values.fromName def values = $values.values private def $new(tag: Int, name: String) = new SearchResult { From 91a26b3f42d1218015acb9b7e1bfc180e3ed779b Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 27 Feb 2017 17:44:02 +0100 Subject: [PATCH 26/37] Support comma-separated enum constants --- .../src/dotty/tools/dotc/ast/Desugar.scala | 18 ++++++++-- .../dotty/tools/dotc/ast/DesugarEnums.scala | 35 ++++++++++--------- .../dotty/tools/dotc/parsing/Parsers.scala | 19 ++++++---- docs/docs/internals/syntax.md | 2 +- tests/run/enum-Color.scala | 4 +-- 5 files changed, 48 insertions(+), 30 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/ast/Desugar.scala b/compiler/src/dotty/tools/dotc/ast/Desugar.scala index 575af97f412c..04834b04b300 100644 --- a/compiler/src/dotty/tools/dotc/ast/Desugar.scala +++ b/compiler/src/dotty/tools/dotc/ast/Desugar.scala @@ -576,11 +576,23 @@ object desugar { /** val p1, ..., pN: T = E * ==> * makePatDef[[val p1: T1 = E]]; ...; makePatDef[[val pN: TN = E]] + * + * case e1, ..., eN + * ==> + * expandSimpleEnumCase([case e1]); ...; expandSimpleEnumCase([case eN]) */ - def patDef(pdef: PatDef)(implicit ctx: Context): Tree = { + def patDef(pdef: PatDef)(implicit ctx: Context): Tree = flatTree { val PatDef(mods, pats, tpt, rhs) = pdef - val pats1 = if (tpt.isEmpty) pats else pats map (Typed(_, tpt)) - flatTree(pats1 map (makePatDef(pdef, mods, _, rhs))) + if (mods.hasMod[Mod.EnumCase] && enumCaseIsLegal(pdef)) + pats map { + case id: Ident => + expandSimpleEnumCase(id.name.asTermName, mods, + Position(pdef.pos.start, id.pos.end, id.pos.start)) + } + else { + val pats1 = if (tpt.isEmpty) pats else pats map (Typed(_, tpt)) + pats1 map (makePatDef(pdef, mods, _, rhs)) + } } /** If `pat` is a variable pattern, diff --git a/compiler/src/dotty/tools/dotc/ast/DesugarEnums.scala b/compiler/src/dotty/tools/dotc/ast/DesugarEnums.scala index 8bd3c8580861..7fdff0e2d6dd 100644 --- a/compiler/src/dotty/tools/dotc/ast/DesugarEnums.scala +++ b/compiler/src/dotty/tools/dotc/ast/DesugarEnums.scala @@ -24,14 +24,15 @@ object DesugarEnums { result } - def isLegalEnumCase(tree: MemberDef)(implicit ctx: Context): Boolean = { - tree.mods.hasMod[Mod.EnumCase] && - ( ctx.owner.is(ModuleClass) && enumClass.derivesFrom(defn.EnumClass) + def isLegalEnumCase(tree: MemberDef)(implicit ctx: Context): Boolean = + tree.mods.hasMod[Mod.EnumCase] && enumCaseIsLegal(tree) + + def enumCaseIsLegal(tree: Tree)(implicit ctx: Context): Boolean = ( + ctx.owner.is(ModuleClass) && enumClass.derivesFrom(defn.EnumClass) || { ctx.error(em"case not allowed here, since owner ${ctx.owner} is not an `enum' object", tree.pos) false } ) - } /** Type parameters reconstituted from the constructor * of the `enum' class corresponding to an enum case. @@ -104,23 +105,25 @@ object DesugarEnums { List(privateValuesDef, valueOfDef, withNameDef, valuesDef, newDef) } - def expandEnumModule(name: TermName, impl: Template, mods: Modifiers, pos: Position)(implicit ctx: Context): Tree = { - def nameLit = Literal(Constant(name.toString)) - if (impl.parents.isEmpty) { - if (reconstitutedEnumTypeParams(pos).nonEmpty) - ctx.error(i"illegal enum value of generic $enumClass: an explicit `extends' clause is needed", pos) - val tag = nextEnumTag - val prefix = if (tag == 0) enumScaffolding else Nil - val creator = Apply(Ident(nme.DOLLAR_NEW), List(Literal(Constant(tag)), nameLit)) - val vdef = ValDef(name, enumClassRef, creator).withMods(mods | Final).withPos(pos) - flatTree(prefix ::: vdef :: Nil).withPos(pos.startPos) - } else { + def expandEnumModule(name: TermName, impl: Template, mods: Modifiers, pos: Position)(implicit ctx: Context): Tree = + if (impl.parents.isEmpty) + expandSimpleEnumCase(name, mods, pos) + else { def toStringMeth = - DefDef(nme.toString_, Nil, Nil, TypeTree(defn.StringType), nameLit) + DefDef(nme.toString_, Nil, Nil, TypeTree(defn.StringType), Literal(Constant(name.toString))) .withFlags(Override) val impl1 = cpy.Template(impl)(body = impl.body ++ List(enumTagMeth, toStringMeth)) ValDef(name, TypeTree(), New(impl1)).withMods(mods | Final).withPos(pos) } + + def expandSimpleEnumCase(name: TermName, mods: Modifiers, pos: Position)(implicit ctx: Context): Tree = { + if (reconstitutedEnumTypeParams(pos).nonEmpty) + ctx.error(i"illegal enum value of generic $enumClass: an explicit `extends' clause is needed", pos) + val tag = nextEnumTag + val prefix = if (tag == 0) enumScaffolding else Nil + val creator = Apply(Ident(nme.DOLLAR_NEW), List(Literal(Constant(tag)), Literal(Constant(name.toString)))) + val vdef = ValDef(name, enumClassRef, creator).withMods(mods | Final).withPos(pos) + flatTree(prefix ::: vdef :: Nil).withPos(pos.startPos) } } diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index ee736179a5e8..9864281a522c 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -2149,8 +2149,8 @@ object Parsers { } /** EnumCaseStats = EnumCaseStat {semi EnumCaseStat */ - def enumCaseStats(): List[MemberDef] = { - val cases = new ListBuffer[MemberDef] += enumCaseStat() + def enumCaseStats(): List[DefTree] = { + val cases = new ListBuffer[DefTree] += enumCaseStat() while (in.token != RBRACE) { acceptStatSep() cases += enumCaseStat() @@ -2159,19 +2159,24 @@ object Parsers { } /** EnumCaseStat = {Annotation [nl]} {Modifier} EnumCase */ - def enumCaseStat(): MemberDef = + def enumCaseStat(): DefTree = enumCase(in.offset, defAnnotsMods(modifierTokens)) /** EnumCase = `case' (EnumClassDef | ObjectDef) */ - def enumCase(start: Offset, mods: Modifiers): MemberDef = { + def enumCase(start: Offset, mods: Modifiers): DefTree = { val mods1 = mods.withAddedMod(atPos(in.offset)(Mod.EnumCase())) | Case accept(CASE) atPos(start, nameStart) { - val name = ident() + val id = termIdent() if (in.token == LBRACKET || in.token == LPAREN) - classDefRest(start, mods1, name.toTypeName) + classDefRest(start, mods1, id.name.toTypeName) + else if (in.token == COMMA) { + in.nextToken() + val ids = commaSeparated(termIdent) + PatDef(mods1, id :: ids, TypeTree(), EmptyTree) + } else - objectDefRest(start, mods1, name) + objectDefRest(start, mods1, id.name.asTermName) } } diff --git a/docs/docs/internals/syntax.md b/docs/docs/internals/syntax.md index c7f282dc195f..e4285e20f902 100644 --- a/docs/docs/internals/syntax.md +++ b/docs/docs/internals/syntax.md @@ -341,7 +341,7 @@ ObjectDef ::= id TemplateOpt EnumDef ::= id ClassConstr [`extends' [ConstrApps]] EnumDef(mods, name, tparams, template) [nl] ‘{’ EnumCaseStat {semi EnumCaseStat} ‘}’ EnumCaseStat ::= {Annotation [nl]} {Modifier} EnumCase -EnumCase ::= `case' (EnumClassDef | ObjectDef) +EnumCase ::= `case' (EnumClassDef | ObjectDef | ids) EnumClassDef ::= id [ClsTpeParamClause | ClsParamClause] ClassDef(mods, name, tparams, templ) ClsParamClauses TemplateOpt TemplateOpt ::= [‘extends’ Template | [nl] TemplateBody] diff --git a/tests/run/enum-Color.scala b/tests/run/enum-Color.scala index 24916abeeb9a..1a077bf8e467 100644 --- a/tests/run/enum-Color.scala +++ b/tests/run/enum-Color.scala @@ -1,7 +1,5 @@ enum Color { - case Red - case Green - case Blue + case Red, Green, Blue } object Test { From 44d9ab81886a9ff0b7256fccb584c049f1c18027 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 28 Feb 2017 18:59:08 +0100 Subject: [PATCH 27/37] Support cases with type parameters that extend a non-parameterized base Support cases with type parameters that implicitly extend a non-parameterized base without needing their own extends clause. The proposal has been updated to make clear that this is supported. Also address other reviewers comments. --- .../src/dotty/tools/dotc/ast/Desugar.scala | 20 ++++++++++------- .../dotty/tools/dotc/ast/DesugarEnums.scala | 20 ++++++++++------- tests/run/enum-HList.scala | 22 +++++++++++++++++++ 3 files changed, 46 insertions(+), 16 deletions(-) create mode 100644 tests/run/enum-HList.scala diff --git a/compiler/src/dotty/tools/dotc/ast/Desugar.scala b/compiler/src/dotty/tools/dotc/ast/Desugar.scala index 04834b04b300..80da75678be9 100644 --- a/compiler/src/dotty/tools/dotc/ast/Desugar.scala +++ b/compiler/src/dotty/tools/dotc/ast/Desugar.scala @@ -296,11 +296,16 @@ object desugar { val isValueClass = parents.nonEmpty && isAnyVal(parents.head) // This is not watertight, but `extends AnyVal` will be replaced by `inline` later. + lazy val reconstitutedTypeParams = reconstitutedEnumTypeParams(cdef.pos.startPos) + val originalTparams = if (isEnumCase && parents.isEmpty) { - if (constr1.tparams.nonEmpty) - ctx.error(em"case with type parameters needs extends clause", constr1.tparams.head.pos) - reconstitutedEnumTypeParams(cdef.pos.startPos) + if (constr1.tparams.nonEmpty) { + if (reconstitutedTypeParams.nonEmpty) + ctx.error(em"case with type parameters needs extends clause", constr1.tparams.head.pos) + constr1.tparams + } + else reconstitutedTypeParams } else constr1.tparams val originalVparamss = constr1.vparamss @@ -339,7 +344,9 @@ object desugar { // a reference to the class type bound by `cdef`, with type parameters coming from the constructor val classTypeRef = appliedRef(classTycon) // a reference to `enumClass`, with type parameters coming from the constructor - lazy val enumClassTypeRef = appliedRef(enumClassRef) + lazy val enumClassTypeRef = + if (reconstitutedTypeParams.isEmpty) enumClassRef + else appliedRef(enumClassRef) // new C[Ts](paramss) lazy val creatorExpr = New(classTypeRef, constrVparamss nestedMap refOfDef) @@ -516,10 +523,7 @@ object desugar { case _ => } - val result = flatTree(cdef1 :: companions ::: implicitWrappers) - //if (isEnum) println(i"enum $cdef\n --->\n$result") - //if (isEnumCase) println(i"enum case $cdef\n --->\n$result") - result + flatTree(cdef1 :: companions ::: implicitWrappers) } val AccessOrSynthetic = AccessFlags | Synthetic diff --git a/compiler/src/dotty/tools/dotc/ast/DesugarEnums.scala b/compiler/src/dotty/tools/dotc/ast/DesugarEnums.scala index 7fdff0e2d6dd..c5c95d647c64 100644 --- a/compiler/src/dotty/tools/dotc/ast/DesugarEnums.scala +++ b/compiler/src/dotty/tools/dotc/ast/DesugarEnums.scala @@ -14,14 +14,17 @@ object DesugarEnums { import untpd._ import desugar.DerivedFromParamTree - val EnumCaseCount = new Property.Key[Int] + /** Attachment containing: The number of enum cases seen so far, and whether a + * simple enum case was already seen. + */ + val EnumCaseCount = new Property.Key[(Int, Boolean)] def enumClass(implicit ctx: Context) = ctx.owner.linkedClass - def nextEnumTag(implicit ctx: Context): Int = { - val result = ctx.tree.removeAttachment(EnumCaseCount).getOrElse(0) - ctx.tree.pushAttachment(EnumCaseCount, result + 1) - result + def nextEnumTag(isSimpleCase: Boolean)(implicit ctx: Context): (Int, Boolean) = { + val (count, simpleSeen) = ctx.tree.removeAttachment(EnumCaseCount).getOrElse((0, false)) + ctx.tree.pushAttachment(EnumCaseCount, (count + 1, simpleSeen | isSimpleCase)) + (count, simpleSeen) } def isLegalEnumCase(tree: MemberDef)(implicit ctx: Context): Boolean = @@ -54,7 +57,8 @@ object DesugarEnums { } def enumTagMeth(implicit ctx: Context) = - DefDef(nme.enumTag, Nil, Nil, TypeTree(), Literal(Constant(nextEnumTag))) + DefDef(nme.enumTag, Nil, Nil, TypeTree(), + Literal(Constant(nextEnumTag(isSimpleCase = false)._1))) def enumClassRef(implicit ctx: Context) = TypeTree(enumClass.typeRef) @@ -120,8 +124,8 @@ object DesugarEnums { def expandSimpleEnumCase(name: TermName, mods: Modifiers, pos: Position)(implicit ctx: Context): Tree = { if (reconstitutedEnumTypeParams(pos).nonEmpty) ctx.error(i"illegal enum value of generic $enumClass: an explicit `extends' clause is needed", pos) - val tag = nextEnumTag - val prefix = if (tag == 0) enumScaffolding else Nil + val (tag, simpleSeen) = nextEnumTag(isSimpleCase = true) + val prefix = if (simpleSeen) Nil else enumScaffolding val creator = Apply(Ident(nme.DOLLAR_NEW), List(Literal(Constant(tag)), Literal(Constant(name.toString)))) val vdef = ValDef(name, enumClassRef, creator).withMods(mods | Final).withPos(pos) flatTree(prefix ::: vdef :: Nil).withPos(pos.startPos) diff --git a/tests/run/enum-HList.scala b/tests/run/enum-HList.scala new file mode 100644 index 000000000000..c019cb6cc78c --- /dev/null +++ b/tests/run/enum-HList.scala @@ -0,0 +1,22 @@ +enum HLst { + case HCons[+Hd, +Tl <: HLst](hd: Hd, tl: Tl) + case HNil +} + +object Test { + import HLst._ + def length(hl: HLst): Int = hl match { + case HCons(_, tl) => 1 + length(tl) + case HNil => 0 + } + def sumInts(hl: HLst): Int = hl match { + case HCons(x: Int, tl) => x + sumInts(tl) + case HCons(_, tl) => sumInts(tl) + case HNil => 0 + } + def main(args: Array[String]) = { + val hl = HCons(1, HCons("A", HNil)) + assert(length(hl) == 2, length(hl)) + assert(sumInts(hl) == 1) + } +} From 62b4eb87aa72c3b4e40a9c847ec61dabe467dabd Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 2 Mar 2017 11:41:22 +0100 Subject: [PATCH 28/37] Fix neg test error count Previous expansion caused 3 spurious follow-on errors which are now avoided. --- tests/neg/enums.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/neg/enums.scala b/tests/neg/enums.scala index 2dc8999fae6c..d6f75e2b9e8a 100644 --- a/tests/neg/enums.scala +++ b/tests/neg/enums.scala @@ -1,7 +1,7 @@ enum List[+T] { case Cons(x: T, xs: List[T]) case Nil // error: illegal enum value - case Snoc[U](xs: List[U], x: U) // error: case with type parameters needs extends clause // error // error // error + case Snoc[U](xs: List[U], x: U) // error: case with type parameters needs extends clause } enum class X { From 4c576bf840e69a227250a6c89e218a6c0f2a05d4 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 4 Apr 2017 13:59:36 +0200 Subject: [PATCH 29/37] Avoid assertion failure on neg test This commit can hopefully be reverted once #2121 is in. --- compiler/src/dotty/tools/dotc/typer/Applications.scala | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Applications.scala b/compiler/src/dotty/tools/dotc/typer/Applications.scala index 5dcf16b626e1..310121f31c55 100644 --- a/compiler/src/dotty/tools/dotc/typer/Applications.scala +++ b/compiler/src/dotty/tools/dotc/typer/Applications.scala @@ -217,7 +217,14 @@ trait Applications extends Compatibility { self: Typer with Dynamic => // apply the result type constraint, unless method type is dependent val resultApprox = resultTypeApprox(methType) val savedConstraint = ctx.typerState.constraint - if (!constrainResult(resultApprox, resultType)) + if (!resultApprox.isInstanceOf[PolyType] && + // temporary fix before #2121 is in. The problem here is that errors in the code + // can lead to the result type begin higher-kinded. Then normalize gets confused + // and we end up with an assertion violation "s"inconsistent: no typevars were + // added to committable constraint". Once we distinguish between type lambdas + // and polytypes again this should hopefully become unnecessary. The error + // was triggered by neg/enums.scala. + !constrainResult(resultApprox, resultType)) if (ctx.typerState.isCommittable) // defer the problem until after the application; // it might be healed by an implicit conversion From 1c79612c57af81acec2480bef56240dcf5ec30d1 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 4 Apr 2017 17:57:44 +0200 Subject: [PATCH 30/37] Fix rebase breakage --- compiler/test/dotty/tools/dotc/repl/TestREPL.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/test/dotty/tools/dotc/repl/TestREPL.scala b/compiler/test/dotty/tools/dotc/repl/TestREPL.scala index 44523aae9de6..c5557b86e985 100644 --- a/compiler/test/dotty/tools/dotc/repl/TestREPL.scala +++ b/compiler/test/dotty/tools/dotc/repl/TestREPL.scala @@ -4,7 +4,7 @@ package repl import core.Contexts.Context import collection.mutable -import java.io.StringWriter +import java.io.{StringWriter, PrintStream} import dotty.tools.io.{ PlainFile, Directory } import org.junit.Test From a46f4f840a456bb70ef4b95e6b18608522075442 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 4 Apr 2017 18:31:52 +0200 Subject: [PATCH 31/37] Infer enum type args from type parameter bounds Infer type arguments for enum paraments from corresponding type parameter bounds. This only works if the type parameter in question is variant and its bound is ground. --- .../dotty/tools/dotc/ast/DesugarEnums.scala | 56 +++++++++++++++---- tests/neg/enums.scala | 13 ++++- 2 files changed, 57 insertions(+), 12 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/ast/DesugarEnums.scala b/compiler/src/dotty/tools/dotc/ast/DesugarEnums.scala index c5c95d647c64..ae4ba23d51d3 100644 --- a/compiler/src/dotty/tools/dotc/ast/DesugarEnums.scala +++ b/compiler/src/dotty/tools/dotc/ast/DesugarEnums.scala @@ -8,7 +8,7 @@ import SymDenotations._, Symbols._, StdNames._, Annotations._, Trees._ import Decorators._ import collection.mutable.ListBuffer import util.Property -import reporting.diagnostic.messages._ +import typer.ErrorReporting._ object DesugarEnums { import untpd._ @@ -56,6 +56,31 @@ object DesugarEnums { } } + /** A reference to the enum class `E`, possibly followed by type arguments. + * Each covariant type parameter is approximated by its lower bound. + * Each contravariant type parameter is approximated by its upper bound. + * It is an error if a type parameter is non-variant, or if its approximation + * refers to pther type parameters. + */ + def interpolatedEnumParent(pos: Position)(implicit ctx: Context): Tree = { + val tparams = enumClass.typeParams + def isGround(tp: Type) = tp.subst(tparams, tparams.map(_ => NoType)) eq tp + val targs = tparams map { tparam => + if (tparam.variance > 0 && isGround(tparam.info.bounds.lo)) + tparam.info.bounds.lo + else if (tparam.variance < 0 && isGround(tparam.info.bounds.hi)) + tparam.info.bounds.hi + else { + def problem = + if (tparam.variance == 0) "is non variant" + else "has bounds that depend on a type parameter in the same parameter list" + errorType(i"""cannot determine type argument for enum parent $enumClass, + |type parameter $tparam $problem""", pos) + } + } + TypeTree(enumClass.typeRef.appliedTo(targs)).withPos(pos) + } + def enumTagMeth(implicit ctx: Context) = DefDef(nme.enumTag, Nil, Nil, TypeTree(), Literal(Constant(nextEnumTag(isSimpleCase = false)._1))) @@ -111,7 +136,12 @@ object DesugarEnums { def expandEnumModule(name: TermName, impl: Template, mods: Modifiers, pos: Position)(implicit ctx: Context): Tree = if (impl.parents.isEmpty) - expandSimpleEnumCase(name, mods, pos) + if (impl.body.isEmpty) + expandSimpleEnumCase(name, mods, pos) + else { + val parent = interpolatedEnumParent(pos) + expandEnumModule(name, cpy.Template(impl)(parents = parent :: Nil), mods, pos) + } else { def toStringMeth = DefDef(nme.toString_, Nil, Nil, TypeTree(defn.StringType), Literal(Constant(name.toString))) @@ -121,13 +151,17 @@ object DesugarEnums { ValDef(name, TypeTree(), New(impl1)).withMods(mods | Final).withPos(pos) } - def expandSimpleEnumCase(name: TermName, mods: Modifiers, pos: Position)(implicit ctx: Context): Tree = { - if (reconstitutedEnumTypeParams(pos).nonEmpty) - ctx.error(i"illegal enum value of generic $enumClass: an explicit `extends' clause is needed", pos) - val (tag, simpleSeen) = nextEnumTag(isSimpleCase = true) - val prefix = if (simpleSeen) Nil else enumScaffolding - val creator = Apply(Ident(nme.DOLLAR_NEW), List(Literal(Constant(tag)), Literal(Constant(name.toString)))) - val vdef = ValDef(name, enumClassRef, creator).withMods(mods | Final).withPos(pos) - flatTree(prefix ::: vdef :: Nil).withPos(pos.startPos) - } + def expandSimpleEnumCase(name: TermName, mods: Modifiers, pos: Position)(implicit ctx: Context): Tree = + if (reconstitutedEnumTypeParams(pos).nonEmpty) { + val parent = interpolatedEnumParent(pos) + val impl = Template(emptyConstructor, parent :: Nil, EmptyValDef, Nil) + expandEnumModule(name, impl, mods, pos) + } + else { + val (tag, simpleSeen) = nextEnumTag(isSimpleCase = true) + val prefix = if (simpleSeen) Nil else enumScaffolding + val creator = Apply(Ident(nme.DOLLAR_NEW), List(Literal(Constant(tag)), Literal(Constant(name.toString)))) + val vdef = ValDef(name, enumClassRef, creator).withMods(mods | Final).withPos(pos) + flatTree(prefix ::: vdef :: Nil).withPos(pos.startPos) + } } diff --git a/tests/neg/enums.scala b/tests/neg/enums.scala index d6f75e2b9e8a..1ed3007e7b7d 100644 --- a/tests/neg/enums.scala +++ b/tests/neg/enums.scala @@ -1,9 +1,20 @@ enum List[+T] { case Cons(x: T, xs: List[T]) - case Nil // error: illegal enum value case Snoc[U](xs: List[U], x: U) // error: case with type parameters needs extends clause } enum class X { case Y // error: case not allowed here } + +enum E1[T] { + case C +} + +enum E2[+T, +U >: T] { + case C +} + +enum E3[-T <: Ordered[T]] { + case C +} From 9b37a7c2202c701691cb12b3d040645835d17ff7 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 4 Apr 2017 22:13:39 +0200 Subject: [PATCH 32/37] New and updated tests --- tests/neg/enums.scala | 6 +++--- tests/run/enum-List2a.scala | 11 +++++++++++ tests/run/enum-approx.scala | 22 ++++++++++++++++++++++ tests/run/enumList2a.check | 1 + 4 files changed, 37 insertions(+), 3 deletions(-) create mode 100644 tests/run/enum-List2a.scala create mode 100644 tests/run/enum-approx.scala create mode 100644 tests/run/enumList2a.check diff --git a/tests/neg/enums.scala b/tests/neg/enums.scala index 1ed3007e7b7d..108ec4a6cbfb 100644 --- a/tests/neg/enums.scala +++ b/tests/neg/enums.scala @@ -8,13 +8,13 @@ enum class X { } enum E1[T] { - case C + case C // error: cannot determine type argument } enum E2[+T, +U >: T] { - case C + case C // error: cannot determine type argument } enum E3[-T <: Ordered[T]] { - case C + case C // error: cannot determine type argument } diff --git a/tests/run/enum-List2a.scala b/tests/run/enum-List2a.scala new file mode 100644 index 000000000000..323a5587c1a3 --- /dev/null +++ b/tests/run/enum-List2a.scala @@ -0,0 +1,11 @@ +enum class List[+T] +object List { + case Cons(x: T, xs: List[T]) + case Nil +} +object Test { + import List._ + val xs = Cons(1, Cons(2, Cons(3, Nil))) + def main(args: Array[String]) = println(xs) +} + diff --git a/tests/run/enum-approx.scala b/tests/run/enum-approx.scala new file mode 100644 index 000000000000..7811b3909bf0 --- /dev/null +++ b/tests/run/enum-approx.scala @@ -0,0 +1,22 @@ +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 + } + case ConstNullSimple +} + +object Test { + def main(args: Array[String]) = { + val x: Null = Fun.ConstNull.f("abc") + val y: Null = Fun.ConstNullClass().f("abc") + assert(Fun.ConstNullSimple.f == null) + } +} + diff --git a/tests/run/enumList2a.check b/tests/run/enumList2a.check new file mode 100644 index 000000000000..1d4812de1f64 --- /dev/null +++ b/tests/run/enumList2a.check @@ -0,0 +1 @@ +Cons(1,Cons(2,Cons(3,Nil))) From 993834473c39b409bb2e21838a0e69fcc5fb4757 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 5 Apr 2017 09:05:36 +0200 Subject: [PATCH 33/37] Add check file --- tests/run/enum-List2a.check | 1 + 1 file changed, 1 insertion(+) create mode 100644 tests/run/enum-List2a.check diff --git a/tests/run/enum-List2a.check b/tests/run/enum-List2a.check new file mode 100644 index 000000000000..1d4812de1f64 --- /dev/null +++ b/tests/run/enum-List2a.check @@ -0,0 +1 @@ +Cons(1,Cons(2,Cons(3,Nil))) From f7027732a1d1e92f4d7525f2a984a24fdb7a0053 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 5 Apr 2017 17:09:15 +0200 Subject: [PATCH 34/37] Implementation of proposal changes - rename utility methods - generate utility methods also for object cases --- .../src/dotty/tools/dotc/ast/Desugar.scala | 2 +- .../dotty/tools/dotc/ast/DesugarEnums.scala | 119 +++++++++++------- tests/run/enum-Color.scala | 2 +- tests/run/enum-Option.scala | 8 +- tests/run/planets.scala | 26 ++++ 5 files changed, 107 insertions(+), 50 deletions(-) create mode 100644 tests/run/planets.scala diff --git a/compiler/src/dotty/tools/dotc/ast/Desugar.scala b/compiler/src/dotty/tools/dotc/ast/Desugar.scala index 80da75678be9..8499330fb2c3 100644 --- a/compiler/src/dotty/tools/dotc/ast/Desugar.scala +++ b/compiler/src/dotty/tools/dotc/ast/Desugar.scala @@ -395,7 +395,7 @@ object desugar { .withMods(synthetic) :: Nil } - val enumTagMeths = if (isEnumCase) enumTagMeth :: Nil else Nil + val enumTagMeths = if (isEnumCase) enumTagMeth(CaseKind.Class)._1 :: Nil else Nil copyMeths ::: enumTagMeths ::: productElemMeths.toList } else Nil diff --git a/compiler/src/dotty/tools/dotc/ast/DesugarEnums.scala b/compiler/src/dotty/tools/dotc/ast/DesugarEnums.scala index ae4ba23d51d3..e051824a1cb5 100644 --- a/compiler/src/dotty/tools/dotc/ast/DesugarEnums.scala +++ b/compiler/src/dotty/tools/dotc/ast/DesugarEnums.scala @@ -10,26 +10,26 @@ import collection.mutable.ListBuffer import util.Property import typer.ErrorReporting._ +/** Helper methods to desugar enums */ object DesugarEnums { import untpd._ import desugar.DerivedFromParamTree - /** Attachment containing: The number of enum cases seen so far, and whether a - * simple enum case was already seen. - */ - val EnumCaseCount = new Property.Key[(Int, Boolean)] + @sharable object CaseKind extends Enumeration { + val Simple, Object, Class = Value + } - def enumClass(implicit ctx: Context) = ctx.owner.linkedClass + /** Attachment containing the number of enum cases and the smallest kind that was seen so far. */ + val EnumCaseCount = new Property.Key[(Int, CaseKind.Value)] - def nextEnumTag(isSimpleCase: Boolean)(implicit ctx: Context): (Int, Boolean) = { - val (count, simpleSeen) = ctx.tree.removeAttachment(EnumCaseCount).getOrElse((0, false)) - ctx.tree.pushAttachment(EnumCaseCount, (count + 1, simpleSeen | isSimpleCase)) - (count, simpleSeen) - } + /** 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) + /** 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(em"case not allowed here, since owner ${ctx.owner} is not an `enum' object", tree.pos) @@ -81,59 +81,89 @@ object DesugarEnums { TypeTree(enumClass.typeRef.appliedTo(targs)).withPos(pos) } - def enumTagMeth(implicit ctx: Context) = - DefDef(nme.enumTag, Nil, Nil, TypeTree(), - Literal(Constant(nextEnumTag(isSimpleCase = false)._1))) - + /** A type tree referring to `enumClass` */ def enumClassRef(implicit ctx: Context) = TypeTree(enumClass.typeRef) + /** 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) else cdef + private def valuesDot(name: String) = Select(Ident(nme.DOLLAR_VALUES), name.toTermName) + private def registerCall = Apply(valuesDot("register"), This(EmptyTypeIdent) :: Nil) + /** The following lists of definitions for an enum type E: * * private val $values = new EnumValues[E] - * def valueOf = $values.fromInt - * def withName = $values.fromName - * def values = $values.values - * + * def enumValue = $values.fromInt + * def enumValueNamed = $values.fromName + * def enumValues = $values.values + */ + private def enumScaffolding(implicit ctx: Context): List[Tree] = { + val enumType = enumClass.typeRef.appliedTo(enumClass.typeParams.map(_ => TypeBounds.empty)) + def enumDefDef(name: String, select: String) = + DefDef(name.toTermName, Nil, Nil, TypeTree(), valuesDot(select)) + val privateValuesDef = + ValDef(nme.DOLLAR_VALUES, TypeTree(), + New(TypeTree(defn.EnumValuesType.appliedTo(enumType :: Nil)), ListOfNil)) + .withFlags(Private) + val valueOfDef = enumDefDef("enumValue", "fromInt") + val withNameDef = enumDefDef("enumValueNamed", "fromName") + val valuesDef = enumDefDef("enumValues", "values") + List(privateValuesDef, valueOfDef, withNameDef, valuesDef) + } + + /** A creation method for a value of enum type `E`, which is defined as follows: + * * private def $new(tag: Int, name: String) = new E { * def enumTag = tag * override def toString = name * $values.register(this) * } */ - private def enumScaffolding(implicit ctx: Context): List[Tree] = { - def valuesDot(name: String) = Select(Ident(nme.DOLLAR_VALUES), name.toTermName) - def enumDefDef(name: String, select: String) = - DefDef(name.toTermName, Nil, Nil, TypeTree(), valuesDot(select)) + private def enumValueCreator(implicit ctx: Context) = { def param(name: TermName, typ: Type) = ValDef(name, TypeTree(typ), EmptyTree).withFlags(Param) - val privateValuesDef = - ValDef(nme.DOLLAR_VALUES, TypeTree(), - New(TypeTree(defn.EnumValuesType.appliedTo(enumClass.typeRef :: Nil)), ListOfNil)) - .withFlags(Private) - val valueOfDef = enumDefDef("valueOf", "fromInt") - val withNameDef = enumDefDef("withName", "fromName") - val valuesDef = enumDefDef("values", "values") val enumTagDef = DefDef(nme.enumTag, Nil, Nil, TypeTree(), Ident(nme.tag)) val toStringDef = DefDef(nme.toString_, Nil, Nil, TypeTree(), Ident(nme.name)) .withFlags(Override) - val registerStat = - Apply(valuesDot("register"), This(EmptyTypeIdent) :: Nil) def creator = New(Template(emptyConstructor, enumClassRef :: Nil, EmptyValDef, - List(enumTagDef, toStringDef, registerStat))) - val newDef = - DefDef(nme.DOLLAR_NEW, Nil, - List(List(param(nme.tag, defn.IntType), param(nme.name, defn.StringType))), - TypeTree(), creator) - List(privateValuesDef, valueOfDef, withNameDef, valuesDef, newDef) + List(enumTagDef, toStringDef, registerCall))) + DefDef(nme.DOLLAR_NEW, Nil, + List(List(param(nme.tag, defn.IntType), param(nme.name, defn.StringType))), + TypeTree(), creator) + } + + /** A pair consisting of + * - the next enum tag + * - scaffolding containing the necessary definitions for singleton enum cases + * unless that scaffolding was already generated by a previous call to `nextEnumKind`. + */ + def nextEnumTag(kind: CaseKind.Value)(implicit ctx: Context): (Int, List[Tree]) = { + val (count, seenKind) = ctx.tree.removeAttachment(EnumCaseCount).getOrElse((0, CaseKind.Class)) + val minKind = if (kind < seenKind) kind else seenKind + ctx.tree.pushAttachment(EnumCaseCount, (count + 1, minKind)) + val scaffolding = + if (kind >= seenKind) Nil + else if (kind == CaseKind.Object) enumScaffolding + else if (seenKind == CaseKind.Object) enumValueCreator :: Nil + else enumScaffolding :+ enumValueCreator + (count, scaffolding) + } + + /** A pair consisting of + * - a method returning the next enum tag + * - scaffolding as defined in `nextEnumTag` + */ + def enumTagMeth(kind: CaseKind.Value)(implicit ctx: Context): (DefDef, List[Tree]) = { + val (tag, scaffolding) = nextEnumTag(kind) + (DefDef(nme.enumTag, Nil, Nil, TypeTree(), Literal(Constant(tag))), scaffolding) } + /** Expand a module definition representing a parameterless enum case */ def expandEnumModule(name: TermName, impl: Template, mods: Modifiers, pos: Position)(implicit ctx: Context): Tree = if (impl.parents.isEmpty) if (impl.body.isEmpty) @@ -146,22 +176,23 @@ object DesugarEnums { def toStringMeth = DefDef(nme.toString_, Nil, Nil, TypeTree(defn.StringType), Literal(Constant(name.toString))) .withFlags(Override) - val impl1 = cpy.Template(impl)(body = - impl.body ++ List(enumTagMeth, toStringMeth)) - ValDef(name, TypeTree(), New(impl1)).withMods(mods | Final).withPos(pos) + val (tagMeth, scaffolding) = enumTagMeth(CaseKind.Object) + val impl1 = cpy.Template(impl)(body = impl.body ++ List(tagMeth, toStringMeth, registerCall)) + val vdef = ValDef(name, TypeTree(), New(impl1)).withMods(mods | Final).withPos(pos) + flatTree(scaffolding ::: vdef :: Nil).withPos(pos.startPos) } + /** Expand a simple enum case */ def expandSimpleEnumCase(name: TermName, mods: Modifiers, pos: Position)(implicit ctx: Context): Tree = - if (reconstitutedEnumTypeParams(pos).nonEmpty) { + if (enumClass.typeParams.nonEmpty) { val parent = interpolatedEnumParent(pos) val impl = Template(emptyConstructor, parent :: Nil, EmptyValDef, Nil) expandEnumModule(name, impl, mods, pos) } else { - val (tag, simpleSeen) = nextEnumTag(isSimpleCase = true) - val prefix = if (simpleSeen) Nil else enumScaffolding + val (tag, scaffolding) = nextEnumTag(CaseKind.Simple) val creator = Apply(Ident(nme.DOLLAR_NEW), List(Literal(Constant(tag)), Literal(Constant(name.toString)))) val vdef = ValDef(name, enumClassRef, creator).withMods(mods | Final).withPos(pos) - flatTree(prefix ::: vdef :: Nil).withPos(pos.startPos) + flatTree(scaffolding ::: vdef :: Nil).withPos(pos.startPos) } } diff --git a/tests/run/enum-Color.scala b/tests/run/enum-Color.scala index 1a077bf8e467..836e02c621ed 100644 --- a/tests/run/enum-Color.scala +++ b/tests/run/enum-Color.scala @@ -4,7 +4,7 @@ enum Color { object Test { def main(args: Array[String]) = - for (color <- Color.values) { + for (color <- Color.enumValues) { println(s"$color: ${color.enumTag}") assert(Color.valueOf(color.enumTag) eq color) } diff --git a/tests/run/enum-Option.scala b/tests/run/enum-Option.scala index b9efadf0d4d9..76f5641b3418 100644 --- a/tests/run/enum-Option.scala +++ b/tests/run/enum-Option.scala @@ -1,12 +1,12 @@ -enum class Option[+T] extends Serializable { +enum class Option[+T >: Null] extends Serializable { def isDefined: Boolean } object Option { - def apply[T](x: T): Option[T] = if (x == null) None else Some(x) + def apply[T >: Null](x: T): Option[T] = if (x == null) None else Some(x) case Some(x: T) { def isDefined = true } - case None extends Option[Nothing] { + case None { def isDefined = false } } @@ -14,6 +14,6 @@ object Option { object Test { def main(args: Array[String]) = { assert(Some(None).isDefined) - Option(22) match { case Option.Some(x) => assert(x == 22) } + Option("22") match { case Option.Some(x) => assert(x == "22") } } } diff --git a/tests/run/planets.scala b/tests/run/planets.scala new file mode 100644 index 000000000000..2fff01edca88 --- /dev/null +++ b/tests/run/planets.scala @@ -0,0 +1,26 @@ +enum class 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) + case MARS extends Planet(6.421e+23, 3.3972e6) + case JUPITER extends Planet(1.9e+27, 7.1492e7) + 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 Test { + def main(args: Array[String]) = { + import Planet._ + assert(enumValueNamed("SATURN") == SATURN) + assert(enumValue(2) == EARTH) + val earthWeight = args(0).toDouble + val mass = earthWeight/EARTH.surfaceGravity + for (p <- enumValues) + println(s"Your weight on $p is ${p.surfaceWeight(mass)}") + } +} From 92018967be69ffe660a225f0b58f9772388678fe Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 5 Apr 2017 17:21:48 +0200 Subject: [PATCH 35/37] Update test and add check file --- tests/run/planets.check | 8 ++++++++ tests/run/planets.scala | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) create mode 100644 tests/run/planets.check diff --git a/tests/run/planets.check b/tests/run/planets.check new file mode 100644 index 000000000000..feb6f737db78 --- /dev/null +++ b/tests/run/planets.check @@ -0,0 +1,8 @@ +Your weight on MERCURY is 37.775761520093525 +Your weight on SATURN is 106.60155388115666 +Your weight on VENUS is 90.49990998410455 +Your weight on URANUS is 90.51271993894251 +Your weight on EARTH is 100.0 +Your weight on NEPTUNE is 113.83280724696579 +Your weight on MARS is 37.873718403712886 +Your weight on JUPITER is 253.05575254957407 diff --git a/tests/run/planets.scala b/tests/run/planets.scala index 2fff01edca88..bcbfd7eeb3cc 100644 --- a/tests/run/planets.scala +++ b/tests/run/planets.scala @@ -18,7 +18,7 @@ object Test { import Planet._ assert(enumValueNamed("SATURN") == SATURN) assert(enumValue(2) == EARTH) - val earthWeight = args(0).toDouble + val earthWeight = 100 val mass = earthWeight/EARTH.surfaceGravity for (p <- enumValues) println(s"Your weight on $p is ${p.surfaceWeight(mass)}") From 18d0ae5e08e66c90377ca6818b9d6f1af7f259ac Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 5 Apr 2017 17:27:09 +0200 Subject: [PATCH 36/37] Another test fixed --- tests/run/enum-Color.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/run/enum-Color.scala b/tests/run/enum-Color.scala index 836e02c621ed..683d18d9ed67 100644 --- a/tests/run/enum-Color.scala +++ b/tests/run/enum-Color.scala @@ -6,6 +6,6 @@ object Test { def main(args: Array[String]) = for (color <- Color.enumValues) { println(s"$color: ${color.enumTag}") - assert(Color.valueOf(color.enumTag) eq color) + assert(Color.enumValue(color.enumTag) eq color) } } From 30d8d878118c537ff82c88ef7ade8780b390bfae Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 6 Apr 2017 08:39:26 +0200 Subject: [PATCH 37/37] Emit enum utility methods only if enum class is not generic --- .../src/dotty/tools/dotc/ast/DesugarEnums.scala | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/ast/DesugarEnums.scala b/compiler/src/dotty/tools/dotc/ast/DesugarEnums.scala index e051824a1cb5..43f915961d8f 100644 --- a/compiler/src/dotty/tools/dotc/ast/DesugarEnums.scala +++ b/compiler/src/dotty/tools/dotc/ast/DesugarEnums.scala @@ -91,7 +91,9 @@ object DesugarEnums { else cdef private def valuesDot(name: String) = Select(Ident(nme.DOLLAR_VALUES), name.toTermName) - private def registerCall = Apply(valuesDot("register"), This(EmptyTypeIdent) :: Nil) + private def registerCall(implicit ctx: Context): List[Tree] = + if (enumClass.typeParams.nonEmpty) Nil + else Apply(valuesDot("register"), This(EmptyTypeIdent) :: Nil) :: Nil /** The following lists of definitions for an enum type E: * @@ -101,12 +103,11 @@ object DesugarEnums { * def enumValues = $values.values */ private def enumScaffolding(implicit ctx: Context): List[Tree] = { - val enumType = enumClass.typeRef.appliedTo(enumClass.typeParams.map(_ => TypeBounds.empty)) def enumDefDef(name: String, select: String) = DefDef(name.toTermName, Nil, Nil, TypeTree(), valuesDot(select)) val privateValuesDef = ValDef(nme.DOLLAR_VALUES, TypeTree(), - New(TypeTree(defn.EnumValuesType.appliedTo(enumType :: Nil)), ListOfNil)) + New(TypeTree(defn.EnumValuesType.appliedTo(enumClass.typeRef :: Nil)), ListOfNil)) .withFlags(Private) val valueOfDef = enumDefDef("enumValue", "fromInt") val withNameDef = enumDefDef("enumValueNamed", "fromName") @@ -131,7 +132,7 @@ object DesugarEnums { DefDef(nme.toString_, Nil, Nil, TypeTree(), Ident(nme.name)) .withFlags(Override) def creator = New(Template(emptyConstructor, enumClassRef :: Nil, EmptyValDef, - List(enumTagDef, toStringDef, registerCall))) + List(enumTagDef, toStringDef) ++ registerCall)) DefDef(nme.DOLLAR_NEW, Nil, List(List(param(nme.tag, defn.IntType), param(nme.name, defn.StringType))), TypeTree(), creator) @@ -147,7 +148,7 @@ object DesugarEnums { val minKind = if (kind < seenKind) kind else seenKind ctx.tree.pushAttachment(EnumCaseCount, (count + 1, minKind)) val scaffolding = - if (kind >= seenKind) Nil + if (enumClass.typeParams.nonEmpty || kind >= seenKind) Nil else if (kind == CaseKind.Object) enumScaffolding else if (seenKind == CaseKind.Object) enumValueCreator :: Nil else enumScaffolding :+ enumValueCreator @@ -177,7 +178,8 @@ object DesugarEnums { 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 = + impl.body ++ List(tagMeth, toStringMeth) ++ registerCall) val vdef = ValDef(name, TypeTree(), New(impl1)).withMods(mods | Final).withPos(pos) flatTree(scaffolding ::: vdef :: Nil).withPos(pos.startPos) }