eclipse-jdt / eclipse.jdt.core

Eclipse Public License 2.0
164 stars 130 forks source link

[23] JEP 455 - Exhaustiveness of Switch #2869

Closed mpalat closed 2 months ago

mpalat commented 2 months ago

Ref -https://cr.openjdk.org/~abimpoudis/instanceof/jep455-20240424/specs/instanceof-jls.html#jls-14.11

Since primitives can also make a switch enhanced the exhaustiveness rules have changes in conjunction with https://cr.openjdk.org/~abimpoudis/instanceof/jep455-20240424/specs/instanceof-jls.html#jls-5.7.2 - the unconditionally exact

stephan-herrmann commented 2 months ago

According to 14.11.1.1 a switch is exhaustive when (only listing cases relevant for primitives, my numbering):

  1. CE contains a pattern that is unconditional (14.30.3) for T.
  2. The type T is boolean or Boolean and CE contains both the constant true and the constant false.
  3. CE contains a type pattern with a primitive type P, and T is the wrapper class for the primitive type W, and the conversion from type W to type P is unconditionally exact (5.7.2).

ad (2)

Most directly, (2) is implemented in SwitchStatement.resolve() in the check lambda:

ad (3)

Next, (3) is implemented in Pattern.coversType(), when the conversion route is one of

ad (1)

Final for (1) §14.30.3 has this:

A pattern p is said to be unconditional for a type T if every value of type T matches p, so that pattern matching requires no action at run time. It is defined as follows:

  • A type pattern that declares a pattern variable of a type S is unconditional for a type T if there is a testing conversion that is unconditionally exact (5.7.2) from |T| to |S|.

This is implemented by the remaining branches of Pattern.coversType(), where we see all four bullets from §5.7.2, corresponding to our PrimitiveConversionRoutes:

Furthermore, to avoid that isSubtypeOf() is applied to byte<->char situations, that case explicitly returns false from WIDENING_AND_NARROWING_PRIMITIVE_CONVERSION.

:heavy_check_mark:

summary

Case (2) directly sets SwitchStatement.Exhaustive during SwitchStatement.resolve()

Cases (1) and (3) are implemented in Pattern.coversType() which then is called from CaseStatement.resolveCasePattern() and upon true this will also set SwitchStatement.Exhaustive.

The difference is due to the fact that in most cases involving primitives exhaustiveness has to be established by a single type pattern (no interaction with other patterns in this regard), but only for boolean two constants achieve the same.

I believe I'm done here :heavy_check_mark:

stephan-herrmann commented 2 months ago

Resolved with #2878