Compile-time type hierarchy information in pattern switch

Peter Levart peter.levart at
Thu Apr 5 14:40:28 UTC 2018


On 04/04/2018 07:07 PM, Brian Goetz wrote:
> The intended implementation strategy is to lower complex switches to 
> densely-numbered `int` switches, and then invoke a classifier function 
> that takes a target and returns the int corresponding to the lowered 
> case number.  The classifier function will be an `invokedynamic`, 
> whose static bootstrap will contain a summary of the patterns.  (We've 
> already done this for switches on strings, enums, longs, non-dense 
> ints, etc.)
> To deliver an early error, that means that (a) the compiler must 
> encode through the static argument list all the assumptions it needs 
> verified at runtime (e.g., `String <: Object`), and (b) at linkage 
> time (the first time the switch is executed), those have to be tested.
> Doing so is plenty easy, but there's a startup cost, which could be as 
> bad as _O(n^2)_, if I have to validate that no two case labels are 
> ordered inconsistently with subtyping.

Not necessarily. O(n log n) at worst for stable-sorting n cases which, 
if already sorted in compile time (i.e. no subtype changes between 
compile and link time), are resorted using just n-1 comparisons.

That's if you want to "fix" the order of cases at link-time in order to 
compute optimal dispatch logic. If you only want to verify and bail-out 
if they are not sorted already (i.e. you only accept changes in type 
hierarchy that don't change order of cases), you always need just n-1 

The question is whether you only want to re-order / check-order 
according to type hierarchy or also according to other aspects of 
"dominance", for example:

case Point p where (p.x >= 0 && p.y >= 0):
case Point p where (p.x >= 0):

Other aspects of dominance usually don't change between compile and link 
time, so stable-sorting cases could take just type hierarchy into 
account, unless you also allow type-hierarchy based conditions in where 
patterns, for example:

case Holder h where (h.value instanceof TypeA):
case Holder h where (h.value instanceof TypeB):

Another problem with re-ordering cases at link time is when you support 
fall-through. What are fall-through(s) in a switch with re-ordered 
cases? For example:

interface A {}
interface B extends A {}

switch (x) {
     case B b:
         // fall-through...
     case A a:
         A ab = ... ? a : b;


What happens when you remove A from supertypes of B in a separately 
compiled code:

interface A {}
interface B {}

Perhaps there's no need to worry about this as verifier would already 
catch such invalid code during runtime. So fall-through(s) could just 
stay the same even if cases are virtually reordered for the purpose of 
computing dispatch logic. The fall-through logic could sometimes survive 
changes in type hierarchy unnoticed by verifier but would give 
questionable results when executed. But that could be said for any 
logic, not necessarily concerned with switch statements.

Here's some experiment I played with that clearly separates 
compile-time, link-time and run-time parts of logic and is just API. You 
can even simulate the effects of adding subtype relationship(s) between 
compile-time of switch and link-time:

Regards, Peter

> A possible mitigation is to do the check as a system assertion, which 
> only gets run if we are run with `-esa`; we then might still have some 
> static code bloat (depending on how we encode the assumptions), but at 
> least skip the dynamic check most of the time.
> On 4/4/2018 1:01 PM, Mark Raynsford wrote:
>> I'm still giving thought to everything you've written, but I am
>> wondering: How feasible is it to get the above to fail early with an
>> informative exception/Error?

More information about the amber-spec-observers mailing list