Relaxed assignment conversions for sealed types

Brian Goetz brian.goetz at oracle.com
Tue Nov 24 15:24:22 UTC 2020


> To throw in the mix - how is some kind of pattern match assignment (we 
> referred to as a "let expression" in some of the earlier docs [1]) 
> would change the picture here? In other words, maybe it's overloading 
> `=` which is at odds here, and we need to make it more explicit that 
> this is more akin to an extraction/match?
>

So, I think "let statements" beat "total casts" to the overload here :)

<background>

Originally, we imagined a "let" statement like:

     let P = e [ else ... ]

where P is an optimistically total pattern.

<digression>

We pulled on the "what can you do in the else, besides throw" string for 
a while, but didn't love where it ended up.  The obvious thing you might 
want to do besides throw or return, is to assign to the pattern 
variables and keep going:

     let Point(var x, var y)
     else { x = 0; y = 0; }

This was messy when pattern variables were implicitly final, but now 
that we backed away from that siren song (whew), these become simple 
assignments to DU locals.

</digression>

But over time, the `let` started to rankle, not only as noise, but 
because it limited the usefulness.  The observation is that (again, now 
that we backed away from the siren song of pattern variables being final):

     Point p = <expression>

could either be the declaration of a local of type Point, _or_ a pattern 
match to the total pattern `Point p`.  In other words, what we 
historically  thought of as local variable declaration, could in fact be 
generalized to total pattern matching.  Not only is this aesthetically 
pleasing, but it means we can extend this idiom in two dimensions: other 
patterns, and other places.

The "other patterns" is straightforward enough:

     Point(var x, var y) = p

The only challenge here is what to do with the remainder, but we've done 
enough work on characterizing the remainder to be comfortable enough 
with throwing in those cases (usually NPE.)

The "other places" takes slightly longer to get used to, such as method 
declarations:

     void foo(Point(var x, var y) p, int z) { ... }

which is equivalent to:

     void foo(Point p, int z) { Point(var x, var y) = p; ... }

and a similar possibility with lambdas:

     (Point(var x, var y) p) -> ...

All of this is to say that (a) the "let" statement structure was getting 
in the way, and (b) there's a generalization of local declaration 
struggling to get out here, and (c) pattern totality is the key to it.

</background>

Given all this, it would be justifiable to not require the cast at all!  
The pattern `BarImpl b` is total on `Bar` (assuming Bar is abstract and 
sealed only to BarImpl), by virtue of the rules for totality that are 
outlined in [1].  So by this interpretation of "local declaration 
generalizes to total pattern matching", we could just say:

     BarImpl b = bar;

and be done, since the expression `bar` is of type Bar and the pattern 
`BarImpl b` is optimistically total on Bar.  (If the sealing changes 
between compile and run time, this could throw CCE.)

But some people might find that uncomfortable, which is how we got here; 
they want the optimism to be explicit.  (Backing off to "let" seems a 
strict loser compared to "total cast", since total cast has much more 
leverage for the same incremental syntactic footprint (and requiring let 
where things are more obvious will be seen as noise, and the 
statement-ness of `let` gets in the way of using it in more places.))  
If this is too aggressive, we would want to limit the `P = e` form to 
strict totality, or more likely, totality with only { null } remainder 
(as this would permit using deconstruction patterns on the LHS.)

The blind cast is surely allowed, but its not something we want to 
encourage -- and as both you and I have found (perhaps the only people 
who have yet written significant libraries with sealing?), it is going 
to be almost irresistible to avoid embedding the blind-cast assumptions 
in your code.

So this is how I got to a "blind cast with assertion", not unlike an 
@Override assertion -- cast, but raise a compile-time error if the cast 
is not optimistically total.  Instanceof and switch learned about 
totality; perhaps casting should too.







[1] 
https://github.com/openjdk/amber-docs/blob/master/site/design-notes/type-patterns-in-switch.md



More information about the amber-spec-experts mailing list