How do I output a non-polymorphic variant using ppxlib Ast_builder?

63 views Asked by At

I am trying to make a PPX extension rewriter that dynamically builds (among other things) a variant type, based on some config in a JSON file...

I have got reasonably far but I am confused whether it is possible to output a non-polymorphic variant using ppxlib Ast_builder, and if so how.

I have a function like this:

let variant_of_defs ~loc defs = 
  let member_of_str name =
    {
      prf_desc = Rtag ({txt = name; loc}, true, []);
      prf_loc = loc;
      prf_attributes = [];
    }
  in
  Ast_builder.Default.ptyp_variant ~loc
    (List.map (fun (_, (def : Loader.t)) -> member_of_str def.name) defs)
    Ppxlib.Closed
    None

Where defs is essentially a list of records I have parsed from the JSON file.

I'm outputting a module that has the new variant type something like:

Ast_builder.Default.pmod_structure ~loc [[%stri type t = [%t variant_of_defs ~loc defs]]]

The code basically works when I try it in utop, e.g.:

utop # module MyPalette = [%palette "colors.json"];;
module MyPalette :
  sig
    type t =
        [ `Aqua
        | `Aquamarine1
        | `Aquamarine3
        | `Black
        | `Blue
        | `Blue1
        | `Blue3
  end

...but it has given me a polymorphic variant, i.e. all the variant members are prefixed with a backtick

Is this just a limitation of the Ast_builder.Default.ptyp_variant helper function? Or is there a way to make it give me a regular variant?

I have a dump of the AST from a hand-written example code so if necessary I can do things the long way, but I'd like to keep the code as concise as possible, i.e. using the Ast_builder helpers.

1

There are 1 answers

0
Anentropic On

To summarise:

  • yes it looks like Ast_builder.Default.ptyp_variant can only make polymorphic variants
  • I had seen the Ptype_variant variant appearing in the dumped AST for my manually written example module and assumed that the similarly-named ptyp_variant helper would produce that type, but they are not so closely related.

Thanks to the suggestion from @glennsl to use the Ast_builder.Default.pstr_type helper instead I found the following recipe for outputting a non-polymorphic variant:

let variant_of_defs ~loc defs = 
  let constructor name =
    (* one member of the variant *)
    {
      pcd_name = {txt = name; loc};
      pcd_args = Pcstr_tuple [];
      pcd_res = None;
      pcd_loc = loc;
      pcd_attributes = [];
    }
  in
  Ast_builder.Default.pstr_type ~loc Recursive [
    Ast_builder.Default.type_declaration
      ~loc
      ~name: {txt = "t"; loc}
      ~params: []
      ~cstrs: []
      ~kind: (Ptype_variant (List.map (fun (_, (def : Loader.t)) -> constructor def.name) defs))
      ~private_: Public
      ~manifest: None;
  ]

In my original attempt I inserted the type t = <variant> declaration into my generated module using metaquot via [%stri type t = [%t variant_of_defs ~loc defs]] ... i.e. the variant was the rhs of a type t = <variant> declaration.

Since there is no non-polymorphic variant helper in Ast_builder we have to generate the whole type t = <variant> declaration using the pstr_type helper instead.

This means that the new function returns a structure_item and we can use it directly when generating the module, i.e.:

Ast_builder.Default.pmod_structure ~loc [variant_of_defs ~loc defs;]