This commit is contained in:
swrup 2025-11-11 02:07:51 +01:00
parent aa2ff7b2f0
commit 2f3113f55d
11742 changed files with 1223940 additions and 0 deletions

View file

@ -0,0 +1,9 @@
(library
(name ppxlib_traverse)
(public_name ppxlib.traverse)
(kind ppx_deriver)
(flags
(:standard -safe-string))
(libraries ppxlib ppxlib_ast ppxlib_traverse_builtins stdppx stdlib-shims)
(preprocess
(pps ppxlib_metaquot)))

View file

@ -0,0 +1,631 @@
open Stdppx
open Ppxlib
open Ast_builder.Default
let alphabet =
Array.init
(Char.code 'z' - Char.code 'a' + 1)
~f:(fun i -> String.make 1 (Char.chr (i + Char.code 'a')))
let vars_of_list ~get_loc l =
List.mapi l ~f:(fun i x -> { txt = alphabet.(i); loc = get_loc x })
let evar_of_var { txt; loc } = evar ~loc txt
let pvar_of_var { txt; loc } = pvar ~loc txt
let tvar_of_var { txt; loc } = ptyp_var ~loc txt
let evars_of_vars = List.map ~f:evar_of_var
let pvars_of_vars = List.map ~f:pvar_of_var
let tvars_of_vars = List.map ~f:tvar_of_var
let fst_expr ~loc expr =
match expr.pexp_desc with
| Pexp_tuple [ fst; _ ] -> fst
| _ -> [%expr Stdlib.fst [%e expr]]
let snd_expr ~loc expr =
match expr.pexp_desc with
| Pexp_tuple [ _; snd ] -> snd
| _ -> [%expr Stdlib.snd [%e expr]]
let methods_of_class_exn = function
| {
pstr_desc =
Pstr_class
[
{
pci_expr = { pcl_desc = Pcl_structure { pcstr_fields = l; _ }; _ };
_;
};
];
_;
} ->
l
| _ -> assert false
module Backends = struct
class reconstructors =
object
method record ~loc flds = pexp_record ~loc flds None
method construct ~loc id args =
pexp_construct ~loc id
(match args with [] -> None | _ -> Some (pexp_tuple ~loc args))
method tuple ~loc es = pexp_tuple ~loc es
end
class type what = object
method name : string
inherit reconstructors
method class_params :
loc:Location.t -> (core_type * (variance * injectivity)) list
method virtual_methods : loc:Location.t -> class_field list
method apply : loc:Location.t -> expression -> expression list -> expression
method abstract : loc:Location.t -> pattern -> expression -> expression
(* Basic combinator type *)
method typ : loc:Location.t -> core_type -> core_type
method any : loc:Location.t -> expression
method combine :
loc:Location.t ->
(string loc * expression) list ->
reconstruct:expression ->
expression
end
let mapper : what =
object
method name = "map"
inherit reconstructors
method class_params ~loc:_ = []
method virtual_methods ~loc:_ = []
method apply ~loc expr args = eapply ~loc expr args
method abstract ~loc patt expr = pexp_fun ~loc Nolabel None patt expr
method typ ~loc ty = ptyp_arrow ~loc Nolabel ty ty
method any ~loc = [%expr fun x -> x]
method combine ~loc combinators ~reconstruct =
List.fold_right combinators ~init:reconstruct ~f:(fun (v, expr) acc ->
pexp_let ~loc Nonrecursive
[ value_binding ~loc ~pat:(pvar_of_var v) ~expr ]
acc)
end
let iterator : what =
object
method name = "iter"
inherit reconstructors
method class_params ~loc:_ = []
method virtual_methods ~loc:_ = []
method apply ~loc expr args = eapply ~loc expr args
method abstract ~loc patt expr = pexp_fun ~loc Nolabel None patt expr
method typ ~loc ty = [%type: [%t ty] -> unit]
method any ~loc = [%expr fun _ -> ()]
method combine ~loc combinators ~reconstruct:_ =
match List.rev combinators with
| [] -> [%expr ()]
| (_, expr) :: rest ->
List.fold_left rest ~init:expr ~f:(fun acc (_v, expr) ->
pexp_sequence ~loc expr acc)
end
let folder : what =
object
method name = "fold"
inherit reconstructors
method class_params ~loc =
[ (ptyp_var ~loc "acc", (NoVariance, NoInjectivity)) ]
method virtual_methods ~loc:_ = []
method apply ~loc expr args = eapply ~loc expr (args @ [ evar ~loc "acc" ])
method abstract ~loc patt expr =
eabstract ~loc [ patt; pvar ~loc "acc" ] expr
method typ ~loc ty = [%type: [%t ty] -> 'acc -> 'acc]
method any ~loc = [%expr fun _ acc -> acc]
method combine ~loc combinators ~reconstruct:_ =
match combinators with
| [ (_, expr) ] -> expr
| _ ->
List.fold_right combinators ~init:[%expr acc]
~f:(fun (_v, expr) acc ->
[%expr
let acc = [%e expr] in
[%e acc]])
end
let fold_mapper : what =
object
method name = "fold_map"
inherit reconstructors
method class_params ~loc =
[ (ptyp_var ~loc "acc", (NoVariance, NoInjectivity)) ]
method virtual_methods ~loc:_ = []
method apply ~loc expr args = eapply ~loc expr (args @ [ evar ~loc "acc" ])
method abstract ~loc patt expr =
eabstract ~loc [ patt; pvar ~loc "acc" ] expr
method typ ~loc ty = [%type: [%t ty] -> 'acc -> [%t ty] * 'acc]
method any ~loc = [%expr fun x acc -> (x, acc)]
method combine ~loc combinators ~reconstruct =
List.fold_right combinators
~init:[%expr [%e reconstruct], acc]
~f:(fun (v, expr) acc ->
[%expr
let [%p pvar_of_var v], acc = [%e expr] in
[%e acc]])
end
exception Found
let uses_var var =
let iter =
object
inherit Ast_traverse.iter as super
method! expression_desc =
function
| Pexp_ident { txt = Lident id; _ } when String.equal id var ->
raise_notrace Found
| e -> super#expression_desc e
end
in
fun e ->
try
iter#expression e;
false
with Found -> true
let mapper_with_context : what =
let uses_ctx = uses_var "ctx" in
object
method name = "map_with_context"
inherit reconstructors
method class_params ~loc =
[ (ptyp_var ~loc "ctx", (NoVariance, NoInjectivity)) ]
method virtual_methods ~loc:_ = []
method apply ~loc expr args = eapply ~loc expr (evar ~loc "ctx" :: args)
method abstract ~loc patt expr =
if uses_ctx expr then eabstract ~loc [ pvar ~loc "ctx"; patt ] expr
else eabstract ~loc [ pvar ~loc "_ctx"; patt ] expr
method typ ~loc ty = [%type: 'ctx -> [%t ty] -> [%t ty]]
method any ~loc = [%expr fun _ctx x -> x]
method combine ~loc combinators ~reconstruct =
List.fold_right combinators ~init:reconstruct ~f:(fun (v, expr) acc ->
[%expr
let [%p pvar_of_var v] = [%e expr] in
[%e acc]])
end
let string_of_lid id = String.concat ~sep:"." (Longident.flatten_exn id)
let lifter : what =
object
method name = "lift"
method class_params ~loc =
[ (ptyp_var ~loc "res", (NoVariance, NoInjectivity)) ]
method virtual_methods ~loc =
methods_of_class_exn
[%stri
class virtual blah =
object
method virtual record : (string * 'res) list -> 'res
method virtual constr : string -> 'res list -> 'res
method virtual tuple : 'res list -> 'res
method virtual other : 'a. 'a -> 'res
end]
method apply ~loc expr args = eapply ~loc expr args
method abstract ~loc patt expr = pexp_fun ~loc Nolabel None patt expr
method typ ~loc ty = [%type: [%t ty] -> 'res]
method any ~loc = [%expr self#other]
method combine ~loc combinators ~reconstruct =
List.fold_right combinators ~init:reconstruct ~f:(fun (v, expr) acc ->
pexp_let ~loc Nonrecursive
[ value_binding ~loc ~pat:(pvar_of_var v) ~expr ]
acc)
method record ~loc flds =
let flds =
elist ~loc
(List.map flds ~f:(fun (lab, e) ->
pexp_tuple
~loc:{ lab.loc with loc_end = e.pexp_loc.loc_end }
[ estring ~loc:lab.loc (string_of_lid lab.txt); e ]))
in
[%expr self#record [%e flds]]
method construct ~loc id args =
let args = elist ~loc args in
[%expr
self#constr [%e estring ~loc:id.loc (string_of_lid id.txt)] [%e args]]
method tuple ~loc es = [%expr self#tuple [%e elist ~loc es]]
end
let lift_mapper_with_context : what =
let uses_ctx = uses_var "ctx" in
object
method name = "lift_map_with_context"
method class_params ~loc =
[
(ptyp_var ~loc "ctx", (NoVariance, NoInjectivity));
(ptyp_var ~loc "res", (NoVariance, NoInjectivity));
]
method virtual_methods ~loc =
methods_of_class_exn
[%stri
class virtual blah =
object
method virtual record : 'ctx -> (string * 'res) list -> 'res
method virtual constr : 'ctx -> string -> 'res list -> 'res
method virtual tuple : 'ctx -> 'res list -> 'res
method virtual other : 'a. 'ctx -> 'a -> 'res
end]
method apply ~loc expr args = eapply ~loc expr (evar ~loc "ctx" :: args)
method abstract ~loc patt expr =
let ctx_pat =
if uses_ctx expr then pvar ~loc "ctx" else pvar ~loc "_ctx"
in
eabstract ~loc [ ctx_pat; patt ] expr
method typ ~loc ty = [%type: 'ctx -> [%t ty] -> [%t ty] * 'res]
method any ~loc = [%expr fun ctx x -> (x, self#other ctx x)]
method combine ~loc combinators ~reconstruct =
List.fold_right combinators ~init:reconstruct ~f:(fun (v, expr) acc ->
pexp_let ~loc Nonrecursive
[ value_binding ~loc ~pat:(pvar_of_var v) ~expr ]
acc)
method record ~loc flds =
let record =
pexp_record ~loc
(List.map flds ~f:(fun (lab, e) -> (lab, fst_expr ~loc e)))
None
in
let flds =
elist ~loc
(List.map flds ~f:(fun (lab, e) ->
pexp_tuple
~loc:{ lab.loc with loc_end = e.pexp_loc.loc_end }
[
estring ~loc:lab.loc (string_of_lid lab.txt);
snd_expr ~loc e;
]))
in
[%expr [%e record], self#record ctx [%e flds]]
method construct ~loc id args =
let constr =
pexp_construct ~loc id
(pexp_tuple_opt ~loc (List.map args ~f:(fst_expr ~loc)))
in
let res =
let args = elist ~loc (List.map args ~f:(snd_expr ~loc)) in
[%expr
self#constr ctx
[%e estring ~loc:id.loc (string_of_lid id.txt)]
[%e args]]
in
[%expr [%e constr], [%e res]]
method tuple ~loc es =
let tuple = pexp_tuple ~loc (List.map es ~f:(fst_expr ~loc)) in
let res =
[%expr
self#tuple ctx [%e elist ~loc (List.map es ~f:(snd_expr ~loc))]]
in
[%expr [%e tuple], [%e res]]
end
let all =
[
mapper;
iterator;
folder;
fold_mapper;
mapper_with_context;
lifter;
lift_mapper_with_context;
]
end
type what = Backends.what
let mapper_type ~(what : what) ~loc type_name params =
let vars = vars_of_list params ~get_loc:(fun t -> t.ptyp_loc) in
let params = tvars_of_vars vars in
let ty = ptyp_constr ~loc type_name params in
let ty =
List.fold_right params ~init:(what#typ ~loc ty) ~f:(fun param ty ->
let loc = param.ptyp_loc in
ptyp_arrow ~loc Nolabel (what#typ ~loc param) ty)
in
ptyp_poly ~loc vars ty
let constrained_mapper ~(what : what) ?(is_gadt = false) mapper td =
let vars = vars_of_list td.ptype_params ~get_loc:(fun (t, _) -> t.ptyp_loc) in
let make_type params =
let loc = td.ptype_loc in
let ty = ptyp_constr ~loc (Loc.map td.ptype_name ~f:lident) params in
List.fold_right params ~init:(what#typ ~loc:td.ptype_loc ty)
~f:(fun param ty ->
let loc = param.ptyp_loc in
ptyp_arrow ~loc Nolabel (what#typ ~loc param) ty)
in
let typ =
let loc = td.ptype_loc in
ptyp_poly ~loc vars (make_type (tvars_of_vars vars))
in
let mapper =
if false || is_gadt then
let typs =
List.map vars ~f:(fun v ->
ptyp_constr ~loc:v.loc (Loc.map v ~f:lident) [])
in
List.fold_right vars
~init:(pexp_constraint ~loc:mapper.pexp_loc mapper (make_type typs))
~f:(fun v e -> pexp_newtype ~loc:v.loc v e)
else mapper
in
pexp_poly ~loc:mapper.pexp_loc mapper (Some typ)
let mangle_type_name lid =
let rec mangled_parts lid ~suffix =
match lid with
| Lident s -> String.lowercase_ascii s :: suffix
| Ldot (lid, s) ->
mangled_parts lid ~suffix:("__" :: String.lowercase_ascii s :: suffix)
| Lapply (a, b) ->
mangled_parts a ~suffix:("_'" :: mangled_parts b ~suffix:("'" :: suffix))
in
mangled_parts lid ~suffix:[] |> String.concat ~sep:""
let rec type_expr_mapper ~(what : what) te =
let loc = te.ptyp_loc in
match te.ptyp_desc with
| Ptyp_var s -> evar ~loc ("_" ^ s)
| Ptyp_tuple tes ->
let vars = vars_of_list tes ~get_loc:(fun t -> t.ptyp_loc) in
let deconstruct = ppat_tuple ~loc (pvars_of_vars vars) in
let reconstruct = what#tuple ~loc (evars_of_vars vars) in
let mappers = map_variables ~what vars tes in
what#abstract ~loc deconstruct (what#combine ~loc mappers ~reconstruct)
| Ptyp_constr (path, params) -> (
let map =
pexp_send ~loc (evar ~loc "self") (Loc.map path ~f:mangle_type_name)
in
match params with
| [] -> map
| _ ->
eapply ~loc map
(List.map params ~f:(fun te -> type_expr_mapper ~what te)))
| _ -> what#any ~loc
and map_variables ~(what : what) vars tes =
List.map2 tes vars ~f:(fun te var ->
( var,
what#apply ~loc:te.ptyp_loc
(type_expr_mapper ~what te)
[ evar_of_var var ] ))
let gen_record' ~(what : what) ~loc lds =
let vars = List.map lds ~f:(fun ld -> ld.pld_name) in
let deconstruct =
ppat_record ~loc
(List.map vars ~f:(fun v -> (Loc.map v ~f:lident, pvar_of_var v)))
Closed
in
let reconstruct =
what#record ~loc
(List.map vars ~f:(fun v -> (Loc.map v ~f:lident, evar_of_var v)))
in
let mappers =
map_variables ~what vars (List.map lds ~f:(fun ld -> ld.pld_type))
in
(deconstruct, reconstruct, mappers)
let gen_record ~(what : what) ~loc lds =
let deconstruct, reconstruct, mappers = gen_record' ~what lds ~loc in
what#abstract ~loc deconstruct (what#combine ~loc mappers ~reconstruct)
let is_constant_constructor cd =
match cd.pcd_args with Pcstr_tuple [] -> true | _ -> false
let erase_type_variables =
object
inherit Ast_traverse.map as super
method! core_type_desc =
function Ptyp_var _ -> Ptyp_any | x -> super#core_type_desc x
end
let gen_variant ~(what : what) ~loc cds =
if
String.( <> ) what#name "lift"
&& List.for_all cds ~f:is_constant_constructor
then what#any ~loc
else
let cases =
List.map cds ~f:(fun cd ->
let cstr = Loc.map cd.pcd_name ~f:lident in
let loc = cd.pcd_loc in
let args =
match cd.pcd_res with
| None -> cd.pcd_args
| Some _ ->
(* This is a big sur-approximation but it's enough for our only use of GADTs
in ppx_custom_format *)
erase_type_variables#constructor_arguments cd.pcd_args
in
match args with
| Pcstr_tuple args ->
let vars = vars_of_list args ~get_loc:(fun t -> t.ptyp_loc) in
let deconstruct =
ppat_construct cstr ~loc
(match vars with
| [] -> None
| _ -> Some (ppat_tuple ~loc (pvars_of_vars vars)))
in
let reconstruct = what#construct cstr ~loc (evars_of_vars vars) in
let mappers = map_variables ~what vars args in
case ~lhs:deconstruct
~rhs:(what#combine ~loc mappers ~reconstruct)
~guard:None
| Pcstr_record labels ->
let deconstruct, reconstruct, mappers =
gen_record' ~loc ~what labels
in
let deconstruct = ppat_construct ~loc cstr (Some deconstruct) in
let reconstruct = what#construct ~loc cstr [ reconstruct ] in
case ~lhs:deconstruct
~rhs:(what#combine ~loc mappers ~reconstruct)
~guard:None)
in
what#abstract ~loc (pvar ~loc "x") (pexp_match ~loc (evar ~loc "x") cases)
let gen_mapper ~(what : what) td =
let body =
let loc = td.ptype_loc in
match td.ptype_kind with
| Ptype_open -> what#any ~loc
| Ptype_record lds -> gen_record ~what lds ~loc
| Ptype_variant cds -> gen_variant ~what cds ~loc
| Ptype_abstract -> (
match td.ptype_manifest with
| None -> what#any ~loc
| Some te -> type_expr_mapper ~what te)
in
List.fold_right td.ptype_params ~init:body ~f:(fun (ty, _) acc ->
let loc = ty.ptyp_loc in
match ty.ptyp_desc with
| Ptyp_var s -> pexp_fun ~loc Nolabel None (pvar ~loc ("_" ^ s)) acc
| _ -> pexp_fun ~loc Nolabel None (ppat_any ~loc) acc)
let type_deps =
let collect =
object
inherit [int Longident.Map.t] Ast_traverse.fold as super
method! core_type t acc =
let acc =
match t.ptyp_desc with
| Ptyp_constr (id, vars) ->
Longident.Map.add id.txt (List.length vars) acc
| _ -> acc
in
super#core_type t acc
end
in
fun tds ->
let empty = Longident.Map.empty in
let map =
List.fold_left tds ~init:empty ~f:(fun map td ->
let map = collect#type_kind td.ptype_kind map in
match (td.ptype_kind, td.ptype_manifest) with
| Ptype_abstract, Some ty -> collect#core_type ty map
| _ -> map)
in
let map =
List.fold_left tds ~init:map ~f:(fun map td ->
Longident.Map.remove (Lident td.ptype_name.txt) map)
in
Longident.Map.bindings map
let filter_virtual_methods ~methods ~virtual_methods =
let collect =
object
inherit [String.Set.t] Ast_traverse.fold as super
method! expression_desc x acc =
match x with
| Pexp_send
( _,
{ txt = ("tuple" | "record" | "constr" | "other") as s; loc = _ }
) ->
String.Set.add s acc
| _ -> super#expression_desc x acc
end
in
let used = collect#list collect#class_field methods String.Set.empty in
List.filter virtual_methods ~f:(fun m ->
match m.pcf_desc with
| Pcf_method (s, _, _) -> String.Set.mem s.txt used
| _ -> false)
let gen_class ~(what : what) ~loc tds =
let class_params = what#class_params ~loc in
let virtual_methods =
List.map (type_deps tds) ~f:(fun (id, arity) ->
pcf_method ~loc
( { txt = mangle_type_name id; loc },
Public,
Cfk_virtual
(mapper_type ~what ~loc { txt = id; loc }
(List.init ~len:arity ~f:(fun _ -> ptyp_any ~loc))) ))
in
let methods =
List.map tds ~f:(fun td ->
let loc = td.ptype_loc in
let mapper = gen_mapper ~what td in
let is_gadt =
match td.ptype_kind with
| Ptype_variant cds ->
List.exists cds ~f:(fun cd -> Option.is_some cd.pcd_res)
| _ -> false
in
let mapper = constrained_mapper ~what ~is_gadt mapper td in
pcf_method ~loc (td.ptype_name, Public, Cfk_concrete (Fresh, mapper)))
in
let virtual_methods =
filter_virtual_methods ~methods ~virtual_methods:(what#virtual_methods ~loc)
@ virtual_methods
in
let virt = if List.is_empty virtual_methods then Concrete else Virtual in
class_infos ~loc ~virt ~params:class_params ~name:{ loc; txt = what#name }
~expr:
(pcl_structure ~loc
(class_structure
~self:(ppat_var ~loc { txt = "self"; loc })
~fields:(virtual_methods @ methods)))
let gen_str ~(what : what) ~loc ~path:_ (rf, tds) =
(match rf with
| Nonrecursive ->
(* The method name would clash... *)
Location.raise_errorf ~loc "ppxlib_traverse doesn't support nonrec"
| Recursive -> ());
let cl = gen_class ~loc ~what tds in
[ pstr_class ~loc:cl.pci_loc [ cl ] ]
let () =
let derivers =
List.map Backends.all ~f:(fun what ->
Deriving.add ("traverse_" ^ what#name)
~str_type_decl:(Deriving.Generator.make_noarg (gen_str ~what)))
in
Deriving.add_alias "traverse" (List.rev derivers) |> Deriving.ignore