open! Base open! Ppxlib open Ast_builder.Default open Helpers open Lifted.Monad_infix (* Generates the signature for type conversion from S-expressions *) module Sig_generate_of_sexp = struct let type_of_of_sexp ~loc t = let loc = { loc with loc_ghost = true } in [%type: Sexplib0.Sexp.t -> [%t t]] ;; let mk_type td = combinator_type_of_type_declaration td ~f:type_of_of_sexp let sig_of_td with_poly td = let of_sexp_type = mk_type td in let loc = td.ptype_loc in let of_sexp_item = psig_value ~loc (value_description ~loc ~name:(Located.map (fun s -> s ^ "_of_sexp") td.ptype_name) ~type_:of_sexp_type ~prim:[]) in match with_poly, is_polymorphic_variant td ~sig_:true with | true, `Surely_not -> Location.raise_errorf ~loc "Sig_generate_of_sexp.sig_of_td: sexp_poly annotation but type is surely not a \ polymorphic variant" | false, (`Surely_not | `Maybe) -> [ of_sexp_item ] | (true | false), `Definitely | true, `Maybe -> [ of_sexp_item ; psig_value ~loc (value_description ~loc ~name:(Located.map (fun s -> "__" ^ s ^ "_of_sexp__") td.ptype_name) ~type_:of_sexp_type ~prim:[]) ] ;; let mk_sig ~poly ~loc:_ ~path:_ (_rf, tds) = List.concat_map tds ~f:(sig_of_td poly) end module Str_generate_of_sexp = struct module Ptag_error_function = struct type t = | Ptag_no_args | Ptag_takes_args end module Row_or_constructor = struct type t = | Row of row_field | Constructor of constructor_declaration end let with_error_source ~loc ~full_type_name make_body = let lifted = let name = lazy (Fresh_name.create "error_source" ~loc) in make_body ~error_source:(fun () -> Fresh_name.expression (force name)) >>| fun body -> match Lazy.is_val name with | false -> (* no references to [name], no need to define it *) body | true -> (* add a definition for [name] *) [%expr let [%p Fresh_name.pattern (force name)] = [%e estring ~loc full_type_name] in [%e body]] in Lifted.let_bind_user_expressions lifted ~loc ;; (* Utility functions for polymorphic variants *) (* Handle backtracking when variants do not match *) let handle_no_variant_match loc expr = [ [%pat? Sexplib0.Sexp_conv_error.No_variant_match] --> expr ] ;; (* Generate code depending on whether to generate a match for the last case of matching a variant *) let handle_variant_match_last loc ~match_last ~fresh_atom matches = match match_last, matches with | true, [ { pc_lhs = _; pc_guard = None; pc_rhs = expr } ] | _, [ { pc_lhs = [%pat? _]; pc_guard = None; pc_rhs = expr } ] -> expr | _ -> pexp_match ~loc (Fresh_name.expression fresh_atom) matches ;; (* Generate code for matching malformed S-expressions *) let mk_variant_other_matches ~error_source ~fresh__sexp loc rev_els call = let coll_structs acc (loc, cnstr) = (pstring ~loc cnstr --> match (call : Ptag_error_function.t) with | Ptag_no_args -> [%expr Sexplib0.Sexp_conv_error.ptag_no_args [%e error_source ()] [%e Fresh_name.expression fresh__sexp]] | Ptag_takes_args -> [%expr Sexplib0.Sexp_conv_error.ptag_takes_args [%e error_source ()] [%e Fresh_name.expression fresh__sexp]]) :: acc in let exc_no_variant_match = [%pat? _] --> [%expr Sexplib0.Sexp_conv_error.no_variant_match ()] in List.fold_left ~f:coll_structs ~init:[ exc_no_variant_match ] rev_els ;; (* Split the row fields of a variant type into lists of atomic variants, structured variants, atomic variants + included variant types, and structured variants + included variant types. *) let split_row_field ~loc (atoms, structs, ainhs, sinhs) row_field = match row_field.prf_desc with | Rtag ({ txt = cnstr; _ }, true, []) -> let tpl = loc, cnstr in tpl :: atoms, structs, `A tpl :: ainhs, sinhs | Rtag ({ txt = cnstr; _ }, false, [ tp ]) -> let loc = tp.ptyp_loc in atoms, (loc, cnstr) :: structs, ainhs, `S (loc, cnstr, tp, row_field) :: sinhs | Rinherit inh -> let iinh = `I inh in atoms, structs, iinh :: ainhs, iinh :: sinhs | Rtag (_, true, [ _ ]) | Rtag (_, _, _ :: _ :: _) -> Location.raise_errorf ~loc "unsupported: polymorphic variant intersection type" | Rtag (_, false, []) -> Location.raise_errorf ~loc "unsupported: polymorphic variant empty type" ;; let type_constr_of_sexp ?(internal = false) id args = type_constr_conv id args ~f:(fun s -> let s = s ^ "_of_sexp" in if internal then "__" ^ s ^ "__" else s) ;; (* Conversion of types *) let rec type_of_sexp ~error_source ~typevars ?full_type ?(internal = false) typ : Conversion.t = let loc = typ.ptyp_loc in match Ppxlib_jane.Jane_syntax.Core_type.of_ast typ with | Some (Jtyp_tuple alist, (_ : attributes)) -> Conversion.of_reference_exn (labeled_tuple_of_sexp ~error_source ~typevars ~loc alist) | Some (Jtyp_layout _, _) | None -> (match typ with | _ when Option.is_some (Attribute.get Attrs.opaque typ) -> Conversion.of_reference_exn [%expr Sexplib0.Sexp_conv.opaque_of_sexp] | [%type: [%t? _] sexp_opaque] | [%type: _] -> Conversion.of_reference_exn [%expr Sexplib0.Sexp_conv.opaque_of_sexp] | [%type: [%t? ty1] sexp_list] -> let arg1 = Conversion.to_expression ~loc (type_of_sexp ~error_source ~typevars ty1) in Conversion.of_reference_exn [%expr Sexplib0.Sexp_conv.list_of_sexp [%e arg1]] | [%type: [%t? ty1] sexp_array] -> let arg1 = Conversion.to_expression ~loc (type_of_sexp ~error_source ~typevars ty1) in Conversion.of_reference_exn [%expr Sexplib0.Sexp_conv.array_of_sexp [%e arg1]] | { ptyp_desc = Ptyp_tuple tp; _ } -> Conversion.of_lambda (tuple_of_sexp ~error_source ~typevars (loc, tp)) | { ptyp_desc = Ptyp_var parm; _ } -> (match Map.find typevars parm with | Some fresh -> Conversion.of_reference_exn (Fresh_name.expression fresh) | None -> Location.raise_errorf ~loc "ppx_sexp_conv: unbound type variable '%s" parm) | { ptyp_desc = Ptyp_constr (id, args); _ } -> let args = List.map args ~f:(fun arg -> Conversion.to_expression ~loc (type_of_sexp ~error_source ~typevars arg)) in Conversion.of_reference_exn (type_constr_of_sexp ~loc ~internal id args) | { ptyp_desc = Ptyp_arrow (_, _, _); _ } -> Conversion.of_reference_exn [%expr Sexplib0.Sexp_conv.fun_of_sexp] | { ptyp_desc = Ptyp_variant (row_fields, Closed, _); _ } -> variant_of_sexp ~error_source ~typevars ?full_type (loc, row_fields) | { ptyp_desc = Ptyp_poly (parms, poly_tp); _ } -> poly_of_sexp ~error_source ~typevars parms poly_tp | { ptyp_desc = Ptyp_variant (_, Open, _); _ } | { ptyp_desc = Ptyp_object (_, _); _ } | { ptyp_desc = Ptyp_class (_, _); _ } | { ptyp_desc = Ptyp_alias (_, _); _ } | { ptyp_desc = Ptyp_package _; _ } | { ptyp_desc = Ptyp_extension _; _ } | { ptyp_desc = Ptyp_open _; _ } -> Location.raise_errorf ~loc "Type unsupported for ppx [of_sexp] conversion") (* Conversion of (unlabeled) tuples *) and tuple_of_sexp ~error_source ~typevars (loc, tps) = let fps = List.map ~f:(type_of_sexp ~error_source ~typevars) tps in let ({ bindings; arguments; converted } : Conversion.Apply_all.t) = Conversion.apply_all ~loc fps in let n = List.length fps in let fresh_sexp = Fresh_name.create "sexp" ~loc in [ [%pat? Sexplib0.Sexp.List [%p plist ~loc arguments]] --> pexp_let ~loc Nonrecursive bindings (pexp_tuple ~loc converted) ; Fresh_name.pattern fresh_sexp --> [%expr Sexplib0.Sexp_conv_error.tuple_of_size_n_expected [%e error_source ()] [%e eint ~loc n] [%e Fresh_name.expression fresh_sexp]] ] (* Conversion of labeled tuples *) and labeled_tuple_of_sexp ~error_source ~typevars ~loc alist = assert (Labeled_tuple.is_valid alist); let fields_expr = List.fold_right alist ~init:[%expr Empty] ~f:(fun (label_option, core_type) rest_expr -> let name_expr = estring ~loc (Labeled_tuple.atom_of_label label_option) in let conv_expr = type_of_sexp ~error_source ~typevars core_type |> Conversion.to_expression ~loc:core_type.ptyp_loc in [%expr Field { name = [%e name_expr]; conv = [%e conv_expr]; rest = [%e rest_expr] }]) in let create_expr = let pats, exprs = List.map alist ~f:(fun (label_option, _) -> let name = Fresh_name.create ~loc "field" in Fresh_name.pattern name, (label_option, Fresh_name.expression name)) |> List.unzip in let pat = List.fold_right pats ~init:(punit ~loc) ~f:(fun pat1 pat2 -> ppat_tuple ~loc [ pat1; pat2 ]) in let expr = Ppxlib_jane.Jane_syntax.Labeled_tuples.expr_of ~loc exprs in [%expr fun [%p pat] -> [%e expr]] in pexp_apply ~loc [%expr Sexplib0.Sexp_conv_labeled_tuple.labeled_tuple_of_sexp] [ Labelled "caller", error_source () ; Labelled "fields", fields_expr ; Labelled "create", create_expr ] (* Generate code for matching included variant types *) and handle_variant_inh ~error_source ~typevars ~fresh_atom ~fresh__sexp full_type ~match_last other_matches inh = let loc = inh.ptyp_loc in let func_expr = type_of_sexp ~error_source ~typevars ~internal:true inh in let app = Conversion.of_reference_exn (Conversion.apply ~loc func_expr (Fresh_name.expression fresh__sexp)) in let match_exc = handle_no_variant_match loc (handle_variant_match_last loc ~match_last ~fresh_atom other_matches) in let new_other_matches = [ [%pat? _] --> pexp_try ~loc [%expr ([%e Conversion.to_expression ~loc app] :> [%t replace_variables_by_underscores full_type])] match_exc ] in new_other_matches, true (* Generate code for matching atomic variants *) and mk_variant_match_atom ~error_source ~typevars ~fresh_atom ~fresh__sexp loc full_type rev_atoms_inhs rev_structs = let coll (other_matches, match_last) = function | `A (loc, cnstr) -> let new_match = pstring ~loc cnstr --> pexp_variant ~loc cnstr None in new_match :: other_matches, false | `I inh -> handle_variant_inh ~error_source ~typevars ~fresh_atom ~fresh__sexp full_type ~match_last other_matches inh in let other_matches = mk_variant_other_matches ~error_source ~fresh__sexp loc rev_structs Ptag_takes_args in let match_atoms_inhs, match_last = List.fold_left ~f:coll ~init:(other_matches, false) rev_atoms_inhs in handle_variant_match_last loc ~match_last ~fresh_atom match_atoms_inhs (* Variant conversions *) (* Match arguments of constructors (variants or sum types) *) and mk_cnstr_args_match ~error_source ~typevars ~loc ~is_variant ~fresh__sexp ~fresh__tag ~fresh_sexp_args cnstr tps row = let cnstr vars_expr = if is_variant then pexp_variant ~loc cnstr (Some vars_expr) else pexp_construct ~loc (Located.lident ~loc cnstr) (Some vars_expr) in match tps with | [ tp ] when Option.is_some (match (row : Row_or_constructor.t) with | Row r -> Attribute.get Attrs.list_poly r | Constructor c -> Attribute.get Attrs.list_variant c) -> (match tp with | [%type: [%t? tp] list] -> let cnv = Conversion.to_expression ~loc (type_of_sexp ~error_source ~typevars tp) in cnstr [%expr Sexplib0.Sexp_conv.list_map [%e cnv] [%e Fresh_name.expression fresh_sexp_args]] | _ -> (match row with | Row _ -> Attrs.invalid_attribute ~loc Attrs.list_poly "_ list" | Constructor _ -> Attrs.invalid_attribute ~loc Attrs.list_variant "_ list")) | [ [%type: [%t? tp] sexp_list] ] -> let cnv = Conversion.to_expression ~loc (type_of_sexp ~error_source ~typevars tp) in cnstr [%expr Sexplib0.Sexp_conv.list_map [%e cnv] [%e Fresh_name.expression fresh_sexp_args]] | _ -> let bindings, patts, good_arg_match = let fps = List.map ~f:(type_of_sexp ~error_source ~typevars) tps in let ({ bindings; arguments; converted } : Conversion.Apply_all.t) = Conversion.apply_all ~loc fps in let good_arg_match = cnstr (pexp_tuple ~loc converted) in bindings, arguments, good_arg_match in [%expr match [%e Fresh_name.expression fresh_sexp_args] with | [%p plist ~loc patts] -> [%e pexp_let ~loc Nonrecursive bindings good_arg_match] | _ -> [%e if is_variant then [%expr Sexplib0.Sexp_conv_error.ptag_incorrect_n_args [%e error_source ()] [%e Fresh_name.expression fresh__tag] [%e Fresh_name.expression fresh__sexp]] else [%expr Sexplib0.Sexp_conv_error.stag_incorrect_n_args [%e error_source ()] [%e Fresh_name.expression fresh__tag] [%e Fresh_name.expression fresh__sexp]]]] (* Generate code for matching structured variants *) and mk_variant_match_struct ~error_source ~typevars ~fresh_atom ~fresh__sexp ~fresh_sexp_args loc full_type rev_structs_inhs rev_atoms = let has_structs_ref = ref false in let coll (other_matches, match_last) = function | `S (loc, cnstr, tp, row) -> has_structs_ref := true; let fresh__tag = Fresh_name.create "_tag" ~loc in let expr = mk_cnstr_args_match ~error_source ~typevars ~loc:tp.ptyp_loc ~is_variant:true ~fresh__sexp ~fresh__tag ~fresh_sexp_args cnstr [ tp ] (Row row) in let new_match = ppat_alias ~loc [%pat? [%p pstring ~loc cnstr]] (Fresh_name.to_string_loc fresh__tag) --> expr in new_match :: other_matches, false | `I inh -> handle_variant_inh ~error_source ~typevars ~fresh_atom ~fresh__sexp full_type ~match_last other_matches inh in let other_matches = mk_variant_other_matches ~error_source ~fresh__sexp loc rev_atoms Ptag_no_args in let match_structs_inhs, match_last = List.fold_left ~f:coll ~init:(other_matches, false) rev_structs_inhs in ( handle_variant_match_last loc ~match_last ~fresh_atom match_structs_inhs , !has_structs_ref ) (* Generate code for handling atomic and structured variants (i.e. not included variant types) *) and handle_variant_tag ~error_source ~typevars loc full_type row_field_list = let fresh_atom = Fresh_name.create "atom" ~loc in let fresh_sexp = Fresh_name.create "sexp" ~loc in let fresh__sexp = Fresh_name.create "_sexp" ~loc in let fresh_sexp_args = Fresh_name.create "sexp_args" ~loc in let rev_atoms, rev_structs, rev_atoms_inhs, rev_structs_inhs = List.fold_left ~f:(split_row_field ~loc) ~init:([], [], [], []) row_field_list in let match_struct, has_structs = mk_variant_match_struct ~error_source ~typevars ~fresh_atom ~fresh__sexp ~fresh_sexp_args loc full_type rev_structs_inhs rev_atoms in let maybe_sexp_args_patt = if has_structs then Fresh_name.pattern fresh_sexp_args else [%pat? _] in [ ppat_alias ~loc [%pat? Sexplib0.Sexp.Atom [%p Fresh_name.pattern fresh_atom]] (Fresh_name.to_string_loc fresh__sexp) --> mk_variant_match_atom ~error_source ~typevars ~fresh_atom ~fresh__sexp loc full_type rev_atoms_inhs rev_structs ; ppat_alias ~loc [%pat? Sexplib0.Sexp.List (Sexplib0.Sexp.Atom [%p Fresh_name.pattern fresh_atom] :: [%p maybe_sexp_args_patt])] (Fresh_name.to_string_loc fresh__sexp) --> match_struct ; ppat_alias ~loc [%pat? Sexplib0.Sexp.List (Sexplib0.Sexp.List _ :: _)] (Fresh_name.to_string_loc fresh_sexp) --> [%expr Sexplib0.Sexp_conv_error.nested_list_invalid_poly_var [%e error_source ()] [%e Fresh_name.expression fresh_sexp]] ; ppat_alias ~loc [%pat? Sexplib0.Sexp.List []] (Fresh_name.to_string_loc fresh_sexp) --> [%expr Sexplib0.Sexp_conv_error.empty_list_invalid_poly_var [%e error_source ()] [%e Fresh_name.expression fresh_sexp]] ] (* Generate matching code for variants *) and variant_of_sexp ~error_source ~typevars ?full_type (loc, row_fields) = let is_contained, full_type = match full_type with | None -> true, ptyp_variant ~loc row_fields Closed None | Some full_type -> false, full_type in let top_match = let fresh_sexp = Fresh_name.create ~loc "sexp" in match row_fields with | { prf_desc = Rinherit inh; _ } :: rest -> let rec loop inh row_fields = let call = [%expr ([%e Conversion.to_expression ~loc (type_of_sexp ~error_source ~typevars ~internal:true inh)] [%e Fresh_name.expression fresh_sexp] :> [%t replace_variables_by_underscores full_type])] in match row_fields with | [] -> call | h :: t -> let expr = match h.prf_desc with | Rinherit inh -> loop inh t | _ -> let rftag_matches = handle_variant_tag ~error_source ~typevars loc full_type row_fields in pexp_match ~loc (Fresh_name.expression fresh_sexp) rftag_matches in pexp_try ~loc call (handle_no_variant_match loc expr) in [ Fresh_name.pattern fresh_sexp --> loop inh rest ] | _ :: _ -> handle_variant_tag ~error_source ~typevars loc full_type row_fields | [] -> Location.raise_errorf ~loc "of_sexp is not supported for empty polymorphic variants (impossible?)" in if is_contained then ( let fresh_sexp = Fresh_name.create "sexp" ~loc in Conversion.of_lambda [ Fresh_name.pattern fresh_sexp --> [%expr try [%e pexp_match ~loc (Fresh_name.expression fresh_sexp) top_match] with | Sexplib0.Sexp_conv_error.No_variant_match -> Sexplib0.Sexp_conv_error.no_matching_variant_found [%e error_source ()] [%e Fresh_name.expression fresh_sexp]] ]) else Conversion.of_lambda top_match and poly_of_sexp ~error_source ~typevars parms tp = let loc = tp.ptyp_loc in let typevars = List.fold parms ~init:typevars ~f:(fun map parm -> Map.set map ~key:parm.txt ~data:(Fresh_name.create ("_of_" ^ parm.txt) ~loc:parm.loc)) in let bindings = let mk_binding parm = let fresh = Map.find_exn typevars parm.txt in let fresh_sexp = Fresh_name.create "sexp" ~loc in value_binding ~loc ~pat:(Fresh_name.pattern fresh) ~expr: [%expr fun [%p Fresh_name.pattern fresh_sexp] -> Sexplib0.Sexp_conv_error.record_poly_field_value [%e error_source ()] [%e Fresh_name.expression fresh_sexp]] in List.map ~f:mk_binding parms in Conversion.bind (type_of_sexp ~error_source ~typevars tp) bindings ;; type record_poly_type = { type_and_field_name : Fresh_name.t ; params : string loc list ; body : core_type } let record_poly_type field = match field.pld_type.ptyp_desc with | Ptyp_poly (params, body) -> let type_and_field_name = Fresh_name.of_string_loc field.pld_name in Some { type_and_field_name; params; body } | _ -> None ;; let record_field_conv field ~poly ~loc ~error_source ~typevars = match poly with | None -> type_of_sexp ~error_source ~typevars field.pld_type |> Conversion.to_expression ~loc | Some { type_and_field_name; params; body } -> let fresh_sexp = Fresh_name.create "sexp" ~loc in let fresh_params = List.map params ~f:(fun { loc; txt } -> Fresh_name.create ~loc ("_" ^ txt)) in let pat = Fresh_name.pattern fresh_sexp in let body = let label = Located.map_lident (Fresh_name.to_string_loc type_and_field_name) in let typevars = List.fold2_exn params fresh_params ~init:typevars ~f:(fun typevars param fresh -> Map.set typevars ~key:param.txt ~data:fresh) in let expr = pexp_let ~loc Nonrecursive (List.map fresh_params ~f:(fun fresh -> let { loc; txt } = Fresh_name.to_string_loc fresh in let expr = [%expr Sexplib0.Sexp_conv_error.record_poly_field_value [%e error_source ()]] in value_binding ~loc ~pat:(pvar ~loc txt) ~expr)) (Conversion.apply (type_of_sexp ~error_source ~typevars body) ~loc (Fresh_name.expression fresh_sexp)) in pexp_record ~loc [ label, expr ] None in eabstract ~loc [ pat ] body ;; let fields_arg_for_record_of_sexp poly_fields ~loc ~error_source ~typevars = List.fold_right poly_fields ~init:(Lifted.return [%expr Empty]) ~f:(fun (poly, field) rest_lifted -> rest_lifted >>= fun rest_expr -> let label_expr = estring ~loc:field.pld_name.loc field.pld_name.txt in match Record_field_attrs.Of_sexp.create ~loc field with | Specific Required -> Lifted.return [%expr Field { name = [%e label_expr] ; kind = Required ; conv = [%e record_field_conv field ~poly ~loc ~error_source ~typevars] ; rest = [%e rest_expr] }] | Specific (Default lifted) -> lifted >>| fun default -> [%expr Field { name = [%e label_expr] ; kind = Default (fun () -> [%e default]) ; conv = [%e record_field_conv field ~poly ~loc ~error_source ~typevars] ; rest = [%e rest_expr] }] | Omit_nil -> Lifted.return [%expr Field { name = [%e label_expr] ; kind = Omit_nil ; conv = [%e record_field_conv field ~poly ~loc ~error_source ~typevars] ; rest = [%e rest_expr] }] | Sexp_bool -> Lifted.return [%expr Field { name = [%e label_expr] ; kind = Sexp_bool ; conv = () ; rest = [%e rest_expr] }] | Sexp_array core_type -> let conv_expr = type_of_sexp ~error_source ~typevars core_type |> Conversion.to_expression ~loc in Lifted.return [%expr Field { name = [%e label_expr] ; kind = Sexp_array ; conv = [%e conv_expr] ; rest = [%e rest_expr] }] | Sexp_list core_type -> let conv_expr = type_of_sexp ~error_source ~typevars core_type |> Conversion.to_expression ~loc in Lifted.return [%expr Field { name = [%e label_expr] ; kind = Sexp_list ; conv = [%e conv_expr] ; rest = [%e rest_expr] }] | Sexp_option core_type -> let conv_expr = type_of_sexp ~error_source ~typevars core_type |> Conversion.to_expression ~loc in Lifted.return [%expr Field { name = [%e label_expr] ; kind = Sexp_option ; conv = [%e conv_expr] ; rest = [%e rest_expr] }]) ;; let index_of_field_arg_for_record_of_sexp fields ~loc = let field_cases = List.mapi fields ~f:(fun i (_, field) -> let lhs = pstring ~loc:field.pld_name.loc field.pld_name.txt in let rhs = eint ~loc i in case ~lhs ~guard:None ~rhs) in let default_case = case ~lhs:(ppat_any ~loc) ~guard:None ~rhs:(eint ~loc (-1)) in let cases = List.concat [ field_cases; [ default_case ] ] in pexp_function_cases ~loc cases ;; let create_arg_for_record_of_sexp td fields ~loc ~constructor = let pat = List.fold_right fields ~init:[%pat? ()] ~f:(fun (poly, field) tail -> let head = let pat = pvar ~loc:field.pld_name.loc field.pld_name.txt in match poly with | None -> pat | Some { type_and_field_name; _ } -> (* Extract a polymorphic value from a polymorphic record defined explicitly for this purpose. *) let label = Located.map_lident (Fresh_name.to_string_loc type_and_field_name) in ppat_record ~loc [ label, pat ] Closed in ppat_tuple ~loc [ head; tail ]) in let body = let record_expr = pexp_record ~loc (List.map fields ~f:(fun (_, field) -> let label = Located.map_lident field.pld_name in let expr = evar ~loc:field.pld_name.loc field.pld_name.txt in label, expr)) None in match constructor with | None -> record_expr | Some label -> (* variant constructor with inline record *) pexp_construct ~loc label (Some record_expr) in let core_type = ptyp_constr ~loc (Located.map_lident td.ptype_name) (List.map td.ptype_params ~f:(fun (core_type, _) -> ptyp_any ~loc:core_type.ptyp_loc)) in eabstract ~loc [ pat ] (pexp_constraint ~loc body core_type) ;; let polymorphic_record_types_for_record_of_sexp fields ~loc = (* Define fresh types to contain polymorphic values parsed from sexps. *) List.filter_map fields ~f:(fun (poly, _) -> match poly with | Some { type_and_field_name; params; body } -> let fresh_field = label_declaration ~loc ~name:(Fresh_name.to_string_loc type_and_field_name) ~mutable_:Immutable ~type_:(strip_attributes#core_type (ptyp_poly ~loc params body)) in let type_decl = type_declaration ~loc ~name:(Fresh_name.to_string_loc type_and_field_name) ~params:[] ~cstrs:[] ~kind:(Ptype_record [ fresh_field ]) ~private_:Public ~manifest:None in Some { type_decl with ptype_attributes = (* define unboxed types to avoid allocation *) [ { attr_loc = loc ; attr_name = { loc; txt = "unboxed" } ; attr_payload = PStr [] } ] } | None -> None) ;; let args_for_record_of_sexp td fields ~loc ~error_source ~typevars ~constructor ~allow_extra_fields = let caller_expr = error_source () in let allow_extra_fields_expr = ebool ~loc allow_extra_fields in let fields = List.map fields ~f:(fun field -> record_poly_type field, field) in let index_of_field_expr = index_of_field_arg_for_record_of_sexp fields ~loc in let create_expr = create_arg_for_record_of_sexp td fields ~loc ~constructor in let fields_expr_lifted = fields_arg_for_record_of_sexp fields ~loc ~error_source ~typevars in fields_expr_lifted >>| fun fields_expr -> let types = polymorphic_record_types_for_record_of_sexp fields ~loc in let args = [ Labelled "caller", caller_expr ; Labelled "fields", fields_expr ; Labelled "index_of_field", index_of_field_expr ; Labelled "allow_extra_fields", allow_extra_fields_expr ; Labelled "create", create_expr ] in types, args ;; (* Generate matching code for records *) let record_of_sexp ~error_source ~typevars ~allow_extra_fields td (loc, flds) = args_for_record_of_sexp td flds ~loc ~error_source ~typevars ~constructor:None ~allow_extra_fields >>| fun (types, args) -> let conv = pexp_apply ~loc [%expr Sexplib0.Sexp_conv_record.record_of_sexp] args |> Conversion.of_reference_exn in Conversion.bind_types conv types ;; (* Sum type conversions *) (* Generate matching code for well-formed S-expressions wrt. sum types *) let mk_good_sum_matches ~error_source ~typevars td (_, cds) = List.map cds ~f:(fun cd -> let loc = cd.pcd_loc in match cd with | { pcd_name = constructor; pcd_args = Pcstr_record fields; _ } -> let allow_extra_fields = Option.is_some (Attribute.get Attrs.allow_extra_fields_cd cd) in args_for_record_of_sexp td fields ~loc ~error_source ~typevars ~constructor:(Some (Located.map_lident constructor)) ~allow_extra_fields >>| fun (types, args) -> let string_pat = let loc = constructor.loc in ppat_or ~loc (pstring ~loc (String.uncapitalize constructor.txt)) (pstring ~loc constructor.txt) in let fresh_sexp = Fresh_name.create "sexp" ~loc in let fresh_sexps = Fresh_name.create "sexps" ~loc in ppat_alias ~loc [%pat? Sexplib0.Sexp.List (Sexplib0.Sexp.Atom [%p string_pat] :: [%p Fresh_name.pattern fresh_sexps])] (Fresh_name.to_string_loc fresh_sexp) --> (pexp_apply ~loc [%expr Sexplib0.Sexp_conv_record.record_of_sexps] (List.concat [ [ Labelled "context", Fresh_name.expression fresh_sexp ] ; args ; [ Nolabel, Fresh_name.expression fresh_sexps ] ]) |> with_types ~loc ~types) | { pcd_name = cnstr; pcd_args = Pcstr_tuple []; _ } -> Attrs.fail_if_allow_extra_field_cd ~loc cd; let lcstr = pstring ~loc (String.uncapitalize cnstr.txt) in let str = pstring ~loc cnstr.txt in [%pat? Sexplib0.Sexp.Atom ([%p lcstr] | [%p str])] --> pexp_construct ~loc (Located.lident ~loc cnstr.txt) None |> Lifted.return | { pcd_name = cnstr; pcd_args = Pcstr_tuple (_ :: _ as tps); _ } -> Attrs.fail_if_allow_extra_field_cd ~loc cd; let lcstr = pstring ~loc (String.uncapitalize cnstr.txt) in let str = pstring ~loc cnstr.txt in let fresh__sexp = Fresh_name.create "_sexp" ~loc in let fresh__tag = Fresh_name.create "_tag" ~loc in let fresh_sexp_args = Fresh_name.create "sexp_args" ~loc in ppat_alias ~loc [%pat? Sexplib0.Sexp.List (Sexplib0.Sexp.Atom [%p ppat_alias ~loc [%pat? [%p lcstr] | [%p str]] (Fresh_name.to_string_loc fresh__tag)] :: [%p Fresh_name.pattern fresh_sexp_args])] (Fresh_name.to_string_loc fresh__sexp) --> mk_cnstr_args_match ~error_source ~typevars ~loc ~is_variant:false ~fresh__sexp ~fresh__tag ~fresh_sexp_args cnstr.txt tps (Constructor cd) |> Lifted.return) ;; (* Generate matching code for malformed S-expressions with good tags wrt. sum types *) let mk_bad_sum_matches ~error_source (loc, cds) = let fresh_sexp = Fresh_name.create "sexp" ~loc in List.map cds ~f:(function | { pcd_name = cnstr; pcd_args = Pcstr_tuple []; _ } -> let lcstr = pstring ~loc (String.uncapitalize cnstr.txt) in let str = pstring ~loc cnstr.txt in ppat_alias ~loc [%pat? Sexplib0.Sexp.List (Sexplib0.Sexp.Atom ([%p lcstr] | [%p str]) :: _)] (Fresh_name.to_string_loc fresh_sexp) --> [%expr Sexplib0.Sexp_conv_error.stag_no_args [%e error_source ()] [%e Fresh_name.expression fresh_sexp]] | { pcd_name = cnstr; pcd_args = Pcstr_tuple (_ :: _) | Pcstr_record _; _ } -> let lcstr = pstring ~loc (String.uncapitalize cnstr.txt) in let str = pstring ~loc cnstr.txt in ppat_alias ~loc [%pat? Sexplib0.Sexp.Atom ([%p lcstr] | [%p str])] (Fresh_name.to_string_loc fresh_sexp) --> [%expr Sexplib0.Sexp_conv_error.stag_takes_args [%e error_source ()] [%e Fresh_name.expression fresh_sexp]]) ;; (* Generate matching code for sum types *) let sum_of_sexp ~error_source ~typevars td (loc, alts) = let fresh_sexp = Fresh_name.create "sexp" ~loc in [ mk_good_sum_matches ~error_source ~typevars td (loc, alts) |> Lifted.all ; mk_bad_sum_matches ~error_source (loc, alts) |> Lifted.return ; [ ppat_alias ~loc [%pat? Sexplib0.Sexp.List (Sexplib0.Sexp.List _ :: _)] (Fresh_name.to_string_loc fresh_sexp) --> [%expr Sexplib0.Sexp_conv_error.nested_list_invalid_sum [%e error_source ()] [%e Fresh_name.expression fresh_sexp]] ; ppat_alias ~loc [%pat? Sexplib0.Sexp.List []] (Fresh_name.to_string_loc fresh_sexp) --> [%expr Sexplib0.Sexp_conv_error.empty_list_invalid_sum [%e error_source ()] [%e Fresh_name.expression fresh_sexp]] ; Fresh_name.pattern fresh_sexp --> [%expr Sexplib0.Sexp_conv_error.unexpected_stag [%e error_source ()] [%e Fresh_name.expression fresh_sexp]] ] |> Lifted.return ] |> Lifted.all >>| List.concat >>| Conversion.of_lambda ;; (* Empty type *) let nil_of_sexp ~error_source loc : Conversion.t = Conversion.of_reference_exn [%expr Sexplib0.Sexp_conv_error.empty_type [%e error_source ()]] ;; (* Generate code from type definitions *) let td_of_sexp ~typevars ~loc:_ ~poly ~path ~rec_flag ~values_being_defined td = let tps = List.map td.ptype_params ~f:get_type_param_name in let { ptype_name = { txt = type_name; loc = _ }; ptype_loc = loc; _ } = td in let full_type = core_type_of_type_declaration td |> replace_variables_by_underscores in let is_private = match td.ptype_private with | Private -> true | Public -> false in if is_private then Location.raise_errorf ~loc "of_sexp is not supported for private type"; let create_internal_function = match is_polymorphic_variant td ~sig_:false with | `Definitely -> true | `Maybe -> poly | `Surely_not -> if poly then Location.raise_errorf ~loc "sexp_poly annotation on a type that is surely not a polymorphic variant"; false in let body ~error_source = let body = match td.ptype_kind with | Ptype_variant alts -> Attrs.fail_if_allow_extra_field_td ~loc td; sum_of_sexp ~error_source ~typevars td (td.ptype_loc, alts) | Ptype_record lbls -> record_of_sexp ~error_source ~typevars ~allow_extra_fields: (Option.is_some (Attribute.get Attrs.allow_extra_fields_td td)) td (loc, lbls) | Ptype_open -> Location.raise_errorf ~loc "ppx_sexp_conv: open types not supported" | Ptype_abstract -> Attrs.fail_if_allow_extra_field_td ~loc td; (match td.ptype_manifest with | None -> nil_of_sexp ~error_source td.ptype_loc |> Lifted.return | Some ty -> type_of_sexp ~error_source ~full_type ~typevars ~internal:create_internal_function ty |> Lifted.return) in (* Prevent violation of value restriction, problems with recursive types, and toplevel effects by eta-expanding function definitions *) body >>| Conversion.to_value_expression ~loc ~rec_flag ~values_being_defined in let external_name = type_name ^ "_of_sexp" in let internal_name = "__" ^ type_name ^ "_of_sexp__" in let arg_patts, arg_exprs = List.unzip (List.map ~f:(fun tp -> let name = Map.find_exn typevars tp.txt in Fresh_name.pattern name, Fresh_name.expression name) tps) in let full_type_name = Printf.sprintf "%s.%s" path type_name in let internal_fun_body = if create_internal_function then Some (with_error_source ~loc ~full_type_name (fun ~error_source -> body ~error_source >>| fun body -> eta_reduce_if_possible_and_nonrec ~rec_flag (eabstract ~loc arg_patts body))) else None in let external_fun_body = let body_below_lambdas ~error_source = let fresh_sexp = Fresh_name.create "sexp" ~loc in if create_internal_function then ( let no_variant_match_mc = [ [%pat? Sexplib0.Sexp_conv_error.No_variant_match] --> [%expr Sexplib0.Sexp_conv_error.no_matching_variant_found [%e error_source ()] [%e Fresh_name.expression fresh_sexp]] ] in let internal_call = let internal_expr = pexp_ident ~loc { loc; txt = Lident internal_name } in eapply ~loc internal_expr (arg_exprs @ [ Fresh_name.expression fresh_sexp ]) in let try_with = pexp_try ~loc internal_call no_variant_match_mc in [%expr fun [%p Fresh_name.pattern fresh_sexp] -> [%e try_with]] |> Lifted.return) else body ~error_source in let body_with_lambdas ~error_source = body_below_lambdas ~error_source >>| fun body -> eta_reduce_if_possible_and_nonrec ~rec_flag (eabstract ~loc arg_patts body) in with_error_source ~loc ~full_type_name body_with_lambdas in let typ = Sig_generate_of_sexp.mk_type td in let mk_binding func_name body = constrained_function_binding loc td typ ~tps ~func_name body in let internal_bindings = match internal_fun_body with | None -> [] | Some body -> [ mk_binding internal_name body ] in let external_binding = mk_binding external_name external_fun_body in internal_bindings, [ external_binding ] ;; (* Generate code from type definitions *) let tds_of_sexp ~loc ~poly ~path (rec_flag, tds) = let tds = List.map ~f:name_type_params_in_td tds in let typevars td = List.fold td.ptype_params ~init:(Map.empty (module String)) ~f:(fun map param -> let name = get_type_param_name param in Map.set map ~key:name.txt ~data:(Fresh_name.create ("_of_" ^ name.txt) ~loc:name.loc)) in let singleton = match tds with | [ _ ] -> true | _ -> false in let values_being_defined = List.map tds ~f:(fun td -> td.ptype_name.txt ^ "_of_sexp") |> Set.of_list (module String) in if singleton then ( let rec_flag = really_recursive_respecting_opaque rec_flag tds in match rec_flag with | Recursive -> let bindings = List.concat_map tds ~f:(fun td -> let typevars = typevars td in let internals, externals = td_of_sexp ~typevars ~loc ~poly ~path ~rec_flag ~values_being_defined td in internals @ externals) in pstr_value_list ~loc Recursive bindings | Nonrecursive -> List.concat_map tds ~f:(fun td -> let typevars = typevars td in let internals, externals = td_of_sexp ~typevars ~loc ~poly ~path ~rec_flag ~values_being_defined td in pstr_value_list ~loc Nonrecursive internals @ pstr_value_list ~loc Nonrecursive externals)) else ( let bindings = List.concat_map tds ~f:(fun td -> let typevars = typevars td in let internals, externals = td_of_sexp ~typevars ~poly ~loc ~path ~rec_flag ~values_being_defined td in internals @ externals) in pstr_value_list ~loc rec_flag bindings) ;; let core_type_of_sexp ~path core_type = let loc = { core_type.ptyp_loc with loc_ghost = true } in let full_type_name = Printf.sprintf "%s line %i: %s" path loc.loc_start.pos_lnum (string_of_core_type core_type) in with_error_source ~loc ~full_type_name (fun ~error_source -> type_of_sexp ~error_source ~typevars:(Map.empty (module String)) core_type |> Conversion.to_value_expression ~loc ~rec_flag:Nonrecursive ~values_being_defined:(Set.empty (module String)) |> Merlin_helpers.hide_expression |> Lifted.return) ;; end