package ppxlib

  1. Overview
  2. Docs
Legend:
Page
Library
Module
Module type
Parameter
Class
Class type
Source

Source file common.ml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
open! Import
open Ast_builder.Default
module Buffer = Stdlib.Buffer
module Format = Stdlib.Format

let lident x = Longident.Lident x

let core_type_of_type_declaration td =
  let loc = td.ptype_name.loc in
  ptyp_constr ~loc
    (Located.map lident td.ptype_name)
    (List.map td.ptype_params ~f:fst)

let strip_gen_symbol_suffix =
  let chop n ~or_more string pos f =
    let target = !pos - n in
    while !pos > 0 && (or_more || !pos > target) && f string.[!pos - 1] do
      pos := !pos - 1
    done;
    !pos <= target
  in
  fun string ->
    let pos = ref (String.length string) in
    if
      chop 1 ~or_more:false string pos (Char.equal '_')
      && chop 3 ~or_more:true string pos (function
           | '0' .. '9' -> true
           | _ -> false)
      && chop 2 ~or_more:false string pos (Char.equal '_')
    then String.prefix string !pos
    else string

let gen_symbol =
  let cnt = ref 0 in
  fun ?(prefix = "_x") () ->
    cnt := !cnt + 1;
    let prefix = strip_gen_symbol_suffix prefix in
    Printf.sprintf "%s__%03i_" prefix !cnt

let name_type_params_in_td_res (td : type_declaration) :
    (type_declaration, _) result =
  let open Result in
  let prefix_string i =
    (* a, b, ..., y, z, aa, bb, ... *)
    String.make ((i / 26) + 1) (Char.chr (Char.code 'a' + (i mod 26)))
  in
  let name_param i (tp, variance) =
    (match tp.ptyp_desc with
    | Ptyp_any -> Ok (Ptyp_var (gen_symbol ~prefix:(prefix_string i) ()))
    | Ptyp_var _ as v -> Ok v
    | _ ->
        Error (Location.Error.createf ~loc:tp.ptyp_loc "not a type parameter"))
    >>| fun ptyp_desc -> ({ tp with ptyp_desc }, variance)
  in
  let ptype_params, errors =
    td.ptype_params |> List.mapi ~f:name_param
    |> List.partition_map (function
         | Ok o -> Either.Left o
         | Error e -> Either.Right e)
  in
  match errors with [] -> Ok { td with ptype_params } | t :: q -> Error (t, q)

let name_type_params_in_td (td : type_declaration) : type_declaration =
  match name_type_params_in_td_res td with
  | Ok res -> res
  | Error (err, _) -> Location.Error.raise err

let combinator_type_of_type_declaration td ~f =
  let td = name_type_params_in_td td in
  let result_type =
    f ~loc:td.ptype_name.loc (core_type_of_type_declaration td)
  in
  List.fold_right td.ptype_params ~init:result_type
    ~f:(fun (tp, _variance) acc ->
      let loc = tp.ptyp_loc in
      ptyp_arrow ~loc Nolabel (f ~loc tp) acc)

let string_of_core_type ct =
  let buf = Buffer.create 128 in
  let ppf = Format.formatter_of_buffer buf in
  Pprintast.core_type ppf ct;
  Format.pp_print_flush ppf ();
  Buffer.contents buf

let get_type_param_name_res (ty, _) =
  let loc = ty.ptyp_loc in
  match ty.ptyp_desc with
  | Ptyp_var name -> Ok (Located.mk ~loc name)
  | _ -> Error (Location.Error.createf ~loc "not a type parameter", [])

let get_type_param_name t =
  match get_type_param_name_res t with
  | Ok e -> e
  | Error (err, _) -> Location.Error.raise err

exception Type_is_recursive

class type_is_recursive rec_flag tds =
  object (self)
    inherit Ast_traverse0.iter as super
    val type_names : string list = List.map tds ~f:(fun td -> td.ptype_name.txt)
    method return_true () = raise_notrace Type_is_recursive

    method! core_type ctype =
      match ctype.ptyp_desc with
      | Ptyp_arrow _ -> ()
      | Ptyp_constr ({ txt = Longident.Lident id; _ }, _)
        when List.mem ~set:type_names id ->
          self#return_true ()
      | _ -> super#core_type ctype

    method! constructor_declaration cd =
      (* Don't recurse through cd.pcd_res *)
      match cd.pcd_args with
      | Pcstr_tuple args -> List.iter args ~f:self#core_type
      | Pcstr_record fields -> List.iter fields ~f:self#label_declaration

    method! attributes _ = (* Don't recurse through attributes *)
                           ()

    method go () =
      match rec_flag with
      | Nonrecursive -> Nonrecursive
      | Recursive -> (
          match List.iter tds ~f:self#type_declaration with
          | exception Type_is_recursive -> Recursive
          | () -> Nonrecursive)
  end

let really_recursive rec_flag tds = (new type_is_recursive rec_flag tds)#go ()
let rec last x l = match l with [] -> x | x :: l -> last x l

let loc_of_name_and_payload name payload =
  match payload with
  | PStr [] -> name.loc
  | PStr (x :: l) -> { x.pstr_loc with loc_end = (last x l).pstr_loc.loc_end }
  | PSig [] -> name.loc
  | PSig (x :: l) -> { x.psig_loc with loc_end = (last x l).psig_loc.loc_end }
  | PTyp t -> t.ptyp_loc
  | PPat (x, None) -> x.ppat_loc
  | PPat (x, Some e) -> { x.ppat_loc with loc_end = e.pexp_loc.loc_end }

let loc_of_payload { attr_name; attr_payload; attr_loc = _ } =
  loc_of_name_and_payload attr_name attr_payload

let loc_of_attribute { attr_name; attr_payload; attr_loc = _ } =
  (* TODO: fix this in the compiler, and move the logic to omp when converting
     from older asts. *)
  (* "ocaml.doc" attributes are generated with [Location.none], which is not helpful for
     error messages. *)
  if Poly.( = ) attr_name.loc Location.none then
    loc_of_name_and_payload attr_name attr_payload
  else
    {
      attr_name.loc with
      loc_end = (loc_of_name_and_payload attr_name attr_payload).loc_end;
    }

let loc_of_extension (name, payload) =
  if Poly.( = ) name.loc Location.none then loc_of_name_and_payload name payload
  else
    { name.loc with loc_end = (loc_of_name_and_payload name payload).loc_end }

let curry_applications expr =
  let open Ast_builder_generated.M in
  match expr.pexp_desc with
  | Pexp_apply (f, orig_forward_args) ->
      let loc = expr.pexp_loc in
      let rec loop = function
        | [] -> f
        | last_arg :: rev_front_args ->
            pexp_apply ~loc (loop rev_front_args) [ last_arg ]
      in
      loop (List.rev orig_forward_args)
  | _ -> expr

let attributes_errors =
  List.filter_map ~f:(function
    | { attr_name = name; attr_loc = _; attr_payload = _ }
      when Name.ignore_checks name.Location.txt ->
        None
    | attr ->
        let loc = loc_of_attribute attr in
        Some (Location.Error.createf ~loc "Attributes not allowed here"))

let collect_attributes_errors =
  object
    inherit [Location.Error.t list] Ast_traverse0.fold
    method! attribute a acc = attributes_errors [ a ] @ acc
  end

let assert_no_attributes l =
  match attributes_errors l with
  | [] -> ()
  | err :: _ -> Location.Error.raise err

let assert_no_attributes_in =
  object
    inherit Ast_traverse0.iter
    method! attribute a = assert_no_attributes [ a ]
  end

let attribute_of_warning loc s =
  {
    attr_name = { loc; txt = "ocaml.ppwarning" };
    attr_payload = PStr [ pstr_eval ~loc (estring ~loc s) [] ];
    attr_loc = loc;
  }

let is_polymorphic_variant =
  let rec check = function
    | { ptyp_desc = Ptyp_variant _; _ } -> `Definitely
    | { ptyp_desc = Ptyp_alias (typ, _); _ } -> check typ
    | { ptyp_desc = Ptyp_constr _; _ } -> `Maybe
    | _ -> `Surely_not
    (* Type vars go here even though they could be polymorphic
       variants, however we don't handle it if they get substituted
       by a polymorphic variant that is then included. *)
  in
  fun td ~sig_ ->
    match td.ptype_kind with
    | Ptype_variant _ | Ptype_record _ | Ptype_open -> `Surely_not
    | Ptype_abstract -> (
        match td.ptype_manifest with
        | None -> if sig_ then `Maybe else `Surely_not
        | Some typ -> check typ)

let mk_named_sig ~loc ~sg_name ~handle_polymorphic_variant = function
  | [ td ]
    when String.equal td.ptype_name.txt "t" && List.is_empty td.ptype_cstrs ->
      if
        (not handle_polymorphic_variant)
        && Poly.( = ) (is_polymorphic_variant td ~sig_:true) `Definitely
      then None
      else
        let arity = List.length td.ptype_params in
        if arity >= 4 then None
        else
          let mty =
            if arity = 0 then sg_name else Printf.sprintf "%s%d" sg_name arity
          in
          let td = name_type_params_in_td td in
          let for_subst =
            Ast_helper.Type.mk ~loc td.ptype_name ~params:td.ptype_params
              ~manifest:
                (ptyp_constr ~loc
                   (Located.map_lident td.ptype_name)
                   (List.map ~f:fst td.ptype_params))
          in
          Some
            (include_infos ~loc
               (pmty_with ~loc
                  (pmty_ident ~loc (Located.lident mty ~loc))
                  [ Pwith_typesubst (Located.lident ~loc "t", for_subst) ]))
  | _ -> None

let exn_to_loc_error exn =
  match Location.Error.of_exn exn with Some error -> error | None -> raise exn

module With_errors = struct
  type 'a t = 'a * Location.Error.t list

  let return e = (e, [])

  let ( >>= ) (x, errors1) f =
    let y, errors2 = f x in
    (y, errors1 @ errors2)

  let ( >>| ) (x, errors) f = (f x, errors)

  let of_result result ~default =
    match result with
    | Ok x -> (x, [])
    | Error errors -> (default, NonEmptyList.to_list errors)

  let combine_errors list = (List.map list ~f:fst, List.concat_map list ~f:snd)
end
OCaml

Innovation. Community. Security.