I've reviewed the draft and put together a few patches, but here are a few more 
stray bits of feedback from me, most of which get into concepts and design:

---

5.5: There's this "downcast compatible" notion that was apparently introduced 
with instanceof pattern matching. It means a cast is legal and not unchecked. 
Ok. But—not all legal casts are downcasts, right? It's pretty confusing to use 
a term that suggests that they are.

jshell> Serializable ser = "abc"
ser ==> "abc"

jshell> if (ser instanceof Comparable<?> c) { System.out.println("matches");}
matches

---

6.3.3.1: Maybe someday we'll have expressions nested in patterns, but for now 
isn't it meaningless to ask whether a pattern variable is in scope within 
another part of the same pattern?

---

13.4.2: I don't know if I'd talk about "migration compatibility" here. It's a 
rather common thing for a separately-compiled change to cause a runtime error 
that is not a linkage error. But previously the phrase "migration compatible" 
doesn't get mentioned in Chapter 13, and we don't go out of our way to label 
these conditions. It's just... a runtime exception.

---

14.11.1: I'm not sure the prohibition of 'when false' makes sense. We don't 
complain about unreachable code in 'if (false) { ... }', for example, or 
consider variables touched inside the 'if' to be DU.

A possible use case is to turn a rule on or off based on a constant somewhere, 
or just by directly editing the code during a debugging session.

---

14.11.1: The compatibility rules are specified as "for all" rules, which means 
you can do some surprising things when they're vacuously true...

double d = 23.0;
switch (d) { default -> System.out.println("ok"); } // no error

(This is an "enhanced switch", so requires a default to be exhaustive.)

This seems okay to me—Java doesn't prevent all stupid programs—but worth a 
sanity check. And we should be careful not to make assertions that claim such 
switches can't be written.

---

14.11.1: A couple of dominance design notes:

- Claiming that a guarded type pattern dominates a constant seems 
overly-nannying to me. Usually, these sorts of errors occur because the 
compiler can *prove* you've done something stupid (e.g., "this cast will always 
fail"). Here, the compiler is assuming the switch rule is stupid unless the 
programmer can prove it's not, to the compiler's satisfaction. And the compiler 
isn't very smart! Suggestion: just leave guarded type patterns out, they don't 
dominate anything else.

- It's weird that 'null' is the one case constant that can't come after a 
'default', given that 'null' is the one value that can't be handled by a 
'default'. :-) Since we have to allow other case constants after 'default' 
anyway, I think it's best to allow 'null' as one more. ("default dominates 
everything except constants" is a rule I can remember.)

(Also note—and I think this is good—that the dominance rules for 'default' 
don't care whether this is an "enhanced switch" or not. The fewer things that 
depend on enhanced vs. traditional, the better.)

---

14.11.1: I don't think we prevent this scenario:

switch (obj) { case String s: case Object o: }

The rules about duplicate pattern labels apply to "a statement is labeled...". 
There's no labeled statement here.

I think we should probably prohibit this, for the same code hygiene reason that 
we prohibit it before a statement group that doesn't try to use any of the 
variables.

(This would not be a legal switch expression, because switch expressions can't 
fall out. But enhanced switch statements can, as long as they're exhaustive.)

---

14.11.2: Design suggestion: rename "enhanced switch" to "pattern switch", 
define it as only those switches that make use of patterns, and don't worry 
about the remaining "switch with new features" corner cases. It's just such an 
important concept that I think there's a benefit to making the distinction 
really clean and obvious. E.g., asking someone new to Java to memorize the ad 
hoc set of types that don't demand exhaustiveness seems unhelpfully complicated.

(Corner cases I'm thinking about: want to use a null constant but not be 
exhaustive? Fine. Want to have an Object input type but an empty switch body? 
Pointless, but fine. Etc.)

---

14.11.3, 15.28.2: Opinionated take: we're continuing to throw ICCE when an 
unmatched enum constant slips through a switch, because it would be a 
(behaviorally) incompatible change to throw something else. Meanwhile, 
unmatched sealed classes get a MatchException. I think there are a tiny number 
of people who would notice if we changed from ICCE to MatchException for enums 
too, and a lot more people who will have to cope with the historical baggage 
going forward if we don't. We should just standardize on MatchException.

Further complication: as written, a sealed-interface switch that doesn't 
mention any enum constants will still throw if the value that gets through 
happens to be an enum constant.

switch (sealedThing) {
    case Foo f -> {}
    case Bar b -> {}
    // an enum class gets added as a permitted subclass: ICCE
    // a regular class gets added as a permitted subclass: ME
}

We could specify this differently, but I'm not sure there's a bright line that 
will be intuitive.

---

14.14.2: I'm sure there's been some discussion about this already, but the use 
of MatchException for nulls in 'for' loops but NPE for nulls in switches also 
seems like a sad historical wart. What's wrong with an NPE from a 'for' loop?

---

14.30.1: Parenthesized patterns are no fun for a spec writer. :-) Are they 
actually useful? I'm not sure I've seen an example demonstrating what they're 
for. (The JEP only talks about them abstractly.)

Followup: would it make sense for a 'for' loop to permit a parenthesized 
pattern?

---

14.30.1, 14.30.2: I'm not sold on *any patterns*, *resolved patterns*, and 
*executable switch blocks*.

The semantics are fine—some type patterns will match null, others will not. But 
it seems to me that this can be a property of the type pattern, one of many 
properties of programs that we determine at compile time. No need to frame it 
as rewriting a program into something else. (Compare the handling of the '+' 
operator. We don't rewrite to a non-denotable "concatenation expression".)

Concretely:
- The pattern matching runtime rules can just say "the null reference matches a 
type pattern if the type pattern is unconditional".
- We can make it a little more clear that a type pattern is determined to be 
unconditional, or not, based on its context-dependent match type (is that what 
we call it?)

For a *compiler*, it will be useful to come up with an encoding that preserves 
the compile-time "unconditional" property in bytecode. But that's a compiler 
problem, not something JLS needs to comment on.

---

14.30.3: To my ear (maybe this is an American thing?), "applicable at" sounds 
wrong, and it would read more naturally to say "applicable for" or "applicable 
to". Same for "unconditional at".

---

14.30.3: A record pattern can't match null, but for the purpose of dominance, 
it's not clear to me why a record pattern can't be considered unconditional, 
and thus dominate the equivalent type pattern or a 'default'.

switch (point) {
    case Point(int x, int y) -> {}
    case Point p -> {} // unreachable
    default -> {} // unreachable
}

Reply via email to