Control flow analysis for exhaustive pattern-switch statement

Tagir Valeev amaembo at gmail.com
Thu Feb 3 05:41:53 UTC 2022


Hello!

JLS requires (14.11.2) that the new "enhanced" switch statement (not
expressions) must be exhaustive. In particular, for the switch over
sealed abstract type, it's required to list all the permitted subtypes
or provide a total/default branch. This is good. However, this means
that from CFG point of view exactly one switch branch must always be
visited. This is indeed so if we take a look at the bytecode: the
synthetic default branch throws IncompatibleClassChangeError(). But
compiler ignores this fact and refuses to compile the code like this
(tried 18-ea+33-2077):

sealed interface Parent {}
record A() implements Parent {}
record B() implements Parent {}

void test(Parent p) {
    int x;
    switch (p) {
        case A a -> x = 1;
        case B b -> x = 2;
    }
    System.out.println(x); // error: variable x might not have been initialized
}

I think this is a mistake that should be corrected: in this code, `x`
should be considered as definitely assigned.

I understand that the same reasoning does not apply for switch over
enums, as for compatibility reasons, default behavior is to do
nothing. However, for patterns, uninitialized `x` cannot appear after
the switch, even if we recompile the sealed interface separately
adding one more inheritor.

I try to understand what's written in 16.2.9 regarding this [1].
Unfortunately, its current state looks confusing to me. Sorry if I'm
missing something, as I'm not very experienced in reading  chapter 16
of JLS. Nevertheless, it says:

V is [un]assigned after a switch statement (14.11) iff all of the
following are true:
...
Original spec:
- If there is no default label in the switch block, or if the switch
block ends with a switch label followed by the } separator, then V is
[un]assigned after the selector expression.
Preview spec:
- If the switch block covers the type of the selector expression, or
if the switch block ends with a switch label followed by the }
separator, then V is [un]assigned after the selector expression.

It looks strange that "no default label" (~= non-exhaustive) was
replaced with "covers the type" (= exhaustive). Was negation lost
somewhere? In current state it looks like, all exhaustive switches
(which is almost all switches), including ones with `default` branch
cannot definitely assign a variable, which contradicts the previous
state.

If negation is actually lost, then the sample above should compile, as
it's exhaustive.

Sorry if this was already discussed.

With best regards,
Tagir Valeev

[1] https://cr.openjdk.java.net/~gbierman/jep406/jep406-20210608/specs/patterns-switch-jls.html#jls-16.2.9


More information about the amber-spec-experts mailing list