Creating TYPES from PPX


#1

Hi,

can types also be created from PPXs?
I can only find examples where modules are extended with functions.

Related: could I also extended an existing type via a PPX?

Thx


#2

Hi,

Yes, you can generate type definitions. The exact way to do that will depend on what library you use to write the ppx, but for example when using ppx_deriving, your plugin returns a structure, which can contain value declarations (let ...), but also type declarations (type ....). (in .mli files, it returns a signature, which corresponds to anything that can go in a mli files). In other words, there are no surprises.

Related: could I also extended an existing type via a PPX?

Extending a type can mean different things. If you’re not just adding stuff to the module (but changing what was written in the file), you’ll need to go a bit lower level than ppx_deriving and write an AST mapper. But yes, it’s possible to do things like automatically insert an extra constructor. You’re basically writing an ast -> ast function.

It might be cause problems with other rewriter though, since these kind of transformations have different meaning depending on the order in which the transformations are applied, unlike more conventional things like [@@deriving].


#3

can types also be created from PPXs?

Yes, types are just structure items. For example, here is a structure_item_mapper that rewrites [%%example] into type example=int.

open Ast_mapper
open Ast_helper
open Asttypes
open Parsetree
open Longident

let structure_item_mapper mapper item = 
   begin match item with
      | { pstr_desc =
          Pstr_extension (({ txt = "example"; loc }, _), _)} ->
            Str.type_ ~loc Nonrecursive [
              Type.mk ~loc { txt = "example"; loc }
              ~manifest:(Typ.constr { txt = Lident "int"; loc } [])]
      (* Delegate to the default mapper. *)
      | x -> default_mapper.structure_item mapper x;
  end

let example_mapper argv =
  { 
    default_mapper with
    structure_item = structure_item_mapper;
  }
 
let () = register "example" example_mapper

Related: could I also extended an existing type via a PPX?

Yes, you may rewrite on type_declaration. For example, here is a type_declaration_mapper that catch [@@example] attribute on variant declarations and add a constant constructor Example.

let type_declaration_mapper mapper (declaration : type_declaration) = 
   begin
     match
       List.find_opt (fun ({ txt; loc = _ }, _) -> txt = "example")
         declaration.ptype_attributes
     with
     | None -> default_mapper.type_declaration mapper declaration
     | Some ({ txt = _; loc }, _) ->
       let loc = declaration.ptype_name.loc in
       match declaration.ptype_kind with
       | Ptype_variant list ->
           { declaration with ptype_kind = Ptype_variant
             (list @ [Type.constructor ~loc { txt = "Example"; loc }])}
       | _ ->
           raise (Location.Error
             (Location.error ~loc "example can only extend variants"))
  end

Note that if you want to write ppx that transforms types, you may want to define a ppx_deriving plugin.