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




Reply via email to