Skip to content

Java-compatible enums #6576

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

Merged
merged 20 commits into from
Jun 6, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
c9982ed
Define ordinal and name for enums
anatoliykmetyuk Jun 4, 2019
2343830
Module methods for enums implemented
anatoliykmetyuk Jun 4, 2019
231b51f
Conform tests to the new Enums API
anatoliykmetyuk Jun 4, 2019
56a76a5
Enums valuesOf exception type conforms to that of Java enums
anatoliykmetyuk Jun 4, 2019
023778c
Tests added for enum java compat API
anatoliykmetyuk Jun 4, 2019
702ced5
Name removed from enum
anatoliykmetyuk Jun 4, 2019
2d7570c
SyntheticMethods adapted to use ordinal instead of enumTag
anatoliykmetyuk Jun 5, 2019
35a20c9
Adapt CompleteJavaEnums to the new method names
anatoliykmetyuk Jun 5, 2019
c7a0149
SyntheticMethods' ordinal method calls the arg's ordinal method
anatoliykmetyuk Jun 5, 2019
78a2596
method added to enums to carry the ordinal number unconditionally
anatoliykmetyuk Jun 5, 2019
c44bf64
Synthetically generated oridnal method for enums
anatoliykmetyuk Jun 5, 2019
af652e9
Fix documentation for
anatoliykmetyuk Jun 5, 2019
86939eb
Do not generate synthetic ordinal for Enum itself
anatoliykmetyuk Jun 5, 2019
1a42c3d
Fix enum-List-control test
anatoliykmetyuk Jun 5, 2019
33f6c77
Stylistical improvements to the Enums code
anatoliykmetyuk Jun 6, 2019
5aa8d91
Documentation fixes for enums
anatoliykmetyuk Jun 6, 2019
857549f
Generate ACC_ENUM flags for Java enums
anatoliykmetyuk Jun 6, 2019
9abfef8
Make enums compatible with the new constructor hijack
anatoliykmetyuk Jun 6, 2019
79db9f7
Add tests for enums interop with Java efficient collections
anatoliykmetyuk Jun 6, 2019
0b4c546
Factor out derivesFromJavaEnum to SymUtils
anatoliykmetyuk Jun 6, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion compiler/src/dotty/tools/backend/jvm/BTypesFromSymbols.scala
Original file line number Diff line number Diff line change
Expand Up @@ -223,7 +223,8 @@ class BTypesFromSymbols[I <: BackendInterface](val int: I) extends BTypes {
if (sym.hasEnumFlag) ACC_ENUM else 0,
if (sym.isVarargsMethod) ACC_VARARGS else 0,
if (sym.isSynchronized) ACC_SYNCHRONIZED else 0,
if (sym.isDeprecated) asm.Opcodes.ACC_DEPRECATED else 0
if (sym.isDeprecated) asm.Opcodes.ACC_DEPRECATED else 0,
if (sym.isJavaEnum) asm.Opcodes.ACC_ENUM else 0
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -512,6 +512,7 @@ abstract class BackendInterface extends BackendInterfaceDefinitions {
def isJavaDefaultMethod: Boolean
def isClassConstructor: Boolean
def isSerializable: Boolean
def isJavaEnum: Boolean

/**
* True for module classes of modules that are top-level or owned only by objects. Module classes
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -703,6 +703,7 @@ class DottyBackendInterface(outputDirectory: AbstractFile, val superCallsMap: Ma
def shouldEmitForwarders: Boolean =
(sym is Flags.Module) && sym.isStatic
def isJavaEntryPoint: Boolean = CollectEntryPoints.isJavaEntryPoint(sym)
def isJavaEnum = sym.derivesFromJavaEnum

def isClassConstructor: Boolean = toDenot(sym).isClassConstructor
def isSerializable: Boolean = toDenot(sym).isSerializable
Expand Down
4 changes: 2 additions & 2 deletions compiler/src/dotty/tools/dotc/ast/Desugar.scala
Original file line number Diff line number Diff line change
Expand Up @@ -545,7 +545,7 @@ object desugar {
yield syntheticProperty(nme.selectorName(i), caseParams(i).tpt,
Select(This(EmptyTypeIdent), caseParams(i).name))
}
def enumTagMeths = if (isEnumCase) enumTagMeth(CaseKind.Class)._1 :: Nil else Nil
def ordinalMeths = if (isEnumCase) ordinalMethLit(nextOrdinal(CaseKind.Class)._1) :: Nil else Nil
def copyMeths = {
val hasRepeatedParam = constrVparamss.exists(_.exists {
case ValDef(_, tpt, _) => isRepeated(tpt)
Expand Down Expand Up @@ -582,7 +582,7 @@ object desugar {
}

if (isCaseClass)
productElemNameMeth :: copyMeths ::: enumTagMeths ::: productElemMeths
productElemNameMeth :: copyMeths ::: ordinalMeths ::: productElemMeths
else Nil
}

Expand Down
94 changes: 57 additions & 37 deletions compiler/src/dotty/tools/dotc/ast/DesugarEnums.scala
Original file line number Diff line number Diff line change
Expand Up @@ -74,57 +74,72 @@ object DesugarEnums {
else if (isEnumCase(cdef)) cdef.withMods(cdef.mods.withFlags(cdef.mods.flags | Final))
else cdef

private def valuesDot(name: String)(implicit src: SourceFile) =
private def valuesDot(name: PreName)(implicit src: SourceFile) =
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
* def valueOf($name: String) =
* try $values.fromName($name) catch
* {
* case ex$:NoSuchElementException =>
* throw new IllegalArgumentException("key not found: ".concat(name))
* }
* def values = $values.values.toArray
*/
private def enumScaffolding(implicit ctx: Context): List[Tree] = {
def enumDefDef(name: String, select: String) =
DefDef(name.toTermName, Nil, Nil, TypeTree(), valuesDot(select))
val valuesDef =
DefDef(nme.values, Nil, Nil, TypeTree(), Select(valuesDot(nme.values), nme.toArray))
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)

val valuesOfExnMessage = Apply(
Select(Literal(Constant("key not found: ")), "concat".toTermName),
Ident(nme.nameDollar) :: Nil)
val valuesOfBody = Try(
expr = Apply(valuesDot("fromName"), Ident(nme.nameDollar) :: Nil),
cases = CaseDef(
pat = Typed(Ident(nme.DEFAULT_EXCEPTION_NAME), TypeTree(defn.NoSuchElementExceptionType)),
guard = EmptyTree,
body = Throw(New(TypeTree(defn.IllegalArgumentExceptionType), List(valuesOfExnMessage :: Nil)))
) :: Nil,
finalizer = EmptyTree
)
val valueOfDef = DefDef(nme.valueOf, Nil, List(param(nme.nameDollar, defn.StringType) :: Nil),
TypeTree(), valuesOfBody)

valuesDef ::
privateValuesDef ::
valueOfDef :: Nil
}

/** 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
* private def $new(_$ordinal: Int, $name: String) = new E {
* def $ordinal = $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)
val ordinalDef = ordinalMeth(Ident(nme.ordinalDollar_))
val toStringDef = toStringMeth(Ident(nme.nameDollar))
val creator = New(Template(
constr = emptyConstructor,
parents = enumClassRef :: Nil,
derived = Nil,
self = EmptyValDef,
body = List(enumTagDef, toStringDef) ++ registerCall
body = List(ordinalDef, toStringDef) ++ registerCall
).withAttachment(ExtendsSingletonMirror, ()))
DefDef(nme.DOLLAR_NEW, Nil,
List(List(param(nme.tag, defn.IntType), param(nme.name, defn.StringType))),
List(List(param(nme.ordinalDollar_, defn.IntType), param(nme.nameDollar, defn.StringType))),
TypeTree(), creator).withFlags(Private | Synthetic)
}

Expand Down Expand Up @@ -232,7 +247,7 @@ object DesugarEnums {
* - 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]) = {
def nextOrdinal(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))
Expand All @@ -244,14 +259,20 @@ object DesugarEnums {
(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)
}
def param(name: TermName, typ: Type)(implicit ctx: Context) =
ValDef(name, TypeTree(typ), EmptyTree).withFlags(Param)

def ordinalMeth(body: Tree)(implicit ctx: Context): DefDef =
DefDef(nme.ordinalDollar, Nil, Nil, TypeTree(defn.IntType), body)

def toStringMeth(body: Tree)(implicit ctx: Context): DefDef =
DefDef(nme.toString_, Nil, Nil, TypeTree(defn.StringType), body).withFlags(Override)

def ordinalMethLit(ord: Int)(implicit ctx: Context): DefDef =
ordinalMeth(Literal(Constant(ord)))

def toStringMethLit(name: String)(implicit ctx: Context): DefDef =
toStringMeth(Literal(Constant(name)))

/** Expand a module definition representing a parameterless enum case */
def expandEnumModule(name: TermName, impl: Template, mods: Modifiers, span: Span)(implicit ctx: Context): Tree = {
Expand All @@ -260,11 +281,10 @@ object DesugarEnums {
else if (impl.parents.isEmpty)
expandSimpleEnumCase(name, mods, span)
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 = List(tagMeth, toStringMeth) ++ registerCall)
val (tag, scaffolding) = nextOrdinal(CaseKind.Object)
val ordinalDef = ordinalMethLit(tag)
val toStringDef = toStringMethLit(name.toString)
val impl1 = cpy.Template(impl)(body = List(ordinalDef, toStringDef) ++ registerCall)
.withAttachment(ExtendsSingletonMirror, ())
val vdef = ValDef(name, TypeTree(), New(impl1)).withMods(mods | Final)
flatTree(scaffolding ::: vdef :: Nil).withSpan(span)
Expand All @@ -280,7 +300,7 @@ object DesugarEnums {
expandEnumModule(name, impl, mods, span)
}
else {
val (tag, scaffolding) = nextEnumTag(CaseKind.Simple)
val (tag, scaffolding) = nextOrdinal(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)
flatTree(scaffolding ::: vdef :: Nil).withSpan(span)
Expand Down
7 changes: 7 additions & 0 deletions compiler/src/dotty/tools/dotc/core/Definitions.scala
Original file line number Diff line number Diff line change
Expand Up @@ -605,6 +605,11 @@ class Definitions {
@threadUnsafe lazy val SystemClass: ClassSymbol = ctx.requiredClass("java.lang.System")
@threadUnsafe lazy val SystemModule: Symbol = SystemClass.linkedClass

@threadUnsafe lazy val NoSuchElementExceptionClass = ctx.requiredClass("java.util.NoSuchElementException")
def NoSuchElementExceptionType = NoSuchElementExceptionClass.typeRef
@threadUnsafe lazy val IllegalArgumentExceptionClass = ctx.requiredClass("java.lang.IllegalArgumentException")
def IllegalArgumentExceptionType = IllegalArgumentExceptionClass.typeRef

// in scalac modified to have Any as parent

@threadUnsafe lazy val ThrowableType: TypeRef = ctx.requiredClassRef("java.lang.Throwable")
Expand Down Expand Up @@ -701,6 +706,8 @@ class Definitions {
def NoneClass(implicit ctx: Context): ClassSymbol = NoneModuleRef.symbol.moduleClass.asClass
@threadUnsafe lazy val EnumType: TypeRef = ctx.requiredClassRef("scala.Enum")
def EnumClass(implicit ctx: Context): ClassSymbol = EnumType.symbol.asClass
@threadUnsafe lazy val Enum_ordinalR: TermRef = EnumClass.requiredMethodRef(nme.ordinal)
def Enum_ordinal(implicit ctx: Context): Symbol = Enum_ordinalR.symbol
@threadUnsafe lazy val EnumValuesType: TypeRef = ctx.requiredClassRef("scala.runtime.EnumValues")
def EnumValuesClass(implicit ctx: Context): ClassSymbol = EnumValuesType.symbol.asClass
@threadUnsafe lazy val ProductType: TypeRef = ctx.requiredClassRef("scala.Product")
Expand Down
4 changes: 3 additions & 1 deletion compiler/src/dotty/tools/dotc/core/StdNames.scala
Original file line number Diff line number Diff line change
Expand Up @@ -420,7 +420,6 @@ object StdNames {
val elems: N = "elems"
val emptyValDef: N = "emptyValDef"
val ensureAccessible : N = "ensureAccessible"
val enumTag: N = "enumTag"
val eq: N = "eq"
val eqInstance: N = "eqInstance"
val equalsNumChar : N = "equalsNumChar"
Expand Down Expand Up @@ -485,6 +484,7 @@ object StdNames {
val mirror : N = "mirror"
val moduleClass : N = "moduleClass"
val name: N = "name"
val nameDollar: N = "$name"
val ne: N = "ne"
val newFreeTerm: N = "newFreeTerm"
val newFreeType: N = "newFreeType"
Expand All @@ -500,6 +500,8 @@ object StdNames {
val ofDim: N = "ofDim"
val opaque: N = "opaque"
val ordinal: N = "ordinal"
val ordinalDollar: N = "$ordinal"
val ordinalDollar_ : N = "_$ordinal"
val origin: N = "origin"
val prefix : N = "prefix"
val productArity: N = "productArity"
Expand Down
31 changes: 13 additions & 18 deletions compiler/src/dotty/tools/dotc/transform/CompleteJavaEnums.scala
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,13 @@ import Constants._
import Decorators._
import DenotTransformers._
import dotty.tools.dotc.ast.Trees._
import SymUtils._

object CompleteJavaEnums {
val name: String = "completeJavaEnums"

private val nameParamName: TermName = "$name".toTermName
private val ordinalParamName: TermName = "$ordinal".toTermName
private val nameParamName: TermName = "_$name".toTermName
private val ordinalParamName: TermName = "_$ordinal".toTermName
}

/** For Scala enums that inherit from java.lang.Enum:
Expand All @@ -37,16 +38,10 @@ class CompleteJavaEnums extends MiniPhase with InfoTransformer { thisPhase =>
def transformInfo(tp: Type, sym: Symbol)(implicit ctx: Context): Type =
if (sym.isConstructor && (
sym == defn.JavaEnumClass.primaryConstructor ||
derivesFromJavaEnum(sym.owner)))
sym.owner.derivesFromJavaEnum))
addConstrParams(sym.info)
else tp

/** Is `sym` a Scala enum class that derives (directly) from `java.lang.Enum`?
*/
private def derivesFromJavaEnum(sym: Symbol)(implicit ctx: Context) =
sym.is(Enum, butNot = Case) &&
sym.info.parents.exists(p => p.typeSymbol == defn.JavaEnumClass)

/** Add constructor parameters `$name: String` and `$ordinal: Int` to the end of
* the last parameter list of (method- or poly-) type `tp`.
*/
Expand Down Expand Up @@ -87,24 +82,24 @@ class CompleteJavaEnums extends MiniPhase with InfoTransformer { thisPhase =>
* 2. If this is a $new method that creates simple cases, pass $name and $ordinal parameters
* to the enum superclass. The $new method looks like this:
*
* def $new(..., enumTag: Int, name: String) = {
* def $new(..., ordinal: Int, name: String) = {
* class $anon extends E(...) { ... }
* new $anon
* }
*
* After the transform it is expanded to
*
* def $new(..., enumTag: Int, name: String) = {
* class $anon extends E(..., name, enumTag) { ... }
* def $new(..., ordinal: Int, name: String) = {
* class $anon extends E(..., name, ordinal) { ... }
* new $anon
* }
*/
override def transformDefDef(tree: DefDef)(implicit ctx: Context): DefDef = {
val sym = tree.symbol
if (sym.isConstructor && derivesFromJavaEnum(sym.owner))
if (sym.isConstructor && sym.owner.derivesFromJavaEnum)
cpy.DefDef(tree)(
vparamss = tree.vparamss.init :+ (tree.vparamss.last ++ addedParams(sym, Param)))
else if (sym.name == nme.DOLLAR_NEW && derivesFromJavaEnum(sym.owner.linkedClass)) {
else if (sym.name == nme.DOLLAR_NEW && sym.owner.linkedClass.derivesFromJavaEnum) {
val Block((tdef @ TypeDef(tpnme.ANON_CLASS, templ: Template)) :: Nil, call) = tree.rhs
val args = tree.vparamss.last.takeRight(2).map(param => ref(param.symbol)).reverse
val templ1 = cpy.Template(templ)(
Expand All @@ -124,7 +119,7 @@ class CompleteJavaEnums extends MiniPhase with InfoTransformer { thisPhase =>
*
* class $anon extends E(...) {
* ...
* def enumTag = N
* def ordinal = N
* def toString = S
* ...
* }
Expand All @@ -137,20 +132,20 @@ class CompleteJavaEnums extends MiniPhase with InfoTransformer { thisPhase =>
*/
override def transformTemplate(templ: Template)(implicit ctx: Context): Template = {
val cls = templ.symbol.owner
if (derivesFromJavaEnum(cls)) {
if (cls.derivesFromJavaEnum) {
val (params, rest) = decomposeTemplateBody(templ.body)
val addedDefs = addedParams(cls, ParamAccessor)
val addedSyms = addedDefs.map(_.symbol.entered)
cpy.Template(templ)(
parents = addEnumConstrArgs(defn.JavaEnumClass, templ.parents, addedSyms.map(ref)),
body = params ++ addedDefs ++ rest)
}
else if (cls.isAnonymousClass && cls.owner.is(EnumCase) && derivesFromJavaEnum(cls.owner.owner.linkedClass)) {
else if (cls.isAnonymousClass && cls.owner.is(EnumCase) && cls.owner.owner.linkedClass.derivesFromJavaEnum) {
def rhsOf(name: TermName) =
templ.body.collect {
case mdef: DefDef if mdef.name == name => mdef.rhs
}.head
val args = List(rhsOf(nme.toString_), rhsOf(nme.enumTag))
val args = List(rhsOf(nme.toString_), rhsOf(nme.ordinalDollar))
cpy.Template(templ)(
parents = addEnumConstrArgs(cls.owner.owner.linkedClass, templ.parents, args))
}
Expand Down
4 changes: 4 additions & 0 deletions compiler/src/dotty/tools/dotc/transform/SymUtils.scala
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,10 @@ class SymUtils(val self: Symbol) extends AnyVal {
def isParamOrAccessor(implicit ctx: Context): Boolean =
self.is(Param) || self.is(ParamAccessor)

def derivesFromJavaEnum(implicit ctx: Context) =
self.is(Enum, butNot = Case) &&
self.info.parents.exists(p => p.typeSymbol == defn.JavaEnumClass)

/** Is this a case class for which a product mirror is generated?
* Excluded are value classes, abstract classes and case classes with more than one
* parameter section.
Expand Down
Loading