Skip to content

Commit 83788d9

Browse files
Merge pull request #6629 from dotty-staging/java-enums
Java enums
2 parents 76e954e + 229c451 commit 83788d9

File tree

12 files changed

+174
-11
lines changed

12 files changed

+174
-11
lines changed

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -224,7 +224,7 @@ class BTypesFromSymbols[I <: BackendInterface](val int: I) extends BTypes {
224224
if (sym.isVarargsMethod) ACC_VARARGS else 0,
225225
if (sym.isSynchronized) ACC_SYNCHRONIZED else 0,
226226
if (sym.isDeprecated) asm.Opcodes.ACC_DEPRECATED else 0,
227-
if (sym.isJavaEnum) asm.Opcodes.ACC_ENUM else 0
227+
if (sym.isEnum) asm.Opcodes.ACC_ENUM else 0
228228
)
229229
}
230230

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -512,7 +512,7 @@ abstract class BackendInterface extends BackendInterfaceDefinitions {
512512
def isJavaDefaultMethod: Boolean
513513
def isClassConstructor: Boolean
514514
def isSerializable: Boolean
515-
def isJavaEnum: Boolean
515+
def isEnum: Boolean
516516

517517
/**
518518
* 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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -703,7 +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
706+
def isEnum = sym.is(Flags.Enum)
707707

708708
def isClassConstructor: Boolean = toDenot(sym).isClassConstructor
709709
def isSerializable: Boolean = toDenot(sym).isSerializable

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -84,13 +84,13 @@ object DesugarEnums {
8484
/** The following lists of definitions for an enum type E:
8585
*
8686
* private val $values = new EnumValues[E]
87+
* def values = $values.values.toArray
8788
* def valueOf($name: String) =
8889
* try $values.fromName($name) catch
8990
* {
9091
* case ex$:NoSuchElementException =>
9192
* throw new IllegalArgumentException("key not found: ".concat(name))
9293
* }
93-
* def values = $values.values.toArray
9494
*/
9595
private def enumScaffolding(implicit ctx: Context): List[Tree] = {
9696
val valuesDef =
@@ -286,7 +286,7 @@ object DesugarEnums {
286286
val toStringDef = toStringMethLit(name.toString)
287287
val impl1 = cpy.Template(impl)(body = List(ordinalDef, toStringDef) ++ registerCall)
288288
.withAttachment(ExtendsSingletonMirror, ())
289-
val vdef = ValDef(name, TypeTree(), New(impl1)).withMods(mods | Final)
289+
val vdef = ValDef(name, TypeTree(), New(impl1)).withMods(mods | EnumValue)
290290
flatTree(scaffolding ::: vdef :: Nil).withSpan(span)
291291
}
292292
}
@@ -302,7 +302,7 @@ object DesugarEnums {
302302
else {
303303
val (tag, scaffolding) = nextOrdinal(CaseKind.Simple)
304304
val creator = Apply(Ident(nme.DOLLAR_NEW), List(Literal(Constant(tag)), Literal(Constant(name.toString))))
305-
val vdef = ValDef(name, enumClassRef, creator).withMods(mods | Final)
305+
val vdef = ValDef(name, enumClassRef, creator).withMods(mods | EnumValue)
306306
flatTree(scaffolding ::: vdef :: Nil).withSpan(span)
307307
}
308308
}

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -693,6 +693,9 @@ object Flags {
693693
/** A Java enum value */
694694
final val JavaEnumValue: FlagConjunction = allOf(StableRealizable, JavaStatic, JavaDefined, Enum)
695695

696+
/** An enum value */
697+
final val EnumValue: FlagConjunction = allOf(StableRealizable, JavaStatic, Enum)
698+
696699
/** Labeled private[this] */
697700
final val PrivateLocal: FlagConjunction = allOf(Private, Local)
698701

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,13 @@ class CompleteJavaEnums extends MiniPhase with InfoTransformer { thisPhase =>
110110
else tree
111111
}
112112

113+
override def transformValDef(tree: ValDef)(implicit ctx: Context): ValDef = {
114+
val sym = tree.symbol
115+
if ((sym.is(EnumValue) || sym.name == nme.DOLLAR_VALUES) && sym.owner.linkedClass.derivesFromJavaEnum)
116+
sym.addAnnotation(Annotations.Annotation(defn.ScalaStaticAnnot))
117+
tree
118+
}
119+
113120
/** 1. If this is an enum class, add $name and $ordinal parameters to its
114121
* parameter accessors and pass them on to the java.lang.Enum constructor.
115122
*

compiler/test/dotc/run-test-pickling.blacklist

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,4 @@ mixin-forwarder-overload
1717
t8905
1818
t10889
1919
i5257.scala
20+
enum-java

docs/docs/reference/enums/desugarEnums.md

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -170,8 +170,8 @@ Companion objects of enumerations that contain at least one simple case define i
170170
ordinal number and name. This method can be thought as being defined as
171171
follows.
172172

173-
private def $new(\_$ordinal: Int, $name: String) = new E {
174-
def $ordinal = $tag
173+
private def $new(_$ordinal: Int, $name: String) = new E {
174+
def $ordinal = $_ordinal
175175
override def toString = $name
176176
$values.register(this) // register enum value so that `valueOf` and `values` can return it.
177177
}
@@ -186,6 +186,14 @@ identifiers.
186186
Even though translated enum cases are located in the enum's companion object, referencing
187187
this object or its members via `this` or a simple identifier is also illegal. The compiler typechecks enum cases in the scope of the enclosing companion object but flags any such illegal accesses as errors.
188188

189+
### Translation of Java-compatible enums
190+
A Java-compatible enum is an enum that extends `java.lang.Enum`. The translation rules are the same as above, with the reservations defined in this section.
191+
192+
It is a compile-time error for a Java-compatible enum to have class cases.
193+
194+
Cases such as `case C` expand to a `@static val` as opposed to a `val`. This allows them to be generated as static fields of the enum type, thus ensuring they are represented the same way as Java enums.
195+
196+
189197
### Other Rules
190198

191199
A normal case class which is not produced from an enum case is not allowed to extend

docs/docs/reference/enums/enums.md

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -89,13 +89,13 @@ object Planet {
8989
```
9090

9191
### Compatibility with Java Enums
92-
If you want to use the Scala-defined enums as Java enums, you can do so by extending `compat.JEnum` class as follows:
92+
If you want to use the Scala-defined enums as Java enums, you can do so by extending `java.lang.Enum` class as follows:
9393

9494
```scala
95-
enum Color extends compat.JEnum[Color] { case Red, Green, Blue }
95+
enum Color extends java.lang.Enum[Color] { case Red, Green, Blue }
9696
```
9797

98-
The type parameter comes from the Java enum [definition](https://docs.oracle.com/javase/8/docs/api/index.html?java/lang/Enum.html) and should me the same as the type of the enum. The compiler will transform the definition above so that `Color` extends `java.lang.Enum`.
98+
The type parameter comes from the Java enum [definition](https://docs.oracle.com/javase/8/docs/api/index.html?java/lang/Enum.html) and should be the same as the type of the enum. There is no need to provide constructor arguments (as defined in the API docs) to `java.lang.Enum` when extending it – the compiler will generate them automatically.
9999

100100
After defining `Color` like that, you can use like you would a Java enum:
101101

@@ -104,6 +104,8 @@ scala> Color.Red.compareTo(Color.Green)
104104
val res15: Int = -1
105105
```
106106

107+
For a more in-depth example of using Scala 3 enums from Java, see [this test](https://github.com/lampepfl/dotty/tree/master/tests/run/enum-java). In the test, the enums are defined in the `MainScala.scala` file and used from a Java source, `Test.java`.
108+
107109
### Implementation
108110

109111
Enums are represented as `sealed` classes that extend the `scala.Enum` trait.

tests/run/enum-java.check

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
API Test A
2+
compareTo greater:-1
3+
compareTo lesser: 1
4+
compareTo self: 0
5+
equals other: false
6+
equals self: true
7+
getDeclaringClass:class A
8+
name: MONDAY
9+
ordinal: 0
10+
toString: MONDAY
11+
12+
API Test B
13+
compareTo greater:-1
14+
compareTo lesser: 1
15+
compareTo self: 0
16+
equals other: false
17+
equals self: true
18+
getDeclaringClass:class B
19+
name: EARTH
20+
ordinal: 0
21+
toString: EARTH
22+
23+
Module Test
24+
Values class: class [LA;
25+
MONDAY : 0
26+
TUESDAY : 1
27+
SATURDAY : 2
28+
By-name value: MONDAY
29+
Correctly failed to retrieve illegal name, message: key not found: stuff
30+
31+
Collections Test
32+
Retrieving Monday: workday
33+
Contains Tuesday: false
34+
All days:
35+
MONDAY
36+
TUESDAY
37+
SATURDAY
38+
39+
Switch Test
40+
Saw tuesday
41+
Jup
42+
43+
Misc Tests
44+
Gravity on Earth: 9.8

tests/run/enum-java/MainScala.scala

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
enum A extends java.lang.Enum[A] {
2+
case MONDAY, TUESDAY, SATURDAY
3+
}
4+
5+
enum B(val gravity: Double) extends java.lang.Enum[B] {
6+
case EARTH extends B(9.8)
7+
case JUPITER extends B(100)
8+
case MOON extends B(4.3)
9+
case Foo extends B(10)
10+
}

tests/run/enum-java/Test.java

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
public class Test {
2+
public static void log(String x) { System.out.println(x); }
3+
public static void check(Boolean p, String message) {
4+
if (!p) throw new RuntimeException("Assertion failed: " + message);
5+
}
6+
7+
public static <E extends java.lang.Enum<E>> void apiTest(E t1, E t2) {
8+
log("compareTo greater:" + t1.compareTo(t2) );
9+
log("compareTo lesser: " + t2.compareTo(t1) );
10+
log("compareTo self: " + t1.compareTo(t1) );
11+
log("equals other: " + t1.equals(t2) );
12+
log("equals self: " + t1.equals(t1) );
13+
log("getDeclaringClass:" + t1.getDeclaringClass() );
14+
log("name: " + t1.name() );
15+
log("ordinal: " + t1.ordinal() );
16+
log("toString: " + t1.toString() );
17+
}
18+
19+
public static void moduleTest() {
20+
A[] values = A.values();
21+
log("Values class: " + values.getClass());
22+
for (A v: values) log(v.name() + " : " + v.ordinal());
23+
log("By-name value: " + A.valueOf("MONDAY"));
24+
try {
25+
A.valueOf("stuff");
26+
}
27+
catch (IllegalArgumentException e) {
28+
log("Correctly failed to retrieve illegal name, message: " + e.getMessage());
29+
}
30+
}
31+
32+
public static void collectionsTest() {
33+
java.util.EnumMap<A, String> days = new java.util.EnumMap<A, String>(A.class);
34+
days.put(A.MONDAY, "workday");
35+
days.put(A.SATURDAY, "weekend");
36+
37+
log("Retrieving Monday: " + days.get(A.MONDAY));
38+
log("Contains Tuesday: " + days.containsKey(A.TUESDAY));
39+
40+
java.util.EnumSet<A> allDays = java.util.EnumSet.allOf(A.class);
41+
log("All days:");
42+
for (A d : allDays) log(d.toString());
43+
}
44+
45+
public static void switchTest() {
46+
A a = A.TUESDAY;
47+
switch(a) {
48+
case MONDAY:
49+
log("Saw monday");
50+
break;
51+
case TUESDAY:
52+
log("Saw tuesday");
53+
break;
54+
}
55+
56+
B b = B.JUPITER;
57+
switch (b) {
58+
case JUPITER: log("Jup"); break;
59+
case EARTH: log("Earth"); break;
60+
}
61+
}
62+
63+
public static void miscTests() {
64+
log("Gravity on Earth: " + B.EARTH.gravity());
65+
check(A.class.isEnum(), "A class must be enum");
66+
check(B.class.isEnum(), "B class must be enum");
67+
}
68+
69+
public static void main(String[] args) {
70+
log("API Test A");
71+
apiTest(A.MONDAY, A.TUESDAY);
72+
73+
log("\nAPI Test B");
74+
apiTest(B.EARTH, B.JUPITER);
75+
76+
log("\nModule Test");
77+
moduleTest();
78+
79+
log("\nCollections Test");
80+
collectionsTest();
81+
82+
log("\nSwitch Test");
83+
switchTest();
84+
85+
log("\nMisc Tests");
86+
miscTests();
87+
}
88+
}

0 commit comments

Comments
 (0)