|
| 1 | +{%html: <div style="display: flex; justify-content:space-between"><div>%}{{!page-"driver"}< The Driver}{%html: </div><div>%}{{!"writing-ppxs"}Writing PPXs >}{%html: </div></div>%} |
| 2 | + |
| 3 | +{1:compat_mult_ver Compatibility With Multiple OCaml Versions} |
| 4 | + |
| 5 | +One of the important issues with working with the |
| 6 | +{{!Ppxlib.Parsetree}[Parsetree]} is that the API is not stable. For instance, in |
| 7 | +the {{:https://ocaml.org/releases/4.13.0}OCaml 4.13 release}, the following |
| 8 | +{{:https://github.com/ocaml/ocaml/pull/9584/files#diff-ebecf307cba2d756cc28f0ec614dfc57d3adc6946eb4faa9825eb25a92b2596d}two} |
| 9 | +{{:https://github.com/ocaml/ocaml/pull/10133/files#diff-ebecf307cba2d756cc28f0ec614dfc57d3adc6946eb4faa9825eb25a92b2596d}changes} |
| 10 | +were made to the {{!Ppxlib.Parsetree}[Parsetree]} type. Although they are small changes, they may |
| 11 | +break any PPX that is written to directly manipulate the (evolving) type. |
| 12 | + |
| 13 | +This instability causes a maintenance issue. PPX authors wish to maintain |
| 14 | +a single version of their PPX, not one per OCaml version, and ideally not have |
| 15 | +to update their code when seemingly irrelevant changes to the OCaml Parsetree happen. |
| 16 | + |
| 17 | +[ppxlib] helps to solve both issues. PPX authors only have to support |
| 18 | +ppxlib's version of the {{!Ppxlib.Parsetree}[Parsetree]}. The |
| 19 | +{{!page-driver.driver}driver} will take care of converting from OCaml's |
| 20 | +Parsetree to ppxlib's, applying all PPX transformations and then converting |
| 21 | +back to the compiler's Parsetree. This means PPX code will always only deal |
| 22 | +with the {{!Ppxlib.Parsetree}[Parsetree]} types as defined in ppxlib, regardless |
| 23 | +of the version of the compiler being used. |
| 24 | + |
| 25 | +Note that [ppxlib]'s {{!Ppxlib.Parsetree}[Parsetree]} is just a frozen version of |
| 26 | +the compiler's that we only update once in a while, instead of every minor |
| 27 | +compiler release, to limit breakage and maintenance burden. |
| 28 | +Such [ppxlib]'s AST bumps only happen on ppxlib major releases from |
| 29 | +[ppxlib.0.36.0] onward. |
| 30 | + |
| 31 | +Even when [ppxlib] does update its AST representation to a more recent one, |
| 32 | +less breakage is caused thanks to: |
| 33 | ++ its stable APIs to {{!page-"generating-code"}generate} and |
| 34 | + {{!page-"matching-code"}destruct} AST nodes |
| 35 | ++ the reduced API surface between a PPX and the [Parsetree] types provided |
| 36 | + by {{!page-"writing-ppxs".context_free}context-free transformations} |
| 37 | + |
| 38 | +{2:future_compilers Supporting latests language features} |
| 39 | + |
| 40 | +Since [ppxlib]'s AST remains frozen for a while, there are times at which it has |
| 41 | +to co-exist with newer compilers. |
| 42 | + |
| 43 | +Those newer compilers have different Parsetrees with new representations of |
| 44 | +existing AST nodes or even entirely new features that cannot be represented |
| 45 | +with older [Parsetree] types such as the ones [ppxlib] uses. |
| 46 | + |
| 47 | +To circumvent this limitation while still providing a certain amount of |
| 48 | +stability, [ppxlib] "encodes" those newer nodes into extension points with the |
| 49 | +[ppxlib.migration] namespace. When working with a newer compiler, the AST is |
| 50 | +migrated down to [ppxlib]'s version, encoding all unsupported nodes. Once the AST |
| 51 | +has been transformed, it is migrated back up to its original version and the |
| 52 | +nodes are automatically decoded back into their proper representation by the |
| 53 | +[ppxlib] {{!page-driver.driver}driver}. |
| 54 | + |
| 55 | +This approach allows not only to compile and preprocess old code with new |
| 56 | +compilers but also to use new language features alongside ppx-es. |
| 57 | + |
| 58 | +The only limitation is that using those features inside parts of the source code |
| 59 | +that are interpreted by a ppx can cause a failure as the ppx might try to |
| 60 | +interpret an encoded node and won't know what to do with it. We cannot reasonably |
| 61 | +expect an old ppx to properly handle a language feature it doesn't even know |
| 62 | +existed anyway. |
| 63 | + |
| 64 | +We do provide ways for ppx-es to build and destruct such encoded nodes so that |
| 65 | +a ppx author can easily update and add support for new features when it makes |
| 66 | +sense. {{!page-"generating-code".ast_builder}[Ast_builder]} functions can be |
| 67 | +used to generate AST nodes, eventually encoding them so they are properly |
| 68 | +migrated upward. |
| 69 | +Be aware that generating code this way will make it compatible only with |
| 70 | +compilers that support said feature. |
| 71 | +Conversely {{!page-"matching-code".ast_pattern}[Ast_pattern]} provides an |
| 72 | +interface for destructing any nodes, including ones that don't have a proper |
| 73 | +representation in [ppxlib]'s {{!Ppxlib.Parsetree}[Parsetree]}. |
| 74 | + |
| 75 | +{3 Example: labeled tuples} |
| 76 | + |
| 77 | +Let's take labeled tuples as an example to illustrate this. |
| 78 | + |
| 79 | +At the time of writing, [ppxlib]'s AST is frozen at 5.2. Labeled tuples have been |
| 80 | +introduced in 5.4, meaning a ppx-author won't be able to consume or produce a |
| 81 | +labeled tuple expression using the regular [Parsetree] types since the 5.2 |
| 82 | +version of the `Pexp_tuple` variant won't allow it, see for yourself: |
| 83 | + |
| 84 | +In 5.2's AST: |
| 85 | +{[ |
| 86 | + | Pexp_tuple of expression list |
| 87 | +]} |
| 88 | + |
| 89 | +In 5.4's AST: |
| 90 | +{[ |
| 91 | + | Pexp_tuple of (string option * expression) list |
| 92 | +]} |
| 93 | + |
| 94 | +If I want to generate a labeled tuple expression I can simply use |
| 95 | +{!Ppxlib.Ast_builder.Default.pexp_labeled_tuple}: |
| 96 | + |
| 97 | +{[ |
| 98 | +val pexp_labeled_tuple : |
| 99 | + ((string option * expression) list -> expression) with_loc |
| 100 | +]} |
| 101 | + |
| 102 | +Not much more to say here. |
| 103 | + |
| 104 | +Now imagine my ppx has the following code to expand expressions it receives |
| 105 | +as a payload: |
| 106 | +{[ |
| 107 | +let expand_expression expr = |
| 108 | + let loc = expr.pexp_loc in |
| 109 | + match expr with |
| 110 | + | { pexp_desc = Pexp_tuple elist; _ } -> expand_tuple ~loc elist |
| 111 | + | _ -> unsupported_payload_error ~loc () |
| 112 | +]} |
| 113 | + |
| 114 | +Now let's say I want to allow users to pass labeled tuples as well, I can |
| 115 | +support that thanks to {!Ppxlib.Ast_pattern.pexp_labeled_tuple}: |
| 116 | + |
| 117 | +{[ |
| 118 | +val pexp_labeled_tuple : |
| 119 | + ((string option * expression) list, 'a, 'b) t -> (expression, 'a, 'b) t |
| 120 | +]} |
| 121 | + |
| 122 | +I simply need to update my code like this: |
| 123 | +{[ |
| 124 | +let expand_expression expr = |
| 125 | + let loc = expr.pexp_loc in |
| 126 | + match expr with |
| 127 | + | { pexp_desc = Pexp_tuple elist; _ } -> expand_tuple ~loc elist |
| 128 | + | _ -> |
| 129 | + let labeled = |
| 130 | + Ast_pattern.(parse_res (pexp_labeled_tuple __)) pexp_loc expr |
| 131 | + (fun x -> x) |
| 132 | + in |
| 133 | + (match labeled with |
| 134 | + | Ok l_and_elist -> expand_labeled_tuple ~loc l_and_elist |
| 135 | + | Error _ -> unsupported_payload_error ~loc ()) |
| 136 | +]} |
| 137 | + |
| 138 | +{%html: <div style="display: flex; justify-content:space-between"><div>%}{{!page-"driver"}< The Driver}{%html: </div><div>%}{{!"writing-ppxs"}Writing PPXs >}{%html: </div></div>%} |
0 commit comments