Refactor: move related helper functions into their own modules
[hiphop-php.git] / hphp / hack / src / server / serverExtractStandalone.ml
blob54b43bae5d59d44e20b02481413e0e338116d87a
1 (*
2 * Copyright (c) Facebook, Inc. and its affiliates.
4 * This source code is licensed under the MIT license found in the
5 * LICENSE file in the "hack" directory of this source tree.
7 *)
9 open Aast
10 open Hh_prelude
11 open Typing_defs
12 open ServerCommandTypes.Extract_standalone
13 module SourceText = Full_fidelity_source_text
14 module Syntax = Full_fidelity_positioned_syntax
15 module SyntaxTree = Full_fidelity_syntax_tree.WithSyntax (Syntax)
16 module SyntaxError = Full_fidelity_syntax_error
17 module SN = Naming_special_names
18 module Class = Decl_provider.Class
20 (* Internal error: for example, we are generating code for a dependency on an enum,
21 but the passed dependency is not an enum *)
22 exception UnexpectedDependency
24 exception DependencyNotFound of string
26 exception Unsupported
28 let records_not_supported () = failwith "Records are not supported"
30 let value_exn ex opt =
31 match opt with
32 | Some s -> s
33 | None -> raise ex
35 let value_or_not_found err_msg opt = value_exn (DependencyNotFound err_msg) opt
37 module Decl = struct
38 let get_class_exn ctx name =
39 let not_found_msg = Printf.sprintf "Class %s" name in
40 value_or_not_found not_found_msg @@ Decl_provider.get_class ctx name
42 let get_fun_pos ctx name =
43 Decl_provider.get_fun ctx name |> Option.map ~f:(fun decl -> decl.fe_pos)
45 let get_fun_pos_exn ctx name = value_or_not_found name (get_fun_pos ctx name)
47 let get_class_pos ctx name =
48 Decl_provider.get_class ctx name
49 |> Option.map ~f:(fun decl -> Class.pos decl)
51 let get_class_pos_exn ctx name =
52 value_or_not_found name (get_class_pos ctx name)
54 let get_typedef_pos ctx name =
55 Decl_provider.get_typedef ctx name
56 |> Option.map ~f:(fun decl -> decl.td_pos)
58 let get_gconst_pos ctx name =
59 Decl_provider.get_gconst ctx name
60 |> Option.map ~f:(fun ty -> Typing_defs.get_pos ty)
62 let get_class_or_typedef_pos ctx name =
63 Option.first_some (get_class_pos ctx name) (get_typedef_pos ctx name)
64 end
66 module Nast_helper = struct
67 let make_nast_getter ~get_pos ~find_in_file ~naming =
68 let nasts = ref SMap.empty in
69 fun ctx name ->
70 if SMap.mem name !nasts then
71 Some (SMap.find name !nasts)
72 else
73 let open Option in
74 get_pos ctx name >>= fun pos ->
75 find_in_file ctx (Pos.filename pos) name >>= fun nast ->
76 let nast = naming ctx nast in
77 nasts := SMap.add name nast !nasts;
78 Some nast
80 let get_fun_nast =
81 make_nast_getter
82 ~get_pos:Decl.get_fun_pos
83 ~find_in_file:Ast_provider.find_fun_in_file
84 ~naming:Naming.fun_
86 let get_fun_nast_exn ctx name =
87 value_or_not_found name (get_fun_nast ctx name)
89 let get_class_nast =
90 make_nast_getter
91 ~get_pos:Decl.get_class_pos
92 ~find_in_file:Ast_provider.find_class_in_file
93 ~naming:Naming.class_
95 let get_typedef_nast =
96 make_nast_getter
97 ~get_pos:Decl.get_typedef_pos
98 ~find_in_file:Ast_provider.find_typedef_in_file
99 ~naming:Naming.typedef
101 let get_typedef_nast_exn ctx name =
102 value_or_not_found name (get_typedef_nast ctx name)
104 let get_gconst_nast =
105 make_nast_getter
106 ~get_pos:Decl.get_gconst_pos
107 ~find_in_file:Ast_provider.find_gconst_in_file
108 ~naming:Naming.global_const
110 let get_gconst_nast_exn ctx name =
111 value_or_not_found name (get_gconst_nast ctx name)
113 let make_class_element_nast_getter ~get_elements ~get_element_name =
114 let elements_by_class_name = ref SMap.empty in
115 fun ctx class_name element_name ->
116 if SMap.mem class_name !elements_by_class_name then
117 SMap.find_opt
118 element_name
119 (SMap.find class_name !elements_by_class_name)
120 else
121 let open Option in
122 get_class_nast ctx class_name >>= fun class_ ->
123 let elements_by_element_name =
124 List.fold_left
125 (get_elements class_)
126 ~f:(fun elements element ->
127 SMap.add (get_element_name element) element elements)
128 ~init:SMap.empty
130 elements_by_class_name :=
131 SMap.add class_name elements_by_element_name !elements_by_class_name;
132 SMap.find_opt element_name elements_by_element_name
134 let get_method_nast =
135 make_class_element_nast_getter
136 ~get_elements:(fun class_ -> class_.c_methods)
137 ~get_element_name:(fun method_ -> snd method_.m_name)
139 let get_method_nast_exn ctx class_name method_name =
140 value_or_not_found
141 (class_name ^ "::" ^ method_name)
142 (get_method_nast ctx class_name method_name)
144 let get_const_nast =
145 make_class_element_nast_getter
146 ~get_elements:(fun class_ -> class_.c_consts)
147 ~get_element_name:(fun const -> snd const.cc_id)
149 let get_typeconst_nast =
150 make_class_element_nast_getter
151 ~get_elements:(fun class_ -> class_.c_typeconsts)
152 ~get_element_name:(fun typeconst -> snd typeconst.c_tconst_name)
154 let get_prop_nast =
155 make_class_element_nast_getter
156 ~get_elements:(fun class_ -> class_.c_vars)
157 ~get_element_name:(fun class_var -> snd class_var.cv_id)
159 let get_prop_nast_exn ctx class_name prop_name =
160 value_or_not_found
161 (class_name ^ "::" ^ prop_name)
162 (get_prop_nast ctx class_name prop_name)
165 module Dep = struct
166 let get_class_name : type a. a Typing_deps.Dep.variant -> string option =
167 (* the OCaml compiler is not smart enough to let us use an or-pattern for all of these
168 * because of how it's matching on a GADT *)
169 fun dep ->
170 Typing_deps.Dep.(
171 match dep with
172 | Const (cls, _) -> Some cls
173 | Method (cls, _) -> Some cls
174 | SMethod (cls, _) -> Some cls
175 | Prop (cls, _) -> Some cls
176 | SProp (cls, _) -> Some cls
177 | Class cls -> Some cls
178 | Cstr cls -> Some cls
179 | AllMembers cls -> Some cls
180 | Extends cls -> Some cls
181 | Fun _
182 | FunName _
183 | GConst _
184 | GConstName _ ->
185 None
186 | RecordDef _ -> records_not_supported ())
188 let global_dep_name dep =
189 Typing_deps.Dep.(
190 match dep with
191 | Fun s
192 | FunName s
193 | Class s
194 | GConst s
195 | GConstName s ->
197 | Const (_, _)
198 | Method (_, _)
199 | SMethod (_, _)
200 | Prop (_, _)
201 | SProp (_, _)
202 | Cstr _
203 | AllMembers _
204 | Extends _ ->
205 raise UnexpectedDependency
206 | RecordDef _ -> records_not_supported ())
208 let get_dep_pos ctx dep =
209 let open Typing_deps.Dep in
210 match dep with
211 | Fun name
212 | FunName name ->
213 Decl.get_fun_pos ctx name
214 | Class name
215 | Const (name, _)
216 | Method (name, _)
217 | SMethod (name, _)
218 | Prop (name, _)
219 | SProp (name, _)
220 | Cstr name
221 | AllMembers name
222 | Extends name ->
223 Decl.get_class_or_typedef_pos ctx name
224 | GConst name
225 | GConstName name ->
226 Decl.get_gconst_pos ctx name
227 | RecordDef _ -> records_not_supported ()
229 let get_fun_mode ctx name =
230 Nast_helper.get_fun_nast ctx name |> Option.map ~f:(fun fun_ -> fun_.f_mode)
232 let get_class_mode ctx name =
233 Nast_helper.get_class_nast ctx name
234 |> Option.map ~f:(fun class_ -> class_.c_mode)
236 let get_typedef_mode ctx name =
237 Nast_helper.get_typedef_nast ctx name
238 |> Option.map ~f:(fun typedef -> typedef.t_mode)
240 let get_gconst_mode ctx name =
241 Nast_helper.get_gconst_nast ctx name
242 |> Option.map ~f:(fun gconst -> gconst.cst_mode)
244 let get_class_or_typedef_mode ctx name =
245 Option.first_some (get_class_mode ctx name) (get_typedef_mode ctx name)
247 let get_dep_mode ctx dep =
248 let open Typing_deps.Dep in
249 match dep with
250 | Fun name
251 | FunName name ->
252 get_fun_mode ctx name
253 | Class name
254 | Const (name, _)
255 | Method (name, _)
256 | SMethod (name, _)
257 | Prop (name, _)
258 | SProp (name, _)
259 | Cstr name
260 | AllMembers name
261 | Extends name ->
262 get_class_or_typedef_mode ctx name
263 | GConst name
264 | GConstName name ->
265 get_gconst_mode ctx name
266 | RecordDef _ -> records_not_supported ()
268 let is_strict_dep ctx dep =
269 match get_dep_mode ctx dep with
270 | Some FileInfo.Mstrict -> true
271 | _ -> false
273 let is_strict_fun ctx name = is_strict_dep ctx (Typing_deps.Dep.Fun name)
275 let is_strict_class ctx name = is_strict_dep ctx (Typing_deps.Dep.Class name)
277 let is_builtin_dep ctx dep =
278 let msg = Typing_deps.Dep.variant_to_string dep in
279 let pos = value_or_not_found msg (get_dep_pos ctx dep) in
280 Relative_path.(is_hhi (prefix (Pos.filename pos)))
282 let is_relevant_dependency
283 (target : target)
284 (dep : Typing_deps.Dep.dependent Typing_deps.Dep.variant) =
285 match target with
286 | Function f ->
287 (match dep with
288 | Typing_deps.Dep.Fun g
289 | Typing_deps.Dep.FunName g ->
290 String.equal f g
291 | _ -> false)
292 (* We have to collect dependencies of the entire class because dependency collection is
293 coarse-grained: if cls's member depends on D, we get a dependency edge cls --> D,
294 not (cls, member) --> D *)
295 | Method (cls, _) ->
296 Option.equal String.equal (get_class_name dep) (Some cls)
299 module Target = struct
300 let get_filename ctx target =
301 let pos =
302 match target with
303 | Function name -> Decl.get_fun_pos_exn ctx name
304 | Method (name, _) -> Decl.get_class_pos_exn ctx name
306 Pos.filename pos
308 let extract_target ctx target =
309 let filename = get_filename ctx target in
310 let abs_filename = Relative_path.to_absolute filename in
311 let file_content = In_channel.read_all abs_filename in
312 let pos =
313 match target with
314 | Function name ->
315 let fun_ = Nast_helper.get_fun_nast_exn ctx name in
316 fun_.f_span
317 | Method (class_name, method_name) ->
318 let method_ =
319 Nast_helper.get_method_nast_exn ctx class_name method_name
321 method_.m_span
323 Pos.get_text_from_pos file_content pos
326 module Pretty = struct
327 let print_error source_text error =
328 let text =
329 SyntaxError.to_positioned_string
330 error
331 (SourceText.offset_to_position source_text)
333 Hh_logger.log "%s\n" text
335 let tree_from_string s =
336 let source_text = SourceText.make Relative_path.default s in
337 let mode = Full_fidelity_parser.parse_mode source_text in
338 let env = Full_fidelity_parser_env.make ?mode () in
339 let tree = SyntaxTree.make ~env source_text in
340 if List.is_empty (SyntaxTree.all_errors tree) then
341 tree
342 else (
343 List.iter (SyntaxTree.all_errors tree) (print_error source_text);
344 raise Hackfmt_error.InvalidSyntax
347 let fixup_xhp =
348 let re = Str.regexp "\\\\:" in
349 Str.global_replace re ":"
351 let format text =
352 try Libhackfmt.format_tree (tree_from_string (fixup_xhp text))
353 with Hackfmt_error.InvalidSyntax -> text
355 let strip_ns obj_name =
356 match String.rsplit2 obj_name '\\' with
357 | Some (_, name) -> name
358 | None -> obj_name
360 let concat_map ~sep ~f list = String.concat ~sep (List.map ~f list)
362 let function_make_default = "extract_standalone_make_default"
364 let call_make_default = Printf.sprintf "\\%s()" function_make_default
366 let extract_standalone_any = "EXTRACT_STANDALONE_ANY"
368 let string_of_tprim = function
369 | Tbool -> "bool"
370 | Tint -> "int"
371 | Tfloat -> "float"
372 | Tnum -> "num"
373 | Tstring -> "string"
374 | Tarraykey -> "arraykey"
375 | Tnull -> "null"
376 | Tvoid -> "void"
377 | Tresource -> "resource"
378 | Tnoreturn -> "noreturn"
380 let string_of_shape_field_name = function
381 | Ast_defs.SFlit_int (_, s) -> s
382 | Ast_defs.SFlit_str (_, s) -> Printf.sprintf "'%s'" s
383 | Ast_defs.SFclass_const ((_, c), (_, s)) -> Printf.sprintf "%s::%s" c s
385 let string_of_xhp_attr_info xhp_attr_info =
386 match xhp_attr_info.xai_tag with
387 | Some Required -> "@required"
388 | Some LateInit -> "@lateinit"
389 | None -> ""
391 let rec string_of_hint hint =
392 match snd hint with
393 | Hoption hint -> "?" ^ string_of_hint hint
394 | Hlike hint -> "~" ^ string_of_hint hint
395 | Hfun
397 hf_reactive_kind = _;
398 hf_param_tys;
399 hf_param_kinds;
400 hf_param_mutability = _;
401 hf_variadic_ty;
402 hf_ctxs = _;
403 (* TODO(vmladenov) support capability types here *)
404 hf_return_ty;
405 hf_is_mutable_return = _;
406 } ->
407 let param_hints = List.map hf_param_tys ~f:string_of_hint in
408 let param_kinds =
409 List.map hf_param_kinds ~f:(function
410 | Some Ast_defs.Pinout -> "inout "
411 | None -> "")
413 let params = List.map2_exn param_kinds param_hints ~f:( ^ ) in
414 let variadic =
415 match hf_variadic_ty with
416 | Some hint -> [string_of_hint hint ^ "..."]
417 | None -> []
419 Printf.sprintf
420 "(function(%s) : %s)"
421 (String.concat ~sep:", " (params @ variadic))
422 (string_of_hint hf_return_ty)
423 | Htuple hints ->
424 Printf.sprintf "(%s)" (concat_map ~sep:", " ~f:string_of_hint hints)
425 | Habstr (name, hints)
426 | Happly ((_, name), hints) ->
427 let params =
428 match hints with
429 | [] -> ""
430 | _ ->
431 Printf.sprintf "<%s>" (concat_map ~sep:", " ~f:string_of_hint hints)
433 name ^ params
434 | Hshape { nsi_allows_unknown_fields; nsi_field_map } ->
435 let string_of_shape_field { sfi_optional; sfi_name; sfi_hint } =
436 let optional_prefix =
437 if sfi_optional then
439 else
442 Printf.sprintf
443 "%s%s => %s"
444 optional_prefix
445 (string_of_shape_field_name sfi_name)
446 (string_of_hint sfi_hint)
448 let shape_fields = List.map nsi_field_map ~f:string_of_shape_field in
449 let shape_suffix =
450 if nsi_allows_unknown_fields then
451 ["..."]
452 else
455 let shape_entries = shape_fields @ shape_suffix in
456 Printf.sprintf "shape(%s)" (String.concat ~sep:", " shape_entries)
457 | Haccess (root, ids) ->
458 String.concat ~sep:"::" (string_of_hint root :: List.map ids ~f:snd)
459 | Hsoft hint -> "@" ^ string_of_hint hint
460 | Hmixed -> "mixed"
461 | Hnonnull -> "nonnull"
462 | Hdarray (khint, vhint) ->
463 Printf.sprintf
464 "darray<%s, %s>"
465 (string_of_hint khint)
466 (string_of_hint vhint)
467 | Hvarray hint -> Printf.sprintf "varray<%s>" (string_of_hint hint)
468 | Hvarray_or_darray (None, vhint) ->
469 Printf.sprintf "varray_or_darray<%s>" (string_of_hint vhint)
470 | Hvarray_or_darray (Some khint, vhint) ->
471 Printf.sprintf
472 "varray_or_darray<%s, %s>"
473 (string_of_hint khint)
474 (string_of_hint vhint)
475 | Hprim prim -> string_of_tprim prim
476 | Hthis -> "this"
477 | Hdynamic -> "dynamic"
478 | Hnothing -> "nothing"
479 | Hunion hints ->
480 Printf.sprintf "(%s)" (concat_map ~sep:" | " ~f:string_of_hint hints)
481 | Hintersection hints ->
482 Printf.sprintf "(%s)" (concat_map ~sep:" & " ~f:string_of_hint hints)
483 | Hany -> extract_standalone_any
484 | Herr -> extract_standalone_any
485 | Hfun_context name -> "ctx " ^ name
486 | Hvar name -> name
488 let maybe_string_of_user_attribute { ua_name; ua_params } =
489 let name = snd ua_name in
490 match ua_params with
491 | [] when SMap.mem name SN.UserAttributes.as_map -> Some name
492 | _ -> None
494 let string_of_user_attributes user_attributes =
495 let user_attributes =
496 List.filter_map ~f:maybe_string_of_user_attribute user_attributes
498 match user_attributes with
499 | [] -> ""
500 | _ -> Printf.sprintf "<<%s>>" (String.concat ~sep:", " user_attributes)
502 let string_of_variance = function
503 | Ast_defs.Covariant -> "+"
504 | Ast_defs.Contravariant -> "-"
505 | Ast_defs.Invariant -> ""
507 let string_of_constraint (kind, hint) =
508 let keyword =
509 match kind with
510 | Ast_defs.Constraint_as -> "as"
511 | Ast_defs.Constraint_eq -> "="
512 | Ast_defs.Constraint_super -> "super"
514 keyword ^ " " ^ string_of_hint hint
516 let rec string_of_tparam
517 Aast.
519 tp_variance;
520 tp_name;
521 tp_parameters;
522 tp_constraints;
523 tp_reified;
524 tp_user_attributes;
526 let variance = string_of_variance tp_variance in
527 let name = snd tp_name in
528 let parameters = string_of_tparams tp_parameters in
529 let constraints = List.map tp_constraints ~f:string_of_constraint in
530 let user_attributes = string_of_user_attributes tp_user_attributes in
531 let reified =
532 match tp_reified with
533 | Erased -> ""
534 | SoftReified
535 | Reified ->
536 "reify"
538 String.concat
539 ~sep:" "
540 ( user_attributes
541 :: reified
542 :: (variance ^ name)
543 :: parameters
544 :: constraints )
546 and string_of_tparams tparams =
547 match tparams with
548 | [] -> ""
549 | _ ->
550 Printf.sprintf "<%s>" (concat_map ~sep:", " ~f:string_of_tparam tparams)
552 let string_of_fun_param
554 param_type_hint;
555 param_is_variadic;
556 param_name;
557 param_expr;
558 param_callconv;
559 param_user_attributes;
562 let user_attributes = string_of_user_attributes param_user_attributes in
563 let inout =
564 match param_callconv with
565 | Some Ast_defs.Pinout -> "inout"
566 | None -> ""
568 let type_hint =
569 match param_type_hint with
570 | (_, Some hint) -> string_of_hint hint
571 | (_, None) -> ""
573 let variadic =
574 if param_is_variadic then
575 "..."
576 else
579 let default =
580 match param_expr with
581 | Some _ -> " = " ^ call_make_default
582 | None -> ""
584 Printf.sprintf
585 "%s %s %s %s%s%s"
586 user_attributes
587 inout
588 type_hint
589 variadic
590 param_name
591 default
593 let get_fun_declaration ctx name =
594 let fun_ = Nast_helper.get_fun_nast_exn ctx name in
595 let user_attributes = string_of_user_attributes fun_.f_user_attributes in
596 let tparams = string_of_tparams fun_.f_tparams in
597 let variadic =
598 match fun_.f_variadic with
599 | FVvariadicArg fp -> [string_of_fun_param fp]
600 | FVellipsis _ -> ["..."]
601 | FVnonVariadic -> []
603 let params =
604 String.concat
605 ~sep:", "
606 (List.map fun_.f_params ~f:string_of_fun_param @ variadic)
608 let ret =
609 match fun_.f_ret with
610 | (_, Some hint) -> ": " ^ string_of_hint hint
611 | (_, None) -> ""
613 Printf.sprintf
614 "%s function %s%s(%s)%s {throw new \\Exception();}"
615 user_attributes
616 (strip_ns name)
617 tparams
618 params
621 let get_init_for_prim = function
622 | Aast_defs.Tnull -> "null"
623 | Aast_defs.Tint
624 | Aast_defs.Tnum ->
626 | Aast_defs.Tbool -> "false"
627 | Aast_defs.Tfloat -> "0.0"
628 | Aast_defs.Tstring
629 | Aast_defs.Tarraykey ->
630 "\"\""
631 | Aast_defs.Tvoid
632 | Aast_defs.Tresource
633 | Aast_defs.Tnoreturn ->
634 raise Unsupported
636 let rec get_init_from_hint ctx tparams_stack hint =
637 let unsupported_hint () =
638 Hh_logger.log
639 "%s: get_init_from_hint: unsupported hint: %s"
640 (Pos.string (Pos.to_absolute (fst hint)))
641 (Aast_defs.show_hint hint);
642 raise Unsupported
644 match snd hint with
645 | Hprim prim -> get_init_for_prim prim
646 | Hoption _ -> "null"
647 | Hlike hint -> get_init_from_hint ctx tparams_stack hint
648 | Hdarray _ -> "darray[]"
649 | Hvarray_or_darray _
650 | Hvarray _ ->
651 "varray[]"
652 | Htuple hints ->
653 Printf.sprintf
654 "tuple(%s)"
655 (concat_map ~sep:", " ~f:(get_init_from_hint ctx tparams_stack) hints)
656 | Happly ((_, name), hints) ->
657 (match () with
659 when String.equal name SN.Collections.cVec
660 || String.equal name SN.Collections.cKeyset
661 || String.equal name SN.Collections.cDict ->
662 Printf.sprintf "%s[]" (strip_ns name)
664 when String.equal name SN.Collections.cVector
665 || String.equal name SN.Collections.cImmVector
666 || String.equal name SN.Collections.cMap
667 || String.equal name SN.Collections.cImmMap
668 || String.equal name SN.Collections.cSet
669 || String.equal name SN.Collections.cImmSet ->
670 Printf.sprintf "%s {}" (strip_ns name)
671 | _ when String.equal name SN.Collections.cPair ->
672 (match hints with
673 | [first; second] ->
674 Printf.sprintf
675 "Pair {%s, %s}"
676 (get_init_from_hint ctx tparams_stack first)
677 (get_init_from_hint ctx tparams_stack second)
678 | _ -> failwith "malformed hint")
679 | _ when String.equal name SN.Classes.cClassname ->
680 (match hints with
681 | [(_, Happly ((_, class_name), _))] ->
682 Printf.sprintf "%s::class" class_name
683 | _ -> raise UnexpectedDependency)
684 | _ ->
685 (match Nast_helper.get_class_nast ctx name with
686 | Some class_ ->
687 (match class_.c_kind with
688 | Ast_defs.Cenum ->
689 let const_name =
690 match class_.c_consts with
691 | [] -> failwith "empty enum"
692 | const :: _ -> snd const.cc_id
694 Printf.sprintf "%s::%s" name const_name
695 | _ -> unsupported_hint ())
696 | None ->
697 let typedef = Nast_helper.get_typedef_nast_exn ctx name in
698 let tparams =
699 List.fold2_exn
700 typedef.t_tparams
701 hints
702 ~init:SMap.empty
703 ~f:(fun tparams tparam hint ->
704 SMap.add (snd tparam.tp_name) hint tparams)
706 get_init_from_hint ctx (tparams :: tparams_stack) typedef.t_kind))
707 | Hshape { nsi_field_map; _ } ->
708 let non_optional_fields =
709 List.filter nsi_field_map ~f:(fun shape_field_info ->
710 not shape_field_info.sfi_optional)
712 let get_init_shape_field { sfi_hint; sfi_name; _ } =
713 Printf.sprintf
714 "%s => %s"
715 (string_of_shape_field_name sfi_name)
716 (get_init_from_hint ctx tparams_stack sfi_hint)
718 Printf.sprintf
719 "shape(%s)"
720 (concat_map ~sep:", " ~f:get_init_shape_field non_optional_fields)
721 | Habstr (name, []) ->
722 (* FIXME: support non-empty type arguments of Habstr here? *)
723 let rec loop tparams_stack =
724 match tparams_stack with
725 | tparams :: tparams_stack' ->
726 (match SMap.find_opt name tparams with
727 | Some hint -> get_init_from_hint ctx tparams_stack' hint
728 | None -> loop tparams_stack')
729 | [] -> unsupported_hint ()
731 loop tparams_stack
732 | _ -> unsupported_hint ()
734 let get_init_from_hint ctx hint = get_init_from_hint ctx [] hint
736 let get_gconst_declaration ctx name =
737 let gconst = Nast_helper.get_gconst_nast_exn ctx name in
738 let hint = value_or_not_found ("type of " ^ name) gconst.cst_type in
739 let init = get_init_from_hint ctx hint in
740 Printf.sprintf
741 "const %s %s = %s;"
742 (string_of_hint hint)
743 (strip_ns name)
744 init
746 let get_const_declaration ctx const =
747 let name = snd const.cc_id in
748 let abstract =
749 match const.cc_expr with
750 | Some _ -> ""
751 | None -> "abstract"
753 let (type_, init) =
754 match (const.cc_type, const.cc_expr) with
755 | (Some hint, _) ->
756 (string_of_hint hint, " = " ^ get_init_from_hint ctx hint)
757 | (None, Some e) ->
758 (match Decl_utils.infer_const e with
759 | Some tprim ->
760 let hint = (fst e, Hprim tprim) in
761 ("", " = " ^ get_init_from_hint ctx hint)
762 | None -> raise Unsupported)
763 | (None, None) -> ("", "")
765 Printf.sprintf "%s const %s %s%s;" abstract type_ name init
767 let get_global_object_declaration ctx obj =
768 Typing_deps.Dep.(
769 match obj with
770 | Fun f
771 | FunName f ->
772 get_fun_declaration ctx f
773 | GConst c
774 | GConstName c ->
775 get_gconst_declaration ctx c
776 (* No other global declarations *)
777 | _ -> raise UnexpectedDependency)
779 let get_class_declaration class_ =
780 let name = snd class_.c_name in
781 let user_attributes = string_of_user_attributes class_.c_user_attributes in
782 let final =
783 if class_.c_final then
784 "final"
785 else
788 let kind =
789 match class_.c_kind with
790 | Ast_defs.Cabstract -> "abstract class"
791 | Ast_defs.Cnormal -> "class"
792 | Ast_defs.Cinterface -> "interface"
793 | Ast_defs.Ctrait -> "trait"
794 | Ast_defs.Cenum -> "enum"
796 let tparams = string_of_tparams class_.c_tparams in
797 let extends =
798 match class_.c_extends with
799 | [] -> ""
800 | _ ->
801 Printf.sprintf
802 "extends %s"
803 (concat_map ~sep:", " ~f:string_of_hint class_.c_extends)
805 let implements =
806 match class_.c_implements with
807 | [] -> ""
808 | _ ->
809 Printf.sprintf
810 "implements %s"
811 (concat_map ~sep:", " ~f:string_of_hint class_.c_implements)
813 Printf.sprintf
814 "%s %s %s %s%s %s %s"
815 user_attributes
816 final
817 kind
818 (strip_ns name)
819 tparams
820 extends
821 implements
823 let get_method_declaration method_ ~from_interface =
824 let abstract =
825 if method_.m_abstract && not from_interface then
826 "abstract"
827 else
830 let final =
831 if method_.m_final then
832 "final"
833 else
836 let visibility = string_of_visibility method_.m_visibility in
837 let static =
838 if method_.m_static then
839 "static"
840 else
843 let user_attributes = string_of_user_attributes method_.m_user_attributes in
844 let name = strip_ns (snd method_.m_name) in
845 let tparams = string_of_tparams method_.m_tparams in
846 let variadic =
847 match method_.m_variadic with
848 | FVvariadicArg fp -> [string_of_fun_param fp]
849 | FVellipsis _ -> ["..."]
850 | FVnonVariadic -> []
852 let params =
853 String.concat
854 ~sep:", "
855 (List.map method_.m_params ~f:string_of_fun_param @ variadic)
857 let ret =
858 match method_.m_ret with
859 | (_, Some hint) -> ": " ^ string_of_hint hint
860 | (_, None) -> ""
862 let body =
863 if method_.m_abstract || from_interface then
865 else
866 "{throw new \\Exception();}"
868 Printf.sprintf
869 "%s %s %s %s %s function %s%s(%s)%s%s"
870 user_attributes
871 abstract
872 final
873 visibility
874 static
875 name
876 tparams
877 params
879 body
881 let get_prop_declaration ctx prop =
882 let name = snd prop.cv_id in
883 let user_attributes = string_of_user_attributes prop.cv_user_attributes in
884 let (type_, init) =
885 match (hint_of_type_hint prop.cv_type, prop.cv_expr) with
886 | (Some hint, Some _) ->
887 ( string_of_hint hint,
888 Printf.sprintf " = %s" (get_init_from_hint ctx hint) )
889 | (Some hint, None) -> (string_of_hint hint, "")
890 | (None, None) -> ("", "")
891 (* Untyped prop, not supported for now *)
892 | (None, Some _) -> raise Unsupported
894 match prop.cv_xhp_attr with
895 | None ->
896 (* Ordinary property *)
897 let visibility = string_of_visibility prop.cv_visibility in
898 let static =
899 if prop.cv_is_static then
900 "static"
901 else
904 Printf.sprintf
905 "%s %s %s %s $%s%s;"
906 user_attributes
907 visibility
908 static
909 type_
910 name
911 init
912 | Some xhp_attr_info ->
913 (* XHP attribute *)
914 Printf.sprintf
915 "%s attribute %s %s %s %s;"
916 user_attributes
917 type_
918 (String.lstrip ~drop:(fun c -> Char.equal c ':') name)
919 init
920 (string_of_xhp_attr_info xhp_attr_info)
922 let get_typeconst_declaration typeconst =
923 let abstract =
924 match typeconst.c_tconst_abstract with
925 | TCAbstract _ -> "abstract"
926 | TCPartiallyAbstract
927 | TCConcrete ->
930 let name = snd typeconst.c_tconst_name in
931 let type_ =
932 match typeconst.c_tconst_type with
933 | Some hint -> " = " ^ string_of_hint hint
934 | None -> ""
936 let constraint_ =
937 match typeconst.c_tconst_constraint with
938 | Some hint -> " as " ^ string_of_hint hint
939 | None -> ""
941 Printf.sprintf "%s const type %s%s%s;" abstract name constraint_ type_
943 let get_method_declaration ctx target class_name method_name =
944 match target with
945 | ServerCommandTypes.Extract_standalone.Method
946 (target_class_name, target_method_name)
947 when String.equal class_name target_class_name
948 && String.equal method_name target_method_name ->
949 None
950 | _ ->
951 let open Option in
952 Nast_helper.get_class_nast ctx class_name >>= fun class_ ->
953 let from_interface = Ast_defs.is_c_interface class_.c_kind in
954 Nast_helper.get_method_nast ctx class_name method_name >>= fun method_ ->
955 Some (get_method_declaration method_ ~from_interface)
957 let get_class_elt_declaration
958 ctx target (class_elt : 'a Typing_deps.Dep.variant) =
959 let open Typing_deps.Dep in
960 match class_elt with
961 | Const (class_name, const_name) ->
962 (match Nast_helper.get_typeconst_nast ctx class_name const_name with
963 | Some typeconst -> Some (get_typeconst_declaration typeconst)
964 | None ->
965 (match Nast_helper.get_const_nast ctx class_name const_name with
966 | Some const -> Some (get_const_declaration ctx const)
967 | None -> raise (DependencyNotFound (class_name ^ "::" ^ const_name))))
968 | Method (class_name, method_name)
969 | SMethod (class_name, method_name) ->
970 get_method_declaration ctx target class_name method_name
971 | Cstr class_name ->
972 get_method_declaration ctx target class_name "__construct"
973 | Prop (class_name, prop_name) ->
974 let prop = Nast_helper.get_prop_nast_exn ctx class_name prop_name in
975 Some (get_prop_declaration ctx prop)
976 | SProp (class_name, sprop_name) ->
977 let sprop_name =
978 String.lstrip ~drop:(fun c -> Char.equal c '$') sprop_name
980 let prop = Nast_helper.get_prop_nast_exn ctx class_name sprop_name in
981 Some (get_prop_declaration ctx prop)
982 (* Constructor should've been tackled earlier, and all other dependencies aren't class elements *)
983 | Extends _
984 | AllMembers _
985 | Class _
986 | Fun _
987 | FunName _
988 | GConst _
989 | GConstName _ ->
990 raise UnexpectedDependency
991 | RecordDef _ -> records_not_supported ()
993 let construct_enum ctx class_ =
994 let name = snd class_.c_name in
995 let enum =
996 match class_.c_enum with
997 | Some enum -> enum
998 | None -> failwith ("not an enum: " ^ snd class_.c_name)
1000 let constraint_ =
1001 match enum.e_constraint with
1002 | Some hint -> " as " ^ string_of_hint hint
1003 | None -> ""
1005 let string_of_enum_const const =
1006 Printf.sprintf
1007 "%s = %s;"
1008 (snd const.cc_id)
1009 (get_init_from_hint ctx enum.e_base)
1011 Printf.sprintf
1012 "enum %s: %s%s {%s}"
1013 (strip_ns name)
1014 (string_of_hint enum.e_base)
1015 constraint_
1016 (concat_map ~sep:"\n" ~f:string_of_enum_const class_.c_consts)
1018 let get_class_body ctx class_ target class_elts =
1019 let name = snd class_.c_name in
1020 let uses =
1021 List.map class_.c_uses ~f:(fun s ->
1022 Printf.sprintf "use %s;" (string_of_hint s))
1024 let (req_extends, req_implements) =
1025 List.partition_map class_.c_reqs ~f:(fun (s, extends) ->
1026 if extends then
1027 `Fst (Printf.sprintf "require extends %s;" (string_of_hint s))
1028 else
1029 `Snd (Printf.sprintf "require implements %s;" (string_of_hint s)))
1031 let open Typing_deps in
1032 let body =
1033 List.filter_map class_elts ~f:(function
1034 | Dep.AllMembers _
1035 | Dep.Extends _ ->
1036 raise UnexpectedDependency
1037 | Dep.Const (_, "class") -> None
1038 | class_elt -> get_class_elt_declaration ctx target class_elt)
1040 (* If we are extracting a method of this class, we should declare it
1041 here, with stubs of other class elements. *)
1042 let extracted_method =
1043 match target with
1044 | Method (cls_name, _) when String.equal cls_name name ->
1045 [Target.extract_target ctx target]
1046 | _ -> []
1048 String.concat
1049 ~sep:"\n"
1050 (req_extends @ req_implements @ uses @ body @ extracted_method)
1052 let construct_class ctx class_ target fields =
1053 let decl = get_class_declaration class_ in
1054 let body = get_class_body ctx class_ target fields in
1055 Printf.sprintf "%s {%s}" decl body
1057 let construct_enum_or_class ctx class_ target fields =
1058 match class_.c_kind with
1059 | Ast_defs.Cabstract
1060 | Ast_defs.Cnormal
1061 | Ast_defs.Cinterface
1062 | Ast_defs.Ctrait ->
1063 construct_class ctx class_ target fields
1064 | Ast_defs.Cenum -> construct_enum ctx class_
1066 let construct_typedef typedef =
1067 let name = snd typedef.t_name in
1068 let keyword =
1069 match typedef.t_vis with
1070 | Aast_defs.Transparent -> "type"
1071 | Aast_defs.Opaque -> "newtype"
1073 let tparams = string_of_tparams typedef.t_tparams in
1074 let constraint_ =
1075 match typedef.t_constraint with
1076 | Some hint -> " as " ^ string_of_hint hint
1077 | None -> ""
1079 let pos = fst typedef.t_name in
1080 let hh_fixmes =
1081 String.concat
1082 (List.map
1083 ~f:(fun code -> Printf.sprintf "/* HH_FIXME[%d] */\n" code)
1084 (ISet.elements (Fixme_provider.get_fixme_codes_for_pos pos)))
1086 Printf.sprintf
1087 "%s%s %s%s%s = %s;"
1088 hh_fixmes
1089 keyword
1090 (strip_ns name)
1091 tparams
1092 constraint_
1093 (string_of_hint typedef.t_kind)
1095 let construct_type_declaration ctx t target fields =
1096 match Nast_helper.get_class_nast ctx t with
1097 | Some class_ -> construct_enum_or_class ctx class_ target fields
1098 | None ->
1099 let typedef = Nast_helper.get_typedef_nast_exn ctx t in
1100 construct_typedef typedef
1103 type extraction_env = {
1104 dependencies: Typing_deps.Dep.dependency Typing_deps.Dep.variant HashSet.t;
1105 depends_on_make_default: bool ref;
1106 depends_on_any: bool ref;
1109 let rec do_add_dep ctx env dep =
1110 let is_wildcard =
1111 match dep with
1112 | Typing_deps.Dep.Class h -> String.equal h SN.Typehints.wildcard
1113 | _ -> false
1116 (not is_wildcard)
1117 && (not (HashSet.mem env.dependencies dep))
1118 && not (Dep.is_builtin_dep ctx dep)
1119 then (
1120 HashSet.add env.dependencies dep;
1121 add_signature_dependencies ctx env dep
1124 and add_dep ctx env ~this ty : unit =
1125 let visitor =
1126 object
1127 inherit [unit] Type_visitor.decl_type_visitor as super
1129 method! on_tany _ _ = env.depends_on_any := true
1131 method! on_tfun () r ft =
1132 if List.exists ~f:Typing_defs.get_fp_has_default ft.ft_params then
1133 env.depends_on_make_default := true;
1134 super#on_tfun () r ft
1136 method! on_tapply _ _ (_, name) tyl =
1137 let dep = Typing_deps.Dep.Class name in
1138 do_add_dep ctx env dep;
1140 (* If we have a constant of a generic type, it can only be an
1141 array type, e.g., vec<A>, for which don't need values of A
1142 to generate an initializer. *)
1143 List.iter tyl ~f:(add_dep ctx env ~this)
1145 method! on_tshape _ _ _ fdm =
1146 Nast.ShapeMap.iter
1147 (fun name { sft_ty; _ } ->
1148 (match name with
1149 | Ast_defs.SFlit_int _
1150 | Ast_defs.SFlit_str _ ->
1152 | Ast_defs.SFclass_const ((_, c), (_, s)) ->
1153 do_add_dep ctx env (Typing_deps.Dep.Class c);
1154 do_add_dep ctx env (Typing_deps.Dep.Const (c, s)));
1155 add_dep ctx env ~this sft_ty)
1158 (* We un-nest (((this::T1)::T2)::T3) into (this, [T1;T2;T3]) and then re-nest
1159 * because legacy representation of Taccess was using lists. TODO: implement
1160 * this more directly instead.
1162 method! on_taccess () r (root, tconst) =
1163 let rec split_taccess root ids =
1164 match Typing_defs.get_node root with
1165 | Taccess (root, id) -> split_taccess root (id :: ids)
1166 | _ -> (root, ids)
1168 let rec make_taccess r root ids =
1169 match ids with
1170 | [] -> root
1171 | id :: ids ->
1172 make_taccess
1173 Reason.Rnone
1174 (mk (r, Typing_defs.Taccess (root, id)))
1177 let (root, tconsts) = split_taccess root [tconst] in
1178 let expand_type_access class_name tconsts =
1179 match tconsts with
1180 | [] -> raise UnexpectedDependency
1181 (* Expand Class::TConst1::TConst2[::...]: get TConst1 in
1182 Class, get its type or upper bound T, continue adding
1183 dependencies of T::TConst2[::...] *)
1184 | (_, tconst) :: tconsts ->
1185 do_add_dep ctx env (Typing_deps.Dep.Const (class_name, tconst));
1186 let cls = Decl.get_class_exn ctx class_name in
1187 (match Decl_provider.Class.get_typeconst cls tconst with
1188 | Some typeconst ->
1189 Option.iter
1190 typeconst.ttc_type
1191 ~f:(add_dep ctx ~this:(Some class_name) env);
1192 if not (List.is_empty tconsts) then (
1193 match (typeconst.ttc_type, typeconst.ttc_constraint) with
1194 | (Some tc_type, _)
1195 | (None, Some tc_type) ->
1196 (* What does 'this' refer to inside of T? *)
1197 let this =
1198 match Typing_defs.get_node tc_type with
1199 | Tapply ((_, name), _) -> Some name
1200 | _ -> this
1202 let taccess = make_taccess r tc_type tconsts in
1203 add_dep ctx ~this env taccess
1204 | (None, None) -> ()
1206 | None -> ())
1208 match Typing_defs.get_node root with
1209 | Taccess (root', tconst) ->
1210 add_dep ctx ~this env (make_taccess r root' (tconst :: tconsts))
1211 | Tapply ((_, name), _) -> expand_type_access name tconsts
1212 | Tthis -> expand_type_access (Option.value_exn this) tconsts
1213 | _ -> raise UnexpectedDependency
1216 visitor#on_type () ty
1218 and add_signature_dependencies ctx env obj =
1219 let open Typing_deps.Dep in
1220 let description = variant_to_string obj in
1221 match Dep.get_class_name obj with
1222 | Some cls_name ->
1223 do_add_dep ctx env (Typing_deps.Dep.Class cls_name);
1224 (match Decl_provider.get_class ctx cls_name with
1225 | None ->
1226 let td =
1227 value_or_not_found description @@ Decl_provider.get_typedef ctx cls_name
1229 add_dep ctx ~this:None env td.td_type;
1230 Option.iter td.td_constraint ~f:(add_dep ctx ~this:None env)
1231 | Some cls ->
1232 let add_dep = add_dep ctx env ~this:(Some cls_name) in
1233 (match obj with
1234 | Prop (_, name) ->
1235 let p = value_or_not_found description @@ Class.get_prop cls name in
1236 add_dep @@ Lazy.force p.ce_type;
1238 (* We need to initialize properties in the constructor, add a dependency on it *)
1239 do_add_dep ctx env (Cstr cls_name)
1240 | SProp (_, name) ->
1241 let sp = value_or_not_found description @@ Class.get_sprop cls name in
1242 add_dep @@ Lazy.force sp.ce_type
1243 | Method (_, name) ->
1244 let m = value_or_not_found description @@ Class.get_method cls name in
1245 add_dep @@ Lazy.force m.ce_type;
1246 Class.all_ancestor_names cls
1247 |> List.iter ~f:(fun ancestor_name ->
1248 match Decl_provider.get_class ctx ancestor_name with
1249 | Some ancestor when Class.has_method ancestor name ->
1250 do_add_dep ctx env (Method (ancestor_name, name))
1251 | _ -> ())
1252 | SMethod (_, name) ->
1253 (match Class.get_smethod cls name with
1254 | Some sm ->
1255 add_dep @@ Lazy.force sm.ce_type;
1256 Class.all_ancestor_names cls
1257 |> List.iter ~f:(fun ancestor_name ->
1258 match Decl_provider.get_class ctx ancestor_name with
1259 | Some ancestor when Class.has_smethod ancestor name ->
1260 do_add_dep ctx env (SMethod (ancestor_name, name))
1261 | _ -> ())
1262 | None ->
1263 (match Class.get_method cls name with
1264 | Some _ ->
1265 HashSet.remove env.dependencies obj;
1266 do_add_dep ctx env (Method (cls_name, name))
1267 | None -> raise (DependencyNotFound description)))
1268 | Const (_, name) ->
1269 (match Class.get_typeconst cls name with
1270 | Some tc ->
1271 if not (String.equal cls_name tc.ttc_origin) then
1272 do_add_dep ctx env (Const (tc.ttc_origin, name));
1273 Option.iter tc.ttc_type ~f:add_dep;
1274 Option.iter tc.ttc_constraint ~f:add_dep
1275 | None ->
1276 let c = value_or_not_found description @@ Class.get_const cls name in
1277 add_dep c.cc_type)
1278 | Cstr _ ->
1279 (match Class.construct cls with
1280 | (Some constr, _) -> add_dep @@ Lazy.force constr.ce_type
1281 | _ -> ())
1282 | Class _ ->
1283 List.iter (Class.all_ancestors cls) (fun (_, ty) -> add_dep ty);
1284 List.iter (Class.all_ancestor_reqs cls) (fun (_, ty) -> add_dep ty);
1285 Option.iter
1286 (Class.enum_type cls)
1287 ~f:(fun { te_base; te_constraint; te_includes; te_enum_class = _ } ->
1288 add_dep te_base;
1289 Option.iter te_constraint ~f:add_dep;
1290 List.iter te_includes ~f:add_dep)
1291 | AllMembers _ ->
1292 (* AllMembers is used for dependencies on enums, so we should depend on all constants *)
1293 List.iter (Class.consts cls) (fun (name, c) ->
1294 if not (String.equal name "class") then add_dep c.cc_type)
1295 (* Ignore, we fetch class hierarchy when we call add_signature_dependencies on a class dep *)
1296 | Extends _ -> ()
1297 | _ -> raise UnexpectedDependency))
1298 | None ->
1299 (match obj with
1300 | Fun f
1301 | FunName f ->
1302 let func =
1303 value_or_not_found description @@ Decl_provider.get_fun ctx f
1305 add_dep ctx ~this:None env @@ func.fe_type
1306 | GConst c
1307 | GConstName c ->
1308 let ty =
1309 value_or_not_found description @@ Decl_provider.get_gconst ctx c
1311 add_dep ctx ~this:None env ty
1312 | _ -> raise UnexpectedDependency)
1314 let get_implementation_dependencies ctx env cls_name =
1315 let open Decl_provider in
1316 match get_class ctx cls_name with
1317 | None -> []
1318 | Some cls ->
1319 let open Typing_deps.Dep in
1320 let add_smethod_impl acc smethod_name =
1321 match Class.get_smethod cls smethod_name with
1322 | Some elt -> SMethod (elt.ce_origin, smethod_name) :: acc
1323 | _ -> acc
1325 let add_method_impl acc method_name =
1326 match Class.get_method cls method_name with
1327 | Some elt -> Method (elt.ce_origin, method_name) :: acc
1328 | _ -> acc
1330 let add_typeconst_impl acc typeconst_name =
1331 match Class.get_typeconst cls typeconst_name with
1332 | Some tc -> Const (tc.ttc_origin, typeconst_name) :: acc
1333 | _ -> acc
1335 let add_const_impl acc const_name =
1336 match Class.get_const cls const_name with
1337 | Some c -> Const (c.cc_origin, const_name) :: acc
1338 | _ -> acc
1340 let add_impls acc ancestor_name =
1341 let ancestor = Decl.get_class_exn ctx ancestor_name in
1342 if Dep.is_builtin_dep ctx (Class ancestor_name) then
1343 let acc =
1344 List.fold
1345 (Class.smethods ancestor)
1346 ~init:acc
1347 ~f:(fun acc (smethod_name, _) -> add_smethod_impl acc smethod_name)
1349 let acc =
1350 List.fold
1351 (Class.methods ancestor)
1352 ~init:acc
1353 ~f:(fun acc (method_name, _) -> add_method_impl acc method_name)
1355 let acc =
1356 List.fold
1357 (Class.typeconsts ancestor)
1358 ~init:acc
1359 ~f:(fun acc (typeconst_name, _) ->
1360 add_typeconst_impl acc typeconst_name)
1362 let acc =
1363 List.fold
1364 (Class.consts ancestor)
1365 ~init:acc
1366 ~f:(fun acc (const_name, _) -> add_const_impl acc const_name)
1369 else
1370 HashSet.fold env.dependencies ~init:acc ~f:(fun dep acc ->
1371 match dep with
1372 | SMethod (class_name, method_name)
1373 when String.equal class_name ancestor_name ->
1374 add_smethod_impl acc method_name
1375 | Method (class_name, method_name)
1376 when String.equal class_name ancestor_name ->
1377 add_method_impl acc method_name
1378 | Const (class_name, name)
1379 when String.equal class_name ancestor_name ->
1380 if Option.is_some (Class.get_typeconst ancestor name) then
1381 add_typeconst_impl acc name
1382 else if Option.is_some (Class.get_const ancestor name) then
1383 add_const_impl acc name
1384 else
1386 | _ -> acc)
1388 let result =
1389 List.fold ~init:[] ~f:add_impls (Class.all_ancestor_names cls)
1391 let result =
1392 List.fold ~init:result ~f:add_impls (Class.all_ancestor_req_names cls)
1394 result
1396 let rec add_implementation_dependencies ctx env =
1397 let open Typing_deps.Dep in
1398 let size = HashSet.length env.dependencies in
1399 HashSet.fold env.dependencies ~init:[] ~f:(fun dep acc ->
1400 match dep with
1401 | Class cls_name -> cls_name :: acc
1402 | _ -> acc)
1403 |> List.concat_map ~f:(get_implementation_dependencies ctx env)
1404 |> List.iter ~f:(do_add_dep ctx env);
1405 if HashSet.length env.dependencies <> size then
1406 add_implementation_dependencies ctx env
1408 let get_dependency_origin ctx cls (dep : 'a Typing_deps.Dep.variant) =
1409 Decl_provider.(
1410 Typing_deps.Dep.(
1411 let description = variant_to_string dep in
1412 let cls = value_or_not_found description @@ get_class ctx cls in
1413 match dep with
1414 | Prop (_, name) ->
1415 let p = value_or_not_found description @@ Class.get_prop cls name in
1416 p.ce_origin
1417 | SProp (_, name) ->
1418 let sp = value_or_not_found description @@ Class.get_sprop cls name in
1419 sp.ce_origin
1420 | Method (_, name) ->
1421 let m = value_or_not_found description @@ Class.get_method cls name in
1422 m.ce_origin
1423 | SMethod (_, name) ->
1424 let sm = value_or_not_found description @@ Class.get_smethod cls name in
1425 sm.ce_origin
1426 | Const (_, name) ->
1427 let c = value_or_not_found description @@ Class.get_const cls name in
1428 c.cc_origin
1429 | Cstr cls -> cls
1430 | _ -> raise UnexpectedDependency))
1432 let collect_dependencies ctx target =
1433 let filename = Target.get_filename ctx target in
1434 let env =
1436 dependencies = HashSet.create ();
1437 depends_on_make_default = ref false;
1438 depends_on_any = ref false;
1441 let add_dependency
1442 (root : Typing_deps.Dep.dependent Typing_deps.Dep.variant)
1443 (obj : Typing_deps.Dep.dependency Typing_deps.Dep.variant) : unit =
1444 if Dep.is_relevant_dependency target root then do_add_dep ctx env obj
1446 Typing_deps.add_dependency_callback "add_dependency" add_dependency;
1447 (* Collect dependencies through side effects of typechecking and remove
1448 * the target function/method from the set of dependencies to avoid
1449 * declaring it twice.
1451 let () =
1452 Typing_deps.Dep.(
1453 match target with
1454 | Function func ->
1455 let (_ : (Tast.def * Typing_inference_env.t_global_with_pos) option) =
1456 Typing_check_service.type_fun ctx filename func
1458 add_implementation_dependencies ctx env;
1459 HashSet.remove env.dependencies (Fun func);
1460 HashSet.remove env.dependencies (FunName func)
1461 | Method (cls, m) ->
1462 let (_
1463 : (Tast.def * Typing_inference_env.t_global_with_pos list) option)
1465 Typing_check_service.type_class ctx filename cls
1467 HashSet.add env.dependencies (Method (cls, m));
1468 HashSet.add env.dependencies (SMethod (cls, m));
1469 add_implementation_dependencies ctx env;
1470 HashSet.remove env.dependencies (Method (cls, m));
1471 HashSet.remove env.dependencies (SMethod (cls, m)))
1475 let group_class_dependencies_by_class ctx dependencies =
1476 List.fold_left
1477 dependencies
1478 ~f:(fun acc obj ->
1479 Typing_deps.Dep.(
1480 match obj with
1481 | Class cls ->
1482 if SMap.mem cls acc then
1484 else
1485 SMap.add cls [] acc
1486 | AllMembers _ -> acc
1487 | Extends _ -> acc
1488 | _ ->
1489 let cls = value_exn UnexpectedDependency (Dep.get_class_name obj) in
1490 let origin = get_dependency_origin ctx cls obj in
1491 (* Consider the following example:
1493 * class Base {
1494 * public static function do(): void {}
1496 * class Derived extends Base {}
1497 * function f(): void {
1498 * Derived::do();
1501 * We will pull both SMethod(Base, do) and SMethod(Derived, do) as
1502 * dependencies, but we should not generate method do() in Derived.
1503 * Therefore, we should ignore dependencies whose origin differs
1504 * from their class.
1506 if String.equal origin cls then
1507 SMap.add cls [obj] acc ~combine:(fun x y -> y @ x)
1508 else
1509 acc))
1510 ~init:SMap.empty
1512 (* Every namespace can contain declarations of classes, functions, constants
1513 as well as nested namespaces *)
1514 type hack_namespace = {
1515 namespaces: (string, hack_namespace) Caml.Hashtbl.t;
1516 decls: string HashSet.t;
1519 let subnamespace index name =
1520 let (nspaces, _) = String.rsplit2_exn ~on:'\\' name in
1521 if String.equal nspaces "" then
1522 None
1523 else
1524 let nspaces = String.strip ~drop:(fun c -> Char.equal c '\\') nspaces in
1525 let nspaces = String.split ~on:'\\' nspaces in
1526 List.nth nspaces index
1528 (* Build the recursive hack_namespace data structure for given declarations *)
1529 let sort_by_namespace declarations =
1530 let rec add_decl nspace decl index =
1531 match subnamespace index decl with
1532 | Some name ->
1533 ( if Option.is_none (Caml.Hashtbl.find_opt nspace.namespaces name) then
1534 let nested = Caml.Hashtbl.create 0 in
1535 let declarations = HashSet.create () in
1536 Caml.Hashtbl.add
1537 nspace.namespaces
1538 name
1539 { namespaces = nested; decls = declarations } );
1540 add_decl (Caml.Hashtbl.find nspace.namespaces name) decl (index + 1)
1541 | None -> HashSet.add nspace.decls decl
1543 let namespaces =
1544 { namespaces = Caml.Hashtbl.create 0; decls = HashSet.create () }
1546 List.iter declarations ~f:(fun decl -> add_decl namespaces decl 0);
1547 namespaces
1549 (* Takes declarations of Hack classes, functions, constants (map name -> code)
1550 and produces file(s) with Hack code:
1551 1) Groups declarations by namespaces, creating hack_namespace data structure
1552 2) Recursively prints the code in every namespace.
1553 Special case: since Hack files cannot contain both namespaces and toplevel
1554 declarations, we "generate" a separate file for toplevel declarations, using
1555 hh_single_type_check multifile syntax.
1557 let get_code
1558 ~depends_on_make_default
1559 ~depends_on_any
1560 strict_declarations
1561 partial_declarations =
1562 let get_code declarations =
1563 let decl_names = SMap.keys declarations in
1564 let global_namespace = sort_by_namespace decl_names in
1565 let code_from_namespace_decls name acc =
1566 Option.value (SMap.find_opt name declarations) ~default:[] @ acc
1568 let toplevel =
1569 HashSet.fold global_namespace.decls ~init:[] ~f:code_from_namespace_decls
1571 let rec code_from_namespace nspace_name nspace_content code =
1572 let code = "}" :: code in
1573 let code =
1574 Caml.Hashtbl.fold code_from_namespace nspace_content.namespaces code
1576 let code =
1577 HashSet.fold
1578 nspace_content.decls
1579 ~init:code
1580 ~f:code_from_namespace_decls
1582 Printf.sprintf "namespace %s {" nspace_name :: code
1584 let namespaces =
1585 Caml.Hashtbl.fold code_from_namespace global_namespace.namespaces []
1587 (toplevel, namespaces)
1589 let (strict_toplevel, strict_namespaces) = get_code strict_declarations in
1590 let (partial_toplevel, partial_namespaces) = get_code partial_declarations in
1591 let helpers =
1592 ( if depends_on_make_default then
1594 Printf.sprintf
1595 "<<__Rx>> function %s(): nothing {throw new \\Exception();}"
1596 Pretty.function_make_default;
1598 else
1599 [] )
1601 if depends_on_any then
1603 "/* HH_FIXME[4101] */";
1604 Printf.sprintf
1605 "type %s = \\%s_;"
1606 Pretty.extract_standalone_any
1607 Pretty.extract_standalone_any;
1608 Printf.sprintf "type %s_<T> = T;" Pretty.extract_standalone_any;
1610 else
1613 let strict_hh_prefix = "<?hh" in
1614 let partial_hh_prefix = "<?hh // partial" in
1615 let sections =
1617 ("//// strict_toplevel.php", (strict_hh_prefix, strict_toplevel @ helpers));
1618 ("//// partial_toplevel.php", (partial_hh_prefix, partial_toplevel));
1619 ("//// strict_namespaces.php", (strict_hh_prefix, strict_namespaces));
1620 ("//// partial_namespaces.php", (partial_hh_prefix, partial_namespaces));
1623 let non_empty_sections =
1624 List.filter sections ~f:(fun (_, (_, decls)) -> not (List.is_empty decls))
1626 let format_section (prefix, decls) =
1627 prefix ^ "\n" ^ Pretty.format (String.concat ~sep:"\n" decls)
1629 match non_empty_sections with
1630 | [(_, section)] -> format_section section
1631 | _ ->
1632 Pretty.concat_map
1633 ~sep:"\n"
1634 ~f:(fun (comment, section) -> comment ^ "\n" ^ format_section section)
1635 non_empty_sections
1637 let get_declarations ctx target class_dependencies global_dependencies =
1638 let (strict_class_dependencies, partial_class_dependencies) =
1639 SMap.partition (fun cls _ -> Dep.is_strict_class ctx cls) class_dependencies
1641 let (strict_global_dependencies, partial_global_dependencies) =
1642 List.partition_tf global_dependencies ~f:(Dep.is_strict_dep ctx)
1644 let add_declaration declarations name declaration =
1645 SMap.add name [declaration] declarations ~combine:(fun x y -> y @ x)
1647 let add_global_declaration declarations dep =
1648 add_declaration
1649 declarations
1650 (Dep.global_dep_name dep)
1651 (Pretty.get_global_object_declaration ctx dep)
1653 let add_class_declaration cls fields declarations =
1654 add_declaration
1655 declarations
1657 (Pretty.construct_type_declaration ctx cls target fields)
1659 let strict_declarations =
1660 List.fold_left
1661 strict_global_dependencies
1662 ~f:add_global_declaration
1663 ~init:SMap.empty
1664 |> SMap.fold add_class_declaration strict_class_dependencies
1666 let partial_declarations =
1667 List.fold_left
1668 partial_global_dependencies
1669 ~f:add_global_declaration
1670 ~init:SMap.empty
1671 |> SMap.fold add_class_declaration partial_class_dependencies
1673 match target with
1674 | Function name ->
1675 let decl = Target.extract_target ctx target in
1676 if Dep.is_strict_fun ctx name then
1677 (add_declaration strict_declarations name decl, partial_declarations)
1678 else
1679 (strict_declarations, add_declaration partial_declarations name decl)
1680 | Method _ -> (strict_declarations, partial_declarations)
1682 let go ctx target =
1684 let env = collect_dependencies ctx target in
1685 let dependencies = HashSet.fold env.dependencies ~init:[] ~f:List.cons in
1686 let (class_dependencies, global_dependencies) =
1687 List.partition_tf dependencies ~f:(fun dep ->
1688 Option.is_some (Dep.get_class_name dep))
1690 let (strict_declarations, partial_declarations) =
1691 get_declarations
1693 target
1694 (group_class_dependencies_by_class ctx class_dependencies)
1695 global_dependencies
1697 get_code
1698 ~depends_on_make_default:!(env.depends_on_make_default)
1699 ~depends_on_any:!(env.depends_on_any)
1700 strict_declarations
1701 partial_declarations
1702 with
1703 | DependencyNotFound d -> Printf.sprintf "Dependency not found: %s" d
1704 | Unsupported
1705 | UnexpectedDependency ->
1706 Printexc.get_backtrace ()