Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add "enum" construct #1958

Merged
merged 37 commits into from Apr 6, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
0a48911
Fix mal-formatting.
odersky Feb 8, 2017
669c5a8
Add enum syntax
odersky Feb 5, 2017
ca039ba
Don't pass docstring as a parameter.
odersky Feb 5, 2017
fe14afb
Simplify syntax
odersky Feb 8, 2017
5c53f53
Allow value expansion of modules in mergeCompanionDefs
odersky Feb 8, 2017
69fd9dc
Adapt generic tests to model modified enum values scheme
odersky Feb 8, 2017
41d83d4
Change handling of enum defs.
odersky Feb 8, 2017
cea243a
Implement enum desugaring
odersky Feb 8, 2017
c279057
Add tests
odersky Feb 8, 2017
b4ece2a
Comment out unused method in Context
odersky Feb 8, 2017
db387a2
Improvement to REPL test
odersky Feb 8, 2017
3ea9831
Fix "closest" computation for docstrings
odersky Feb 8, 2017
adf9009
Make `getDocComment` referentially transparent
felixmulder Feb 9, 2017
2be70e6
Another test
odersky Feb 9, 2017
7a7db73
Link generic test to actual implementations of Enum and EnumValues
odersky Feb 9, 2017
a5345a3
Be more systematic about result of apply method
odersky Feb 10, 2017
d3b2c37
New test case
odersky Feb 10, 2017
8f3c9a8
Fix typo
odersky Feb 10, 2017
c245600
More fine-grained distinctions when flags are defined.
odersky Feb 10, 2017
cb3e536
Fix cheeky comment in nested scope
felixmulder Feb 10, 2017
1196533
Fix typo in syntax
odersky Feb 11, 2017
a30e7ec
Check that cases with type parameters also have an extends clause
odersky Feb 13, 2017
c58555e
Don't change the return type of the `copy` method
odersky Feb 14, 2017
4bdad3c
Change return type of `apply`.
odersky Feb 14, 2017
cf10e28
Change enumeration members.
odersky Feb 14, 2017
91a26b3
Support comma-separated enum constants
odersky Feb 27, 2017
44d9ab8
Support cases with type parameters that extend a non-parameterized base
odersky Feb 28, 2017
62b4eb8
Fix neg test error count
odersky Mar 2, 2017
4c576bf
Avoid assertion failure on neg test
odersky Apr 4, 2017
1c79612
Fix rebase breakage
odersky Apr 4, 2017
a46f4f8
Infer enum type args from type parameter bounds
odersky Apr 4, 2017
9b37a7c
New and updated tests
odersky Apr 4, 2017
9938344
Add check file
odersky Apr 5, 2017
f702773
Implementation of proposal changes
odersky Apr 5, 2017
9201896
Update test and add check file
odersky Apr 5, 2017
18d0ae5
Another test fixed
odersky Apr 5, 2017
30d8d87
Emit enum utility methods only if enum class is not generic
odersky Apr 6, 2017
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
129 changes: 89 additions & 40 deletions compiler/src/dotty/tools/dotc/ast/Desugar.scala
Expand Up @@ -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.
Expand Down Expand Up @@ -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)
Expand All @@ -288,17 +291,31 @@ 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
lazy val reconstitutedTypeParams = reconstitutedEnumTypeParams(cdef.pos.startPos)

val originalTparams =
if (isEnumCase && parents.isEmpty) {
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
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
Expand All @@ -312,21 +329,24 @@ 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 reference to `enumClass`, with type parameters coming from the constructor
lazy val enumClassTypeRef =
if (reconstitutedTypeParams.isEmpty) enumClassRef
else appliedRef(enumClassRef)

// new C[Ts](paramss)
lazy val creatorExpr = New(classTypeRef, constrVparamss nestedMap refOfDef)
Expand Down Expand Up @@ -374,7 +394,9 @@ object desugar {
DefDef(nme.copy, derivedTparams, copyFirstParams :: copyRestParamss, TypeTree(), creatorExpr)
.withMods(synthetic) :: Nil
}
copyMeths ::: productElemMeths.toList

val enumTagMeths = if (isEnumCase) enumTagMeth(CaseKind.Class)._1 :: Nil else Nil
copyMeths ::: enumTagMeths ::: productElemMeths.toList
}
else Nil

Expand All @@ -387,8 +409,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 }
Expand All @@ -410,17 +436,26 @@ 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 ||
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))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I may miss some point here, it seems to me the fold is useless here, because constrVparamss can only be of size 0?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

size 0 or 1

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

size 0 or 1, actually.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, I see, thanks, obviously I've some problems with basic math :)

val applyMeths =
if (mods is Abstract) Nil
else
DefDef(nme.apply, derivedTparams, derivedVparamss, TypeTree(), creatorExpr)
DefDef(nme.apply, derivedTparams, derivedVparamss, applyResultTpt, creatorExpr)
.withFlags(Synthetic | (constr1.mods.flags & DefaultParameterized)) :: Nil
val unapplyMeth = {
val unapplyParam = makeSyntheticParameter(tpt = classTypeRef)
Expand Down Expand Up @@ -464,15 +499,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
}
Expand Down Expand Up @@ -503,23 +538,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))
Expand All @@ -542,11 +580,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,
Expand Down Expand Up @@ -923,7 +973,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 }
Expand Down Expand Up @@ -1024,7 +1074,6 @@ object desugar {
List(CaseDef(Ident(nme.DEFAULT_EXCEPTION_NAME), EmptyTree, Apply(handler, Ident(nme.DEFAULT_EXCEPTION_NAME)))),
finalizer)
}

}
}.withPos(tree.pos)

Expand Down