[enhanced enums] - end of the road?

Maurizio Cimadamore maurizio.cimadamore at oracle.com
Tue May 23 17:49:55 UTC 2017


Hi,
over the last few weeks we've been carrying out experiments to test the 
feasibility of the enhanced enums feature. As described in the JEP, this 
feature is particularly powerful as it allows enums constants to be 
carrier of generic type information, which can then be fed back to the 
inference machinery.

One experiment we wanted to make was to see if enhanced enums could make 
javac's own Option [1] enum better. This enum defines a bunch of 
constants, one for each supported javac command line arguments (e.g 
-classpath, -d, etc.). Furthermore, the enum defines method so that each 
constant can be parsed given the javac command line and processed, 
accordingly to some OptionHelper. Most options, along with the value of 
their arguments would simply be stored into a Map<String, String>, which 
is the backbone of the Options class [2].

One problem with storing option values as Strings is that clients need 
to do the parsing. So, if an option has an integer argument, it's up to 
the client to get the value of that option off the map, parse it into a 
number and (maybe) check as to whether the range makes sense.

With enhanced enums it should be possible to do better than this; more 
specifically, if enums supported generics, each option could specify a 
type argument - that is, the type of the argument that javac expects for 
that particular option.

So, an option with a plain String argument would be encoded as 
Option<String>, as follows:

D<String>("-d", ...)

While an option for which multiple choices are available, could be 
encoded using an enum as a type-argument - for instance:

PROC<ProcOption>("-proc", ...)

where ProcOption would be defined as follows:

enum ProcOption {
    NONE, ONLY;
}

Finally, for an option whose argument can be a set of values, we would 
use the following encoding:

G_CUSTOM<EnumSet<DebugOption>>("-g:",  ...)

where DebugOption would be defined as follows:

enum DebugOption {
     LINES, VARS, SOURCE;
}

So, instead of storing all options into a Map<String, String>, we could 
store them into a Map<Option, Object>. Then, we would turn the 
Options.get method from this:

public String get(String option) { ... }

to something like this:

public Z get(Option<Z> option) { ... }

granted, there will be some unchecked operations carried out by the body 
of this method, but the map should be well-constructed by design, so it 
should be safe. What we get back is that now clients can do things like:

boolean g_vars = options.get(Option.G_CUSTOM).contains(DebugOption.VARS);

Note how we raised the expressiveness level of the client, which no 
longer has to do parsing duties (and domain conversions). So, that was 
the experiment we wanted to carry out - ultimately, this is the kind of 
stuff you'd like to be writing with enhanced enums, so this seemed like 
a reasonably comprehensive test for the feature.


Unfortunately, the results of the experiment were not as successful as 
we'd hoped. As soon as we turned the Option enum into a generic class 
(by merely adding a type parameter in its declaration), we immediately 
started hitting dozens of compile-time errors. The errors were rather 
cryptic, all pointing to some obscure failure when calling 
EnumSet.noneOf or EnumSet.allOf with the newly generified Option class. 
In other words, code like this:

EnumSet.noneOf(Option.class)

Was now failing. The issue that was underpinning all these failures is - 
in retrospect - rather obvious: the following type:

EnumSet<Option>

is *not* a well-formed type if Option is a generic class. Why? Well, 
EnumSet is declared like this:

class EnumSet<*E extends Enum<E>*> { ... }

which means the type parameter has an f-bound. In concrete terms, we 
have to check that:

Option <: [E:=Option]Enum<E>

That is, the actual type-argument must conform to its declared bound. 
But if we follow that check, we obtain:

Option <: [E:=Option]Enum<E>
Option <: Enum<Option>
Enum (*) <: Enum<Option>
false

(*) note that Option is now a 'raw' type - and a raw type has all 
supertypes erased, as per JLS 4.8.

In other words, there's no way to write down the type of an enum set 
which contains heterogeneous options - the wildcard path doesn't help 
either:

EnumSet<Option<?>>

As, the above check would develop in the following way:

Option<?> <: [E:=Option<?>]Enum<E>
Option<?> <: Enum<Option<?>>
Enum<#CAP> (**) <: Enum<Option<?>>
false

(**) the supertype of a wildcard-parameterized type is obtained by first 
capturing, and then recursing to the supertype, as per JLS 4.10.2


In other words, generic enums are not interoperable with common data 
structures such as enum sets (and, more generally, with any f-bounded 
generic data structure).

While we could just deliver the part of JEP 301 regarding sharper typing 
of enum constants and leave generic enums alone, we feel there's not 
much value into pursuing that path alone. After all, the benefits of 
enhanced enums were exactly in combining sharper typing with generic 
type information, so that enum constants could be used as type carriers. 
If generic enums are not viable, then much of the usefulness of this JEP 
is lost.

It is unclear at this point in time if type system improvements (which 
we are pursuing as part of a separate activity [Dan is there a link for 
this??]) would ameliorate the situation.

Until we figure this out, I suggest that we put this JEP on hold for the 
time being

[1] - 
http://hg.openjdk.java.net/jdk10/jdk10/langtools/file/tip/src/jdk.compiler/share/classes/com/sun/tools/javac/main/Option.java
[2] - 
http://hg.openjdk.java.net/jdk10/jdk10/langtools/file/tip/src/jdk.compiler/share/classes/com/sun/tools/javac/util/Options.java#l48 



Cheers
Maurizio


-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.openjdk.java.net/pipermail/amber-spec-experts/attachments/20170523/e799036f/attachment.html>


More information about the amber-spec-experts mailing list