I always liked Kevin's commentary on how the underlying cause of an
unrecognized enum constant was classpath misconfiguration arising from
inconsistent separate compilation.
Still, the use by switch expressions of ICCE (a LinkageError) was
irregular because it was the _only place_ where the Java language
actively handled inconsistent separate compilation. All the other
LinkageErrors in the JLS merely reflect what the JVM does when
inconsistent separate compilation leads to linking failures. For
example, 8.1.4 says "If circularly declared classes are detected at run
time, as classes are loaded, then a ClassCircularityError is thrown
(§12.2.1)." where 12.2.1 presents JVM behavior from the viewpoint of the
Java language.
The JVM throws its LinkageErrors regardless of the source language, of
course, so the JLS explicitly "adopting" a LinkageError in the rules for
switch expressions was a bit "raw". I'm glad that pattern matching has
evolved to the point where ICCE can hand over to MatchException, whose
shape in Java 20 could not reasonably have been foreseen in Java 12-14.
Dropping the use of ICCE in switch expressions leaves AssertionError in
14.10 as the only Error mandated by the Java language itself. That's
appropriate, since an assertion failure lies in the plane of the
program. Assertion failures have some extra-lingual flavor (being
enabled from outside the program) but are still very different than the
plethora of extra-lingual failures arising from inconsistent separate
compilation and reported as LinkageErrors.
(I think JLS 11.1.2 "The Causes of Exceptions" ought to mention an
`assert` statement -- filed https://bugs.openjdk.org/browse/JDK-8296951)
Alex
On 11/14/2022 4:38 AM, Gavin Bierman wrote:
Dear Experts,
As we put the final polish on features for JDK20, we noticed that we have an
opportunity to make a very small breaking change (as part of the preview
feature) to simplify our lives. I’m writing to see what you think.
tldr: A switch expression over an enum class should throw MatchException rather
than IncompatibleClassChangeError if no switch label applies at runtime.
Details:
When we introduced switch expressions, we opted for a design where the switch
body had to be exhaustive. When switching over an enum type, a switch body with
case labels supporting all the enum constants for the enum type is considered
exhaustive, meaning a default clause is not needed.
However, there is a possibility that the enum class is changed after
compilation of the switch expression, and a new enum constant added. Then when
executing the switchexpression, no label would apply.
The question we faced in JDK14 was what to do at this point. We decided on
IncompatibleClassChangeError as that was a pre-existing exception that was
generally understood by developers as a signal that things have got out of sync
and re-compilation is needed.
Back to the present day, with the support of pattern switches, we can now write
switches over a sealed type. When switching over a sealed type, a switch body
with case labels with type patterns matching all the permitted subclasses is
considered exhaustive, meaning a default clause is not needed.
If the sealed hierarchy has been changed after compilation of the switch, it is
possible that when executing the switch that no label would apply. In this case
we have settled on throwing a MatchException.
Throughout our design process, we have noticed the connection between enum
classes/enum constants and sealed class/permitted subclasses – they are
essentially the same thing up the term/type hierarchy. Moreover, in a future
release, we plan to support case labels with a mix of sealed class type
patterns and enum constants.
But we now have an inconsistency - one throws IncompatibleClassChangeException
in a bad situation and the other MatchException which will make this future
development almost impossible. We need these cases to throw the same exception:
MatchException. So we propose to make the small breaking case to the language
that switch expressions over enum classes throw MatchException should no switch
label apply in the switch body.
People who deliberately change their enum classes by adding new constants, and
do not recompile their switches over this enum class, and rely on this throwing
ICCE will notice this breaking change. We think this is a vanishingly small set
of developers. The vast majority of developers, on the other hand, will thank
us for this unification, especially if it enables other new features down the
road.
What do you think?
Thanks,
Gavin