[ANN] ppx_let_locs - improve stack traces

A common problem that I personally had when developing async code in OCaml is that both Lwt and Async leads to useless stack traces. When using lwt_ppx that is handled by the ppx, but the same is not possible with let*, let.await or >>=.

If you want to solve that, you can use ppx_let_locs, it will detect your calls and when there is a replacement function available that would allow stack traces to be enhanced it will enhance that by default for you.

How it works

Everytime an ident is applied it tries to find a backtrace_ident or ident_backtrace version of it, if that is present and it has the right signature, the PPX will then apply the backtraced one with the additional argument. This means it also works with operators, like >>=_backtrace, as they’re just normal function application.

For letop there is a special case, the signature of the letop must be slightly different to ensure that the typing order was not changed, essentially the let*_backtrace must have the following signature (exn -> exn * 'a) -> 'b -> 'c.

To achieve that this is actually a typed PPX, which runs a slightly patched version of the OCaml typechecker, then untype it and pass to the OCaml compiler. This implies a couple of things:

  • it needs to be executed as (staged_pps ppx_let_locs) on dune
  • versions must be explicitly supported, for now only 4.10 and 4.12 are supported, if you need any other version feel free to request.
  • it can be quite slow on some codebases

To solve the above problems, an important property is that, assuming the backtraced version behaves identically to the non backtraced one, it is in theory a noop, as removing or adding the PPX should not change the behavior of the code. So it can be used only for development or for production, as adding or removing the PPX will not break your code.

Async

Currently it seems like Async doesn’t provide anything similar to Lwt.backtrace_bind and Async.try_with also looses the stack trace, so I would love help in this area.

Examples

from:

to:

As you can see, on the second stack trace you can find out that it was actually being called on test_interpretation.ml.

16 Likes