Enforcing order of ppx rewrites

When building with dune, is there a way to enforce the order of ppx rewrites?

For example, if I have a ppx that contains a type definition, and there’s a deriving clause attached to the type definition, I want to be sure the containing ppx is applied, and next the deriver is applied.

3 Likes

staged_pps may be what you need.

I don’t think I need staged_pps, really.

I’m hoping that ppxs are just applied in left-to-right order, maybe that’s enough.

I faced the same issue and no solution yet.

I created a demo project. https://github.com/Kakadu/dune-demo-two-ppxes I will be glad to see some advice about it.

1 Like

Dune simply link the various ppx rewriters in the order they are specified in the dune file. It’s up to the underlying driver mechanism to decide whether to take this order into account or not.

They are currently two drivers out there:

  • the ocaml-migrate-parsetree one
  • the ppxlib one

The ppxlib one is built on top of the ocaml-migrate-parsetree one, and in particular all transformations registered with ppxlib appear as a single transformation registered with ocaml-migrate-parsetree.

ocaml-migrate-parsetree only accepts whole file transofmations, i.e. ast -> ast functions. These are ordered by the version of the OCaml AST they use in order to minimise the number of Ast upgrade/downgrade. It is however possible to attach a priority to a particular transformation to force its position in the pipeline.

ppxlib accepts two kinds of transformations, whole file ones just like ocaml-migrate-parsetree and more high-level and well defined rule that can be merged together. A typical rule is to rewrite a particular extension point. All rules are merged together and applied in a single pass, which ensures both good performances and good semantic. Indeed, the output of individual rules is rewritten recursively, which ensures that if the expansion of a particular extension point produces more extension points or even [@@deriving] attributes, these are properly expanded no matter the order in which the ppx rewriters were specified in the dune file.

In general, trying to reason about the order of whole file transformations is tedious. For authors of ppx rewriters, you don’t know what other ppx rewriters the user is going to use in combination with your own ppx rewriter and how they will all interact with each others. For users of ppx rewriters, they usually don’t know the low-level implementation details of each ppx rewriters and how they should be ordered. That’s why in ppxlib we made the design choice that the overall rewriting is always completely independent of the order in which the ppx rewriters are specified.

In conclusion, in today’s world you cannot reliably enforce a particular order. I would suggest to describe your use case more in detail so that we can see how it can fit in the current world or how we can extend the world to make it work.

There are three ppxs we’d like to use:

  • ppx1 : traverses the AST, and makes sure that invocations of ppx2 do not occur in particular syntactic positions, and that “deriving bin_io” and another deriving target do not occur anywhere in the original AST; fail if these properties do not hold
  • ppx2 : rewrites some types to include “deriving bin_io” and the other deriving target
  • ppx3 : rewrites based on the other deriving target

So if ppx2 were applied before ppx1, for example, compilation will fail unnecessarily.