Feedback wanted: switch expression typing

(Looking for some feedback on real-world code usage. Please read to the end, then if you can experiment with the code you work on and report back, I'd appreciate it!) Switch expressions, from a type checking perspective, are basically generalizations of conditional expressions: instead of 2 operands to check, we have n. A reasonable expectation is that, if I rewrite my conditional expression as a switch expression, it will behave the same: test ? foo() : bar() is equivalent to switch (test) { case true -> foo(); case false -> bar(); } So, as a starting point, the typing rules for switches should be the same as the typing rules for conditionals, but generalized to an arbitrary number of results. (The "results" of a switch expression are all expressions appearing after a '->' or a 'break'.) Conditional expressions and switch expressions are typically used as poly expressions (in a context that has a target type). But that won't always be the case. One notable usage that doesn't have a target type is an initializer for 'var': "var x = ...". So they are sometimes poly expressions, sometimes standalone. Conditional expression typing is driven by an ad hoc categorization scheme which looks at the result expressions and tries to predict whether they will all have type boolean/Boolean, primitive/boxed number, or something else/a mix ("tries to predict" because in some cases we can't type-check the expression until we've completed the categorization). In the numeric case, we then identify the narrowest primitive type that can contain the results. In the other/mixed case, we then type check by pushing down a target type, or, if none is available, producing a reference type from the lub operation. A couple of observations: - The primitive vs. reference choice is meaningful, because the primitive and reference type hierarchies are different (e.g., int can be widened to long, but Integer can't be widened to Long). Preferring primitive typing where possible seems like the right choice. - The ad hoc categorization is a bit of a mess. It's complex and imperfect. What people probably expect is that, where a target type is available, that's what the compiler will use—but the compiler ignores the target type in the primitive cases. Why? Well, in 8, when we introduced target typing of conditionals, we identified some incompatibilities that would occur if we changed the handing of primitives, and we didn't want to be disruptive. Some examples: Boolean x = test ? z : zbox; // specified: can NPE; target typing: no null check Integer x = test ? s : i; // specified: ok; target typing: can't convert short->Integer Number x = test ? s : i; // specified: box to Integer; target typing: box to Short or Integer double d = test ? l : f; // specified: long->float loses precision; target typing: long->double better precision m(test ? z : zbox); // specified: prefers m(boolean); target typing: m(boolean) and m(Boolean) are ambiguous At this point, we've got a choice: A) Fully mimic the conditional behavior in switch expressions B) Do target typing (when available) for all switch expressions, diverging from conditionals C) Do target typing (when available) for all switches and conditionals, accepting the incompatibilities (A) sacrifices simplicity. (B) sacrifices consistency. (C) sacrifices compatibility. General thoughts on simplicity (is the current behavior hard to understand?) and consistency (is it bad if the conditional/switch refactoring leads to subtly different typing?) are welcome. And we could use some clarification is just how significant the compatibility costs of (C) are. With that in mind, here's a javac patch: http://cr.openjdk.java.net/~dlsmith/logPrimitiveConditionals.patch A javac built with this patch supports an option that will output diagnostics wherever conditionals at risk of incompatible change are detected: javac -XDlogPrimitiveConditionals Foo.java If you're able to build OpenJDK with this patch and run it on some real-world code, I'd appreciate any insights about what you find. —Dan