Dear experts,

I have amended the spec for JEP 406 (Pattern Matching for Switch) to reflect 
the consensus following the recent lengthy discussions on this list about two 
design changes, specifically:

  1.  treating total type patterns as match-everything switch labels (i.e. 
matching null)
  2.  requiring non-legacy switches to be total like switch expressions

The updated spec is available at:

http://cr.openjdk.java.net/~gbierman/jep406/latest/

Please let us know what you think!

Thanks,
Gavin

PS: The details:

To capture the notion of a total type pattern matching everything, I have not 
followed the idea of extending the notion of pattern matching to be a 
three-place relation, taking a value, a compile-time type, and a pattern. This 
passing of a compile-time type around at *runtime* is not at all how the JLS 
works (which maintains a pretty strict phase distinction), and it doesn’t 
accurately reflect what we do in the compiler either. So, here’s how it has 
been specified:

1. I introduced a new pattern, called an *any pattern*. Right now, it’s a 
pattern that is expressible but not denotable, i.e. it is an error if it 
appears anywhere in the source program. It arises from a process of “resolving” 
a pattern...

2. There is a new notion of "resolving" a pattern with a type. It works like 
this:

A pattern P resolves with a type U as follows:

1. if P is T t where U <: T, P resolves to an any pattern `var t`, where the 
type of t is T; otherwise P resolves to P.

2. if P is var x, P resolves to an any pattern `var x` where the type of x is U;

3. if P is (Q), then P resolves to (Q'), where Q' is the result of resolving Q 
with U

4. if P is Q && e, then
    (i)  If Q && e is total for U then P resolves to Q' && e where Q' is the 
result of resolving Q with U;
    (ii.) If Q && e is not total for U then P resolves to P.

So the basic idea of resolving a pattern is that it replaces any total patterns 
with an any pattern.

Now, the runtime behaviour of pattern matching is specified to always be with 
respect to a resolved pattern.

In other words, with e instanceof P, we resolve P with the type of e to get a 
resolved pattern Q, and then we pattern match the value of e with the resolved 
pattern Q. With switches we resolve any pattern labels with the type of the 
selector expression before finding a switch label that matches.

Pattern matching can now be more fully specified as follows:

-   A value v (including null) matches `var x` and x is initialized to v.

-   The null reference does not match T t.

-   A non-null value v matches T t if v can be cast to T without raising a 
ClassCastException, and t is initialized to v; and does not match otherwise.

-   A value v matches p && e if (i) v matches p, and (ii) e evaluates to true; 
and does not match otherwise.

-   A value v matches (p) if v matches p; and does not match otherwise.

This means that total type patterns appearing as switch labels will now match 
all values *including null*, as they will be resolved to any patterns.

I have also changed the specification of the runtime semantics of switches by 
breaking it up into two different cases depending on whether the selector 
expression evaluates to `null` or not. If it does evaluate to null then we look 
for either a `case null` or a total pattern case label. If we find none then we 
NPE. Otherwise, we look at all the switch labels as before (including the 
legacy ones involving constants). This simplifies matters with the legacy 
labels so they don't need to explicitly exclude null.

I have added a new condition on switch statements that if it is a non-legacy 
switch then the switch block must be complete (the same condition as for switch 
expressions). The execution of a switch statement now has a new test: if no 
switch label matches in the body, then either (i) if it is a non-legacy switch 
then ICCE is thrown; or (ii) if it is a legacy switch then execution completes 
normally (as before).

Some other minor tweaks were also made.

On 23 Apr 2021, at 16:58, Gavin Bierman 
<gavin.bier...@oracle.com<mailto:gavin.bier...@oracle.com>> wrote:

Dear experts,

Apologies for the delay, but here is the first draft of the spec for JEP 406 
(Pattern Matching for Switch):

http://cr.openjdk.java.net/~gbierman/jep406/latest/

As you will see, the design has evolved a little since we wrote the JEP (which 
I will update shortly). The chief changes are:

1. Instead of enumerating the special case labels, e.g. case null, default, 
case null, T t and so on; the notion of switch label has been made more 
general, with a set of restrictions to cut out the things we don’t want. In 
particular, this means that we get (a) symmetric versions of the compound 
patterns, e.g. case null, default and case default, null: and (b) we get a 
perfect alignment of the old : style with the new , style, e.g. we can write 
both `case null, default: …` and `case null: default: …` with identical 
treatment.

2.  I figured out a way to remove the restriction from the JEP that switches 
must be all pattern-matching, or all non-pattern-matching. Apart from 
simplifying matters, this allows for some nice new features, e.g.

   enum E { F, G, H }

   switch(...) { // A switch block with an enum constant and pattern label!
       case F -> …
       case E e -> … // Acts like a default but e is in scope!
   }

A couple of odd extras come for the ride, just for your information:

switch(o) { /* empty */ }

is now well-typed for all types of o, as is:

switch(o) { default: … }

[Basically any type restrictions on the selector expression come from the 
switch labels. We still maintain that if you use a constant case label, the 
type of the selector must be char, byte, short, int, Character, Byte, Short, 
Integer, or String as in Java 16.]

As always, your opinions are welcomed!

Thanks,
Gavin


Reply via email to