Good point (though I'd be wary of extrapolating too much from multicatch.)
I think the discussion Aggelos was looking to stimulate is the one that
leads to a more principled understanding of when we should and should
not try to detect dead patterns. There's no limit to what we *could*
do, but we don't want to make these decisions on the basis of "hey, we
could have an error here, cool", nor do we want to set in motion an
endless whack-a-mole of "we complain about X, and X is like Y, but we
don't complain about Y."
The treatment of "domination" to detect dead cases is new with pattern
matching (previously the only dead cases where "used the same constant
twice".) So to some extent we are left extrapolating from very little
actual data.
We do detect "same constant twice" even in lists:
switch (i) {
case 1, 2 -> ...
case 2, 3 -> ... // duplicate label
}
But I'm not sure we want to bootstrap our way from that into trying too
hard to detect impossible cases. For example, suppose we had range
patterns:
switch (i) {
case 1: ...
case 10: ...
case 2..<=9: ...
case 1..10: // dead
}
Would we expect the compiler to do this analysis?
On 2/22/2023 11:07 AM, Remi Forax wrote:
------------------------------------------------------------------------
*From: *"Brian Goetz" <[email protected]>
*To: *"Tagir Valeev" <[email protected]>, "Angelos Bimpoudis"
<[email protected]>
*Cc: *"amber-spec-experts" <[email protected]>
*Sent: *Wednesday, February 22, 2023 4:45:38 PM
*Subject: *Re: Draft JLS Spec about unnamed patterns and variables
It's a tricky question, because there's lots of ways to come at
it. For example, we do make a distinction between dead
*statements* and dead code in general, and in particular
conditionals can have lots of dead code.
For example, in
if (true || destroyTheWorld()) { ... }
we don't remark that `destroyTheWorld()` is dead code (though fine
IDEs will call our attention with tasteful highlighting). We're
pretty aggressive about avoiding _unreachable statements_, but
considerably less aggressive about dead code in general.
Thought experiment: what if we had union type patterns? Then the
case label `case String _, Integer _` would be like matching the
the union type pattern `(String|Integer) _`:
case Number n: ...
case (String|Integer) _: ...
Would javac then complain that `String|Integer` could be
simplified to just `String` on the bsais of flow analysis?
(IntelliJ would, of course.)
At least, the compiler complains in a try/catch, by example
try {
ioCall();
} catch(IOException e) {
...
} catch(FileNotFoundException | RuntimeException e) {
...
}
does not compile.
I initially thought as Tagir did, but then Gavin turned me around
and reminded me that it was not dead code, but unreachable
statements that we try to avoid. So now I am torn...
Rémi
On 2/22/2023 10:26 AM, Tagir Valeev wrote:
Hello!
I think we should consider dead patterns as dead code, so this
sample should not compile.
With best regards,
Tagir Valeev
On Wed, Feb 22, 2023, 15:34 Angelos Bimpoudis
<[email protected]> wrote:
Coming back to the topic of dominance for a moment before
I circulate a revised draft spec.
Dominance is the way of pattern matching to detect/dead
code/(meaning that code on the RHS of a dominated case
will never be executed, provably).
Assume the example where|Number|dominates|Integer|--all
values of|Integer|are going to be matched by a proceeding
case,|Number|. This is a compile-time error. Additionally
notice that all binding variables happen to be unused.
|switch (o) { case Number n -> 1; case String
s -> 2; case Integer i -> 2; } |
Under this JEP this code could be rewritten blindly into:
|switch (o) { case Number _ -> 1; case String
_, Integer _-> 2; } |
Under the definition of dead code above, the common case
that was grouped together,|-> 2|, is not dead anymore. It
can be reached via|*case String _*, Integer _-> 2|. As a
result, the code above is correct. It just happens that
the sub-pattern|Integer _|will never be reachable. This
can be a warning but the overall case is correct.
An alternative interpretation would be to treat
sub-patterns as "dead code". Under that interpretation the
second|case|of the second example would be dominated
because there is at least one preceding sub-pattern (or
whole case label with one pattern as in this case) that
dominates at least one of its sub-patterns (|Integer _|).
That case could be rejected (symmetrically to the first
example). This seems restrictive but also a valid direction.
So, my question is what would be the pros and cons of each
approach?
Many, thanks,
Aggelos
------------------------------------------------------------------------
*From:* Brian Goetz <[email protected]>
*Sent:* 26 January 2023 20:33
*To:* Angelos Bimpoudis <[email protected]>;
amber-spec-experts <[email protected]>
*Subject:* Re: Draft JLS Spec about unnamed patterns and
variables
Small wording nit... in "an unnamed declaration can be
used in place of the following declarations"
I'm not sure "in place of" is the right wording; I think
you may just want to say "in", since the grammar permits
it in all of these places. (What you're really doing here
is signalling that there are places the grammar allows it,
but the semantics do not; you are going to call these out
individually in the appropriate places.)
Similar for the second "in place of" in this section.
In 14.11.1, I might refactor the text a little further.
The second sentence of the first paragraph below is about
case constants only, but now comes after you talk about
case patterns or case constants:
A|case|label has either one or more|case|constants,
ora*one or more*|case|pattern*s*. Every|case|constant
must be either (1) the|null|literal, (2) a constant
expression (15.29
<https://docs.oracle.com/javase/specs/jls/se19/html/jls-15.html#jls-15.29>),
or (3) the name of an enum constant (8.9.1
<https://docs.oracle.com/javase/specs/jls/se19/html/jls-8.html#jls-8.9.1>);
otherwise a compile-time error occurs. A|case|label
that has a|null||case|constant may have an
optional|default|.
It is a compile-time error if for any|case|label with
more than one|case|patterns, any of its|case|patterns
declares one or more pattern variables.
I suggest:
A|case|label has either one or more|case|constants,
ora*one or more*|case|pattern*s*.
/For a case label with case constants,
/every|case|constant must be either (1) the|null|literal,
(2) a constant expression (15.29
<https://docs.oracle.com/javase/specs/jls/se19/html/jls-15.html#jls-15.29>),
or (3) the name of an enum constant (8.9.1
<https://docs.oracle.com/javase/specs/jls/se19/html/jls-8.html#jls-8.9.1>);
otherwise a compile-time error occurs. A|case|label that
has a|null||case|constant may have an optional|default|.
/For a case label with case patterns/, it is a
compile-time error if any of its|case|patterns declares
one or more pattern variables.
I am not sure about the definition of dominance here. If
I have:
case Integer _, String _: A;
case Number _ : B;
Number dominates Integer, but it doesn't dominate
Integer|String. I think you mean "if at least one of
pi..pn dominates *all* of the patterns ri..rm, no?
But I'm not even sure if this is the right formulation,
because:
sealed interface I permits A, B { }
record A() implements I {}
record B() implements I {}
case A _, B _: ...
case I i: ...
The first case label dominates I. So I think you have to
appeal to exhaustiveness:
"A case label with case patterns p1...pm dominates another
case label with case patterns q1...qm if the set of
patterns { p1..pm } dominates each qi", no?
You probably have to slightly refactor the second
statement about "compile time error if dominance" accordingly.
On 1/26/2023 5:36 AM, Angelos Bimpoudis wrote:
Dear experts,
The first draft of the JLS spec about unnamed patterns
and variables (https://openjdk.org/jeps/8294349) is
available at:
https://cr.openjdk.java.net/~abimpoudis/unnamed/latest/
Comments very much welcomed!
Angelos