package ppx_deriving_rpc

  1. Overview
  2. Docs

Source file ppx_deriving_rpcty.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
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
open Ppxlib
open Ast_builder.Default
open Common

let deriver = "rpcty"

let typ_of_f s =
  match s with
  | "t" -> "typ_of"
  | s -> "typ_of_" ^ s


(* For these types we have convertors in rpc.ml *)

module Typ_of = struct
  (** [expr_of_typ ~loc typ] returns an expression of type `Rpc.Types.typ` that 
      represents the  core_type typ`. *)
  let rec expr_of_typ ~loc typ =
    let expr =
      match typ with
      | { ptyp_desc = Ptyp_constr ({ txt = Lident name; _ }, _); _ }
        when list_assoc_mem (core_types loc) ~equal:String.equal name ->
        list_assoc_find_exn (core_types loc) ~equal:String.equal name
      | { ptyp_desc = Ptyp_constr ({ txt = Lident "char"; _ }, _); _ } ->
        [%expr Rpc.Types.(Basic Char)]
      | [%type: (string * [%t? typ]) list] ->
        [%expr Rpc.Types.Dict (Rpc.Types.String, [%e expr_of_typ ~loc typ])]
      | [%type: [%t? typ] list] -> [%expr Rpc.Types.List [%e expr_of_typ ~loc typ]]
      | [%type: [%t? typ] array] -> [%expr Rpc.Types.Array [%e expr_of_typ ~loc typ]]
      | { ptyp_desc = Ptyp_tuple [ t1; t2 ]; _ } ->
        [%expr Rpc.Types.Tuple ([%e expr_of_typ ~loc t1], [%e expr_of_typ ~loc t2])]
      | { ptyp_desc = Ptyp_tuple [ t1; t2; t3 ]; _ } ->
        [%expr
          Rpc.Types.Tuple3
            ([%e expr_of_typ ~loc t1], [%e expr_of_typ ~loc t2], [%e expr_of_typ ~loc t3])]
      | { ptyp_desc = Ptyp_tuple [ t1; t2; t3; t4 ]; _ } ->
        [%expr
          Rpc.Types.Tuple4
            ( [%e expr_of_typ ~loc t1]
            , [%e expr_of_typ ~loc t2]
            , [%e expr_of_typ ~loc t3]
            , [%e expr_of_typ ~loc t4] )]
      | { ptyp_desc = Ptyp_tuple _; _ } ->
        failwith "Tuples with arity > 4 are not supported"
      | [%type: [%t? typ] option] -> [%expr Rpc.Types.Option [%e expr_of_typ ~loc typ]]
      | { ptyp_desc = Ptyp_constr (lid, _); _ } ->
        type_constr_conv lid ~loc ~f:typ_of_f []
      | { ptyp_desc = Ptyp_variant (_, _, _); _ } -> failwith "Ptyp_variant not handled"
      | { ptyp_desc = Ptyp_any; _ } -> failwith "Ptyp_any not handled"
      | { ptyp_desc = Ptyp_var name; _ } -> evar ~loc ("poly_" ^ name)
      | { ptyp_desc = Ptyp_poly (_, _); _ } -> failwith "Ptyp_poly not handled"
      | { ptyp_desc = Ptyp_extension _; _ } -> failwith "Ptyp_extension not handled"
      | { ptyp_desc = Ptyp_arrow (_, _, _); _ } -> failwith "Ptyp_arrow not handled"
      | { ptyp_desc = Ptyp_object (_, _); _ } -> failwith "Ptyp_object not handled"
      | { ptyp_desc = Ptyp_alias (_, _); _ } -> failwith "Ptyp_alias not handled"
      | { ptyp_desc = Ptyp_class (_, _); _ } -> failwith "Ptyp_class not handled"
      | { ptyp_desc = Ptyp_package _; _ } -> failwith "Ptyp_package not handled"
    in
    expr


  type field_def =
    { f_name : string
    ; rpc_name : string
    ; value_name : string
    ; expr : expression
    ; default : expression option
    ; typ : expression
    }

  (** [str_of_type loc type_decl] returns a list of value bindings that are values of
      type `Rpc.Types.def` and `Rpc.Types.typ` that represent the newly defined type.
       *)
  let str_of_type loc type_decl =
    let module Ast_builder = (val Ast_builder.make loc) in
    let open Ast_builder in
    let name = type_decl.ptype_name.txt in
    let core_type = core_type_of_type_declaration type_decl in
    let polymorphize = poly_fun_of_type_decl ~loc type_decl in
    let typ_of_lid = typ_of_f name in
    let typ_of =
      match type_decl.ptype_kind, type_decl.ptype_manifest with
      | Ptype_abstract, Some manifest ->
        [ value_binding
            ~pat:(pvar typ_of_lid)
            ~expr:(polymorphize (expr_of_typ ~loc manifest))
        ]
      | Ptype_record labels, _ ->
        let one_field =
          match labels with
          | [ _ ] -> true
          | _ -> false
        in
        let analyse_field label_declaration =
          let f_name = label_declaration.pld_name.txt in
          let { pld_type; pld_attributes; _ } = label_declaration in
          let rpc_name =
            Option.value (Attribute.get Attrs.key label_declaration) ~default:f_name
          in
          let value_name = string_concat ~sep:"_" [ name; f_name ] in
          let default = Attribute.get Attrs.label_default label_declaration in
          let version = Attribute.get Attrs.label_version label_declaration in
          let typ =
            Option.value
              (Attribute.get Attrs.label_typ label_declaration)
              ~default:(expr_of_typ ~loc pld_type)
          in
          let doc =
            Common.get_doc
              ~loc
              (Attribute.get Attrs.label_doc label_declaration)
              pld_attributes
          in
          let fget =
            [%expr fun _r -> [%e pexp_field (evar "_r") (Located.mk (Lident f_name))]]
          in
          let fset =
            [%expr
              fun v _s ->
                [%e
                  pexp_record
                    [ Located.mk (lident f_name), [%expr v] ]
                    (if one_field then None else Some [%expr _s])]]
          in
          let expr =
            pexp_record
              (ListLabels.map
                 ~f:(fun (x, y) -> Located.mk (Ldot (Ldot (Lident "Rpc", "Types"), x)), y)
                 [ "fname", estring rpc_name
                 ; "field", expr_of_typ ~loc pld_type
                 ; "fdefault", expr_of_option ~loc default
                 ; "fdescription", doc
                 ; "fversion", expr_of_option ~loc version
                 ; "fget", fget
                 ; "fset", fset
                 ])
              None
          in
          { f_name; rpc_name; value_name; expr; default; typ }
        in
        let fields = ListLabels.map ~f:analyse_field labels in
        let field_name_bindings =
          ListLabels.map
            ~f:(fun fld ->
              value_binding
                ~pat:
                  (ppat_constraint
                     (pvar fld.value_name)
                     [%type: (_, [%t core_type]) Rpc.Types.field])
                ~expr:fld.expr)
            fields
        in
        let boxed_fields =
          elist
            (ListLabels.map
               ~f:(fun fld ->
                 [%expr
                   Rpc.Types.BoxedField
                     [%e pexp_ident (Located.mk (lident fld.value_name))]])
               fields)
        in
        let construct_record =
          ListLabels.fold_left
            ~f:(fun expr fld ->
              match fld.default with
              | Some d ->
                [%expr
                  (match
                     getter.Rpc.Types.field_get [%e estring fld.rpc_name] [%e fld.typ]
                   with
                  | Result.Ok _ as y -> y
                  | Result.Error _ -> Result.Ok [%e d])
                  >>= fun [%p pvar fld.value_name] -> [%e expr]]
              | None ->
                [%expr
                  getter.Rpc.Types.field_get [%e estring fld.rpc_name] [%e fld.typ]
                  >>= fun [%p pvar fld.value_name] -> [%e expr]])
            ~init:
              [%expr
                return
                  [%e
                    pexp_record
                      (ListLabels.map
                         ~f:(fun fld ->
                           Located.mk (lident fld.f_name), evar fld.value_name)
                         fields)
                      None]]
            fields
        in
        field_name_bindings
        @ [ value_binding
              ~pat:(pvar typ_of_lid)
              ~expr:
                (polymorphize
                   [%expr
                     Rpc.Types.Struct
                       ({ Rpc.Types.fields = [%e boxed_fields]
                        ; Rpc.Types.sname = [%e estring name]
                        ; Rpc.Types.version =
                            [%e
                              expr_of_option
                                ~loc
                                (Attribute.get Attrs.td_version type_decl)]
                        ; Rpc.Types.constructor =
                            (fun getter ->
                              let open Rresult.R in
                              [%e construct_record])
                        }
                         : [%t core_type] Rpc.Types.structure)])
          ]
      | Ptype_abstract, None -> failwith "Unhandled"
      | Ptype_open, _ -> failwith "Unhandled"
      | Ptype_variant constrs, _ ->
        let default_case = Attribute.get Attrs.td_default type_decl in
        let argn = Printf.sprintf "a%d" in
        let analyse_case constructor =
          let { pcd_name = { txt = cname; _ }; pcd_args; _ } = constructor in
          let rpc_name =
            Option.value (Attribute.get Attrs.constr_name constructor) ~default:cname
          in
          let lower_rpc_name = String.lowercase_ascii rpc_name in
          let typs =
            match pcd_args with
            | Pcstr_tuple typs -> typs
            | Pcstr_record _ ->
              Location.raise_errorf "%s: record variants are not supported" deriver
          in
          let contents =
            match typs with
            | [] -> [%expr Unit]
            | [ t1 ] -> expr_of_typ ~loc t1
            | [ t1; t2 ] ->
              [%expr Tuple ([%e expr_of_typ ~loc t1], [%e expr_of_typ ~loc t2])]
            | [ t1; t2; t3 ] ->
              [%expr
                Tuple3
                  ( [%e expr_of_typ ~loc t1]
                  , [%e expr_of_typ ~loc t2]
                  , [%e expr_of_typ ~loc t3] )]
            | [ t1; t2; t3; t4 ] ->
              [%expr
                Tuple4
                  ( [%e expr_of_typ ~loc t1]
                  , [%e expr_of_typ ~loc t2]
                  , [%e expr_of_typ ~loc t3]
                  , [%e expr_of_typ ~loc t4] )]
            | _ ->
              Location.raise_errorf "%s: Tuples with arity > 4 are not supported" deriver
          in
          let version =
            expr_of_option ~loc @@ Attribute.get Attrs.constr_version constructor
          in
          let doc =
            Common.get_doc
              ~loc
              (Attribute.get Attrs.constr_doc constructor)
              constructor.pcd_attributes
          in
          let args = ListLabels.mapi ~f:(fun i _ -> evar (argn i)) typs in
          let pattern = ListLabels.mapi ~f:(fun i _ -> pvar (argn i)) typs in
          let pat = ppat_tuple_opt pattern in
          let pat' = Option.value pat ~default:punit in
          let opt_args = pexp_tuple_opt args in
          let expr = Option.value opt_args ~default:eunit in
          let constr = pexp_construct (Located.mk (lident cname)) opt_args in
          let vpreview_default =
            if List.length constrs = 1
            then []
            else [ case ~guard:None ~lhs:ppat_any ~rhs:[%expr None] ]
          in
          let vpreview =
            pexp_function
              ([ case
                   ~lhs:(ppat_construct (Located.mk (lident cname)) pat)
                   ~guard:None
                   ~rhs:[%expr Some [%e expr]]
               ]
              @ vpreview_default)
          in
          let vreview = pexp_function [ case ~lhs:pat' ~guard:None ~rhs:constr ] in
          let variant =
            [%expr
              BoxedTag
                [%e
                  pexp_record
                    (ListLabels.map
                       ~f:(fun (x, y) ->
                         Located.mk (Ldot (Ldot (Lident "Rpc", "Types"), x)), y)
                       [ "tname", estring rpc_name
                       ; "tcontents", contents
                       ; "tversion", version
                       ; "tdescription", doc
                       ; "tpreview", vpreview
                       ; "treview", vreview
                       ])
                    None]]
          in
          let vconstructor_case =
            case
              ~lhs:(pstring lower_rpc_name)
              ~guard:None
              ~rhs:
                [%expr
                  Rresult.R.bind
                    (t.tget [%e contents])
                    [%e
                      pexp_function
                        [ case ~lhs:pat' ~guard:None ~rhs:[%expr Rresult.R.ok [%e constr]]
                        ]]]
          in
          variant, vconstructor_case
        in
        let cases = ListLabels.map ~f:analyse_case constrs in
        let default =
          [ case
              ~lhs:ppat_any
              ~guard:None
              ~rhs:
                (match default_case with
                | None ->
                  [%expr Rresult.R.error_msg (Printf.sprintf "Unknown tag '%s'" s)]
                | Some d -> [%expr Result.Ok [%e d]])
          ]
        in
        let version = Attribute.get Attrs.td_version type_decl in
        let vconstructor =
          [%expr
            fun s' t ->
              let s = String.lowercase_ascii s' in
              [%e pexp_match (evar "s") (ListLabels.map ~f:snd cases @ default)]]
        in
        [ value_binding
            ~pat:(pvar typ_of_lid)
            ~expr:
              (polymorphize
                 [%expr
                   Rpc.Types.Variant
                     ({ Rpc.Types.vname = [%e estring name]
                      ; Rpc.Types.variants = [%e elist (ListLabels.map ~f:fst cases)]
                      ; Rpc.Types.vdefault = [%e expr_of_option ~loc default_case]
                      ; Rpc.Types.vversion = [%e expr_of_option ~loc version]
                      ; Rpc.Types.vconstructor = [%e vconstructor]
                      }
                       : [%t core_type] Rpc.Types.variant)])
        ]
    in
    let doc =
      get_doc ~loc (Attribute.get Attrs.td_doc type_decl) type_decl.ptype_attributes
    in
    let name = type_decl.ptype_name.txt in
    typ_of
    @ [ value_binding
          ~pat:(pvar name)
          ~expr:
            (polymorphize
               (pexp_record
                  (ListLabels.map
                     ~f:(fun (x, y) ->
                       Located.mk (Ldot (Ldot (Lident "Rpc", "Types"), x)), y)
                     [ "name", estring name
                     ; "description", doc
                     ; ( "ty"
                       , poly_apply_of_type_decl
                           ~loc
                           type_decl
                           (pexp_ident (Located.mk (lident typ_of_lid))) )
                     ])
                  None))
      ]
end

let my_str_type_decl ~loc ~path:_ (rec_flag, tds) =
  pstr_value_list
    ~loc
    rec_flag
    (List.concat (ListLabels.map ~f:(Typ_of.str_of_type loc) tds))


let str_type_decl = Deriving.Generator.make_noarg my_str_type_decl
let deriver = Deriving.add deriver ~str_type_decl