Skip to content

Commit c46553a

Browse files
authored
Merge pull request #6576 from dotty-staging/java-enums
Java-compatible enums
2 parents 3c591ed + 0b4c546 commit c46553a

32 files changed

+253
-135
lines changed

compiler/src/dotty/tools/backend/jvm/BTypesFromSymbols.scala

+2-1
Original file line numberDiff line numberDiff line change
@@ -223,7 +223,8 @@ class BTypesFromSymbols[I <: BackendInterface](val int: I) extends BTypes {
223223
if (sym.hasEnumFlag) ACC_ENUM else 0,
224224
if (sym.isVarargsMethod) ACC_VARARGS else 0,
225225
if (sym.isSynchronized) ACC_SYNCHRONIZED else 0,
226-
if (sym.isDeprecated) asm.Opcodes.ACC_DEPRECATED else 0
226+
if (sym.isDeprecated) asm.Opcodes.ACC_DEPRECATED else 0,
227+
if (sym.isJavaEnum) asm.Opcodes.ACC_ENUM else 0
227228
)
228229
}
229230

compiler/src/dotty/tools/backend/jvm/BackendInterface.scala

+1
Original file line numberDiff line numberDiff line change
@@ -512,6 +512,7 @@ abstract class BackendInterface extends BackendInterfaceDefinitions {
512512
def isJavaDefaultMethod: Boolean
513513
def isClassConstructor: Boolean
514514
def isSerializable: Boolean
515+
def isJavaEnum: Boolean
515516

516517
/**
517518
* True for module classes of modules that are top-level or owned only by objects. Module classes

compiler/src/dotty/tools/backend/jvm/DottyBackendInterface.scala

+1
Original file line numberDiff line numberDiff line change
@@ -703,6 +703,7 @@ class DottyBackendInterface(outputDirectory: AbstractFile, val superCallsMap: Ma
703703
def shouldEmitForwarders: Boolean =
704704
(sym is Flags.Module) && sym.isStatic
705705
def isJavaEntryPoint: Boolean = CollectEntryPoints.isJavaEntryPoint(sym)
706+
def isJavaEnum = sym.derivesFromJavaEnum
706707

707708
def isClassConstructor: Boolean = toDenot(sym).isClassConstructor
708709
def isSerializable: Boolean = toDenot(sym).isSerializable

compiler/src/dotty/tools/dotc/ast/Desugar.scala

+2-2
Original file line numberDiff line numberDiff line change
@@ -545,7 +545,7 @@ object desugar {
545545
yield syntheticProperty(nme.selectorName(i), caseParams(i).tpt,
546546
Select(This(EmptyTypeIdent), caseParams(i).name))
547547
}
548-
def enumTagMeths = if (isEnumCase) enumTagMeth(CaseKind.Class)._1 :: Nil else Nil
548+
def ordinalMeths = if (isEnumCase) ordinalMethLit(nextOrdinal(CaseKind.Class)._1) :: Nil else Nil
549549
def copyMeths = {
550550
val hasRepeatedParam = constrVparamss.exists(_.exists {
551551
case ValDef(_, tpt, _) => isRepeated(tpt)
@@ -582,7 +582,7 @@ object desugar {
582582
}
583583

584584
if (isCaseClass)
585-
productElemNameMeth :: copyMeths ::: enumTagMeths ::: productElemMeths
585+
productElemNameMeth :: copyMeths ::: ordinalMeths ::: productElemMeths
586586
else Nil
587587
}
588588

compiler/src/dotty/tools/dotc/ast/DesugarEnums.scala

+57-37
Original file line numberDiff line numberDiff line change
@@ -74,57 +74,72 @@ object DesugarEnums {
7474
else if (isEnumCase(cdef)) cdef.withMods(cdef.mods.withFlags(cdef.mods.flags | Final))
7575
else cdef
7676

77-
private def valuesDot(name: String)(implicit src: SourceFile) =
77+
private def valuesDot(name: PreName)(implicit src: SourceFile) =
7878
Select(Ident(nme.DOLLAR_VALUES), name.toTermName)
79+
7980
private def registerCall(implicit ctx: Context): List[Tree] =
8081
if (enumClass.typeParams.nonEmpty) Nil
8182
else Apply(valuesDot("register"), This(EmptyTypeIdent) :: Nil) :: Nil
8283

8384
/** The following lists of definitions for an enum type E:
8485
*
8586
* private val $values = new EnumValues[E]
86-
* def enumValue = $values.fromInt
87-
* def enumValueNamed = $values.fromName
88-
* def enumValues = $values.values
87+
* def valueOf($name: String) =
88+
* try $values.fromName($name) catch
89+
* {
90+
* case ex$:NoSuchElementException =>
91+
* throw new IllegalArgumentException("key not found: ".concat(name))
92+
* }
93+
* def values = $values.values.toArray
8994
*/
9095
private def enumScaffolding(implicit ctx: Context): List[Tree] = {
91-
def enumDefDef(name: String, select: String) =
92-
DefDef(name.toTermName, Nil, Nil, TypeTree(), valuesDot(select))
96+
val valuesDef =
97+
DefDef(nme.values, Nil, Nil, TypeTree(), Select(valuesDot(nme.values), nme.toArray))
9398
val privateValuesDef =
9499
ValDef(nme.DOLLAR_VALUES, TypeTree(),
95100
New(TypeTree(defn.EnumValuesType.appliedTo(enumClass.typeRef :: Nil)), ListOfNil))
96101
.withFlags(Private)
97-
val valueOfDef = enumDefDef("enumValue", "fromInt")
98-
val withNameDef = enumDefDef("enumValueNamed", "fromName")
99-
val valuesDef = enumDefDef("enumValues", "values")
100-
List(privateValuesDef, valueOfDef, withNameDef, valuesDef)
102+
103+
val valuesOfExnMessage = Apply(
104+
Select(Literal(Constant("key not found: ")), "concat".toTermName),
105+
Ident(nme.nameDollar) :: Nil)
106+
val valuesOfBody = Try(
107+
expr = Apply(valuesDot("fromName"), Ident(nme.nameDollar) :: Nil),
108+
cases = CaseDef(
109+
pat = Typed(Ident(nme.DEFAULT_EXCEPTION_NAME), TypeTree(defn.NoSuchElementExceptionType)),
110+
guard = EmptyTree,
111+
body = Throw(New(TypeTree(defn.IllegalArgumentExceptionType), List(valuesOfExnMessage :: Nil)))
112+
) :: Nil,
113+
finalizer = EmptyTree
114+
)
115+
val valueOfDef = DefDef(nme.valueOf, Nil, List(param(nme.nameDollar, defn.StringType) :: Nil),
116+
TypeTree(), valuesOfBody)
117+
118+
valuesDef ::
119+
privateValuesDef ::
120+
valueOfDef :: Nil
101121
}
102122

103123
/** A creation method for a value of enum type `E`, which is defined as follows:
104124
*
105-
* private def $new(tag: Int, name: String) = new E {
106-
* def enumTag = tag
107-
* override def toString = name
125+
* private def $new(_$ordinal: Int, $name: String) = new E {
126+
* def $ordinal = $tag
127+
* override def toString = $name
108128
* $values.register(this)
109129
* }
110130
*/
111131
private def enumValueCreator(implicit ctx: Context) = {
112-
def param(name: TermName, typ: Type) =
113-
ValDef(name, TypeTree(typ), EmptyTree).withFlags(Param)
114-
val enumTagDef =
115-
DefDef(nme.enumTag, Nil, Nil, TypeTree(), Ident(nme.tag))
116-
val toStringDef =
117-
DefDef(nme.toString_, Nil, Nil, TypeTree(), Ident(nme.name))
118-
.withFlags(Override)
132+
val ordinalDef = ordinalMeth(Ident(nme.ordinalDollar_))
133+
val toStringDef = toStringMeth(Ident(nme.nameDollar))
119134
val creator = New(Template(
120135
constr = emptyConstructor,
121136
parents = enumClassRef :: Nil,
122137
derived = Nil,
123138
self = EmptyValDef,
124-
body = List(enumTagDef, toStringDef) ++ registerCall
139+
body = List(ordinalDef, toStringDef) ++ registerCall
125140
).withAttachment(ExtendsSingletonMirror, ()))
126141
DefDef(nme.DOLLAR_NEW, Nil,
127-
List(List(param(nme.tag, defn.IntType), param(nme.name, defn.StringType))),
142+
List(List(param(nme.ordinalDollar_, defn.IntType), param(nme.nameDollar, defn.StringType))),
128143
TypeTree(), creator).withFlags(Private | Synthetic)
129144
}
130145

@@ -232,7 +247,7 @@ object DesugarEnums {
232247
* - scaffolding containing the necessary definitions for singleton enum cases
233248
* unless that scaffolding was already generated by a previous call to `nextEnumKind`.
234249
*/
235-
def nextEnumTag(kind: CaseKind.Value)(implicit ctx: Context): (Int, List[Tree]) = {
250+
def nextOrdinal(kind: CaseKind.Value)(implicit ctx: Context): (Int, List[Tree]) = {
236251
val (count, seenKind) = ctx.tree.removeAttachment(EnumCaseCount).getOrElse((0, CaseKind.Class))
237252
val minKind = if (kind < seenKind) kind else seenKind
238253
ctx.tree.pushAttachment(EnumCaseCount, (count + 1, minKind))
@@ -244,14 +259,20 @@ object DesugarEnums {
244259
(count, scaffolding)
245260
}
246261

247-
/** A pair consisting of
248-
* - a method returning the next enum tag
249-
* - scaffolding as defined in `nextEnumTag`
250-
*/
251-
def enumTagMeth(kind: CaseKind.Value)(implicit ctx: Context): (DefDef, List[Tree]) = {
252-
val (tag, scaffolding) = nextEnumTag(kind)
253-
(DefDef(nme.enumTag, Nil, Nil, TypeTree(), Literal(Constant(tag))), scaffolding)
254-
}
262+
def param(name: TermName, typ: Type)(implicit ctx: Context) =
263+
ValDef(name, TypeTree(typ), EmptyTree).withFlags(Param)
264+
265+
def ordinalMeth(body: Tree)(implicit ctx: Context): DefDef =
266+
DefDef(nme.ordinalDollar, Nil, Nil, TypeTree(defn.IntType), body)
267+
268+
def toStringMeth(body: Tree)(implicit ctx: Context): DefDef =
269+
DefDef(nme.toString_, Nil, Nil, TypeTree(defn.StringType), body).withFlags(Override)
270+
271+
def ordinalMethLit(ord: Int)(implicit ctx: Context): DefDef =
272+
ordinalMeth(Literal(Constant(ord)))
273+
274+
def toStringMethLit(name: String)(implicit ctx: Context): DefDef =
275+
toStringMeth(Literal(Constant(name)))
255276

256277
/** Expand a module definition representing a parameterless enum case */
257278
def expandEnumModule(name: TermName, impl: Template, mods: Modifiers, span: Span)(implicit ctx: Context): Tree = {
@@ -260,11 +281,10 @@ object DesugarEnums {
260281
else if (impl.parents.isEmpty)
261282
expandSimpleEnumCase(name, mods, span)
262283
else {
263-
def toStringMeth =
264-
DefDef(nme.toString_, Nil, Nil, TypeTree(defn.StringType), Literal(Constant(name.toString)))
265-
.withFlags(Override)
266-
val (tagMeth, scaffolding) = enumTagMeth(CaseKind.Object)
267-
val impl1 = cpy.Template(impl)(body = List(tagMeth, toStringMeth) ++ registerCall)
284+
val (tag, scaffolding) = nextOrdinal(CaseKind.Object)
285+
val ordinalDef = ordinalMethLit(tag)
286+
val toStringDef = toStringMethLit(name.toString)
287+
val impl1 = cpy.Template(impl)(body = List(ordinalDef, toStringDef) ++ registerCall)
268288
.withAttachment(ExtendsSingletonMirror, ())
269289
val vdef = ValDef(name, TypeTree(), New(impl1)).withMods(mods | Final)
270290
flatTree(scaffolding ::: vdef :: Nil).withSpan(span)
@@ -280,7 +300,7 @@ object DesugarEnums {
280300
expandEnumModule(name, impl, mods, span)
281301
}
282302
else {
283-
val (tag, scaffolding) = nextEnumTag(CaseKind.Simple)
303+
val (tag, scaffolding) = nextOrdinal(CaseKind.Simple)
284304
val creator = Apply(Ident(nme.DOLLAR_NEW), List(Literal(Constant(tag)), Literal(Constant(name.toString))))
285305
val vdef = ValDef(name, enumClassRef, creator).withMods(mods | Final)
286306
flatTree(scaffolding ::: vdef :: Nil).withSpan(span)

compiler/src/dotty/tools/dotc/core/Definitions.scala

+7
Original file line numberDiff line numberDiff line change
@@ -605,6 +605,11 @@ class Definitions {
605605
@threadUnsafe lazy val SystemClass: ClassSymbol = ctx.requiredClass("java.lang.System")
606606
@threadUnsafe lazy val SystemModule: Symbol = SystemClass.linkedClass
607607

608+
@threadUnsafe lazy val NoSuchElementExceptionClass = ctx.requiredClass("java.util.NoSuchElementException")
609+
def NoSuchElementExceptionType = NoSuchElementExceptionClass.typeRef
610+
@threadUnsafe lazy val IllegalArgumentExceptionClass = ctx.requiredClass("java.lang.IllegalArgumentException")
611+
def IllegalArgumentExceptionType = IllegalArgumentExceptionClass.typeRef
612+
608613
// in scalac modified to have Any as parent
609614

610615
@threadUnsafe lazy val ThrowableType: TypeRef = ctx.requiredClassRef("java.lang.Throwable")
@@ -701,6 +706,8 @@ class Definitions {
701706
def NoneClass(implicit ctx: Context): ClassSymbol = NoneModuleRef.symbol.moduleClass.asClass
702707
@threadUnsafe lazy val EnumType: TypeRef = ctx.requiredClassRef("scala.Enum")
703708
def EnumClass(implicit ctx: Context): ClassSymbol = EnumType.symbol.asClass
709+
@threadUnsafe lazy val Enum_ordinalR: TermRef = EnumClass.requiredMethodRef(nme.ordinal)
710+
def Enum_ordinal(implicit ctx: Context): Symbol = Enum_ordinalR.symbol
704711
@threadUnsafe lazy val EnumValuesType: TypeRef = ctx.requiredClassRef("scala.runtime.EnumValues")
705712
def EnumValuesClass(implicit ctx: Context): ClassSymbol = EnumValuesType.symbol.asClass
706713
@threadUnsafe lazy val ProductType: TypeRef = ctx.requiredClassRef("scala.Product")

compiler/src/dotty/tools/dotc/core/StdNames.scala

+3-1
Original file line numberDiff line numberDiff line change
@@ -420,7 +420,6 @@ object StdNames {
420420
val elems: N = "elems"
421421
val emptyValDef: N = "emptyValDef"
422422
val ensureAccessible : N = "ensureAccessible"
423-
val enumTag: N = "enumTag"
424423
val eq: N = "eq"
425424
val eqInstance: N = "eqInstance"
426425
val equalsNumChar : N = "equalsNumChar"
@@ -485,6 +484,7 @@ object StdNames {
485484
val mirror : N = "mirror"
486485
val moduleClass : N = "moduleClass"
487486
val name: N = "name"
487+
val nameDollar: N = "$name"
488488
val ne: N = "ne"
489489
val newFreeTerm: N = "newFreeTerm"
490490
val newFreeType: N = "newFreeType"
@@ -500,6 +500,8 @@ object StdNames {
500500
val ofDim: N = "ofDim"
501501
val opaque: N = "opaque"
502502
val ordinal: N = "ordinal"
503+
val ordinalDollar: N = "$ordinal"
504+
val ordinalDollar_ : N = "_$ordinal"
503505
val origin: N = "origin"
504506
val prefix : N = "prefix"
505507
val productArity: N = "productArity"

compiler/src/dotty/tools/dotc/transform/CompleteJavaEnums.scala

+13-18
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,13 @@ import Constants._
1313
import Decorators._
1414
import DenotTransformers._
1515
import dotty.tools.dotc.ast.Trees._
16+
import SymUtils._
1617

1718
object CompleteJavaEnums {
1819
val name: String = "completeJavaEnums"
1920

20-
private val nameParamName: TermName = "$name".toTermName
21-
private val ordinalParamName: TermName = "$ordinal".toTermName
21+
private val nameParamName: TermName = "_$name".toTermName
22+
private val ordinalParamName: TermName = "_$ordinal".toTermName
2223
}
2324

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

44-
/** Is `sym` a Scala enum class that derives (directly) from `java.lang.Enum`?
45-
*/
46-
private def derivesFromJavaEnum(sym: Symbol)(implicit ctx: Context) =
47-
sym.is(Enum, butNot = Case) &&
48-
sym.info.parents.exists(p => p.typeSymbol == defn.JavaEnumClass)
49-
5045
/** Add constructor parameters `$name: String` and `$ordinal: Int` to the end of
5146
* the last parameter list of (method- or poly-) type `tp`.
5247
*/
@@ -87,24 +82,24 @@ class CompleteJavaEnums extends MiniPhase with InfoTransformer { thisPhase =>
8782
* 2. If this is a $new method that creates simple cases, pass $name and $ordinal parameters
8883
* to the enum superclass. The $new method looks like this:
8984
*
90-
* def $new(..., enumTag: Int, name: String) = {
85+
* def $new(..., ordinal: Int, name: String) = {
9186
* class $anon extends E(...) { ... }
9287
* new $anon
9388
* }
9489
*
9590
* After the transform it is expanded to
9691
*
97-
* def $new(..., enumTag: Int, name: String) = {
98-
* class $anon extends E(..., name, enumTag) { ... }
92+
* def $new(..., ordinal: Int, name: String) = {
93+
* class $anon extends E(..., name, ordinal) { ... }
9994
* new $anon
10095
* }
10196
*/
10297
override def transformDefDef(tree: DefDef)(implicit ctx: Context): DefDef = {
10398
val sym = tree.symbol
104-
if (sym.isConstructor && derivesFromJavaEnum(sym.owner))
99+
if (sym.isConstructor && sym.owner.derivesFromJavaEnum)
105100
cpy.DefDef(tree)(
106101
vparamss = tree.vparamss.init :+ (tree.vparamss.last ++ addedParams(sym, Param)))
107-
else if (sym.name == nme.DOLLAR_NEW && derivesFromJavaEnum(sym.owner.linkedClass)) {
102+
else if (sym.name == nme.DOLLAR_NEW && sym.owner.linkedClass.derivesFromJavaEnum) {
108103
val Block((tdef @ TypeDef(tpnme.ANON_CLASS, templ: Template)) :: Nil, call) = tree.rhs
109104
val args = tree.vparamss.last.takeRight(2).map(param => ref(param.symbol)).reverse
110105
val templ1 = cpy.Template(templ)(
@@ -124,7 +119,7 @@ class CompleteJavaEnums extends MiniPhase with InfoTransformer { thisPhase =>
124119
*
125120
* class $anon extends E(...) {
126121
* ...
127-
* def enumTag = N
122+
* def ordinal = N
128123
* def toString = S
129124
* ...
130125
* }
@@ -137,20 +132,20 @@ class CompleteJavaEnums extends MiniPhase with InfoTransformer { thisPhase =>
137132
*/
138133
override def transformTemplate(templ: Template)(implicit ctx: Context): Template = {
139134
val cls = templ.symbol.owner
140-
if (derivesFromJavaEnum(cls)) {
135+
if (cls.derivesFromJavaEnum) {
141136
val (params, rest) = decomposeTemplateBody(templ.body)
142137
val addedDefs = addedParams(cls, ParamAccessor)
143138
val addedSyms = addedDefs.map(_.symbol.entered)
144139
cpy.Template(templ)(
145140
parents = addEnumConstrArgs(defn.JavaEnumClass, templ.parents, addedSyms.map(ref)),
146141
body = params ++ addedDefs ++ rest)
147142
}
148-
else if (cls.isAnonymousClass && cls.owner.is(EnumCase) && derivesFromJavaEnum(cls.owner.owner.linkedClass)) {
143+
else if (cls.isAnonymousClass && cls.owner.is(EnumCase) && cls.owner.owner.linkedClass.derivesFromJavaEnum) {
149144
def rhsOf(name: TermName) =
150145
templ.body.collect {
151146
case mdef: DefDef if mdef.name == name => mdef.rhs
152147
}.head
153-
val args = List(rhsOf(nme.toString_), rhsOf(nme.enumTag))
148+
val args = List(rhsOf(nme.toString_), rhsOf(nme.ordinalDollar))
154149
cpy.Template(templ)(
155150
parents = addEnumConstrArgs(cls.owner.owner.linkedClass, templ.parents, args))
156151
}

compiler/src/dotty/tools/dotc/transform/SymUtils.scala

+4
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,10 @@ class SymUtils(val self: Symbol) extends AnyVal {
6767
def isParamOrAccessor(implicit ctx: Context): Boolean =
6868
self.is(Param) || self.is(ParamAccessor)
6969

70+
def derivesFromJavaEnum(implicit ctx: Context) =
71+
self.is(Enum, butNot = Case) &&
72+
self.info.parents.exists(p => p.typeSymbol == defn.JavaEnumClass)
73+
7074
/** Is this a case class for which a product mirror is generated?
7175
* Excluded are value classes, abstract classes and case classes with more than one
7276
* parameter section.

0 commit comments

Comments
 (0)