1

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.

Anentropic
  • 32,188
  • 12
  • 99
  • 147
  • 1
    I believe the `ptyp_*` functions create type expressions, and `[%stri type t = ...]` create a type abbreviation (i.e. alias) for that type expression. What you actually want is a type declaration, which is a kind of structure item. `pstr_type` should be a good place to start. – glennsl Sep 24 '22 at 22:18
  • @glennsl If I look at an example AST from `ppx_tools/dumpast` it seems like `Pstr_type` is equivalent to the whole `type t = One | Two | Three` declaration, with the rhs being a `Ptype_variant`. For that reason I thought that `type t = [%t variant_of_defs]` was doing the right thing ... and it seems to be, except the generated variant is polymorphic – Anentropic Sep 24 '22 at 22:49
  • I was assuming `[%stri ...]` can be used to fill in the members of the `= struct ... end` block of the module declaration – Anentropic Sep 24 '22 at 22:51
  • @glennsl thanks very much - you were totally right! (I was kind of right too... seems like `Ast_builder.Default.ptyp_variant` only produces polymorphic variants, and [`Ptype_variant`](https://ocaml-ppx.github.io/ppxlib/ppxlib/Ppxlib/index.html#type-type_kind) is not so closely related but is part of the recipe for outputting a variant from the Ast_builder `pstr_type` helper) – Anentropic Sep 24 '22 at 23:20
  • Np :) It's important to understand the difference between type declarations, which are structure items and can only occur there, and type expressions (or "core type"s) which can occur as a type annotation pretty much anywhere after a `:`, or on the rhs of a type alias (or "type abbreviation"), which is what you created here. – glennsl Sep 25 '22 at 08:35
  • Also, feel free to answer your own question with the actual code needed to create an ordinary variant type and your own wording of my comments. I think many people would find that useful in the future. – glennsl Sep 25 '22 at 08:36
  • re type declarations vs type expressions... are you sure I created the wrong type of AST element (an alias vs an actual new type) using `[%stri type t = [%t variant_of_defs]]`? ... I figured if it type-checked and compiled it couldn't be wrong that way. I am also thinking: how can I inadvertently make an alias to a type that doesn't exist? – Anentropic Sep 26 '22 at 16:54
  • Polymorphic variants are structural, meaning they don't need to be declared to "exist". You can specify structural types directly in type annotations. Ordinary variants are nominal and do need to be declared before they are used. Therefore`type t = ` will create a type alias, not a type declaration. – glennsl Sep 26 '22 at 17:46

1 Answers1

0

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;]
Anentropic
  • 32,188
  • 12
  • 99
  • 147