6.4. The type of a catch binding
A catch binding is typed at the least common ancestor of the exception types
that can reach it: the types thrown directly in the try body, and the declared
throws types of the procedures the body calls. When those share a common ancestor
T, the binding has type T, and reading a field of e needs no downcast.
composite Exception {
var message: string
}
composite NotFound extends Exception {}
composite Invalid extends Exception {}
procedure logFailure(which: int) returns (out: string)
opaque
{
out := "";
try {
if which == 1 then {
var f: NotFound := new NotFound;
f#message := "missing";
throw f
};
var i: Invalid := new Invalid;
i#message := "invalid";
throw i
} catch e {
// `NotFound` and `Invalid` join at `Exception`, so `e` is an `Exception` and
// the inherited field is readable without a cast.
out := e#message
}
};
If the types reaching a catch share no common ancestor, Laurel reports an error
rather than leaving the binding untyped. If the body throws nothing determinable,
no exception can reach the clauses, and they are dropped as unreachable.
A frontend that needs to catch values with no useful common ancestor — unrelated types, or JavaScript's arbitrary thrown values — can box them: wrap the value in a composite field when throwing, and unwrap it in the handler.