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

Java enums #6629

Merged
merged 3 commits into from
Jun 8, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,7 @@ class BTypesFromSymbols[I <: BackendInterface](val int: I) extends BTypes {
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.isJavaEnum) asm.Opcodes.ACC_ENUM else 0
if (sym.isEnum) asm.Opcodes.ACC_ENUM else 0
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -512,7 +512,7 @@ abstract class BackendInterface extends BackendInterfaceDefinitions {
def isJavaDefaultMethod: Boolean
def isClassConstructor: Boolean
def isSerializable: Boolean
def isJavaEnum: Boolean
def isEnum: 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,7 +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 isEnum = sym.is(Flags.Enum)

def isClassConstructor: Boolean = toDenot(sym).isClassConstructor
def isSerializable: Boolean = toDenot(sym).isSerializable
Expand Down
6 changes: 3 additions & 3 deletions compiler/src/dotty/tools/dotc/ast/DesugarEnums.scala
Original file line number Diff line number Diff line change
Expand Up @@ -84,13 +84,13 @@ object DesugarEnums {
/** The following lists of definitions for an enum type E:
*
* private val $values = new EnumValues[E]
* def values = $values.values.toArray
* 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] = {
val valuesDef =
Expand Down Expand Up @@ -286,7 +286,7 @@ object DesugarEnums {
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)
val vdef = ValDef(name, TypeTree(), New(impl1)).withMods(mods | EnumValue)
flatTree(scaffolding ::: vdef :: Nil).withSpan(span)
}
}
Expand All @@ -302,7 +302,7 @@ object DesugarEnums {
else {
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)
val vdef = ValDef(name, enumClassRef, creator).withMods(mods | EnumValue)
flatTree(scaffolding ::: vdef :: Nil).withSpan(span)
}
}
3 changes: 3 additions & 0 deletions compiler/src/dotty/tools/dotc/core/Flags.scala
Original file line number Diff line number Diff line change
Expand Up @@ -693,6 +693,9 @@ object Flags {
/** A Java enum value */
final val JavaEnumValue: FlagConjunction = allOf(StableRealizable, JavaStatic, JavaDefined, Enum)

/** An enum value */
final val EnumValue: FlagConjunction = allOf(StableRealizable, JavaStatic, Enum)

/** Labeled private[this] */
final val PrivateLocal: FlagConjunction = allOf(Private, Local)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,13 @@ class CompleteJavaEnums extends MiniPhase with InfoTransformer { thisPhase =>
else tree
}

override def transformValDef(tree: ValDef)(implicit ctx: Context): ValDef = {
val sym = tree.symbol
if ((sym.is(EnumValue) || sym.name == nme.DOLLAR_VALUES) && sym.owner.linkedClass.derivesFromJavaEnum)
sym.addAnnotation(Annotations.Annotation(defn.ScalaStaticAnnot))
Copy link
Contributor

Choose a reason for hiding this comment

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

Should this go into a SymTransformer?

tree
}

/** 1. If this is an enum class, add $name and $ordinal parameters to its
* parameter accessors and pass them on to the java.lang.Enum constructor.
*
Expand Down
1 change: 1 addition & 0 deletions compiler/test/dotc/run-test-pickling.blacklist
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,4 @@ mixin-forwarder-overload
t8905
t10889
i5257.scala
enum-java
12 changes: 10 additions & 2 deletions docs/docs/reference/enums/desugarEnums.md
Original file line number Diff line number Diff line change
Expand Up @@ -170,8 +170,8 @@ Companion objects of enumerations that contain at least one simple case define i
ordinal number and name. This method can be thought as being defined as
follows.

private def $new(\_$ordinal: Int, $name: String) = new E {
def $ordinal = $tag
private def $new(_$ordinal: Int, $name: String) = new E {
def $ordinal = $_ordinal
override def toString = $name
$values.register(this) // register enum value so that `valueOf` and `values` can return it.
}
Expand All @@ -186,6 +186,14 @@ identifiers.
Even though translated enum cases are located in the enum's companion object, referencing
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.

### Translation of Java-compatible enums
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.

It is a compile-time error for a Java-compatible enum to have class cases.

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.


### Other Rules

A normal case class which is not produced from an enum case is not allowed to extend
Expand Down
8 changes: 5 additions & 3 deletions docs/docs/reference/enums/enums.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,13 +89,13 @@ object Planet {
```

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

```scala
enum Color extends compat.JEnum[Color] { case Red, Green, Blue }
enum Color extends java.lang.Enum[Color] { case Red, Green, Blue }
```

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`.
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.

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

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

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`.

### Implementation

Enums are represented as `sealed` classes that extend the `scala.Enum` trait.
Expand Down
44 changes: 44 additions & 0 deletions tests/run/enum-java.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
API Test A
compareTo greater:-1
compareTo lesser: 1
compareTo self: 0
equals other: false
equals self: true
getDeclaringClass:class A
name: MONDAY
ordinal: 0
toString: MONDAY

API Test B
compareTo greater:-1
compareTo lesser: 1
compareTo self: 0
equals other: false
equals self: true
getDeclaringClass:class B
name: EARTH
ordinal: 0
toString: EARTH

Module Test
Values class: class [LA;
MONDAY : 0
TUESDAY : 1
SATURDAY : 2
By-name value: MONDAY
Correctly failed to retrieve illegal name, message: key not found: stuff

Collections Test
Retrieving Monday: workday
Contains Tuesday: false
All days:
MONDAY
TUESDAY
SATURDAY

Switch Test
Saw tuesday
Jup

Misc Tests
Gravity on Earth: 9.8
10 changes: 10 additions & 0 deletions tests/run/enum-java/MainScala.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
enum A extends java.lang.Enum[A] {
case MONDAY, TUESDAY, SATURDAY
}

enum B(val gravity: Double) extends java.lang.Enum[B] {
case EARTH extends B(9.8)
case JUPITER extends B(100)
case MOON extends B(4.3)
case Foo extends B(10)
}
88 changes: 88 additions & 0 deletions tests/run/enum-java/Test.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
public class Test {
public static void log(String x) { System.out.println(x); }
public static void check(Boolean p, String message) {
if (!p) throw new RuntimeException("Assertion failed: " + message);
}

public static <E extends java.lang.Enum<E>> void apiTest(E t1, E t2) {
log("compareTo greater:" + t1.compareTo(t2) );
log("compareTo lesser: " + t2.compareTo(t1) );
log("compareTo self: " + t1.compareTo(t1) );
log("equals other: " + t1.equals(t2) );
log("equals self: " + t1.equals(t1) );
log("getDeclaringClass:" + t1.getDeclaringClass() );
log("name: " + t1.name() );
log("ordinal: " + t1.ordinal() );
log("toString: " + t1.toString() );
}

public static void moduleTest() {
A[] values = A.values();
log("Values class: " + values.getClass());
for (A v: values) log(v.name() + " : " + v.ordinal());
log("By-name value: " + A.valueOf("MONDAY"));
try {
A.valueOf("stuff");
}
catch (IllegalArgumentException e) {
log("Correctly failed to retrieve illegal name, message: " + e.getMessage());
}
}

public static void collectionsTest() {
java.util.EnumMap<A, String> days = new java.util.EnumMap<A, String>(A.class);
days.put(A.MONDAY, "workday");
days.put(A.SATURDAY, "weekend");

log("Retrieving Monday: " + days.get(A.MONDAY));
log("Contains Tuesday: " + days.containsKey(A.TUESDAY));

java.util.EnumSet<A> allDays = java.util.EnumSet.allOf(A.class);
log("All days:");
for (A d : allDays) log(d.toString());
}

public static void switchTest() {
A a = A.TUESDAY;
switch(a) {
case MONDAY:
log("Saw monday");
break;
case TUESDAY:
log("Saw tuesday");
break;
}

B b = B.JUPITER;
switch (b) {
case JUPITER: log("Jup"); break;
case EARTH: log("Earth"); break;
}
}

public static void miscTests() {
log("Gravity on Earth: " + B.EARTH.gravity());
check(A.class.isEnum(), "A class must be enum");
check(B.class.isEnum(), "B class must be enum");
}

public static void main(String[] args) {
log("API Test A");
apiTest(A.MONDAY, A.TUESDAY);

log("\nAPI Test B");
apiTest(B.EARTH, B.JUPITER);

log("\nModule Test");
moduleTest();

log("\nCollections Test");
collectionsTest();

log("\nSwitch Test");
switchTest();

log("\nMisc Tests");
miscTests();
}
}