ocaml-ppx / ppxlib

Base library and tools for ppx rewriters
MIT License
246 stars 98 forks source link

`Ast_patter.map0` actually call the continuation when it has only one argument #400

Open panglesd opened 1 year ago

panglesd commented 1 year ago

For instance,

open Ppxlib

let ext =
  Extension.declare "example" Extension.Context.module_expr
    Ast_pattern.(
      ptyp
        (map0 ~f:"foo" @@ ptyp_constr (lident (string "foo")) nil
        ||| map0 ~f:"bar" @@ ptyp_constr (lident (string "bar")) nil))
    (fun ~loc ~path:_ arg ->
      Stdlib.Printf.eprintf "expand: %s\n" arg;
      Ast_builder.Default.pmod_structure ~loc [])

let () = Ppxlib.Driver.register_transformation "example" ~extensions:[ ext ]

applied on

module _ = [%example: bar]

will write

expand: foo
expand: bar

instead of only

expand: bar

The reason is that map0 will actually call the continuation when this one has one argument, even though the "matching" is not finished:

let map0 (T func) ~f = T (fun ctx loc x k -> func ctx loc x (k f))

I think (but I'm not confident) that one way to fix this would be to abstract the continuation to make sure it is never executed too early:

type ('matched_value, 'k, 'k_result) t =
  | T of (context -> Location.t -> 'matched_value -> (unit -> 'k) -> 'k_result)

As a workaround before this is fixed, map_result can be used to protect the continuation to be executed:

open Ppxlib

let ext =
  Extension.declare "example" Extension.Context.module_expr
    Ast_pattern.(
      ptyp
        (map0 ~f:"foo"
         @@ map_result ~f:(fun k -> k ())
         @@ ptyp_constr (lident (string "foo")) nil
        ||| map0 ~f:"bar"
            @@ map_result ~f:(fun k -> k ())
            @@ ptyp_constr (lident (string "bar")) nil))
    (fun ~loc ~path:_ arg () ->
      Stdlib.Printf.eprintf "expand: %s\n" arg;
      Ast_builder.Default.pmod_structure ~loc [])

let () = Ppxlib.Driver.register_transformation "example" ~extensions:[ ext ]

Thanks @ceastlund for finding the bug and the minimal example!