Transformation for parameter dependent contexts
[hiphop-php.git] / hphp / hack / src / server / serverExtractStandalone.ml
blobf043bb12e25d7441a990b674093df93e37f0aa88
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 let get_class_exn ctx name =
38 let not_found_msg = Printf.sprintf "Class %s" name in
39 value_or_not_found not_found_msg @@ Decl_provider.get_class ctx name
41 let get_class_name : type a. a Typing_deps.Dep.variant -> string option =
42 (* the OCaml compiler is not smart enough to let us use an or-pattern for all of these
43 * because of how it's matching on a GADT *)
44 fun dep ->
45 Typing_deps.Dep.(
46 match dep with
47 | Const (cls, _) -> Some cls
48 | Method (cls, _) -> Some cls
49 | SMethod (cls, _) -> Some cls
50 | Prop (cls, _) -> Some cls
51 | SProp (cls, _) -> Some cls
52 | Class cls -> Some cls
53 | Cstr cls -> Some cls
54 | AllMembers cls -> Some cls
55 | Extends cls -> Some cls
56 | Fun _
57 | FunName _
58 | GConst _
59 | GConstName _ ->
60 None
61 | RecordDef _ -> records_not_supported ())
63 let global_dep_name dep =
64 Typing_deps.Dep.(
65 match dep with
66 | Fun s
67 | FunName s
68 | Class s
69 | GConst s
70 | GConstName s ->
72 | Const (_, _)
73 | Method (_, _)
74 | SMethod (_, _)
75 | Prop (_, _)
76 | SProp (_, _)
77 | Cstr _
78 | AllMembers _
79 | Extends _ ->
80 raise UnexpectedDependency
81 | RecordDef _ -> records_not_supported ())
83 let get_fun_pos ctx name =
84 Decl_provider.get_fun ctx name |> Option.map ~f:(fun decl -> decl.fe_pos)
86 let get_fun_pos_exn ctx name = value_or_not_found name (get_fun_pos ctx name)
88 let get_class_pos ctx name =
89 Decl_provider.get_class ctx name |> Option.map ~f:(fun decl -> Class.pos decl)
91 let get_class_pos_exn ctx name =
92 value_or_not_found name (get_class_pos ctx name)
94 let get_typedef_pos ctx name =
95 Decl_provider.get_typedef ctx name |> Option.map ~f:(fun decl -> decl.td_pos)
97 let get_gconst_pos ctx name =
98 Decl_provider.get_gconst ctx name
99 |> Option.map ~f:(fun ty -> Typing_defs.get_pos ty)
101 let get_class_or_typedef_pos ctx name =
102 Option.first_some (get_class_pos ctx name) (get_typedef_pos ctx name)
104 let get_dep_pos ctx dep =
105 let open Typing_deps.Dep in
106 match dep with
107 | Fun name
108 | FunName name ->
109 get_fun_pos ctx name
110 | Class name
111 | Const (name, _)
112 | Method (name, _)
113 | SMethod (name, _)
114 | Prop (name, _)
115 | SProp (name, _)
116 | Cstr name
117 | AllMembers name
118 | Extends name ->
119 get_class_or_typedef_pos ctx name
120 | GConst name
121 | GConstName name ->
122 get_gconst_pos ctx name
123 | RecordDef _ -> records_not_supported ()
125 let make_nast_getter ~get_pos ~find_in_file ~naming =
126 let nasts = ref SMap.empty in
127 fun ctx name ->
128 if SMap.mem name !nasts then
129 Some (SMap.find name !nasts)
130 else
131 let open Option in
132 get_pos ctx name >>= fun pos ->
133 find_in_file ctx (Pos.filename pos) name >>= fun nast ->
134 let nast = naming ctx nast in
135 nasts := SMap.add name nast !nasts;
136 Some nast
138 let get_fun_nast =
139 make_nast_getter
140 ~get_pos:get_fun_pos
141 ~find_in_file:Ast_provider.find_fun_in_file
142 ~naming:Naming.fun_
144 let get_fun_nast_exn ctx name = value_or_not_found name (get_fun_nast ctx name)
146 let get_class_nast =
147 make_nast_getter
148 ~get_pos:get_class_pos
149 ~find_in_file:Ast_provider.find_class_in_file
150 ~naming:Naming.class_
152 let get_typedef_nast =
153 make_nast_getter
154 ~get_pos:get_typedef_pos
155 ~find_in_file:Ast_provider.find_typedef_in_file
156 ~naming:Naming.typedef
158 let get_typedef_nast_exn ctx name =
159 value_or_not_found name (get_typedef_nast ctx name)
161 let get_gconst_nast =
162 make_nast_getter
163 ~get_pos:get_gconst_pos
164 ~find_in_file:Ast_provider.find_gconst_in_file
165 ~naming:Naming.global_const
167 let get_gconst_nast_exn ctx name =
168 value_or_not_found name (get_gconst_nast ctx name)
170 let make_class_element_nast_getter ~get_elements ~get_element_name =
171 let elements_by_class_name = ref SMap.empty in
172 fun ctx class_name element_name ->
173 if SMap.mem class_name !elements_by_class_name then
174 SMap.find_opt element_name (SMap.find class_name !elements_by_class_name)
175 else
176 let open Option in
177 get_class_nast ctx class_name >>= fun class_ ->
178 let elements_by_element_name =
179 List.fold_left
180 (get_elements class_)
181 ~f:(fun elements element ->
182 SMap.add (get_element_name element) element elements)
183 ~init:SMap.empty
185 elements_by_class_name :=
186 SMap.add class_name elements_by_element_name !elements_by_class_name;
187 SMap.find_opt element_name elements_by_element_name
189 let get_method_nast =
190 make_class_element_nast_getter
191 ~get_elements:(fun class_ -> class_.c_methods)
192 ~get_element_name:(fun method_ -> snd method_.m_name)
194 let get_method_nast_exn ctx class_name method_name =
195 value_or_not_found
196 (class_name ^ "::" ^ method_name)
197 (get_method_nast ctx class_name method_name)
199 let get_const_nast =
200 make_class_element_nast_getter
201 ~get_elements:(fun class_ -> class_.c_consts)
202 ~get_element_name:(fun const -> snd const.cc_id)
204 let get_typeconst_nast =
205 make_class_element_nast_getter
206 ~get_elements:(fun class_ -> class_.c_typeconsts)
207 ~get_element_name:(fun typeconst -> snd typeconst.c_tconst_name)
209 let get_prop_nast =
210 make_class_element_nast_getter
211 ~get_elements:(fun class_ -> class_.c_vars)
212 ~get_element_name:(fun class_var -> snd class_var.cv_id)
214 let get_prop_nast_exn ctx class_name prop_name =
215 value_or_not_found
216 (class_name ^ "::" ^ prop_name)
217 (get_prop_nast ctx class_name prop_name)
219 let get_fun_mode ctx name =
220 get_fun_nast ctx name |> Option.map ~f:(fun fun_ -> fun_.f_mode)
222 let get_class_mode ctx name =
223 get_class_nast ctx name |> Option.map ~f:(fun class_ -> class_.c_mode)
225 let get_typedef_mode ctx name =
226 get_typedef_nast ctx name |> Option.map ~f:(fun typedef -> typedef.t_mode)
228 let get_gconst_mode ctx name =
229 get_gconst_nast ctx name |> Option.map ~f:(fun gconst -> gconst.cst_mode)
231 let get_class_or_typedef_mode ctx name =
232 Option.first_some (get_class_mode ctx name) (get_typedef_mode ctx name)
234 let get_dep_mode ctx dep =
235 let open Typing_deps.Dep in
236 match dep with
237 | Fun name
238 | FunName name ->
239 get_fun_mode ctx name
240 | Class name
241 | Const (name, _)
242 | Method (name, _)
243 | SMethod (name, _)
244 | Prop (name, _)
245 | SProp (name, _)
246 | Cstr name
247 | AllMembers name
248 | Extends name ->
249 get_class_or_typedef_mode ctx name
250 | GConst name
251 | GConstName name ->
252 get_gconst_mode ctx name
253 | RecordDef _ -> records_not_supported ()
255 let is_strict_dep ctx dep =
256 match get_dep_mode ctx dep with
257 | Some FileInfo.Mstrict -> true
258 | _ -> false
260 let is_strict_fun ctx name = is_strict_dep ctx (Typing_deps.Dep.Fun name)
262 let is_strict_class ctx name = is_strict_dep ctx (Typing_deps.Dep.Class name)
264 let is_builtin_dep ctx dep =
265 let msg = Typing_deps.Dep.variant_to_string dep in
266 let pos = value_or_not_found msg (get_dep_pos ctx dep) in
267 Relative_path.(is_hhi (prefix (Pos.filename pos)))
269 let is_relevant_dependency
270 (target : target) (dep : Typing_deps.Dep.dependent Typing_deps.Dep.variant)
272 match target with
273 | Function f ->
274 (match dep with
275 | Typing_deps.Dep.Fun g
276 | Typing_deps.Dep.FunName g ->
277 String.equal f g
278 | _ -> false)
279 (* We have to collect dependencies of the entire class because dependency collection is
280 coarse-grained: if cls's member depends on D, we get a dependency edge cls --> D,
281 not (cls, member) --> D *)
282 | Method (cls, _) -> Option.equal String.equal (get_class_name dep) (Some cls)
284 let get_filename ctx target =
285 let pos =
286 match target with
287 | Function name -> get_fun_pos_exn ctx name
288 | Method (name, _) -> get_class_pos_exn ctx name
290 Pos.filename pos
292 let extract_target ctx target =
293 let filename = get_filename ctx target in
294 let abs_filename = Relative_path.to_absolute filename in
295 let file_content = In_channel.read_all abs_filename in
296 let pos =
297 match target with
298 | Function name ->
299 let fun_ = get_fun_nast_exn ctx name in
300 fun_.f_span
301 | Method (class_name, method_name) ->
302 let method_ = get_method_nast_exn ctx class_name method_name in
303 method_.m_span
305 Pos.get_text_from_pos file_content pos
307 let print_error source_text error =
308 let text =
309 SyntaxError.to_positioned_string
310 error
311 (SourceText.offset_to_position source_text)
313 Hh_logger.log "%s\n" text
315 let tree_from_string s =
316 let source_text = SourceText.make Relative_path.default s in
317 let mode = Full_fidelity_parser.parse_mode source_text in
318 let env = Full_fidelity_parser_env.make ?mode () in
319 let tree = SyntaxTree.make ~env source_text in
320 if List.is_empty (SyntaxTree.all_errors tree) then
321 tree
322 else (
323 List.iter (SyntaxTree.all_errors tree) (print_error source_text);
324 raise Hackfmt_error.InvalidSyntax
327 let fixup_xhp =
328 let re = Str.regexp "\\\\:" in
329 Str.global_replace re ":"
331 let format text =
332 try Libhackfmt.format_tree (tree_from_string (fixup_xhp text))
333 with Hackfmt_error.InvalidSyntax -> text
335 let strip_ns obj_name =
336 match String.rsplit2 obj_name '\\' with
337 | Some (_, name) -> name
338 | None -> obj_name
340 let concat_map ~sep ~f list = String.concat ~sep (List.map ~f list)
342 let function_make_default = "extract_standalone_make_default"
344 let call_make_default = Printf.sprintf "\\%s()" function_make_default
346 let extract_standalone_any = "EXTRACT_STANDALONE_ANY"
348 let string_of_tprim = function
349 | Tbool -> "bool"
350 | Tint -> "int"
351 | Tfloat -> "float"
352 | Tnum -> "num"
353 | Tstring -> "string"
354 | Tarraykey -> "arraykey"
355 | Tnull -> "null"
356 | Tvoid -> "void"
357 | Tresource -> "resource"
358 | Tnoreturn -> "noreturn"
360 let string_of_shape_field_name = function
361 | Ast_defs.SFlit_int (_, s) -> s
362 | Ast_defs.SFlit_str (_, s) -> Printf.sprintf "'%s'" s
363 | Ast_defs.SFclass_const ((_, c), (_, s)) -> Printf.sprintf "%s::%s" c s
365 let string_of_xhp_attr_info xhp_attr_info =
366 match xhp_attr_info.xai_tag with
367 | Some Required -> "@required"
368 | Some LateInit -> "@lateinit"
369 | None -> ""
371 let rec string_of_hint hint =
372 match snd hint with
373 | Hoption hint -> "?" ^ string_of_hint hint
374 | Hlike hint -> "~" ^ string_of_hint hint
375 | Hfun
377 hf_reactive_kind = _;
378 hf_param_tys;
379 hf_param_kinds;
380 hf_param_mutability = _;
381 hf_variadic_ty;
382 hf_ctxs = _;
383 (* TODO(vmladenov) support capability types here *)
384 hf_return_ty;
385 hf_is_mutable_return = _;
386 } ->
387 let param_hints = List.map hf_param_tys ~f:string_of_hint in
388 let param_kinds =
389 List.map hf_param_kinds ~f:(function
390 | Some Ast_defs.Pinout -> "inout "
391 | None -> "")
393 let params = List.map2_exn param_kinds param_hints ~f:( ^ ) in
394 let variadic =
395 match hf_variadic_ty with
396 | Some hint -> [string_of_hint hint ^ "..."]
397 | None -> []
399 Printf.sprintf
400 "(function(%s) : %s)"
401 (String.concat ~sep:", " (params @ variadic))
402 (string_of_hint hf_return_ty)
403 | Htuple hints ->
404 Printf.sprintf "(%s)" (concat_map ~sep:", " ~f:string_of_hint hints)
405 | Habstr (name, hints)
406 | Happly ((_, name), hints) ->
407 let params =
408 match hints with
409 | [] -> ""
410 | _ ->
411 Printf.sprintf "<%s>" (concat_map ~sep:", " ~f:string_of_hint hints)
413 name ^ params
414 | Hshape { nsi_allows_unknown_fields; nsi_field_map } ->
415 let string_of_shape_field { sfi_optional; sfi_name; sfi_hint } =
416 let optional_prefix =
417 if sfi_optional then
419 else
422 Printf.sprintf
423 "%s%s => %s"
424 optional_prefix
425 (string_of_shape_field_name sfi_name)
426 (string_of_hint sfi_hint)
428 let shape_fields = List.map nsi_field_map ~f:string_of_shape_field in
429 let shape_suffix =
430 if nsi_allows_unknown_fields then
431 ["..."]
432 else
435 let shape_entries = shape_fields @ shape_suffix in
436 Printf.sprintf "shape(%s)" (String.concat ~sep:", " shape_entries)
437 | Haccess (root, ids) ->
438 String.concat ~sep:"::" (string_of_hint root :: List.map ids ~f:snd)
439 | Hsoft hint -> "@" ^ string_of_hint hint
440 | Hmixed -> "mixed"
441 | Hnonnull -> "nonnull"
442 | Hdarray (khint, vhint) ->
443 Printf.sprintf
444 "darray<%s, %s>"
445 (string_of_hint khint)
446 (string_of_hint vhint)
447 | Hvarray hint -> Printf.sprintf "varray<%s>" (string_of_hint hint)
448 | Hvarray_or_darray (None, vhint) ->
449 Printf.sprintf "varray_or_darray<%s>" (string_of_hint vhint)
450 | Hvarray_or_darray (Some khint, vhint) ->
451 Printf.sprintf
452 "varray_or_darray<%s, %s>"
453 (string_of_hint khint)
454 (string_of_hint vhint)
455 | Hprim prim -> string_of_tprim prim
456 | Hthis -> "this"
457 | Hdynamic -> "dynamic"
458 | Hnothing -> "nothing"
459 | Hunion hints ->
460 Printf.sprintf "(%s)" (concat_map ~sep:" | " ~f:string_of_hint hints)
461 | Hintersection hints ->
462 Printf.sprintf "(%s)" (concat_map ~sep:" & " ~f:string_of_hint hints)
463 | Hany -> extract_standalone_any
464 | Herr -> extract_standalone_any
465 | Hfun_context name -> "ctx " ^ name
466 | Hvar name -> name
468 let maybe_string_of_user_attribute { ua_name; ua_params } =
469 let name = snd ua_name in
470 match ua_params with
471 | [] when SMap.mem name SN.UserAttributes.as_map -> Some name
472 | _ -> None
474 let string_of_user_attributes user_attributes =
475 let user_attributes =
476 List.filter_map ~f:maybe_string_of_user_attribute user_attributes
478 match user_attributes with
479 | [] -> ""
480 | _ -> Printf.sprintf "<<%s>>" (String.concat ~sep:", " user_attributes)
482 let string_of_variance = function
483 | Ast_defs.Covariant -> "+"
484 | Ast_defs.Contravariant -> "-"
485 | Ast_defs.Invariant -> ""
487 let string_of_constraint (kind, hint) =
488 let keyword =
489 match kind with
490 | Ast_defs.Constraint_as -> "as"
491 | Ast_defs.Constraint_eq -> "="
492 | Ast_defs.Constraint_super -> "super"
494 keyword ^ " " ^ string_of_hint hint
496 let rec string_of_tparam
497 Aast.
499 tp_variance;
500 tp_name;
501 tp_parameters;
502 tp_constraints;
503 tp_reified;
504 tp_user_attributes;
506 let variance = string_of_variance tp_variance in
507 let name = snd tp_name in
508 let parameters = string_of_tparams tp_parameters in
509 let constraints = List.map tp_constraints ~f:string_of_constraint in
510 let user_attributes = string_of_user_attributes tp_user_attributes in
511 let reified =
512 match tp_reified with
513 | Erased -> ""
514 | SoftReified
515 | Reified ->
516 "reify"
518 String.concat
519 ~sep:" "
520 ( user_attributes
521 :: reified
522 :: (variance ^ name)
523 :: parameters
524 :: constraints )
526 and string_of_tparams tparams =
527 match tparams with
528 | [] -> ""
529 | _ ->
530 Printf.sprintf "<%s>" (concat_map ~sep:", " ~f:string_of_tparam tparams)
532 let string_of_fun_param
534 param_type_hint;
535 param_is_variadic;
536 param_name;
537 param_expr;
538 param_callconv;
539 param_user_attributes;
542 let user_attributes = string_of_user_attributes param_user_attributes in
543 let inout =
544 match param_callconv with
545 | Some Ast_defs.Pinout -> "inout"
546 | None -> ""
548 let type_hint =
549 match param_type_hint with
550 | (_, Some hint) -> string_of_hint hint
551 | (_, None) -> ""
553 let variadic =
554 if param_is_variadic then
555 "..."
556 else
559 let default =
560 match param_expr with
561 | Some _ -> " = " ^ call_make_default
562 | None -> ""
564 Printf.sprintf
565 "%s %s %s %s%s%s"
566 user_attributes
567 inout
568 type_hint
569 variadic
570 param_name
571 default
573 let get_fun_declaration ctx name =
574 let fun_ = get_fun_nast_exn ctx name in
575 let user_attributes = string_of_user_attributes fun_.f_user_attributes in
576 let tparams = string_of_tparams fun_.f_tparams in
577 let variadic =
578 match fun_.f_variadic with
579 | FVvariadicArg fp -> [string_of_fun_param fp]
580 | FVellipsis _ -> ["..."]
581 | FVnonVariadic -> []
583 let params =
584 String.concat
585 ~sep:", "
586 (List.map fun_.f_params ~f:string_of_fun_param @ variadic)
588 let ret =
589 match fun_.f_ret with
590 | (_, Some hint) -> ": " ^ string_of_hint hint
591 | (_, None) -> ""
593 Printf.sprintf
594 "%s function %s%s(%s)%s {throw new \\Exception();}"
595 user_attributes
596 (strip_ns name)
597 tparams
598 params
601 let get_init_for_prim = function
602 | Aast_defs.Tnull -> "null"
603 | Aast_defs.Tint
604 | Aast_defs.Tnum ->
606 | Aast_defs.Tbool -> "false"
607 | Aast_defs.Tfloat -> "0.0"
608 | Aast_defs.Tstring
609 | Aast_defs.Tarraykey ->
610 "\"\""
611 | Aast_defs.Tvoid
612 | Aast_defs.Tresource
613 | Aast_defs.Tnoreturn ->
614 raise Unsupported
616 let rec get_init_from_hint ctx tparams_stack hint =
617 let unsupported_hint () =
618 Hh_logger.log
619 "%s: get_init_from_hint: unsupported hint: %s"
620 (Pos.string (Pos.to_absolute (fst hint)))
621 (Aast_defs.show_hint hint);
622 raise Unsupported
624 match snd hint with
625 | Hprim prim -> get_init_for_prim prim
626 | Hoption _ -> "null"
627 | Hlike hint -> get_init_from_hint ctx tparams_stack hint
628 | Hdarray _ -> "darray[]"
629 | Hvarray_or_darray _
630 | Hvarray _ ->
631 "varray[]"
632 | Htuple hints ->
633 Printf.sprintf
634 "tuple(%s)"
635 (concat_map ~sep:", " ~f:(get_init_from_hint ctx tparams_stack) hints)
636 | Happly ((_, name), hints) ->
637 (match () with
639 when String.equal name SN.Collections.cVec
640 || String.equal name SN.Collections.cKeyset
641 || String.equal name SN.Collections.cDict ->
642 Printf.sprintf "%s[]" (strip_ns name)
644 when String.equal name SN.Collections.cVector
645 || String.equal name SN.Collections.cImmVector
646 || String.equal name SN.Collections.cMap
647 || String.equal name SN.Collections.cImmMap
648 || String.equal name SN.Collections.cSet
649 || String.equal name SN.Collections.cImmSet ->
650 Printf.sprintf "%s {}" (strip_ns name)
651 | _ when String.equal name SN.Collections.cPair ->
652 (match hints with
653 | [first; second] ->
654 Printf.sprintf
655 "Pair {%s, %s}"
656 (get_init_from_hint ctx tparams_stack first)
657 (get_init_from_hint ctx tparams_stack second)
658 | _ -> failwith "malformed hint")
659 | _ when String.equal name SN.Classes.cClassname ->
660 (match hints with
661 | [(_, Happly ((_, class_name), _))] ->
662 Printf.sprintf "%s::class" class_name
663 | _ -> raise UnexpectedDependency)
664 | _ ->
665 (match get_class_nast ctx name with
666 | Some class_ ->
667 (match class_.c_kind with
668 | Ast_defs.Cenum ->
669 let const_name =
670 match class_.c_consts with
671 | [] -> failwith "empty enum"
672 | const :: _ -> snd const.cc_id
674 Printf.sprintf "%s::%s" name const_name
675 | _ -> unsupported_hint ())
676 | None ->
677 let typedef = get_typedef_nast_exn ctx name in
678 let tparams =
679 List.fold2_exn
680 typedef.t_tparams
681 hints
682 ~init:SMap.empty
683 ~f:(fun tparams tparam hint ->
684 SMap.add (snd tparam.tp_name) hint tparams)
686 get_init_from_hint ctx (tparams :: tparams_stack) typedef.t_kind))
687 | Hshape { nsi_field_map; _ } ->
688 let non_optional_fields =
689 List.filter nsi_field_map ~f:(fun shape_field_info ->
690 not shape_field_info.sfi_optional)
692 let get_init_shape_field { sfi_hint; sfi_name; _ } =
693 Printf.sprintf
694 "%s => %s"
695 (string_of_shape_field_name sfi_name)
696 (get_init_from_hint ctx tparams_stack sfi_hint)
698 Printf.sprintf
699 "shape(%s)"
700 (concat_map ~sep:", " ~f:get_init_shape_field non_optional_fields)
701 | Habstr (name, []) ->
702 (* FIXME: support non-empty type arguments of Habstr here? *)
703 let rec loop tparams_stack =
704 match tparams_stack with
705 | tparams :: tparams_stack' ->
706 (match SMap.find_opt name tparams with
707 | Some hint -> get_init_from_hint ctx tparams_stack' hint
708 | None -> loop tparams_stack')
709 | [] -> unsupported_hint ()
711 loop tparams_stack
712 | _ -> unsupported_hint ()
714 let get_init_from_hint ctx hint = get_init_from_hint ctx [] hint
716 let get_gconst_declaration ctx name =
717 let gconst = get_gconst_nast_exn ctx name in
718 let hint = value_or_not_found ("type of " ^ name) gconst.cst_type in
719 let init = get_init_from_hint ctx hint in
720 Printf.sprintf "const %s %s = %s;" (string_of_hint hint) (strip_ns name) init
722 let get_const_declaration ctx const =
723 let name = snd const.cc_id in
724 let abstract =
725 match const.cc_expr with
726 | Some _ -> ""
727 | None -> "abstract"
729 let (type_, init) =
730 match (const.cc_type, const.cc_expr) with
731 | (Some hint, _) ->
732 (string_of_hint hint, " = " ^ get_init_from_hint ctx hint)
733 | (None, Some e) ->
734 (match Decl_utils.infer_const e with
735 | Some tprim ->
736 let hint = (fst e, Hprim tprim) in
737 ("", " = " ^ get_init_from_hint ctx hint)
738 | None -> raise Unsupported)
739 | (None, None) -> ("", "")
741 Printf.sprintf "%s const %s %s%s;" abstract type_ name init
743 let get_global_object_declaration ctx obj =
744 Typing_deps.Dep.(
745 match obj with
746 | Fun f
747 | FunName f ->
748 get_fun_declaration ctx f
749 | GConst c
750 | GConstName c ->
751 get_gconst_declaration ctx c
752 (* No other global declarations *)
753 | _ -> raise UnexpectedDependency)
755 let get_class_declaration class_ =
756 let name = snd class_.c_name in
757 let user_attributes = string_of_user_attributes class_.c_user_attributes in
758 let final =
759 if class_.c_final then
760 "final"
761 else
764 let kind =
765 match class_.c_kind with
766 | Ast_defs.Cabstract -> "abstract class"
767 | Ast_defs.Cnormal -> "class"
768 | Ast_defs.Cinterface -> "interface"
769 | Ast_defs.Ctrait -> "trait"
770 | Ast_defs.Cenum -> "enum"
772 let tparams = string_of_tparams class_.c_tparams in
773 let extends =
774 match class_.c_extends with
775 | [] -> ""
776 | _ ->
777 Printf.sprintf
778 "extends %s"
779 (concat_map ~sep:", " ~f:string_of_hint class_.c_extends)
781 let implements =
782 match class_.c_implements with
783 | [] -> ""
784 | _ ->
785 Printf.sprintf
786 "implements %s"
787 (concat_map ~sep:", " ~f:string_of_hint class_.c_implements)
789 Printf.sprintf
790 "%s %s %s %s%s %s %s"
791 user_attributes
792 final
793 kind
794 (strip_ns name)
795 tparams
796 extends
797 implements
799 let get_method_declaration method_ ~from_interface =
800 let abstract =
801 if method_.m_abstract && not from_interface then
802 "abstract"
803 else
806 let final =
807 if method_.m_final then
808 "final"
809 else
812 let visibility = string_of_visibility method_.m_visibility in
813 let static =
814 if method_.m_static then
815 "static"
816 else
819 let user_attributes = string_of_user_attributes method_.m_user_attributes in
820 let name = strip_ns (snd method_.m_name) in
821 let tparams = string_of_tparams method_.m_tparams in
822 let variadic =
823 match method_.m_variadic with
824 | FVvariadicArg fp -> [string_of_fun_param fp]
825 | FVellipsis _ -> ["..."]
826 | FVnonVariadic -> []
828 let params =
829 String.concat
830 ~sep:", "
831 (List.map method_.m_params ~f:string_of_fun_param @ variadic)
833 let ret =
834 match method_.m_ret with
835 | (_, Some hint) -> ": " ^ string_of_hint hint
836 | (_, None) -> ""
838 let body =
839 if method_.m_abstract || from_interface then
841 else
842 "{throw new \\Exception();}"
844 Printf.sprintf
845 "%s %s %s %s %s function %s%s(%s)%s%s"
846 user_attributes
847 abstract
848 final
849 visibility
850 static
851 name
852 tparams
853 params
855 body
857 let get_prop_declaration ctx prop =
858 let name = snd prop.cv_id in
859 let user_attributes = string_of_user_attributes prop.cv_user_attributes in
860 let (type_, init) =
861 match (hint_of_type_hint prop.cv_type, prop.cv_expr) with
862 | (Some hint, Some _) ->
863 (string_of_hint hint, Printf.sprintf " = %s" (get_init_from_hint ctx hint))
864 | (Some hint, None) -> (string_of_hint hint, "")
865 | (None, None) -> ("", "")
866 (* Untyped prop, not supported for now *)
867 | (None, Some _) -> raise Unsupported
869 match prop.cv_xhp_attr with
870 | None ->
871 (* Ordinary property *)
872 let visibility = string_of_visibility prop.cv_visibility in
873 let static =
874 if prop.cv_is_static then
875 "static"
876 else
879 Printf.sprintf
880 "%s %s %s %s $%s%s;"
881 user_attributes
882 visibility
883 static
884 type_
885 name
886 init
887 | Some xhp_attr_info ->
888 (* XHP attribute *)
889 Printf.sprintf
890 "%s attribute %s %s %s %s;"
891 user_attributes
892 type_
893 (String.lstrip ~drop:(fun c -> Char.equal c ':') name)
894 init
895 (string_of_xhp_attr_info xhp_attr_info)
897 let get_typeconst_declaration typeconst =
898 let abstract =
899 match typeconst.c_tconst_abstract with
900 | TCAbstract _ -> "abstract"
901 | TCPartiallyAbstract
902 | TCConcrete ->
905 let name = snd typeconst.c_tconst_name in
906 let type_ =
907 match typeconst.c_tconst_type with
908 | Some hint -> " = " ^ string_of_hint hint
909 | None -> ""
911 let constraint_ =
912 match typeconst.c_tconst_constraint with
913 | Some hint -> " as " ^ string_of_hint hint
914 | None -> ""
916 Printf.sprintf "%s const type %s%s%s;" abstract name constraint_ type_
918 let get_method_declaration ctx target class_name method_name =
919 match target with
920 | ServerCommandTypes.Extract_standalone.Method
921 (target_class_name, target_method_name)
922 when String.equal class_name target_class_name
923 && String.equal method_name target_method_name ->
924 None
925 | _ ->
926 let open Option in
927 get_class_nast ctx class_name >>= fun class_ ->
928 let from_interface = Ast_defs.is_c_interface class_.c_kind in
929 get_method_nast ctx class_name method_name >>= fun method_ ->
930 Some (get_method_declaration method_ ~from_interface)
932 let get_class_elt_declaration
933 ctx target (class_elt : 'a Typing_deps.Dep.variant) =
934 let open Typing_deps.Dep in
935 match class_elt with
936 | Const (class_name, const_name) ->
937 (match get_typeconst_nast ctx class_name const_name with
938 | Some typeconst -> Some (get_typeconst_declaration typeconst)
939 | None ->
940 (match get_const_nast ctx class_name const_name with
941 | Some const -> Some (get_const_declaration ctx const)
942 | None -> raise (DependencyNotFound (class_name ^ "::" ^ const_name))))
943 | Method (class_name, method_name)
944 | SMethod (class_name, method_name) ->
945 get_method_declaration ctx target class_name method_name
946 | Cstr class_name ->
947 get_method_declaration ctx target class_name "__construct"
948 | Prop (class_name, prop_name) ->
949 let prop = get_prop_nast_exn ctx class_name prop_name in
950 Some (get_prop_declaration ctx prop)
951 | SProp (class_name, sprop_name) ->
952 let sprop_name =
953 String.lstrip ~drop:(fun c -> Char.equal c '$') sprop_name
955 let prop = get_prop_nast_exn ctx class_name sprop_name in
956 Some (get_prop_declaration ctx prop)
957 (* Constructor should've been tackled earlier, and all other dependencies aren't class elements *)
958 | Extends _
959 | AllMembers _
960 | Class _
961 | Fun _
962 | FunName _
963 | GConst _
964 | GConstName _ ->
965 raise UnexpectedDependency
966 | RecordDef _ -> records_not_supported ()
968 let construct_enum ctx class_ =
969 let name = snd class_.c_name in
970 let enum =
971 match class_.c_enum with
972 | Some enum -> enum
973 | None -> failwith ("not an enum: " ^ snd class_.c_name)
975 let constraint_ =
976 match enum.e_constraint with
977 | Some hint -> " as " ^ string_of_hint hint
978 | None -> ""
980 let string_of_enum_const const =
981 Printf.sprintf
982 "%s = %s;"
983 (snd const.cc_id)
984 (get_init_from_hint ctx enum.e_base)
986 Printf.sprintf
987 "enum %s: %s%s {%s}"
988 (strip_ns name)
989 (string_of_hint enum.e_base)
990 constraint_
991 (concat_map ~sep:"\n" ~f:string_of_enum_const class_.c_consts)
993 let get_class_body ctx class_ target class_elts =
994 let name = snd class_.c_name in
995 let uses =
996 List.map class_.c_uses ~f:(fun s ->
997 Printf.sprintf "use %s;" (string_of_hint s))
999 let (req_extends, req_implements) =
1000 List.partition_map class_.c_reqs ~f:(fun (s, extends) ->
1001 if extends then
1002 `Fst (Printf.sprintf "require extends %s;" (string_of_hint s))
1003 else
1004 `Snd (Printf.sprintf "require implements %s;" (string_of_hint s)))
1006 let open Typing_deps in
1007 let body =
1008 List.filter_map class_elts ~f:(function
1009 | Dep.AllMembers _
1010 | Dep.Extends _ ->
1011 raise UnexpectedDependency
1012 | Dep.Const (_, "class") -> None
1013 | class_elt -> get_class_elt_declaration ctx target class_elt)
1015 (* If we are extracting a method of this class, we should declare it
1016 here, with stubs of other class elements. *)
1017 let extracted_method =
1018 match target with
1019 | Method (cls_name, _) when String.equal cls_name name ->
1020 [extract_target ctx target]
1021 | _ -> []
1023 String.concat
1024 ~sep:"\n"
1025 (req_extends @ req_implements @ uses @ body @ extracted_method)
1027 let construct_class ctx class_ target fields =
1028 let decl = get_class_declaration class_ in
1029 let body = get_class_body ctx class_ target fields in
1030 Printf.sprintf "%s {%s}" decl body
1032 let construct_enum_or_class ctx class_ target fields =
1033 match class_.c_kind with
1034 | Ast_defs.Cabstract
1035 | Ast_defs.Cnormal
1036 | Ast_defs.Cinterface
1037 | Ast_defs.Ctrait ->
1038 construct_class ctx class_ target fields
1039 | Ast_defs.Cenum -> construct_enum ctx class_
1041 let construct_typedef typedef =
1042 let name = snd typedef.t_name in
1043 let keyword =
1044 match typedef.t_vis with
1045 | Aast_defs.Transparent -> "type"
1046 | Aast_defs.Opaque -> "newtype"
1048 let tparams = string_of_tparams typedef.t_tparams in
1049 let constraint_ =
1050 match typedef.t_constraint with
1051 | Some hint -> " as " ^ string_of_hint hint
1052 | None -> ""
1054 let pos = fst typedef.t_name in
1055 let hh_fixmes =
1056 String.concat
1057 (List.map
1058 ~f:(fun code -> Printf.sprintf "/* HH_FIXME[%d] */\n" code)
1059 (ISet.elements (Fixme_provider.get_fixme_codes_for_pos pos)))
1061 Printf.sprintf
1062 "%s%s %s%s%s = %s;"
1063 hh_fixmes
1064 keyword
1065 (strip_ns name)
1066 tparams
1067 constraint_
1068 (string_of_hint typedef.t_kind)
1070 let construct_type_declaration ctx t target fields =
1071 match get_class_nast ctx t with
1072 | Some class_ -> construct_enum_or_class ctx class_ target fields
1073 | None ->
1074 let typedef = get_typedef_nast_exn ctx t in
1075 construct_typedef typedef
1077 type extraction_env = {
1078 dependencies: Typing_deps.Dep.dependency Typing_deps.Dep.variant HashSet.t;
1079 depends_on_make_default: bool ref;
1080 depends_on_any: bool ref;
1083 let rec do_add_dep ctx env dep =
1084 let is_wildcard =
1085 match dep with
1086 | Typing_deps.Dep.Class h -> String.equal h SN.Typehints.wildcard
1087 | _ -> false
1090 (not is_wildcard)
1091 && (not (HashSet.mem env.dependencies dep))
1092 && not (is_builtin_dep ctx dep)
1093 then (
1094 HashSet.add env.dependencies dep;
1095 add_signature_dependencies ctx env dep
1098 and add_dep ctx env ~this ty : unit =
1099 let visitor =
1100 object
1101 inherit [unit] Type_visitor.decl_type_visitor as super
1103 method! on_tany _ _ = env.depends_on_any := true
1105 method! on_tfun () r ft =
1106 if List.exists ~f:Typing_defs.get_fp_has_default ft.ft_params then
1107 env.depends_on_make_default := true;
1108 super#on_tfun () r ft
1110 method! on_tapply _ _ (_, name) tyl =
1111 let dep = Typing_deps.Dep.Class name in
1112 do_add_dep ctx env dep;
1114 (* If we have a constant of a generic type, it can only be an
1115 array type, e.g., vec<A>, for which don't need values of A
1116 to generate an initializer. *)
1117 List.iter tyl ~f:(add_dep ctx env ~this)
1119 method! on_tshape _ _ _ fdm =
1120 Nast.ShapeMap.iter
1121 (fun name { sft_ty; _ } ->
1122 (match name with
1123 | Ast_defs.SFlit_int _
1124 | Ast_defs.SFlit_str _ ->
1126 | Ast_defs.SFclass_const ((_, c), (_, s)) ->
1127 do_add_dep ctx env (Typing_deps.Dep.Class c);
1128 do_add_dep ctx env (Typing_deps.Dep.Const (c, s)));
1129 add_dep ctx env ~this sft_ty)
1132 (* We un-nest (((this::T1)::T2)::T3) into (this, [T1;T2;T3]) and then re-nest
1133 * because legacy representation of Taccess was using lists. TODO: implement
1134 * this more directly instead.
1136 method! on_taccess () r (root, tconst) =
1137 let rec split_taccess root ids =
1138 match Typing_defs.get_node root with
1139 | Taccess (root, id) -> split_taccess root (id :: ids)
1140 | _ -> (root, ids)
1142 let rec make_taccess r root ids =
1143 match ids with
1144 | [] -> root
1145 | id :: ids ->
1146 make_taccess
1147 Reason.Rnone
1148 (mk (r, Typing_defs.Taccess (root, id)))
1151 let (root, tconsts) = split_taccess root [tconst] in
1152 let expand_type_access class_name tconsts =
1153 match tconsts with
1154 | [] -> raise UnexpectedDependency
1155 (* Expand Class::TConst1::TConst2[::...]: get TConst1 in
1156 Class, get its type or upper bound T, continue adding
1157 dependencies of T::TConst2[::...] *)
1158 | (_, tconst) :: tconsts ->
1159 do_add_dep ctx env (Typing_deps.Dep.Const (class_name, tconst));
1160 let cls = get_class_exn ctx class_name in
1161 (match Decl_provider.Class.get_typeconst cls tconst with
1162 | Some typeconst ->
1163 Option.iter
1164 typeconst.ttc_type
1165 ~f:(add_dep ctx ~this:(Some class_name) env);
1166 if not (List.is_empty tconsts) then (
1167 match (typeconst.ttc_type, typeconst.ttc_constraint) with
1168 | (Some tc_type, _)
1169 | (None, Some tc_type) ->
1170 (* What does 'this' refer to inside of T? *)
1171 let this =
1172 match Typing_defs.get_node tc_type with
1173 | Tapply ((_, name), _) -> Some name
1174 | _ -> this
1176 let taccess = make_taccess r tc_type tconsts in
1177 add_dep ctx ~this env taccess
1178 | (None, None) -> ()
1180 | None -> ())
1182 match Typing_defs.get_node root with
1183 | Taccess (root', tconst) ->
1184 add_dep ctx ~this env (make_taccess r root' (tconst :: tconsts))
1185 | Tapply ((_, name), _) -> expand_type_access name tconsts
1186 | Tthis -> expand_type_access (Option.value_exn this) tconsts
1187 | _ -> raise UnexpectedDependency
1190 visitor#on_type () ty
1192 and add_signature_dependencies ctx env obj =
1193 let open Typing_deps.Dep in
1194 let description = variant_to_string obj in
1195 match get_class_name obj with
1196 | Some cls_name ->
1197 do_add_dep ctx env (Typing_deps.Dep.Class cls_name);
1198 (match Decl_provider.get_class ctx cls_name with
1199 | None ->
1200 let td =
1201 value_or_not_found description @@ Decl_provider.get_typedef ctx cls_name
1203 add_dep ctx ~this:None env td.td_type;
1204 Option.iter td.td_constraint ~f:(add_dep ctx ~this:None env)
1205 | Some cls ->
1206 let add_dep = add_dep ctx env ~this:(Some cls_name) in
1207 (match obj with
1208 | Prop (_, name) ->
1209 let p = value_or_not_found description @@ Class.get_prop cls name in
1210 add_dep @@ Lazy.force p.ce_type;
1212 (* We need to initialize properties in the constructor, add a dependency on it *)
1213 do_add_dep ctx env (Cstr cls_name)
1214 | SProp (_, name) ->
1215 let sp = value_or_not_found description @@ Class.get_sprop cls name in
1216 add_dep @@ Lazy.force sp.ce_type
1217 | Method (_, name) ->
1218 let m = value_or_not_found description @@ Class.get_method cls name in
1219 add_dep @@ Lazy.force m.ce_type;
1220 Class.all_ancestor_names cls
1221 |> List.iter ~f:(fun ancestor_name ->
1222 match Decl_provider.get_class ctx ancestor_name with
1223 | Some ancestor when Class.has_method ancestor name ->
1224 do_add_dep ctx env (Method (ancestor_name, name))
1225 | _ -> ())
1226 | SMethod (_, name) ->
1227 (match Class.get_smethod cls name with
1228 | Some sm ->
1229 add_dep @@ Lazy.force sm.ce_type;
1230 Class.all_ancestor_names cls
1231 |> List.iter ~f:(fun ancestor_name ->
1232 match Decl_provider.get_class ctx ancestor_name with
1233 | Some ancestor when Class.has_smethod ancestor name ->
1234 do_add_dep ctx env (SMethod (ancestor_name, name))
1235 | _ -> ())
1236 | None ->
1237 (match Class.get_method cls name with
1238 | Some _ ->
1239 HashSet.remove env.dependencies obj;
1240 do_add_dep ctx env (Method (cls_name, name))
1241 | None -> raise (DependencyNotFound description)))
1242 | Const (_, name) ->
1243 (match Class.get_typeconst cls name with
1244 | Some tc ->
1245 if not (String.equal cls_name tc.ttc_origin) then
1246 do_add_dep ctx env (Const (tc.ttc_origin, name));
1247 Option.iter tc.ttc_type ~f:add_dep;
1248 Option.iter tc.ttc_constraint ~f:add_dep
1249 | None ->
1250 let c = value_or_not_found description @@ Class.get_const cls name in
1251 add_dep c.cc_type)
1252 | Cstr _ ->
1253 (match Class.construct cls with
1254 | (Some constr, _) -> add_dep @@ Lazy.force constr.ce_type
1255 | _ -> ())
1256 | Class _ ->
1257 List.iter (Class.all_ancestors cls) (fun (_, ty) -> add_dep ty);
1258 List.iter (Class.all_ancestor_reqs cls) (fun (_, ty) -> add_dep ty);
1259 Option.iter
1260 (Class.enum_type cls)
1261 ~f:(fun { te_base; te_constraint; te_includes; te_enum_class = _ } ->
1262 add_dep te_base;
1263 Option.iter te_constraint ~f:add_dep;
1264 List.iter te_includes ~f:add_dep)
1265 | AllMembers _ ->
1266 (* AllMembers is used for dependencies on enums, so we should depend on all constants *)
1267 List.iter (Class.consts cls) (fun (name, c) ->
1268 if not (String.equal name "class") then add_dep c.cc_type)
1269 (* Ignore, we fetch class hierarchy when we call add_signature_dependencies on a class dep *)
1270 | Extends _ -> ()
1271 | _ -> raise UnexpectedDependency))
1272 | None ->
1273 (match obj with
1274 | Fun f
1275 | FunName f ->
1276 let func =
1277 value_or_not_found description @@ Decl_provider.get_fun ctx f
1279 add_dep ctx ~this:None env @@ func.fe_type
1280 | GConst c
1281 | GConstName c ->
1282 let ty =
1283 value_or_not_found description @@ Decl_provider.get_gconst ctx c
1285 add_dep ctx ~this:None env ty
1286 | _ -> raise UnexpectedDependency)
1288 let get_implementation_dependencies ctx env cls_name =
1289 let open Decl_provider in
1290 match get_class ctx cls_name with
1291 | None -> []
1292 | Some cls ->
1293 let open Typing_deps.Dep in
1294 let add_smethod_impl acc smethod_name =
1295 match Class.get_smethod cls smethod_name with
1296 | Some elt -> SMethod (elt.ce_origin, smethod_name) :: acc
1297 | _ -> acc
1299 let add_method_impl acc method_name =
1300 match Class.get_method cls method_name with
1301 | Some elt -> Method (elt.ce_origin, method_name) :: acc
1302 | _ -> acc
1304 let add_typeconst_impl acc typeconst_name =
1305 match Class.get_typeconst cls typeconst_name with
1306 | Some tc -> Const (tc.ttc_origin, typeconst_name) :: acc
1307 | _ -> acc
1309 let add_const_impl acc const_name =
1310 match Class.get_const cls const_name with
1311 | Some c -> Const (c.cc_origin, const_name) :: acc
1312 | _ -> acc
1314 let add_impls acc ancestor_name =
1315 let ancestor = get_class_exn ctx ancestor_name in
1316 if is_builtin_dep ctx (Class ancestor_name) then
1317 let acc =
1318 List.fold
1319 (Class.smethods ancestor)
1320 ~init:acc
1321 ~f:(fun acc (smethod_name, _) -> add_smethod_impl acc smethod_name)
1323 let acc =
1324 List.fold
1325 (Class.methods ancestor)
1326 ~init:acc
1327 ~f:(fun acc (method_name, _) -> add_method_impl acc method_name)
1329 let acc =
1330 List.fold
1331 (Class.typeconsts ancestor)
1332 ~init:acc
1333 ~f:(fun acc (typeconst_name, _) ->
1334 add_typeconst_impl acc typeconst_name)
1336 let acc =
1337 List.fold
1338 (Class.consts ancestor)
1339 ~init:acc
1340 ~f:(fun acc (const_name, _) -> add_const_impl acc const_name)
1343 else
1344 HashSet.fold env.dependencies ~init:acc ~f:(fun dep acc ->
1345 match dep with
1346 | SMethod (class_name, method_name)
1347 when String.equal class_name ancestor_name ->
1348 add_smethod_impl acc method_name
1349 | Method (class_name, method_name)
1350 when String.equal class_name ancestor_name ->
1351 add_method_impl acc method_name
1352 | Const (class_name, name)
1353 when String.equal class_name ancestor_name ->
1354 if Option.is_some (Class.get_typeconst ancestor name) then
1355 add_typeconst_impl acc name
1356 else if Option.is_some (Class.get_const ancestor name) then
1357 add_const_impl acc name
1358 else
1360 | _ -> acc)
1362 let result =
1363 List.fold ~init:[] ~f:add_impls (Class.all_ancestor_names cls)
1365 let result =
1366 List.fold ~init:result ~f:add_impls (Class.all_ancestor_req_names cls)
1368 result
1370 let rec add_implementation_dependencies ctx env =
1371 let open Typing_deps.Dep in
1372 let size = HashSet.length env.dependencies in
1373 HashSet.fold env.dependencies ~init:[] ~f:(fun dep acc ->
1374 match dep with
1375 | Class cls_name -> cls_name :: acc
1376 | _ -> acc)
1377 |> List.concat_map ~f:(get_implementation_dependencies ctx env)
1378 |> List.iter ~f:(do_add_dep ctx env);
1379 if HashSet.length env.dependencies <> size then
1380 add_implementation_dependencies ctx env
1382 let get_dependency_origin ctx cls (dep : 'a Typing_deps.Dep.variant) =
1383 Decl_provider.(
1384 Typing_deps.Dep.(
1385 let description = variant_to_string dep in
1386 let cls = value_or_not_found description @@ get_class ctx cls in
1387 match dep with
1388 | Prop (_, name) ->
1389 let p = value_or_not_found description @@ Class.get_prop cls name in
1390 p.ce_origin
1391 | SProp (_, name) ->
1392 let sp = value_or_not_found description @@ Class.get_sprop cls name in
1393 sp.ce_origin
1394 | Method (_, name) ->
1395 let m = value_or_not_found description @@ Class.get_method cls name in
1396 m.ce_origin
1397 | SMethod (_, name) ->
1398 let sm = value_or_not_found description @@ Class.get_smethod cls name in
1399 sm.ce_origin
1400 | Const (_, name) ->
1401 let c = value_or_not_found description @@ Class.get_const cls name in
1402 c.cc_origin
1403 | Cstr cls -> cls
1404 | _ -> raise UnexpectedDependency))
1406 let collect_dependencies ctx target =
1407 let filename = get_filename ctx target in
1408 let env =
1410 dependencies = HashSet.create ();
1411 depends_on_make_default = ref false;
1412 depends_on_any = ref false;
1415 let add_dependency
1416 (root : Typing_deps.Dep.dependent Typing_deps.Dep.variant)
1417 (obj : Typing_deps.Dep.dependency Typing_deps.Dep.variant) : unit =
1418 if is_relevant_dependency target root then do_add_dep ctx env obj
1420 Typing_deps.add_dependency_callback "add_dependency" add_dependency;
1421 (* Collect dependencies through side effects of typechecking and remove
1422 * the target function/method from the set of dependencies to avoid
1423 * declaring it twice.
1425 let () =
1426 Typing_deps.Dep.(
1427 match target with
1428 | Function func ->
1429 let (_ : (Tast.def * Typing_inference_env.t_global_with_pos) option) =
1430 Typing_check_service.type_fun ctx filename func
1432 add_implementation_dependencies ctx env;
1433 HashSet.remove env.dependencies (Fun func);
1434 HashSet.remove env.dependencies (FunName func)
1435 | Method (cls, m) ->
1436 let (_
1437 : (Tast.def * Typing_inference_env.t_global_with_pos list) option)
1439 Typing_check_service.type_class ctx filename cls
1441 HashSet.add env.dependencies (Method (cls, m));
1442 HashSet.add env.dependencies (SMethod (cls, m));
1443 add_implementation_dependencies ctx env;
1444 HashSet.remove env.dependencies (Method (cls, m));
1445 HashSet.remove env.dependencies (SMethod (cls, m)))
1449 let group_class_dependencies_by_class ctx dependencies =
1450 List.fold_left
1451 dependencies
1452 ~f:(fun acc obj ->
1453 Typing_deps.Dep.(
1454 match obj with
1455 | Class cls ->
1456 if SMap.mem cls acc then
1458 else
1459 SMap.add cls [] acc
1460 | AllMembers _ -> acc
1461 | Extends _ -> acc
1462 | _ ->
1463 let cls = value_exn UnexpectedDependency (get_class_name obj) in
1464 let origin = get_dependency_origin ctx cls obj in
1465 (* Consider the following example:
1467 * class Base {
1468 * public static function do(): void {}
1470 * class Derived extends Base {}
1471 * function f(): void {
1472 * Derived::do();
1475 * We will pull both SMethod(Base, do) and SMethod(Derived, do) as
1476 * dependencies, but we should not generate method do() in Derived.
1477 * Therefore, we should ignore dependencies whose origin differs
1478 * from their class.
1480 if String.equal origin cls then
1481 SMap.add cls [obj] acc ~combine:(fun x y -> y @ x)
1482 else
1483 acc))
1484 ~init:SMap.empty
1486 (* Every namespace can contain declarations of classes, functions, constants
1487 as well as nested namespaces *)
1488 type hack_namespace = {
1489 namespaces: (string, hack_namespace) Caml.Hashtbl.t;
1490 decls: string HashSet.t;
1493 let subnamespace index name =
1494 let (nspaces, _) = String.rsplit2_exn ~on:'\\' name in
1495 if String.equal nspaces "" then
1496 None
1497 else
1498 let nspaces = String.strip ~drop:(fun c -> Char.equal c '\\') nspaces in
1499 let nspaces = String.split ~on:'\\' nspaces in
1500 List.nth nspaces index
1502 (* Build the recursive hack_namespace data structure for given declarations *)
1503 let sort_by_namespace declarations =
1504 let rec add_decl nspace decl index =
1505 match subnamespace index decl with
1506 | Some name ->
1507 ( if Option.is_none (Caml.Hashtbl.find_opt nspace.namespaces name) then
1508 let nested = Caml.Hashtbl.create 0 in
1509 let declarations = HashSet.create () in
1510 Caml.Hashtbl.add
1511 nspace.namespaces
1512 name
1513 { namespaces = nested; decls = declarations } );
1514 add_decl (Caml.Hashtbl.find nspace.namespaces name) decl (index + 1)
1515 | None -> HashSet.add nspace.decls decl
1517 let namespaces =
1518 { namespaces = Caml.Hashtbl.create 0; decls = HashSet.create () }
1520 List.iter declarations ~f:(fun decl -> add_decl namespaces decl 0);
1521 namespaces
1523 (* Takes declarations of Hack classes, functions, constants (map name -> code)
1524 and produces file(s) with Hack code:
1525 1) Groups declarations by namespaces, creating hack_namespace data structure
1526 2) Recursively prints the code in every namespace.
1527 Special case: since Hack files cannot contain both namespaces and toplevel
1528 declarations, we "generate" a separate file for toplevel declarations, using
1529 hh_single_type_check multifile syntax.
1531 let get_code
1532 ~depends_on_make_default
1533 ~depends_on_any
1534 strict_declarations
1535 partial_declarations =
1536 let get_code declarations =
1537 let decl_names = SMap.keys declarations in
1538 let global_namespace = sort_by_namespace decl_names in
1539 let code_from_namespace_decls name acc =
1540 Option.value (SMap.find_opt name declarations) ~default:[] @ acc
1542 let toplevel =
1543 HashSet.fold global_namespace.decls ~init:[] ~f:code_from_namespace_decls
1545 let rec code_from_namespace nspace_name nspace_content code =
1546 let code = "}" :: code in
1547 let code =
1548 Caml.Hashtbl.fold code_from_namespace nspace_content.namespaces code
1550 let code =
1551 HashSet.fold
1552 nspace_content.decls
1553 ~init:code
1554 ~f:code_from_namespace_decls
1556 Printf.sprintf "namespace %s {" nspace_name :: code
1558 let namespaces =
1559 Caml.Hashtbl.fold code_from_namespace global_namespace.namespaces []
1561 (toplevel, namespaces)
1563 let (strict_toplevel, strict_namespaces) = get_code strict_declarations in
1564 let (partial_toplevel, partial_namespaces) = get_code partial_declarations in
1565 let helpers =
1566 ( if depends_on_make_default then
1568 Printf.sprintf
1569 "<<__Rx>> function %s(): nothing {throw new \\Exception();}"
1570 function_make_default;
1572 else
1573 [] )
1575 if depends_on_any then
1577 "/* HH_FIXME[4101] */";
1578 Printf.sprintf
1579 "type %s = \\%s_;"
1580 extract_standalone_any
1581 extract_standalone_any;
1582 Printf.sprintf "type %s_<T> = T;" extract_standalone_any;
1584 else
1587 let strict_hh_prefix = "<?hh" in
1588 let partial_hh_prefix = "<?hh // partial" in
1589 let sections =
1591 ("//// strict_toplevel.php", (strict_hh_prefix, strict_toplevel @ helpers));
1592 ("//// partial_toplevel.php", (partial_hh_prefix, partial_toplevel));
1593 ("//// strict_namespaces.php", (strict_hh_prefix, strict_namespaces));
1594 ("//// partial_namespaces.php", (partial_hh_prefix, partial_namespaces));
1597 let non_empty_sections =
1598 List.filter sections ~f:(fun (_, (_, decls)) -> not (List.is_empty decls))
1600 let format_section (prefix, decls) =
1601 prefix ^ "\n" ^ format (String.concat ~sep:"\n" decls)
1603 match non_empty_sections with
1604 | [(_, section)] -> format_section section
1605 | _ ->
1606 concat_map
1607 ~sep:"\n"
1608 ~f:(fun (comment, section) -> comment ^ "\n" ^ format_section section)
1609 non_empty_sections
1611 let get_declarations ctx target class_dependencies global_dependencies =
1612 let (strict_class_dependencies, partial_class_dependencies) =
1613 SMap.partition (fun cls _ -> is_strict_class ctx cls) class_dependencies
1615 let (strict_global_dependencies, partial_global_dependencies) =
1616 List.partition_tf global_dependencies ~f:(is_strict_dep ctx)
1618 let add_declaration declarations name declaration =
1619 SMap.add name [declaration] declarations ~combine:(fun x y -> y @ x)
1621 let add_global_declaration declarations dep =
1622 add_declaration
1623 declarations
1624 (global_dep_name dep)
1625 (get_global_object_declaration ctx dep)
1627 let add_class_declaration cls fields declarations =
1628 add_declaration
1629 declarations
1631 (construct_type_declaration ctx cls target fields)
1633 let strict_declarations =
1634 List.fold_left
1635 strict_global_dependencies
1636 ~f:add_global_declaration
1637 ~init:SMap.empty
1638 |> SMap.fold add_class_declaration strict_class_dependencies
1640 let partial_declarations =
1641 List.fold_left
1642 partial_global_dependencies
1643 ~f:add_global_declaration
1644 ~init:SMap.empty
1645 |> SMap.fold add_class_declaration partial_class_dependencies
1647 match target with
1648 | Function name ->
1649 let decl = extract_target ctx target in
1650 if is_strict_fun ctx name then
1651 (add_declaration strict_declarations name decl, partial_declarations)
1652 else
1653 (strict_declarations, add_declaration partial_declarations name decl)
1654 | Method _ -> (strict_declarations, partial_declarations)
1656 let go ctx target =
1658 let env = collect_dependencies ctx target in
1659 let dependencies = HashSet.fold env.dependencies ~init:[] ~f:List.cons in
1660 let (class_dependencies, global_dependencies) =
1661 List.partition_tf dependencies ~f:(fun dep ->
1662 Option.is_some (get_class_name dep))
1664 let (strict_declarations, partial_declarations) =
1665 get_declarations
1667 target
1668 (group_class_dependencies_by_class ctx class_dependencies)
1669 global_dependencies
1671 get_code
1672 ~depends_on_make_default:!(env.depends_on_make_default)
1673 ~depends_on_any:!(env.depends_on_any)
1674 strict_declarations
1675 partial_declarations
1676 with
1677 | DependencyNotFound d -> Printf.sprintf "Dependency not found: %s" d
1678 | Unsupported
1679 | UnexpectedDependency ->
1680 Printexc.get_backtrace ()