[External] : Re: Enhanced Enums Redux (with working code)
Clement Cherlin
ccherlin at gmail.com
Tue Feb 24 13:16:34 UTC 2026
On Mon, Feb 23, 2026 at 6:26 AM Maurizio Cimadamore <
maurizio.cimadamore at oracle.com> wrote:
> Hi Clement
>
> On 22/02/2026 18:21, Clement Cherlin wrote:
> > Greetings,
> >
> > I have been working on the problem of generic enums off and on for
> > quite some time (see
> > https://github.com/Mooninaut/cursed-generic-enum-java for a previous
> > dead-end attempt).
> >
> > I think I have an actual solution this time, and it doesn't require
> > any changes to Java's type system. It only requires a slight tweak to
> > the "extends" clause of generic enums: Replace "extends
> > Enum<SomeEnum<T>>" with "extends Enum SomeEnum<?>>".
>
> I'd need more time to think about the consequences of this. That said,
> this has a good property, in that it only affects generic enum
> declarations. Non-generic enum would stay exactly the same.
>
> > There is one slightly awkward issue. It's not possible to simply pass
> > an enum class literal to EnumSet's static factory methods without a
> > raw cast.
> >
> > EnumSet.allOf((Class<Option<?>>) (Class) Option.class);
>
> This makes me more worried -- because this is such a common idiom that
> (I think) has to work like for any other enum.
>
> In the spirit of your earlier suggestion, a possible way out would be to
> also redefine what Foo.class mean when Foo is a generic enum -- e.g. not
> just Class<Foo> (class-of-raw) but Class<Foo<?>> (class-of-wildcard).
>
> All such changes would indeed help making generic enums more useful
> (and interoperable with EnumSet and friends), but whether all such
> changes are actually "sound", that's a different story.
>
> For instance, code like:
>
> MyEnumSet<Option> set = MyEnumSet.allOf(Option.class)
>
> Would _still_ fail. You need the LHS to be _exactly_
> MyEnumSet<Option<?>> for this to work. Which means graduating an enum
> into a generic enum would still be a source incompatible change
> Cheers
> Maurizio
That's an excellent point. Doing the migration in a single step is source
incompatible.
However, it is possible to perform the migration in multiple steps, each
step source
compatible with the previous step, allowing for gradual migration. This
would
require multiple major releases of a library to complete. The result would
be
that at each step, clients can update the library without incompatibility,
as long
as they complete the required migration steps before upgrading to a major
version
that takes the next migration step.
See
https://github.com/Mooninaut/java-generic-enums/tree/main/src/main/java/org/duckdns/mooninaut/migration
for a step-by-step example.
Given a library that has an enum, which can usefully be migrated to a
generic enum:
Step 1:
* Library changes method parameters of type "EnumSet<X>" (if any) to
"EnumSet<? extends X>". This change is source-compatible for clients.
* Casts from "EnumSet<? extends X>" to "EnumSet<X>" may be
required inside library methods.
* Suggestion: Some sort of @MigratingToGenericEnum annotation to prompt
clients to update references, ideally with tool support.
Step 2:
* Clients update library, and change variables of type "EnumSet<X>" to
"EnumSet<? extends X>"
* Both "EnumSet<X>" and "EnumSet<? extends X>" compile.
Step 3:
* Library makes X generic, updates methods that return "EnumSet<X>" to
return "EnumSet<X<?>>" This is source-compatible for clients that have
completed
step 2, with an unchecked warning on "EnumSet.allOf(X.class)" and related
methods.
* Library methods may require casts from "EnumSet<? extends X>" to
"EnumSet<X<?>>"
* Library changes "EnumSet.allOf(X.class)" to
"EnumSet.allOf(X.CONSTANT.getDeclaringClass())"
* Library removes @MigratingToGenericEnum.
Step 4:
* Clients update library, change usages of "EnumSet<? extends X>" to
"EnumSet<X<?>>"
and change "EnumSet.allOf(X.class)" to
"EnumSet.allOf(X.CONSTANT.getDeclaringClass())"
* Usages of "EnumSet<X>" now produce compilation errors.
* Both "EnumSet<? extends X> set = EnumSet.allOf(X.class)" and
"EnumSet<X<?>> set = EnumSet.allOf(X.CONSTANT.getDeclaringClass())"
compile.
* Tools should prompt these changes when seeing use of a generic enum with
old syntax.
Step 5 (optional):
* Library replaces "EnumSet<? extends X>" with "EnumSet<X<?>>" in method
parameter types.
* Library methods can no longer be called with values of type "EnumSet<?
extends X>".
* This is source-compatible with clients that have completed step 4.
* No further client changes are required.
* Casts to EnumSet<X<?>> are no longer required.
* This step is optional. The only benefit is removing remaining casts.
Libraries can delay this step for as long as desired to allow clients
time to complete step 4.
This process is analogous to the gradual transition from a deprecated API
to its replacement
over several major versions. The non-generic syntax becomes "deprecated" at
step 1, but
still compiles, possibly with warnings, until step 4.
Cheers,
Clement
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://mail.openjdk.org/pipermail/amber-dev/attachments/20260224/c5942777/attachment.htm>
More information about the amber-dev
mailing list