Here's some candidate spec text for MatchException:

Prototype spec for MatchException ( a preview API class ).

Thrown to indicate an unexpected failure in pattern matching.

MatchException may be thrown when an exhaustive pattern matching language construct (such as a switch expression) encounters a match target that does not match any of the provided patterns at runtime.  This can arise from a number of cases:

 - Separate compilation anomalies, where a sealed interface has a different set of permitted subtypes at runtime than it had at compilation time, an enum has a different set of constants at runtime than it had at compilation time, or the type hierarchy has changed in incompatible ways between compile time and run time.  - Null targets and sealed types.  If an interface or abstract class `C` is sealed to permit `A` and `B`, then the set of record patterns `R(A a)` and `R(B b)` are exhaustive on a record `R` whose sole component is of type `C`, but neither of these patterns will match `new R(null)`.  - Null targets and nested record patterns.  Given a record type `R` whose sole component is `S`, which in turn is a record whose sole component is `String`, then the nested record pattern `R(S(String s))` will not match `new R(null)`.

Match failures arising from unexpected inputs will generally throw `MatchException` only after all patterns have been tried; even if `R(S(String s))` does not match `new R(null)`, a later pattern (such as `R r`) may still match the target.

MatchException may also be thrown when operations performed as part of pattern matching throw an unexpected exception.  For example, pattern matching may cause methods such as record component accessors to be implicitly invoked in order to extract pattern bindings.  If these methods throw an exception, execution of the pattern matching construct may fail with `MatchException`.



On 3/30/2022 2:43 PM, Dan Heidinga wrote:
On Wed, Mar 30, 2022 at 2:38 PM Brian Goetz<brian.go...@oracle.com>  wrote:
Another way to think about this is:

  - If any of the code that the user actually wrote (the RHS of case clauses, 
or guards on case labels) throws, then the switch throws that
  - If any of the machinery of the switch dispatch throws, it throws 
MatchException.

That's a reasonable way to factor this and makes the difference
between the machinery and the direct user code clear, even when
looking at stacktraces.

And from your other response:

Another thing it gains is that it discourages people
from thinking they can use exceptions in dtors; having these laundered
through MatchException discourages using this as a side channel, though
that's a more minor thing.
This is a stronger argument than you give it credit for being.
Wrapping the exception adds a bit of friction to doing the wrong thing
which will pay off in helping guide users to the intended behaviour.

--Dan

On 3/30/2022 2:12 PM, Dan Heidinga wrote:

The rules regarding NPE, ICCE and MatchException look reasonable to me.


As a separate but not-separate exception problem, we have to deal with at least 
two additional sources of exceptions:

  - A dtor / record acessor may throw an arbitrary exception in the course of 
evaluating whether a case matches.

  - User code in the switch may throw an arbitrary exception.

For the latter, this has always been handled by having the switch terminate 
abruptly with the same exception, and we should continue to do this.

For the former, we surely do not want to swallow this exception (such an 
exception indicates a bug).  The choices here are to treat this the same way we 
do with user code, throwing it out of the switch, or to wrap with 
MatchException.

I prefer the latter -- wrapping with MatchException -- because the exception is thrown 
from synthetic code between the user code and the ultimate thrower, which means the 
pattern matching feature is mediating access to the thrower.  I think we should handle 
this as "if a pattern invoked from pattern matching completes abruptly by throwing 
X, pattern matching completes abruptly with MatchException", because the specific X 
is not a detail we want the user to bind to.  (We don't want them to bind to anything, 
but if they do, we want them to bind to the logical action, not the implementation 
details.)

My intuition (and maybe I have the wrong mental model?) is that the
pattern matching calling a user written dtor / record accessor is akin
to calling a method.  We don't wrap the exceptions thrown by methods
apart from some very narrow cases (ie: reflection), and I thought part
of reflection's behaviour was related to needing to ensure exceptions
(particularly checked ones) were converted to something explicitly
handled by the caller.

If the dtor / record accessor can declare they throw checked
exceptions, then I can kind of see the rationale for wrapping them.
Otherwise, it seems clearer to me to let them be thrown without
wrapping.

I don't think we expect users to explicitly handle MatchException when
using pattern matching so what does wrapping gain us here?

--Dan


Reply via email to