Open Kha opened 1 week ago
that they in fact are not caught by
CoreM
try-catch by default (but are inCommandElabM
, unclear to me whether that inconsistency should exist)
I have spent a good amount of time debugging an issue caused by this once. In that situation, I expected the CoreM
try-catch to catch all exceptions and was surprised to find out it did not. The other way to ensure consistency would be to also skip runtime exceptions in CommandElabM
try-catch, but I would personally prefer that they were always caught everywhere, and there was a separate catchEverythingButRuntimeAndInternal
construct.
I think the main reason for not catching some exceptions by default is that it is just too easy to unintentionally misuse try-catch otherwise. If there is no reasonable default, it may be better just not to implement MonadExcept
and provide only specifically named combinators. Out of interest, would it have helped you if the try-catch types would have been more clear about its behavior, e.g. if we had separate MonadExcept
s for internal exceptions and errors?
Out of interest, would it have helped you if the try-catch types would have been more clear about its behavior, e.g. if we had separate
MonadExcept
s for internal exceptions and errors?
My mistaken mental model was that try-catch
catches everything. I suppose anything that makes it clear that that's not the case would have prevented the confusion. Out of the various possible clarifications, ones that encode that in the type system rather than just writing a docstring are probably preferrable since not many people will read the docstring for a well-understood construct such as exception handling.
The
Lean.Exception
type used during elaboration comes in two flavors: errors and internal exceptionsAt the time of writing, the internal exceptions are
As documented, internal exceptions are mainly about visibility. Note however that this usage is not enforced by the API: as
try catch
etc catch anyException
, it is very easy to accidentally catch internal exceptions as well and e.g. add them to the log (where they will be printed as "internal exception: ...").While
error
s can/should generally only be handled generically such as by putting them into the log, internal exceptions give an easy and efficient way to be handled case by case via the exception ID. An interesting question is whether they in turn should never be handled in a generic way; some names likeabort*
certainly imply that they should never be caught except by the specific code handling them, but for others we should make sure there are no cases where e.g. generic backtracking logic is supposed to catch them (very quick review:Term.observing
and Elab.App make sure not to backtrack on generic internal exceptions but Elab.Match does not). If this is indeed the expectation, it would be good to ensure this in the API by leaving internal exception catching tocatchInternalId(s)
.To complicate matters, there is one more kind of exception used in the code: "runtime exceptions", which currently consist of heartbeat and max rec depth exceptions (see
Exception.isRuntime
). They are encoded aserror
s but a comment mentions this is mostly because of module dependencies. On the one hand, they are handled individually in some cases so having an internal exception ID would make sense; on the other hand, they are clearly user-visible. So it might be the case that this is better handled by givingerror
s an ID as well which we will want at some point for purposes of an error index.One special handling of runtime exceptions is that they in fact are not caught by
CoreM
try-catch by default (but are inCommandElabM
, unclear to me whether that inconsistency should exist). This behavior can be turned off with a flag but it turns out that both uses of this flag follow the exact same pattern:So as a first refactoring, the flag could be replaced with a new combinator, changing the code to
For context, this all came up because in #3940, I fixed elab interruption (
checkInterrupted
) to never be caught inside the elaborator, which raised the questions above for the existing exceptions.In summary, possible refactoring steps to be done here:
with(out)CatchingRuntimeEx
with newtryCatchRuntimeEx
combinator