Hello! Existing ?: behavior is certainly surprising and continuous source of puzzlers. E.g. the following was used in our latest Java Puzzlers NG S03 talk:
boolean x = false; System.out.println(x ? 42 : null); // prints null System.out.println(x ? 42 : x ? 42 : null); // NPE It's really hard to guess what will happen and even if you know, it's usually hard to explain. Such problems actually cause bugs in real code, I encountered some of them in my practice. Nevertheless I'm for consistent behavior (A or C). This would simplify the understanding of both constructs and also simplify the spec as ?: chapter may just explain the operator as syntactic sugar over switch(condition) { case true -> e1; case false -> e2; } referring to the switch expression chapter for the complete semantics explanation. With best regards, Tagir Valeev. On Thu, Mar 29, 2018 at 2:37 AM, Dan Smith <daniel.sm...@oracle.com> wrote: > (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