Feedback wanted: switch expression typing

Tagir Valeev amaembo at
Thu Mar 29 06:37:26 UTC 2018


Existing ?: behavior is certainly surprising and continuous source of
puzzlers. E.g. the following was used in our latest Java Puzzlers NG S03

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.smith at> 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:
> A javac built with this patch supports an option that will output
> diagnostics wherever conditionals at risk of incompatible change are
> detected:
> javac -XDlogPrimitiveConditionals
> 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

More information about the amber-spec-observers mailing list