Skip to content

Exception contexts#63

Merged
edsko merged 2 commits into
mainfrom
edsko/exception-contexts
May 22, 2026
Merged

Exception contexts#63
edsko merged 2 commits into
mainfrom
edsko/exception-contexts

Conversation

@edsko

@edsko edsko commented May 21, 2026

Copy link
Copy Markdown
Collaborator

Supersedes #60 .

@ruifengx In this PR I have taken the various ideas from your PR (#60), and combined them with some work I did another project, as well as applied all the various insights that I learned while trying to make sense of the situation regarding exception annotations (which led to my blog post Exception Annotations: Lay of the Land).

The technical details of the PR looks quite a bit different from yours, but its essence is still there. There is one important different in approach though. In your version of handleUnliftUsing, you were explicitly using catchNoPropagate and rethrowIO. Initially I did not understand why you opted for this, but then I realized that without that, simpleFold, foldWithHandler and foldTry would all add unnecessary WhileHandling annotations because they all rethrow the exception (unconditionally in simpleFold and when the cast fails in foldWithHandler and foldTry). And I agree: unnecessary WhileHandling annotations result in a lot of unnecessary noise, making backtraces more difficult to read than needed (for example, I submitted a patch against async a few days ago for the same reason).

However, if the exception handler itself throws a (different) exception, then not adding the WhileHandling exception means that we lose the original exception, and its annotations: that's bad. So we want some kind of combination of handle and onException; I tackled this problem by changing the type of the handler result: the handler can now distinguish between (1) returning a normal result, (2) throwing a new exception (which will get a WhileHandling annotaiton), or (3) indicating that the original exception should be rethrown (without a WhileHandling annotation).

The other changes are more minor:

  • I renamed your Rethrow newtype wrapper to ExactException; this is a pattern I recently starting adopting in grapesy, and I find it quite useful; it emphasises "this exact exception, and not some variant of it with annotations altered". The nice thing is that this can be defined almost without CPP and it will work across all GHC versions.
  • I no longer use SomeException anywhere anymore now; it's always ExactException, with throwExact as a helper to avoid mistakes. I consider throwing SomeException a red flag these days; in your version, you basically fixed throwIO instead, which also works, but I find this approach a bit more explicit.
  • For try I think the version from base is just fine; although catch adds a WhileHandling annotations to exceptions thrown by the exception handler, try never throws, so this does not matter.
  • This also means that the amount of CPP is now much less; there is very little anymore that is GHC dependent (though this critically depends on using ExactException).

Thanks for the fix for using freeHaskellFunPtr! (How did you even notice that?). I modified it slightly to use bracket instead.

Thanks again for your contribution :) We've added you to the list of contributors in https://github.com/well-typed/hs-bindgen/ . Let me know if anything in this PR looks wrong to you!

@edsko
edsko marked this pull request as draft May 21, 2026 10:33
@edsko
edsko force-pushed the edsko/exception-contexts branch 6 times, most recently from 73925cd to 55ed5db Compare May 21, 2026 12:06
edsko and others added 2 commits May 21, 2026 14:22
Co-authored-by: Ruifeng Xie <ruifengx@outlook.com>
Co-authored-by: Edsko de Vries <edsko@well-typed.com>
@edsko
edsko force-pushed the edsko/exception-contexts branch from 55ed5db to 6040c53 Compare May 21, 2026 12:22
@edsko
edsko marked this pull request as ready for review May 21, 2026 12:28
@edsko
edsko added this pull request to the merge queue May 22, 2026
Merged via the queue into main with commit 51b4b49 May 22, 2026
10 checks passed
@edsko
edsko deleted the edsko/exception-contexts branch May 22, 2026 05:50
@ruifengx

ruifengx commented Jun 1, 2026

Copy link
Copy Markdown
Contributor

Glad to see this resolved! I saw your blog post (before this PR actually) and really enjoyed the read.

You are absolutely right that my PR did not correctly handle the case where the exception handler throws (in that the WhileHandling annotation is lost). I completely forgot about this,, because In my use case, I just let the exceptions bubble up.

Regarding ExactException and throwExact, that for sure is an interesting idea. I have the feeling that it might be a good addition to base, serving as an optimised version of ExceptionWithContext SomeException.

Regarding try, I also believe that Control.Exception.try should just work, but somehow when I tested locally I still saw spurious WhileHandling annotations. Since it works correctly in this PR, I must have messed up something when I was experimenting with the fix.

I will close my original PR, given that the fix already lands in the repo. Thanks again for working on binding generation.


P.S. I had several attempts on a Haskell binding generation tool. It worked for some toy examples, but it never reached the maturity and generality I expect. I guess all that efforts were not futile in the end anyway, because at least I learned about freeHaskellFunPtr and helped on that matter here. 😄


As a side note, now that the clang_visitChildren API does not leak FunPtrs any more, I actually prefer to stick with traversals with CXChild_Continue only (and CXChild_Break when necessary). To recurse into a child cursor, I just start a new clang_visitChildren. This seems to be comparable in efficiency, with the extra benefit that recursive visits are actually properly scoped, and exception handlers (and e.g., effect handlers from effectful) can be attached in the usual way.

@edsko

edsko commented Jun 19, 2026

Copy link
Copy Markdown
Collaborator Author

As a side note, now that the clang_visitChildren API does not leak FunPtrs any more, I actually prefer to stick with traversals with CXChild_Continue only (and CXChild_Break when necessary). To recurse into a child cursor, I just start a new clang_visitChildren.

Ah, that's how you noticed it was leaking! Makes sense now :) And yes, that's definitely a reasonable approach, the exception infra is definitely complicated.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants