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 typer.ErrorReporting._
/** Helper methods to desugar enums */
object DesugarEnums {
import untpd._
import desugar.DerivedFromParamTree
@sharable object CaseKind extends Enumeration {
val Simple, Object, Class = Value
}
/** Attachment containing the number of enum cases and the smallest kind that was seen so far. */
val EnumCaseCount = new Property.Key[(Int, CaseKind.Value)]
/** the enumeration class that is a companion of the current object */
def enumClass(implicit ctx: Context) = ctx.owner.linkedClass
/** Is this an enum case that's situated in a companion object of an enum class? */
def isLegalEnumCase(tree: MemberDef)(implicit ctx: Context): Boolean =
tree.mods.hasMod[Mod.EnumCase] && enumCaseIsLegal(tree)
/** 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)
false
}
)
/** Type parameters reconstituted from the constructor
* 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 {
case info: PolyType =>
ctx.newTypeParams(ctx.newLocalDummy(enumClass), info.paramNames, EmptyFlags, info.instantiateBounds)
case _ =>
Nil
}
(tparams, enumClass.typeParams).zipped.map { (tparam, ecTparam) =>
val tbounds = new DerivedFromParamTree
tbounds.pushAttachment(OriginalSymbol, tparam)
TypeDef(tparam.name, tbounds)
.withFlags(Param | PrivateLocal | ecTparam.flags & VarianceFlags).withPos(pos)
}
}
/** 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)
}
/** 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(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:
*
* private val $values = new EnumValues[E]
* def enumValue = $values.fromInt
* def enumValueNamed = $values.fromName
* def enumValues = $values.values
*/
private def enumScaffolding(implicit ctx: Context): List[Tree] = {
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(enumClass.typeRef :: 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 enumValueCreator(implicit ctx: Context) = {
def param(name: TermName, typ: Type) =
ValDef(name, TypeTree(typ), EmptyTree).withFlags(Param)
val enumTagDef =
DefDef(nme.enumTag, Nil, Nil, TypeTree(), Ident(nme.tag))
val toStringDef =
DefDef(nme.toString_, Nil, Nil, TypeTree(), Ident(nme.name))
.withFlags(Override)
def creator = New(Template(emptyConstructor, enumClassRef :: Nil, EmptyValDef,
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 (enumClass.typeParams.nonEmpty || 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)
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)))
.withFlags(Override)
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 (enumClass.typeParams.nonEmpty) {
val parent = interpolatedEnumParent(pos)
val impl = Template(emptyConstructor, parent :: Nil, EmptyValDef, Nil)
expandEnumModule(name, impl, mods, pos)
}
else {
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(scaffolding ::: vdef :: Nil).withPos(pos.startPos)
}
}