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
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
open Std
open Typedtree
let { Logger.log } = Logger.for_section "construct"
type values_scope = Null | Local
type what = Modtype | Mod
exception Not_allowed of string
exception Not_a_hole
exception Modtype_not_found of what * string
exception No_constraint
let () =
Location.register_error_of_exn (function
| Not_a_hole -> Some (Location.error "Construct only works on holes.")
| Modtype_not_found (Modtype, s) ->
let txt = Format.sprintf "Module type not found: %s" s in
Some (Location.error txt)
| Modtype_not_found (Mod, s) ->
let txt = Format.sprintf "Module not found: %s" s in
Some (Location.error txt)
| No_constraint ->
Some
(Location.error
"Could not find a module type to construct from. Check that you \
used a correct constraint.")
| _ -> None)
module Util = struct
open Misc_utils.Path
open Types
let predef_types =
let tbl = Hashtbl.create 14 in
let () =
let constant c = Ast_helper.Exp.constant c in
let construct s =
Ast_helper.Exp.construct (Location.mknoloc (Longident.Lident s)) None
in
let ident s =
Ast_helper.Exp.ident (Location.mknoloc (Longident.Lident s))
in
List.iter
~f:(fun (k, v) -> Hashtbl.add tbl k v)
Parsetree.
[ (Predef.path_int, constant (Pconst_integer ("0", None)));
(Predef.path_float, constant (Pconst_float ("0.0", None)));
(Predef.path_char, constant (Pconst_char 'c'));
( Predef.path_string,
constant (Pconst_string ("", Location.none, None)) );
(Predef.path_bool, construct "false");
(Predef.path_unit, construct "()");
(Predef.path_exn, ident "exn");
(Predef.path_array, Ast_helper.Exp.array []);
(Predef.path_nativeint, constant (Pconst_integer ("0", Some 'n')));
(Predef.path_int32, constant (Pconst_integer ("0", Some 'l')));
(Predef.path_int64, constant (Pconst_integer ("0", Some 'L')));
(Predef.path_lazy_t, Ast_helper.Exp.lazy_ (construct "()"))
]
in
tbl
let prefix env ~env_check path name =
to_shortest_lid ~env ~env_check ~name path
let maybe_prefix env ~env_check path name =
match Warnings.is_active (Disambiguated_name "") with
| false -> Longident.Lident name
| true -> prefix env ~env_check path name
let var_of_id id = Location.mknoloc @@ Ident.name id
let type_to_string t =
Printtyp.type_expr Format.str_formatter t;
Format.flush_str_formatter ()
let unifiable env type_expr type_expected =
let snap = Btype.snapshot () in
try
Ctype.unify env type_expected type_expr |> ignore;
Some snap
with Ctype.Unify _ ->
Btype.backtrack snap;
None
let typeable env exp type_expected =
let snap = Btype.snapshot () in
let typeable =
match
Typecore.type_expect env exp (Typecore.mk_expected type_expected)
with
| (_ : Typedtree.expression) -> true
| exception _ -> false
in
if not typeable then
log ~title:"constructor" "%a does not have the expected type %a"
Logger.fmt
(fun fmt -> Printast.expression 0 fmt exp)
Logger.fmt
(fun fmt -> Printtyp.type_expr fmt type_expected);
Btype.backtrack snap;
typeable
let is_in_stdlib path = Path.head path |> Ident.name = "Stdlib"
(** [find_values_for_type env typ] searches the environment [env] for
{i values} with a return type compatible with [typ] *)
let find_values_for_type env typ =
let aux name path value_description acc =
let rec check_type type_expr params =
let type_expr = Transient_expr.repr type_expr in
match unifiable env (Transient_expr.type_expr type_expr) typ with
| Some snap ->
Btype.backtrack snap;
Some params
| None -> begin
match type_expr.desc with
| Tarrow (arg_label, _, te, _) -> check_type te (arg_label :: params)
| _ -> None
end
in
match (is_in_stdlib path, check_type value_description.val_type []) with
| false, Some params ->
Path.Map.add path (name, value_description, params) acc
| _, _ -> acc
in
let fold_values path acc = Env.fold_values aux path env acc in
let init = fold_values None Path.Map.empty in
Env.fold_modules
(fun _name path _module_decl acc ->
if (not (is_in_stdlib path)) && not (is_opened env path) then
fold_values (Some (Untypeast.lident_of_path path)) acc
else acc)
None env init
(** The idents_table is used to keep track of already used names when
generating function arguments in the same expression *)
let idents_table ~keywords =
let table = Hashtbl.create 50 in
List.iter keywords ~f:(fun k -> Hashtbl.add table k (-1));
table
let combinations l =
List.fold_left l ~init:[ [] ] ~f:(fun acc_l choices_for_arg_i ->
List.fold_left choices_for_arg_i ~init:[] ~f:(fun acc choice_arg_i ->
let choices =
List.map acc_l ~f:(fun l -> List.rev (choice_arg_i :: l))
in
List.rev_append acc choices))
(** [panache2 l1 l2] returns a new list containing an interleaving of the
values in [l1] and [l2] *)
let panache2 l1 l2 =
let rec aux acc l1 l2 =
match (l1, l2) with
| [], [] -> List.rev acc
| tl, [] | [], tl -> List.rev_append acc tl
| a :: tl1, b :: tl2 -> aux (a :: b :: acc) tl1 tl2
in
aux [] l1 l2
let panache l = List.fold_left ~init:[] ~f:panache2 l
end
module Gen = struct
open Types
let make_value env (path, (name, _value_description, params)) =
let open Ast_helper in
let env_check = Env.find_value_by_name in
let lid = Location.mknoloc (Util.prefix env ~env_check path name) in
let params = List.map params ~f:(fun label -> (label, Exp.hole ())) in
if List.length params > 0 then Exp.(apply (ident lid) params)
else Exp.ident lid
let rec module_ env =
let open Ast_helper in
function
| Mty_ident path -> begin
try
let m = Env.find_modtype path env in
match m.mtd_type with
| Some t -> module_ env t
| None -> raise Not_found
with Not_found ->
let name = Ident.name (Path.head path) in
raise (Modtype_not_found (Modtype, name))
end
| Mty_signature sig_items ->
let env = Env.add_signature sig_items env in
Mod.structure @@ structure env sig_items
| Mty_functor (param, out) ->
let param =
match param with
| Unit -> Parsetree.Unit
| Named (id, in_) ->
Parsetree.Named
( Location.mknoloc (Option.map ~f:Ident.name id),
Ptyp_of_type.module_type in_ )
in
Mod.functor_ param @@ module_ env out
| Mty_alias path -> begin
try
let m = Env.find_module path env in
module_ env m.md_type
with Not_found ->
let name = Ident.name (Path.head path) in
raise (Modtype_not_found (Mod, name))
end
| Mty_for_hole -> Mod.hole ()
and structure_item env =
let open Ast_helper in
function
| Sig_value (id, _vd, _visibility) ->
let vb = Vb.mk (Pat.var (Util.var_of_id id)) (Exp.hole ()) in
Str.value Nonrecursive [ vb ]
| Sig_type (id, type_declaration, rec_flag, _visibility) ->
let td = Ptyp_of_type.type_declaration id type_declaration in
let rec_flag =
match rec_flag with
| Trec_first | Trec_next -> Asttypes.Recursive
| Trec_not -> Nonrecursive
in
Str.type_ rec_flag [ td ]
| Sig_modtype (id, { mtd_type; _ }, _visibility) ->
let mtd =
Ast_helper.Mtd.mk ?typ:(Option.map ~f:Ptyp_of_type.module_type mtd_type)
@@ Util.var_of_id id
in
Ast_helper.Str.modtype mtd
| Sig_module (id, _, mod_decl, _, _) ->
let module_binding =
Ast_helper.Mb.mk (Location.mknoloc (Some (Ident.name id)))
@@ module_ env mod_decl.md_type
in
Str.module_ module_binding
| Sig_typext (id, ext_constructor, _, _) ->
let lid =
Untypeast.lident_of_path ext_constructor.ext_type_path
|> Location.mknoloc
in
Str.type_extension
@@ Ast_helper.Te.mk ~attrs:ext_constructor.ext_attributes ~params:[]
~priv:ext_constructor.ext_private lid
[ Ptyp_of_type.extension_constructor id ext_constructor ]
| Sig_class_type (id, _class_type_decl, _, _) ->
let str =
Format.asprintf
"Construct does not handle class types yet. Please replace this \
comment by [%s]'s definition."
(Ident.name id)
in
Str.text [ Docstrings.docstring str Location.none ] |> List.hd
| Sig_class (id, _class_decl, _, _) ->
let str =
Format.asprintf
"Construct does not handle classes yet. Please replace this comment \
by [%s]'s definition."
(Ident.name id)
in
Str.text [ Docstrings.docstring str Location.none ] |> List.hd
and structure env (items : Types.signature_item list) =
List.map (Ptyp_of_type.group_items items) ~f:(function
| Ptyp_of_type.Item item -> structure_item env item
| Ptyp_of_type.Type (rec_flag, type_decls) ->
Ast_helper.Str.type_ rec_flag type_decls)
let rec expression ~idents_table values_scope ~depth =
let exp_or_hole env typ =
if depth > 1 then
expression ~idents_table values_scope ~depth:(depth - 1) env typ
else
[ Ast_helper.Exp.hole () ]
in
let arrow_rhs env typ =
match (Transient_expr.repr typ).desc with
| Tarrow _ -> expression ~idents_table values_scope ~depth env typ
| _ -> exp_or_hole env typ
in
let make_arg =
let make_i n i =
Hashtbl.replace idents_table n i;
Printf.sprintf "%s_%i" n i
in
let uniq_name env n =
let id = Ident.create_local n in
try
let i = Hashtbl.find idents_table n + 1 in
make_i n i
with Not_found -> (
try
let _ = Env.find_value (Path.Pident id) env in
make_i n 0
with Not_found ->
Hashtbl.add idents_table n 0;
n)
in
fun env label ty ->
let open Asttypes in
match label with
| Labelled s | Optional s ->
(Ast_helper.Pat.var (Location.mknoloc s), s)
| Nolabel -> begin
match get_desc ty with
| Tconstr (path, _, _) ->
let name = uniq_name env (Path.last path) in
(Ast_helper.Pat.var (Location.mknoloc name), name)
| _ -> (Ast_helper.Pat.any (), "_")
end
in
let constructor env type_expr path constrs =
log ~title:"constructors" "[%s]"
(String.concat ~sep:"; "
(List.map constrs ~f:(fun c -> c.Types.cstr_name)));
let make_constr env path type_expr cstr_descr =
let ty_args, ty_res, _ =
Ctype.instance_constructor Keep_existentials_flexible cstr_descr
in
match Util.unifiable env type_expr ty_res with
| Some snap ->
let lid =
Util.maybe_prefix env ~env_check:Env.find_constructor_by_name path
cstr_descr.cstr_name
|> Location.mknoloc
in
let args = List.map ty_args ~f:(exp_or_hole env) in
let args_combinations = Util.combinations args in
let exps =
List.map args_combinations ~f:(function
| [] -> None
| [ e ] -> Some e
| l -> Some (Ast_helper.Exp.tuple l))
in
Btype.backtrack snap;
List.filter_map exps ~f:(fun exp ->
let exp = Ast_helper.Exp.construct lid exp in
if Util.typeable env exp type_expr then Some exp
else (
log ~title:"constructor" "%s's type is not unifiable with %a"
cstr_descr.Types.cstr_name Logger.fmt (fun fmt ->
Printtyp.type_expr fmt type_expr);
None))
| None -> []
in
List.map constrs ~f:(make_constr env path type_expr)
|> List.rev
|> Util.panache
in
let variant env _typ row_desc =
let fields =
List.filter
~f:(fun (_lbl, row_field) ->
match row_field_repr row_field with
| Rpresent _ | Reither (true, [], _) | Reither (false, [ _ ], _) ->
true
| _ -> false)
(row_fields row_desc)
|> List.rev
in
match fields with
| [] -> raise (Not_allowed "empty variant type")
| row_descrs ->
List.map row_descrs ~f:(fun (lbl, row_field) ->
(match row_field_repr row_field with
| Reither (false, [ ty ], _) | Rpresent (Some ty) ->
List.map ~f:(fun s -> Some s) (exp_or_hole env ty)
| _ -> [ None ])
|> List.map ~f:(fun e -> Ast_helper.Exp.variant lbl e))
|> List.flatten |> List.rev
in
let record env typ path labels =
log ~title:"record labels" "[%s]"
(String.concat ~sep:"; "
(List.map labels ~f:(fun l -> l.Types.lbl_name)));
let labels =
List.map labels ~f:(fun ({ lbl_name; _ } as lbl) ->
let _, arg, res = Ctype.instance_label true lbl in
Ctype.unify env res typ;
let lid =
Util.maybe_prefix env ~env_check:Env.find_label_by_name path
lbl_name
|> Location.mknoloc
in
let exprs = exp_or_hole env arg in
(lid, exprs))
in
let lbl_lids, lbl_exprs = List.split labels in
Util.combinations lbl_exprs
|> List.map ~f:(fun lbl_exprs ->
let labels =
List.map2 lbl_lids lbl_exprs ~f:(fun lid exp -> (lid, exp))
in
Ast_helper.Exp.record labels None)
in
fun env typ ->
log ~title:"construct expr" "Looking for expressions of type %s"
(Util.type_to_string typ);
let rtyp = Ctype.full_expand ~may_forget_scope:true env typ in
let constructed_from_type =
match get_desc rtyp with
| Tlink _ | Tsubst _ -> assert false
| Tpoly (texp, _) ->
expression ~idents_table values_scope ~depth env texp
| Tunivar _ | Tvar _ -> []
| Tconstr (path, [ texp ], _) when path = Predef.path_lazy_t ->
let exps = exp_or_hole env texp in
List.map exps ~f:Ast_helper.Exp.lazy_
| Tconstr (path, _params, _) -> begin
try
[ Hashtbl.find Util.predef_types path ]
with Not_found -> (
let def = Env.find_type_descrs path env in
match def with
| Type_variant (constrs, _) -> constructor env rtyp path constrs
| Type_record (labels, _) -> record env rtyp path labels
| Type_abstract | Type_open -> [])
end
| Tarrow (label, tyleft, tyright, _) ->
let argument, name = make_arg env label tyleft in
let value_description =
{ val_type = tyleft;
val_kind = Val_reg;
val_loc = Location.none;
val_attributes = [];
val_uid = Uid.mk ~current_unit:(Env.get_unit_name ())
}
in
let env =
Env.add_value (Ident.create_local name) value_description env
in
let exps = arrow_rhs env tyright in
List.map exps ~f:(Ast_helper.Exp.fun_ label None argument)
| Ttuple types ->
let choices =
List.map types ~f:(exp_or_hole env) |> Util.combinations
in
List.map choices ~f:Ast_helper.Exp.tuple
| Tvariant row_desc -> variant env rtyp row_desc
| Tpackage (path, lids_args) -> begin
let open Ast_helper in
try
let ty =
Typemod.modtype_of_package env Location.none path lids_args
in
let ast =
Exp.constraint_
(Exp.pack (module_ env ty))
(Ptyp_of_type.core_type typ)
in
[ ast ]
with Typemod.Error _ ->
let name = Ident.name (Path.head path) in
raise (Modtype_not_found (Modtype, name))
end
| Tobject (fields, _) ->
let rec aux acc fields =
match get_desc fields with
| Tnil -> acc
| Tvar _ | Tunivar _ -> acc
| Tfield ("*dummy method*", _, _, fields) -> aux acc fields
| Tfield (name, _, type_expr, fields) ->
let exprs =
exp_or_hole env type_expr
|> List.map ~f:(fun expr ->
let open Ast_helper in
Cf.method_ (Location.mknoloc name) Asttypes.Public
@@ Ast_helper.Cf.concrete Asttypes.Fresh expr)
in
aux (exprs :: acc) fields
| _ ->
failwith
@@ Format.asprintf
"Unexpected type constructor in fields list: %a"
Printtyp.type_expr fields
in
let all_fields = aux [] fields |> Util.combinations in
List.map all_fields ~f:(fun fields ->
let open Ast_helper in
Exp.object_ @@ Ast_helper.Cstr.mk (Pat.any ()) fields)
| Tfield _ | Tnil -> failwith "Found a field type outside an object"
in
let matching_values =
if values_scope = Local then
Path.Map.bindings (Util.find_values_for_type env typ)
|> List.map ~f:(make_value env)
else []
in
List.append constructed_from_type matching_values
end
let needs_parentheses e =
match e.Parsetree.pexp_desc with
| Pexp_fun _ | Pexp_lazy _ | Pexp_apply _
| Pexp_variant (_, Some _)
| Pexp_construct (_, Some _) -> true
| _ -> false
let to_string_with_parentheses exp =
let f : _ format6 = if needs_parentheses exp then "(%a)" else "%a" in
Format.asprintf f Pprintast.expression exp
let node ?(depth = 1) ~(config : Mconfig.t) ~keywords ~values_scope node =
Warnings.with_state config.ocaml.warnings (fun () ->
match node with
| Browse_raw.Expression { exp_type; exp_env; _ } ->
let idents_table = Util.idents_table ~keywords in
Gen.expression ~idents_table values_scope ~depth exp_env exp_type
|> List.map ~f:to_string_with_parentheses
| Browse_raw.Module_expr
{ mod_desc = Tmod_constraint _; mod_type; mod_env; _ }
| Browse_raw.Module_expr { mod_desc = Tmod_apply _; mod_type; mod_env; _ }
->
let m = Gen.module_ mod_env mod_type in
[ Format.asprintf "%a" Pprintast.module_expr m ]
| Browse_raw.Module_expr _ | Browse_raw.Module_binding _ ->
raise No_constraint
| _ ->
raise Not_a_hole)