Fix stack overflow bug
[hiphop-php.git] / hphp / hack / src / hh_single_type_check.ml
blobb48f09379fd4041888d77d955b7fb624512a68d6
1 (*
2 * Copyright (c) 2015, Facebook, Inc.
3 * All rights reserved.
5 * This source code is licensed under the MIT license found in the
6 * LICENSE file in the "hack" directory of this source tree.
8 *)
10 open Hh_prelude
11 open String_utils
12 open Sys_utils
13 open Typing_env_types
14 module Inf = Typing_inference_env
15 module Cls = Decl_provider.Class
17 (*****************************************************************************)
18 (* Profiling utilities *)
19 (*****************************************************************************)
21 let mean samples =
22 List.fold ~init:0.0 ~f:( +. ) samples /. Float.of_int (List.length samples)
24 let standard_deviation mean samples =
25 let sosq =
26 List.fold
27 ~init:0.0
28 ~f:(fun acc sample ->
29 let diff = sample -. mean in
30 (diff *. diff) +. acc)
31 samples
33 let sosqm = sosq /. Float.of_int (List.length samples) in
34 Float.sqrt sosqm
36 (*****************************************************************************)
37 (* Types, constants *)
38 (*****************************************************************************)
40 type mode =
41 | Ifc of string * string
42 | Color
43 | Coverage
44 | Cst_search
45 | Dump_symbol_info
46 | Glean_index of string
47 | Dump_inheritance
48 | Errors
49 | Lint
50 | Lint_json
51 | Dump_deps
52 | Dump_dep_hashes
53 | Get_some_file_deps of int
54 | Identify_symbol of int * int
55 | Find_local of int * int
56 | Get_member of string
57 | Outline
58 | Dump_nast
59 | Dump_stripped_tast
60 | Dump_tast
61 | Type
62 | Check_tast
63 | RewriteGlobalInference
64 | Find_refs of int * int
65 | Highlight_refs of int * int
66 | Decl_compare
67 | Shallow_class_diff
68 | Go_to_impl of int * int
69 | Dump_glean_deps
70 | Hover of (int * int) option
71 | Apply_quickfixes
72 | Shape_analysis of string
73 | Refactor_sound_dynamic of string * string * string
74 | RemoveDeadUnsafeCasts
75 | CountImpreciseTypes
76 | SDT_analysis of string
78 type options = {
79 files: string list;
80 extra_builtins: string list;
81 mode: mode;
82 error_format: Errors.format;
83 no_builtins: bool;
84 max_errors: int option;
85 tcopt: GlobalOptions.t;
86 batch_mode: bool;
87 out_extension: string;
88 verbosity: int;
89 should_print_position: bool;
90 custom_hhi_path: string option;
91 profile_type_check_multi: int option;
92 memtrace: string option;
93 pessimise_builtins: bool;
94 rust_provider_backend: bool;
97 (** If the user passed --root, then all pathnames have to be canonicalized.
98 The fact of whether they passed --root is kind of stored inside Relative_path
99 global variables: the Relative_path.(path_of_prefix Root) is either "/"
100 if they failed to pass something, or the thing that they passed. *)
101 let use_canonical_filenames () =
102 not (String.equal "/" (Relative_path.path_of_prefix Relative_path.Root))
104 (* Canonical builtins from our hhi library *)
105 let hhi_builtins = Hhi.get_raw_hhi_contents ()
107 (* All of the stuff that hh_single_type_check relies on is sadly not contained
108 * in the hhi library, so we include a very small number of magic builtins *)
109 let magic_builtins =
111 ( "hh_single_type_check_magic.hhi",
112 "<?hh\n"
113 ^ "namespace {\n"
114 ^ "function hh_show<T>(<<__AcceptDisposable>> readonly T $val)[]:T {}\n"
115 ^ "function hh_expect<T>(<<__AcceptDisposable>> readonly T $val)[]:T {}\n"
116 ^ "function hh_expect_equivalent<T>(<<__AcceptDisposable>> readonly T $val)[]:T {}\n"
117 ^ "function hh_show_env()[]:void {}\n"
118 ^ "function hh_log_level(string $key, int $level)[]:void {}\n"
119 ^ "function hh_force_solve()[]:void {}"
120 ^ "function hh_time(string $command, string $tag = '_'):void {}\n"
121 ^ "}\n" );
124 let pessimised_magic_builtins =
126 ( "hh_single_type_check_magic.hhi",
127 "<?hh\n"
128 ^ "namespace {\n"
129 ^ "function hh_show<T as supportdyn<mixed>>(<<__AcceptDisposable>> readonly ~T $val)[]:~T {}\n"
130 ^ "function hh_expect<T as supportdyn<mixed>>(<<__AcceptDisposable>> readonly ~T $val)[]:~T {}\n"
131 ^ "function hh_expect_equivalent<T as supportdyn<mixed>>(<<__AcceptDisposable>> readonly ~T $val)[]:~T {}\n"
132 ^ "function hh_show_env()[]:void {}\n"
133 ^ "function hh_log_level(string $key, int $level)[]:void {}\n"
134 ^ "function hh_force_solve()[]:void {}"
135 ^ "}\n" );
138 (*****************************************************************************)
139 (* Helpers *)
140 (*****************************************************************************)
142 let die str =
143 let oc = stderr in
144 Out_channel.output_string oc str;
145 Out_channel.close oc;
146 exit 2
148 let print_error format ?(oc = stderr) l =
149 let formatter =
150 match format with
151 | Errors.Context -> (fun e -> Contextual_error_formatter.to_string e)
152 | Errors.Raw -> (fun e -> Raw_error_formatter.to_string e)
153 | Errors.Plain -> (fun e -> Errors.to_string e)
154 | Errors.Highlighted -> Highlighted_error_formatter.to_string
156 let absolute_errors = User_error.to_absolute l in
157 Out_channel.output_string oc (formatter absolute_errors)
159 let write_error_list format errors oc max_errors =
160 let (shown_errors, dropped_errors) =
161 match max_errors with
162 | Some max_errors -> List.split_n errors max_errors
163 | None -> (errors, [])
165 if not (List.is_empty errors) then (
166 List.iter ~f:(print_error format ~oc) shown_errors;
167 match
168 Errors.format_summary
169 format
170 errors
171 (List.length dropped_errors)
172 max_errors
173 with
174 | Some summary -> Out_channel.output_string oc summary
175 | None -> ()
176 ) else
177 Out_channel.output_string oc "No errors\n";
178 Out_channel.close oc
180 let print_error_list format errors max_errors =
181 let (shown_errors, dropped_errors) =
182 match max_errors with
183 | Some max_errors -> List.split_n errors max_errors
184 | None -> (errors, [])
186 if not (List.is_empty errors) then (
187 List.iter ~f:(print_error format) shown_errors;
188 match
189 Errors.format_summary
190 format
191 errors
192 (List.length dropped_errors)
193 max_errors
194 with
195 | Some summary -> Out_channel.output_string stderr summary
196 | None -> ()
197 ) else
198 Printf.printf "No errors\n"
200 let print_errors format (errors : Errors.t) max_errors : unit =
201 print_error_list format (Errors.get_sorted_error_list errors) max_errors
203 let print_errors_if_present (errors : Errors.error list) =
204 if not (List.is_empty errors) then (
205 let errors_output = Errors.convert_errors_to_string errors in
206 Printf.printf "Errors:\n";
207 List.iter errors_output ~f:(fun err_output ->
208 Printf.printf " %s\n" err_output)
211 let comma_string_to_iset (s : string) : ISet.t =
212 Str.split (Str.regexp ", *") s |> List.map ~f:int_of_string |> ISet.of_list
214 let parse_options () =
215 let fn_ref = ref [] in
216 let extra_builtins = ref [] in
217 let usage = Printf.sprintf "Usage: %s filename\n" Sys.argv.(0) in
218 let mode = ref Errors in
219 let no_builtins = ref false in
220 let line = ref 0 in
221 let log_key = ref "" in
222 let log_levels = ref SMap.empty in
223 let max_errors = ref None in
224 let batch_mode = ref false in
225 let set_mode x () =
226 match !mode with
227 | Errors -> mode := x
228 | _ -> raise (Arg.Bad "only a single mode should be specified")
230 let ifc_mode = ref "" in
231 let set_ifc lattice =
232 set_mode (Ifc (!ifc_mode, lattice)) ();
233 batch_mode := true
235 let error_format = ref Errors.Highlighted in
236 let forbid_nullable_cast = ref false in
237 let deregister_attributes = ref None in
238 let auto_namespace_map = ref None in
239 let log_inference_constraints = ref None in
240 let timeout = ref None in
241 let disallow_byref_dynamic_calls = ref (Some false) in
242 let disallow_byref_calls = ref (Some false) in
243 let set_bool x () = x := Some true in
244 let set_bool_ x () = x := true in
245 let set_float_ x f = x := f in
246 let rust_provider_backend = ref false in
247 let skip_hierarchy_checks = ref false in
248 let skip_tast_checks = ref false in
249 let out_extension = ref ".out" in
250 let like_type_hints = ref false in
251 let union_intersection_type_hints = ref false in
252 let call_coeffects = ref true in
253 let local_coeffects = ref true in
254 let strict_contexts = ref true in
255 let like_casts = ref false in
256 let simple_pessimize = ref 0.0 in
257 let symbolindex_file = ref None in
258 let check_xhp_attribute = ref false in
259 let check_redundant_generics = ref false in
260 let disallow_static_memoized = ref false in
261 let enable_supportdyn_hint = ref false in
262 let enable_class_level_where_clauses = ref false in
263 let disable_legacy_soft_typehints = ref false in
264 let allow_new_attribute_syntax = ref false in
265 let allow_toplevel_requires = ref false in
266 let global_inference = ref false in
267 let ordered_solving = ref false in
268 let reinfer_types = ref [] in
269 let const_static_props = ref false in
270 let disable_legacy_attribute_syntax = ref false in
271 let const_attribute = ref false in
272 let const_default_func_args = ref false in
273 let const_default_lambda_args = ref false in
274 let disallow_silence = ref false in
275 let abstract_static_props = ref false in
276 let glean_service = ref (GleanOptions.service GlobalOptions.default) in
277 let glean_hostname = ref (GleanOptions.hostname GlobalOptions.default) in
278 let glean_port = ref (GleanOptions.port GlobalOptions.default) in
279 let glean_reponame = ref (GleanOptions.reponame GlobalOptions.default) in
280 let disallow_func_ptrs_in_constants = ref false in
281 let error_php_lambdas = ref false in
282 let disallow_discarded_nullable_awaitables = ref false in
283 let disable_xhp_element_mangling = ref false in
284 let disable_xhp_children_declarations = ref false in
285 let enable_xhp_class_modifier = ref false in
286 let verbosity = ref 0 in
287 let disable_hh_ignore_error = ref 0 in
288 let is_systemlib = ref false in
289 let enable_higher_kinded_types = ref false in
290 let allowed_fixme_codes_strict = ref None in
291 let allowed_decl_fixme_codes = ref None in
292 let method_call_inference = ref false in
293 let report_pos_from_reason = ref false in
294 let enable_sound_dynamic = ref false in
295 let always_pessimise_return = ref false in
296 let consider_type_const_enforceable = ref false in
297 let disallow_fun_and_cls_meth_pseudo_funcs = ref false in
298 let disallow_inst_meth = ref false in
299 let disable_enum_classes = ref false in
300 let interpret_soft_types_as_like_types = ref false in
301 let enable_strict_string_concat_interp = ref false in
302 let ignore_unsafe_cast = ref false in
303 let math_new_code = ref false in
304 let typeconst_concrete_concrete_error = ref false in
305 let enable_strict_const_semantics = ref 0 in
306 let strict_wellformedness = ref 0 in
307 let meth_caller_only_public_visibility = ref true in
308 let require_extends_implements_ancestors = ref false in
309 let strict_value_equality = ref false in
310 let expression_tree_virtualize_functions = ref false in
311 let naming_table = ref None in
312 let root = ref None in
313 let sharedmem_config = ref SharedMem.default_config in
314 let print_position = ref true in
315 let enforce_sealed_subclasses = ref false in
316 let everything_sdt = ref false in
317 let pessimise_builtins = ref false in
318 let custom_hhi_path = ref None in
319 let explicit_consistent_constructors = ref 0 in
320 let require_types_class_consts = ref 0 in
321 let type_printer_fuel =
322 ref (TypecheckerOptions.type_printer_fuel GlobalOptions.default)
324 let profile_type_check_multi = ref None in
325 let profile_top_level_definitions =
326 ref (TypecheckerOptions.profile_top_level_definitions GlobalOptions.default)
328 let memtrace = ref None in
329 let enable_global_access_check_files = ref [] in
330 let enable_global_access_check_functions = ref SSet.empty in
331 let global_access_check_on_write = ref true in
332 let global_access_check_on_read = ref true in
333 let refactor_mode = ref "" in
334 let refactor_analysis_mode = ref "" in
335 let set_enable_global_access_check_functions s =
336 let json_obj = Hh_json.json_of_file s in
337 let add_function f =
338 match f with
339 | Hh_json.JSON_String str ->
340 enable_global_access_check_functions :=
341 SSet.add str !enable_global_access_check_functions
342 | _ -> ()
344 match json_obj with
345 | Hh_json.JSON_Array lst -> List.iter lst ~f:add_function
346 | _ -> enable_global_access_check_functions := SSet.empty
348 let allow_all_files_for_module_declarations = ref true in
349 let loop_iteration_upper_bound = ref None in
350 let substitution_mutation = ref false in
351 let allow_all_locations_for_type_constant_in_enum_class = ref true in
352 let tast_under_dynamic = ref false in
353 let options =
355 ( "--no-print-position",
356 Arg.Unit (fun _ -> print_position := false),
357 " Don't print positions while printing TASTs and NASTs" );
358 ( "--naming-table",
359 Arg.String (fun s -> naming_table := Some s),
360 " Naming table, to look up undefined symbols; needs --root" );
361 ( "--root",
362 Arg.String (fun s -> root := Some s),
363 " Root for where to look up undefined symbols; needs --naming-table" );
364 ( "--extra-builtin",
365 Arg.String (fun f -> extra_builtins := f :: !extra_builtins),
366 " HHI file to parse and declare" );
367 ( "--ifc",
368 Arg.Tuple [Arg.String (fun m -> ifc_mode := m); Arg.String set_ifc],
369 " Run the flow analysis" );
370 ( "--shape-analysis",
371 Arg.String
372 (fun mode ->
373 batch_mode := true;
374 set_mode (Shape_analysis mode) ()),
375 " Run the flow analysis" );
376 ( "--refactor-sound-dynamic",
377 Arg.Tuple
379 Arg.String (fun mode -> refactor_analysis_mode := mode);
380 Arg.String (fun mode -> refactor_mode := mode);
381 Arg.String
382 (fun x ->
383 batch_mode := true;
384 set_mode
385 (Refactor_sound_dynamic
386 (!refactor_analysis_mode, !refactor_mode, x))
387 ());
389 " Run the flow analysis" );
390 ( "--deregister-attributes",
391 Arg.Unit (set_bool deregister_attributes),
392 " Ignore all functions with attribute '__PHPStdLib'" );
393 ( "--auto-namespace-map",
394 Arg.String
395 (fun m ->
396 auto_namespace_map :=
397 Some (ServerConfig.convert_auto_namespace_to_map m)),
398 " Alias namespaces" );
399 ( "--no-call-coeffects",
400 Arg.Unit (fun () -> call_coeffects := false),
401 " Turns off call coeffects" );
402 ( "--no-local-coeffects",
403 Arg.Unit (fun () -> local_coeffects := false),
404 " Turns off local coeffects" );
405 ( "--no-strict-contexts",
406 Arg.Unit (fun () -> strict_contexts := false),
407 " Do not enforce contexts to be defined within Contexts namespace" );
408 ("--colour", Arg.Unit (set_mode Color), " Produce colour output");
409 ("--color", Arg.Unit (set_mode Color), " Produce color output");
410 ("--coverage", Arg.Unit (set_mode Coverage), " Produce coverage output");
411 ( "--cst-search",
412 Arg.Unit (set_mode Cst_search),
413 " Search the concrete syntax tree of the given file using the pattern"
414 ^ " given on stdin."
415 ^ " (The pattern is a JSON object adhering to the search DSL.)" );
416 ( "--dump-symbol-info",
417 Arg.Unit (set_mode Dump_symbol_info),
418 " Dump all symbol information" );
419 ( "--glean-index",
420 Arg.String (fun output_dir -> set_mode (Glean_index output_dir) ()),
421 " Run indexer and output json in provided dir" );
422 ( "--error-format",
423 Arg.String
424 (fun s ->
425 match s with
426 | "raw" -> error_format := Errors.Raw
427 | "context" -> error_format := Errors.Context
428 | "highlighted" -> error_format := Errors.Highlighted
429 | "plain" -> error_format := Errors.Plain
430 | _ -> print_string "Warning: unrecognized error format.\n"),
431 "<raw|context|highlighted|plain> Error formatting style; (default: highlighted)"
433 ("--lint", Arg.Unit (set_mode Lint), " Produce lint errors");
434 ("--lint-json", Arg.Unit (set_mode Lint_json), " Produce json lint output");
435 ( "--no-builtins",
436 Arg.Set no_builtins,
437 " Don't use builtins (e.g. ConstSet); implied by --root" );
438 ( "--out-extension",
439 Arg.String (fun s -> out_extension := s),
440 " output file extension (default .out)" );
441 ("--dump-deps", Arg.Unit (set_mode Dump_deps), " Print dependencies");
442 ( "--dump-dep-hashes",
443 Arg.Unit (set_mode Dump_dep_hashes),
444 " Print dependency hashes" );
445 ( "--dump-glean-deps",
446 Arg.Unit (set_mode Dump_glean_deps),
447 " Print dependencies in the Glean format" );
448 ( "--dump-inheritance",
449 Arg.Unit (set_mode Dump_inheritance),
450 " Print inheritance" );
451 ( "--get-some-file-deps",
452 Arg.Int (fun depth -> set_mode (Get_some_file_deps depth) ()),
453 " Print a list of files this file depends on. The provided integer is the depth of the traversal. Requires --root, --naming-table and --depth"
455 ( "--identify-symbol",
456 Arg.Tuple
458 Arg.Int (fun x -> line := x);
459 Arg.Int
460 (fun column -> set_mode (Identify_symbol (!line, column)) ());
462 "<pos> Show info about symbol at given line and column" );
463 ( "--find-local",
464 Arg.Tuple
466 Arg.Int (fun x -> line := x);
467 Arg.Int (fun column -> set_mode (Find_local (!line, column)) ());
469 "<pos> Find all usages of local at given line and column" );
470 ( "--max-errors",
471 Arg.Int (fun num_errors -> max_errors := Some num_errors),
472 " Maximum number of errors to display" );
473 ("--outline", Arg.Unit (set_mode Outline), " Print file outline");
474 ("--nast", Arg.Unit (set_mode Dump_nast), " Print out the named AST");
475 ("--tast", Arg.Unit (set_mode Dump_tast), " Print out the typed AST");
476 ( "--type",
477 Arg.Unit (set_mode Type),
478 " Extract types from the typed AST and print one per line as JSON" );
479 ("--tast-check", Arg.Unit (set_mode Check_tast), " Typecheck the tast");
480 ( "--stripped-tast",
481 Arg.Unit (set_mode Dump_stripped_tast),
482 " Print out the typed AST, stripped of type information."
483 ^ " This can be compared against the named AST to look for holes." );
484 ( "--rewrite",
485 Arg.Unit (set_mode RewriteGlobalInference),
486 " Rewrite the file after inferring types using global inference"
487 ^ " (requires --global-inference)." );
488 ( "--global-inference",
489 Arg.Set global_inference,
490 " Global type inference to infer missing type annotations." );
491 ( "--ordered-solving",
492 Arg.Set ordered_solving,
493 " Optimized solver for type variables. Experimental." );
494 ( "--reinfer-types",
495 Arg.String (fun s -> reinfer_types := Str.split (Str.regexp ", *") s),
496 " List of type hint to be ignored and inferred again using global inference."
498 ( "--find-refs",
499 Arg.Tuple
501 Arg.Int (fun x -> line := x);
502 Arg.Int (fun column -> set_mode (Find_refs (!line, column)) ());
504 "<pos> Find all usages of a symbol at given line and column" );
505 ( "--go-to-impl",
506 Arg.Tuple
508 Arg.Int (fun x -> line := x);
509 Arg.Int (fun column -> set_mode (Go_to_impl (!line, column)) ());
511 "<pos> Find all implementations of a symbol at given line and column" );
512 ( "--highlight-refs",
513 Arg.Tuple
515 Arg.Int (fun x -> line := x);
516 Arg.Int (fun column -> set_mode (Highlight_refs (!line, column)) ());
518 "<pos> Highlight all usages of a symbol at given line and column" );
519 ( "--decl-compare",
520 Arg.Unit (set_mode Decl_compare),
521 " Test comparison functions used in incremental mode on declarations"
522 ^ " in provided file" );
523 ( "--shallow-class-diff",
524 Arg.Unit (set_mode Shallow_class_diff),
525 " Test shallow class comparison used in incremental mode on shallow class declarations"
527 ( "--forbid_nullable_cast",
528 Arg.Set forbid_nullable_cast,
529 " Forbid casting from nullable values." );
530 ( "--get-member",
531 Arg.String
532 (fun class_and_member_id ->
533 set_mode (Get_member class_and_member_id) ()),
534 " Given ClassName::MemberName, fetch the decl of members with that name and print them."
536 ( "--log-inference-constraints",
537 Arg.Unit (set_bool log_inference_constraints),
538 " Log inference constraints to Scuba." );
539 ( "--timeout",
540 Arg.Int (fun secs -> timeout := Some secs),
541 " Timeout in seconds for checking a function or a class." );
542 ( "--hh-log-level",
543 Arg.Tuple
545 Arg.String (fun x -> log_key := x);
546 Arg.Int
547 (fun level -> log_levels := SMap.add !log_key level !log_levels);
549 " Set the log level for a key" );
550 ( "--batch-files",
551 Arg.Set batch_mode,
552 " Typecheck each file passed in independently" );
553 ( "--disallow-static-memoized",
554 Arg.Set disallow_static_memoized,
555 " Disallow static memoized methods on non-final methods" );
556 ( "--check-xhp-attribute",
557 Arg.Set check_xhp_attribute,
558 " Typechecks xhp required attributes" );
559 ( "--disallow-byref-dynamic-calls",
560 Arg.Unit (set_bool disallow_byref_dynamic_calls),
561 " Disallow passing arguments by reference to dynamically called functions [e.g. $foo(&$bar)]"
563 ( "--disallow-byref-calls",
564 Arg.Unit (set_bool disallow_byref_calls),
565 " Disallow passing arguments by reference in any form [e.g. foo(&$bar)]"
567 ( "--rust-provider-backend",
568 Arg.Set rust_provider_backend,
569 " Use the Rust implementation of Provider_backend (including decl-folding)"
571 ( "--skip-hierarchy-checks",
572 Arg.Set skip_hierarchy_checks,
573 " Do not apply checks on class hierarchy (override, implements, etc)" );
574 ( "--skip-tast-checks",
575 Arg.Set skip_tast_checks,
576 " Do not apply checks using TAST visitors" );
577 ( "--union-intersection-type-hints",
578 Arg.Set union_intersection_type_hints,
579 " Allows union and intersection types to be written in type hint positions"
581 ( "--like-type-hints",
582 Arg.Set like_type_hints,
583 " Allows like types to be written in type hint positions" );
584 ( "--like-casts",
585 Arg.Set like_casts,
586 " Allows like types to be written in as expressions" );
587 ( "--simple-pessimize",
588 Arg.Set_float simple_pessimize,
589 " At coercion points, if a type is not enforceable, wrap it in like. Float argument 0.0 to 1.0 sets frequency"
591 ( "--like-types-all",
592 Arg.Unit
593 (fun () ->
594 set_bool_ like_type_hints ();
595 set_bool_ like_casts ();
596 set_float_ simple_pessimize 1.0),
597 " Enables all like types features" );
598 ( "--naive-implicit-pess",
599 Arg.Unit
600 (fun () ->
601 set_bool_ enable_sound_dynamic ();
602 set_bool_ everything_sdt ();
603 set_bool_ like_type_hints ();
604 set_bool_ always_pessimise_return ();
605 set_bool_ consider_type_const_enforceable ();
606 set_bool_ enable_supportdyn_hint ();
607 set_bool_ pessimise_builtins ()),
608 " Enables naive implicit pessimisation" );
609 ( "--implicit-pess",
610 Arg.Unit
611 (fun () ->
612 set_bool_ enable_sound_dynamic ();
613 set_bool_ everything_sdt ();
614 set_bool_ like_type_hints ();
615 set_bool_ enable_supportdyn_hint ();
616 set_bool_ pessimise_builtins ()),
617 " Enables implicit pessimisation" );
618 ( "--explicit-pess",
619 Arg.String
620 (fun dir ->
621 set_bool_ enable_sound_dynamic ();
622 set_bool_ like_type_hints ();
623 set_bool_ enable_supportdyn_hint ();
624 set_bool_ pessimise_builtins ();
625 custom_hhi_path := Some dir),
626 " Enables checking explicitly pessimised files. Requires path to pessimised .hhi files "
628 ( "--symbolindex-file",
629 Arg.String (fun str -> symbolindex_file := Some str),
630 " Load the symbol index from this file" );
631 ( "--enable-supportdyn-hint",
632 Arg.Set enable_supportdyn_hint,
633 " Allow the supportdyn type hint" );
634 ( "--enable-class-level-where-clauses",
635 Arg.Set enable_class_level_where_clauses,
636 " Enables support for class-level where clauses" );
637 ( "--disable-legacy-soft-typehints",
638 Arg.Set disable_legacy_soft_typehints,
639 " Disables the legacy @ syntax for soft typehints (use __Soft instead)"
641 ( "--allow-new-attribute-syntax",
642 Arg.Set allow_new_attribute_syntax,
643 " Allow the new @ attribute syntax (disables legacy soft typehints)" );
644 ( "--allow-toplevel-requires",
645 Arg.Set allow_toplevel_requires,
646 " Allow `require()` and similar at the top-level" );
647 ( "--const-static-props",
648 Arg.Set const_static_props,
649 " Enable static properties to be const" );
650 ( "--disable-legacy-attribute-syntax",
651 Arg.Set disable_legacy_attribute_syntax,
652 " Disable the legacy <<...>> user attribute syntax" );
653 ("--const-attribute", Arg.Set const_attribute, " Allow __Const attribute");
654 ( "--const-default-func-args",
655 Arg.Set const_default_func_args,
656 " Statically check default function arguments are constant initializers"
658 ( "--const-default-lambda-args",
659 Arg.Set const_default_lambda_args,
660 " Statically check default lambda args are constant."
661 ^ " Produces a subset of errors of const-default-func-args" );
662 ( "--disallow-silence",
663 Arg.Set disallow_silence,
664 " Disallow the error suppression operator, @" );
665 ( "--abstract-static-props",
666 Arg.Set abstract_static_props,
667 " Static properties can be abstract" );
668 ( "--glean-service",
669 Arg.String (fun str -> glean_service := str),
670 " glean service name" );
671 ( "--glean-hostname",
672 Arg.String (fun str -> glean_hostname := str),
673 " glean hostname" );
674 ("--glean-port", Arg.Int (fun x -> glean_port := x), " glean port number");
675 ( "--glean-reponame",
676 Arg.String (fun str -> glean_reponame := str),
677 " glean repo name" );
678 ( "--disallow-func-ptrs-in-constants",
679 Arg.Set disallow_func_ptrs_in_constants,
680 " Disallow use of HH\\fun and HH\\class_meth in constants and constant initializers"
682 ( "--disallow-php-lambdas",
683 Arg.Set error_php_lambdas,
684 " Disallow php style anonymous functions." );
685 ( "--disallow-discarded-nullable-awaitables",
686 Arg.Set disallow_discarded_nullable_awaitables,
687 " Error on using discarded nullable awaitables" );
688 ( "--disable-xhp-element-mangling",
689 Arg.Set disable_xhp_element_mangling,
690 " Disable mangling of XHP elements :foo. That is, :foo:bar is now \\foo\\bar, not xhp_foo__bar"
692 ( "--disable-xhp-children-declarations",
693 Arg.Set disable_xhp_children_declarations,
694 " Disable XHP children declarations, e.g. children (foo, bar+)" );
695 ( "--enable-xhp-class-modifier",
696 Arg.Set enable_xhp_class_modifier,
697 " Enable the XHP class modifier, xhp class name {} will define an xhp class."
699 ( "--verbose",
700 Arg.Int (fun v -> verbosity := v),
701 " Verbosity as an integer." );
702 ( "--disable-hh-ignore-error",
703 Arg.Int (( := ) disable_hh_ignore_error),
704 " Forbid HH_IGNORE_ERROR comments as an alternative to HH_FIXME, or treat them as normal comments."
706 ( "--is-systemlib",
707 Arg.Set is_systemlib,
708 " Enable systemlib annotations and other internal-only features" );
709 ( "--enable-higher-kinded-types",
710 Arg.Set enable_higher_kinded_types,
711 " Enable support for higher-kinded types" );
712 ( "--allowed-fixme-codes-strict",
713 Arg.String
714 (fun s -> allowed_fixme_codes_strict := Some (comma_string_to_iset s)),
715 " List of fixmes that are allowed in strict mode." );
716 ( "--allowed-decl-fixme-codes",
717 Arg.String
718 (fun s -> allowed_decl_fixme_codes := Some (comma_string_to_iset s)),
719 " List of fixmes that are allowed in declarations." );
720 ( "--method-call-inference",
721 Arg.Set method_call_inference,
722 " Infer constraints for method calls. NB: incompatible with like types."
724 ( "--report-pos-from-reason",
725 Arg.Set report_pos_from_reason,
726 " Flag errors whose position is derived from reason information in types."
728 ( "--enable-sound-dynamic-type",
729 Arg.Set enable_sound_dynamic,
730 " Enforce sound dynamic types. Experimental." );
731 ( "--always-pessimise-return",
732 Arg.Set always_pessimise_return,
733 " Consider all return types unenforceable." );
734 ( "--consider-type-const-enforceable",
735 Arg.Set consider_type_const_enforceable,
736 " Consider type constants to potentially be enforceable." );
737 ( "--disallow-fun-and-cls-meth-pseudo-funcs",
738 Arg.Set disallow_fun_and_cls_meth_pseudo_funcs,
739 " Disable parsing of fun() and class_meth()." );
740 ( "--disallow-inst-meth",
741 Arg.Set disallow_inst_meth,
742 " Disable parsing of inst_meth()." );
743 ( "--disable-enum-classes",
744 Arg.Set disable_enum_classes,
745 " Disable the enum classes extension." );
746 ( "--interpret-soft-types-as-like-types",
747 Arg.Set interpret_soft_types_as_like_types,
748 " Types declared with <<__Soft>> (runtime logs but doesn't throw) become like types."
750 ( "--enable-strict-string-concat-interp",
751 Arg.Set enable_strict_string_concat_interp,
752 " Require arguments are arraykey types in string concatenation and interpolation."
754 ( "--ignore-unsafe-cast",
755 Arg.Set ignore_unsafe_cast,
756 " Ignore unsafe_cast and retain the original type of the expression" );
757 ( "--math-new-code",
758 Arg.Set math_new_code,
759 " Use a new error code for math operations: addition, subtraction, division, multiplication, exponentiation"
761 ( "--typeconst-concrete-concrete-error",
762 Arg.Set typeconst_concrete_concrete_error,
763 " Raise an error when a concrete type constant is overridden by a concrete type constant in a child class."
765 ( "--enable-strict-const-semantics",
766 Arg.Int (fun x -> enable_strict_const_semantics := x),
767 " Raise an error when a concrete constants is overridden or multiply defined"
769 ( "--strict-wellformedness",
770 Arg.Int (fun x -> strict_wellformedness := x),
771 " Re-introduce missing well-formedness checks in AST positions" );
772 ( "--meth-caller-only-public-visibility",
773 Arg.Bool (fun x -> meth_caller_only_public_visibility := x),
774 " Controls whether meth_caller can be used on non-public methods" );
775 ( "--hover",
776 Arg.Tuple
778 Arg.Int (fun x -> line := x);
779 Arg.Int (fun column -> set_mode (Hover (Some (!line, column))) ());
781 "<pos> Display hover tooltip" );
782 ( "--hover-at-caret",
783 Arg.Unit (fun () -> set_mode (Hover None) ()),
784 " Show the hover information indicated by // ^ hover-at-caret" );
785 ( "--fix",
786 Arg.Unit (fun () -> set_mode Apply_quickfixes ()),
787 " Apply quickfixes for all the errors in the file, and print the resulting code."
789 ( "--require-extends-implements-ancestors",
790 Arg.Set require_extends_implements_ancestors,
791 " Consider `require extends` and `require implements` as ancestors when checking a class"
793 ( "--strict-value-equality",
794 Arg.Set strict_value_equality,
795 " Emit an error when \"==\" or \"!=\" is used to compare values that are incompatible types."
797 ( "--enable-sealed-subclasses",
798 Arg.Set enforce_sealed_subclasses,
799 " Require all __Sealed arguments to be subclasses" );
800 ( "--everything-sdt",
801 Arg.Set everything_sdt,
802 " Treat all classes, functions, and traits as though they are annotated with <<__SupportDynamicType>>, unless they are annotated with <<__NoAutoDynamic>>"
804 ( "--pessimise-builtins",
805 Arg.Set pessimise_builtins,
806 " Treat built-in collections and Hack arrays as though they contain ~T"
808 ( "--custom-hhi-path",
809 Arg.String (fun s -> custom_hhi_path := Some s),
810 " Use custom hhis" );
811 ( "--explicit-consistent-constructors",
812 Arg.Int (( := ) explicit_consistent_constructors),
813 " Raise an error for <<__ConsistentConstruct>> without an explicit constructor; 1 for traits, 2 for all "
815 ( "--require-types-class-consts",
816 Arg.Int (( := ) require_types_class_consts),
817 " Raise an error for class constants missing types; 1 for abstract constants, 2 for all "
819 ( "--profile-type-check-twice",
820 Arg.Unit (fun () -> profile_type_check_multi := Some 1),
821 " Typecheck the file twice" );
822 ( "--profile-type-check-multi",
823 Arg.Int (fun n -> profile_type_check_multi := Some n),
824 " Typecheck the files n times extra (!)" );
825 ( "--profile-top-level-definitions",
826 Arg.Set profile_top_level_definitions,
827 " Profile typechecking of top-level definitions" );
828 ( "--memtrace",
829 Arg.String (fun s -> memtrace := Some s),
830 " Write memtrace to this file (typical extension .ctf)" );
831 ( "--type-printer-fuel",
832 Arg.Int (( := ) type_printer_fuel),
833 " Sets the amount of fuel that the type printer can use to display an individual type. Default: "
834 ^ string_of_int
835 (TypecheckerOptions.type_printer_fuel GlobalOptions.default) );
836 ( "--enable-global-access-check-files",
837 Arg.String
838 (fun s ->
839 enable_global_access_check_files := String_utils.split ',' s),
840 " Run global access checker on any file whose path is prefixed by the argument (use \"\\\" for hh_single_type_check)"
842 ( "--enable-global-access-check-functions",
843 Arg.String set_enable_global_access_check_functions,
844 " Run global access checker on functions listed in the given JSON file"
846 ( "--disable-global-access-check-on-write",
847 Arg.Clear global_access_check_on_write,
848 " Disable global access checker to check global writes" );
849 ( "--disable-global-access-check-on-read",
850 Arg.Clear global_access_check_on_read,
851 " Disable global access checker to check global reads" );
852 ( "--overwrite-loop-iteration-upper-bound",
853 Arg.Int (fun u -> loop_iteration_upper_bound := Some u),
854 " Sets the maximum number of iterations that will be used to typecheck loops"
856 ( "--expression-tree-virtualize-functions",
857 Arg.Set expression_tree_virtualize_functions,
858 " Enables function virtualization in Expression Trees" );
859 ( "--substitution-mutation",
860 Arg.Set substitution_mutation,
861 " Applies substitution mutation to applicable entities and typechecks them"
863 ( "--remove-dead-unsafe-casts",
864 Arg.Unit (fun () -> set_mode RemoveDeadUnsafeCasts ()),
865 " Removes dead unsafe casts from a file" );
866 ( "--count-imprecise-types",
867 Arg.Unit (fun () -> set_mode CountImpreciseTypes ()),
868 " Counts the number of mixed, dynamic, and nonnull types in a file" );
869 ( "--tast-under-dynamic",
870 Arg.Set tast_under_dynamic,
871 " Produce variations of definitions as they are checked under dynamic assumptions"
873 ( "--sdt-analysis",
874 Arg.String
875 (fun command ->
876 batch_mode := true;
877 set_mode (SDT_analysis command) ()),
878 " Analyses to support Sound Dynamic rollout" );
882 (* Sanity check that all option descriptions are well-formed. *)
883 List.iter options ~f:(fun (_, _, description) ->
885 String.is_prefix description ~prefix:" "
886 || String.is_prefix description ~prefix:"<"
887 then
889 else
890 failwith
891 (Printf.sprintf
892 "Descriptions should start with <foo> or a leading space, got: %S"
893 description));
895 let options = Arg.align ~limit:25 options in
896 Arg.parse options (fun fn -> fn_ref := fn :: !fn_ref) usage;
897 let fns =
898 match (!fn_ref, !mode) with
899 | ([], (Get_member _ | Type)) -> []
900 | ([], _) -> die usage
901 | (x, _) -> x
903 let is_ifc_mode =
904 match !mode with
905 | Ifc _ -> true
906 | _ -> false
909 (match !mode with
910 | Get_some_file_deps _ ->
911 if Option.is_none !naming_table then
912 raise (Arg.Bad "--get-some-file-deps requires --naming-table");
913 if Option.is_none !root then
914 raise (Arg.Bad "--get-some-file-deps requires --root")
915 | _ -> ());
917 if Option.is_some !naming_table && Option.is_none !root then
918 failwith "--naming-table needs --root";
920 (* --root implies certain things... *)
921 let root =
922 match !root with
923 | None -> Path.make "/" (* if none specified, we use this dummy *)
924 | Some root ->
925 if Option.is_none !naming_table then
926 failwith "--root needs --naming-table";
927 (* builtins are already provided by project at --root, so we shouldn't provide our own *)
928 no_builtins := true;
929 (* Following will throw an exception if .hhconfig not found *)
930 let (_config_hash, config) =
931 Config_file.parse_hhconfig
932 (Filename.concat root Config_file.file_path_relative_to_repo_root)
934 (* We will pick up values from .hhconfig, unless they've been overridden at the command-line. *)
935 if Option.is_none !auto_namespace_map then
936 auto_namespace_map :=
937 config
938 |> Config_file.Getters.string_opt "auto_namespace_map"
939 |> Option.map ~f:ServerConfig.convert_auto_namespace_to_map;
940 if Option.is_none !allowed_fixme_codes_strict then
941 allowed_fixme_codes_strict :=
942 config
943 |> Config_file.Getters.string_opt "allowed_fixme_codes_strict"
944 |> Option.map ~f:comma_string_to_iset;
945 sharedmem_config :=
946 ServerConfig.make_sharedmem_config
947 config
948 (ServerArgs.default_options ~root)
949 ServerLocalConfig.default;
950 (* Path.make canonicalizes it, i.e. resolves symlinks *)
951 Path.make root
954 let tcopt : GlobalOptions.t =
955 GlobalOptions.make
956 ~tco_saved_state_loading:GlobalOptions.default_saved_state_loading
957 ?po_deregister_php_stdlib:!deregister_attributes
958 ?tco_log_inference_constraints:!log_inference_constraints
959 ?tco_timeout:!timeout
960 ?po_auto_namespace_map:!auto_namespace_map
961 ?tco_disallow_byref_dynamic_calls:!disallow_byref_dynamic_calls
962 ?tco_disallow_byref_calls:!disallow_byref_calls
963 ~allowed_fixme_codes_strict:
964 (Option.value !allowed_fixme_codes_strict ~default:ISet.empty)
965 ~tco_check_xhp_attribute:!check_xhp_attribute
966 ~tco_check_redundant_generics:!check_redundant_generics
967 ~tco_skip_hierarchy_checks:!skip_hierarchy_checks
968 ~tco_skip_tast_checks:!skip_tast_checks
969 ~tco_like_type_hints:!like_type_hints
970 ~tco_union_intersection_type_hints:!union_intersection_type_hints
971 ~tco_strict_contexts:!strict_contexts
972 ~tco_coeffects:!call_coeffects
973 ~tco_coeffects_local:!local_coeffects
974 ~tco_like_casts:!like_casts
975 ~tco_simple_pessimize:!simple_pessimize
976 ~log_levels:!log_levels
977 ~po_enable_class_level_where_clauses:!enable_class_level_where_clauses
978 ~po_disable_legacy_soft_typehints:!disable_legacy_soft_typehints
979 ~po_allow_new_attribute_syntax:!allow_new_attribute_syntax
980 ~po_disallow_toplevel_requires:(not !allow_toplevel_requires)
981 ~tco_const_static_props:!const_static_props
982 ~tco_global_inference:!global_inference
983 ~tco_ordered_solving:!ordered_solving
984 ~tco_gi_reinfer_types:!reinfer_types
985 ~po_disable_legacy_attribute_syntax:!disable_legacy_attribute_syntax
986 ~tco_const_attribute:!const_attribute
987 ~po_const_default_func_args:!const_default_func_args
988 ~po_const_default_lambda_args:!const_default_lambda_args
989 ~po_disallow_silence:!disallow_silence
990 ~po_abstract_static_props:!abstract_static_props
991 ~po_disallow_func_ptrs_in_constants:!disallow_func_ptrs_in_constants
992 ~tco_check_attribute_locations:true
993 ~tco_error_php_lambdas:!error_php_lambdas
994 ~tco_disallow_discarded_nullable_awaitables:
995 !disallow_discarded_nullable_awaitables
996 ~glean_service:!glean_service
997 ~glean_hostname:!glean_hostname
998 ~glean_port:!glean_port
999 ~glean_reponame:!glean_reponame
1000 ~po_disable_xhp_element_mangling:!disable_xhp_element_mangling
1001 ~po_disable_xhp_children_declarations:!disable_xhp_children_declarations
1002 ~po_enable_xhp_class_modifier:!enable_xhp_class_modifier
1003 ~po_disable_hh_ignore_error:!disable_hh_ignore_error
1004 ~tco_is_systemlib:!is_systemlib
1005 ~tco_higher_kinded_types:!enable_higher_kinded_types
1006 ~po_allowed_decl_fixme_codes:
1007 (Option.value !allowed_decl_fixme_codes ~default:ISet.empty)
1008 ~po_allow_unstable_features:true
1009 ~tco_method_call_inference:!method_call_inference
1010 ~tco_report_pos_from_reason:!report_pos_from_reason
1011 ~tco_enable_sound_dynamic:!enable_sound_dynamic
1012 ~po_disallow_fun_and_cls_meth_pseudo_funcs:
1013 !disallow_fun_and_cls_meth_pseudo_funcs
1014 ~po_disallow_inst_meth:!disallow_inst_meth
1015 ~tco_ifc_enabled:
1016 (if is_ifc_mode then
1017 ["/"]
1018 else
1020 ~tco_global_access_check_files_enabled:!enable_global_access_check_files
1021 ~tco_global_access_check_functions_enabled:
1022 !enable_global_access_check_functions
1023 ~tco_global_access_check_on_write:!global_access_check_on_write
1024 ~tco_global_access_check_on_read:!global_access_check_on_read
1025 ~po_enable_enum_classes:(not !disable_enum_classes)
1026 ~po_interpret_soft_types_as_like_types:!interpret_soft_types_as_like_types
1027 ~tco_enable_strict_string_concat_interp:
1028 !enable_strict_string_concat_interp
1029 ~tco_ignore_unsafe_cast:!ignore_unsafe_cast
1030 ~tco_math_new_code:!math_new_code
1031 ~tco_typeconst_concrete_concrete_error:!typeconst_concrete_concrete_error
1032 ~tco_enable_strict_const_semantics:!enable_strict_const_semantics
1033 ~tco_strict_wellformedness:!strict_wellformedness
1034 ~tco_meth_caller_only_public_visibility:
1035 !meth_caller_only_public_visibility
1036 ~tco_require_extends_implements_ancestors:
1037 !require_extends_implements_ancestors
1038 ~tco_strict_value_equality:!strict_value_equality
1039 ~tco_enforce_sealed_subclasses:!enforce_sealed_subclasses
1040 ~tco_everything_sdt:!everything_sdt
1041 ~tco_pessimise_builtins:!pessimise_builtins
1042 ~tco_explicit_consistent_constructors:!explicit_consistent_constructors
1043 ~tco_require_types_class_consts:!require_types_class_consts
1044 ~tco_type_printer_fuel:!type_printer_fuel
1045 ~tco_profile_top_level_definitions:!profile_top_level_definitions
1046 ~tco_allow_all_files_for_module_declarations:
1047 !allow_all_files_for_module_declarations
1048 ~tco_loop_iteration_upper_bound:!loop_iteration_upper_bound
1049 ~tco_expression_tree_virtualize_functions:
1050 !expression_tree_virtualize_functions
1051 ~tco_substitution_mutation:!substitution_mutation
1052 ~tco_allow_all_locations_for_type_constant_in_enum_class:
1053 !allow_all_locations_for_type_constant_in_enum_class
1054 ~tco_tast_under_dynamic:!tast_under_dynamic
1057 Errors.allowed_fixme_codes_strict :=
1058 GlobalOptions.allowed_fixme_codes_strict tcopt;
1059 Errors.report_pos_from_reason :=
1060 TypecheckerOptions.report_pos_from_reason tcopt;
1062 let tco_experimental_features =
1063 tcopt.GlobalOptions.tco_experimental_features
1065 let tco_experimental_features =
1066 if !forbid_nullable_cast then
1067 SSet.add
1068 TypecheckerOptions.experimental_forbid_nullable_cast
1069 tco_experimental_features
1070 else
1071 tco_experimental_features
1073 let tco_experimental_features =
1074 if is_ifc_mode then
1075 SSet.add
1076 TypecheckerOptions.experimental_infer_flows
1077 tco_experimental_features
1078 else
1079 tco_experimental_features
1081 let tco_experimental_features =
1082 if !disallow_static_memoized then
1083 SSet.add
1084 TypecheckerOptions.experimental_disallow_static_memoized
1085 tco_experimental_features
1086 else
1087 tco_experimental_features
1089 let tco_experimental_features =
1090 if !enable_supportdyn_hint then
1091 SSet.add
1092 TypecheckerOptions.experimental_supportdynamic_type_hint
1093 tco_experimental_features
1094 else
1095 tco_experimental_features
1097 let tco_experimental_features =
1098 if !always_pessimise_return then
1099 SSet.add
1100 TypecheckerOptions.experimental_always_pessimise_return
1101 tco_experimental_features
1102 else
1103 tco_experimental_features
1105 let tco_experimental_features =
1106 if !consider_type_const_enforceable then
1107 SSet.add
1108 TypecheckerOptions.experimental_consider_type_const_enforceable
1109 tco_experimental_features
1110 else
1111 tco_experimental_features
1114 let tcopt = { tcopt with GlobalOptions.tco_experimental_features } in
1116 files = fns;
1117 extra_builtins = !extra_builtins;
1118 mode = !mode;
1119 no_builtins = !no_builtins;
1120 max_errors = !max_errors;
1121 error_format = !error_format;
1122 tcopt;
1123 batch_mode = !batch_mode;
1124 out_extension = !out_extension;
1125 verbosity = !verbosity;
1126 should_print_position = !print_position;
1127 custom_hhi_path = !custom_hhi_path;
1128 profile_type_check_multi = !profile_type_check_multi;
1129 memtrace = !memtrace;
1130 pessimise_builtins = !pessimise_builtins;
1131 rust_provider_backend = !rust_provider_backend;
1133 root,
1134 !naming_table,
1135 if !rust_provider_backend then
1136 SharedMem.
1138 !sharedmem_config with
1139 shm_use_sharded_hashtbl = true;
1140 shm_cache_size =
1141 max !sharedmem_config.shm_cache_size (2 * 1024 * 1024 * 1024);
1143 else
1144 !sharedmem_config )
1146 (* Make readable test output *)
1147 let replace_color input =
1148 Ide_api_types.(
1149 match input with
1150 | (Some Unchecked, str) -> "<unchecked>" ^ str ^ "</unchecked>"
1151 | (Some Checked, str) -> "<checked>" ^ str ^ "</checked>"
1152 | (Some Partial, str) -> "<partial>" ^ str ^ "</partial>"
1153 | (None, str) -> str)
1155 let print_colored fn type_acc =
1156 let content = cat (Relative_path.to_absolute fn) in
1157 let results = ColorFile.go content type_acc in
1158 if Unix.isatty Unix.stdout then
1159 Tty.cprint (ClientColorFile.replace_colors results)
1160 else
1161 print_string (List.map ~f:replace_color results |> String.concat ~sep:"")
1163 let print_coverage type_acc =
1164 ClientCoverageMetric.go ~json:false (Some (Coverage_level_defs.Leaf type_acc))
1166 let print_global_inference_envs ctx ~verbosity gienvs =
1167 let gienvs =
1168 Typing_global_inference.StateSubConstraintGraphs.global_tvenvs gienvs
1170 let tco_global_inference =
1171 TypecheckerOptions.global_inference (Provider_context.get_tcopt ctx)
1173 if verbosity >= 2 && tco_global_inference then
1174 let should_log (pos, gienv) =
1175 let file_relevant =
1176 match verbosity with
1178 when Filename.check_suffix
1179 (Relative_path.suffix (Pos.filename pos))
1180 ".hhi" ->
1181 false
1182 | _ -> true
1184 file_relevant && (not @@ List.is_empty @@ Inf.get_vars_g gienv)
1186 let env = Typing_env_types.empty ctx Relative_path.default ~droot:None in
1188 List.filter gienvs ~f:should_log
1189 |> List.iter ~f:(fun (pos, gienv) ->
1190 Typing_log.log_global_inference_env pos env gienv)
1192 let merge_global_inference_envs_opt ctx gienvs :
1193 Typing_global_inference.StateConstraintGraph.t option =
1194 if TypecheckerOptions.global_inference (Provider_context.get_tcopt ctx) then
1195 let open Typing_global_inference in
1196 let (type_map, env, state_errors) =
1197 StateConstraintGraph.merge_subgraphs ctx [gienvs]
1199 (* we are not going to print type variables without any bounds *)
1200 let env = { env with inference_env = Inf.compress env.inference_env } in
1201 Some (type_map, env, state_errors)
1202 else
1203 None
1205 let print_global_inference_env
1206 env ~step_name state_errors error_format max_errors =
1207 let print_header s =
1208 print_endline "";
1209 print_endline (String.map s ~f:(const '='));
1210 print_endline s;
1211 print_endline (String.map s ~f:(const '='))
1213 print_header (Printf.sprintf "%sd environment" step_name);
1214 Typing_log.hh_show_full_env Pos.none env;
1216 print_header (Printf.sprintf "%s errors" step_name);
1217 List.iter
1218 (Typing_global_inference.StateErrors.elements state_errors)
1219 ~f:(fun (var, errl) ->
1220 Printf.fprintf stderr "#%d\n" var;
1221 print_error_list error_format errl max_errors);
1222 Out_channel.flush stderr
1224 let print_merged_global_inference_env
1225 ~verbosity
1226 (gienv : Typing_global_inference.StateConstraintGraph.t option)
1227 error_format
1228 max_errors =
1229 if verbosity >= 1 then
1230 match gienv with
1231 | None -> ()
1232 | Some (_type_map, gienv, state_errors) ->
1233 print_global_inference_env
1234 gienv
1235 ~step_name:"Merge"
1236 state_errors
1237 error_format
1238 max_errors
1240 let print_solved_global_inference_env
1241 ~verbosity
1242 (gienv : Typing_global_inference.StateSolvedGraph.t option)
1243 error_format
1244 max_errors =
1245 if verbosity >= 1 then
1246 match gienv with
1247 | None -> ()
1248 | Some (gienv, state_errors, _type_map) ->
1249 print_global_inference_env
1250 gienv
1251 ~step_name:"Solve"
1252 state_errors
1253 error_format
1254 max_errors
1256 let solve_global_inference_env
1257 (gienv : Typing_global_inference.StateConstraintGraph.t) :
1258 Typing_global_inference.StateSolvedGraph.t =
1259 Typing_global_inference.StateSolvedGraph.from_constraint_graph gienv
1261 let global_inference_merge_and_solve
1262 ~verbosity ?(error_format = Errors.Plain) ?max_errors ctx gienvs =
1263 print_global_inference_envs ctx ~verbosity gienvs;
1264 let gienv = merge_global_inference_envs_opt ctx gienvs in
1265 print_merged_global_inference_env ~verbosity gienv error_format max_errors;
1266 let gienv = Option.map gienv ~f:solve_global_inference_env in
1267 print_solved_global_inference_env ~verbosity gienv error_format max_errors;
1268 gienv
1270 let print_elapsed fn desc ~start_time =
1271 let elapsed_ms = Float.(Unix.gettimeofday () - start_time) *. 1000. in
1272 Printf.printf
1273 "%s: %s - %0.2fms\n"
1274 (Relative_path.to_absolute fn |> Filename.basename)
1275 desc
1276 elapsed_ms
1278 let check_file ctx errors files_info ~profile_type_check_multi ~memtrace =
1279 let profiling = Option.is_some profile_type_check_multi in
1280 if profiling then
1281 Relative_path.Map.iter files_info ~f:(fun fn fileinfo ->
1282 let start_time = Unix.gettimeofday () in
1283 let _ = Typing_check_utils.type_file ctx fn fileinfo in
1284 print_elapsed fn "first typecheck+decl" ~start_time);
1285 let tracer =
1286 Option.map memtrace ~f:(fun filename ->
1287 Memtrace.start_tracing
1288 ~context:None
1289 ~sampling_rate:Memtrace.default_sampling_rate
1290 ~filename)
1292 let add_timing fn timings closure =
1293 let start_cpu = Sys.time () in
1294 let result = Lazy.force closure in
1295 let elapsed_cpu_time = Sys.time () -. start_cpu in
1296 let add_sample = function
1297 | Some samples -> Some (elapsed_cpu_time :: samples)
1298 | None -> Some [elapsed_cpu_time]
1300 let timings = Relative_path.Map.update fn add_sample timings in
1301 (result, timings)
1303 let rec go n timings =
1304 let (errors, timings) =
1305 Relative_path.Map.fold
1306 files_info
1307 ~f:(fun fn fileinfo (errors, timings) ->
1308 let ((_, new_errors), timings) =
1309 add_timing fn timings
1310 @@ lazy (Typing_check_utils.type_file ctx fn fileinfo)
1312 (errors @ Errors.get_sorted_error_list new_errors, timings))
1313 ~init:(errors, timings)
1315 if n > 1 then
1316 go (n - 1) timings
1317 else
1318 (errors, timings)
1320 let n_of_times_to_typecheck =
1321 max 1 (Option.value ~default:1 profile_type_check_multi)
1323 let timings = Relative_path.Map.empty in
1324 let (errors, timings) = go n_of_times_to_typecheck timings in
1325 let print_elapsed_cpu_time fn samples =
1326 let mean = mean samples in
1327 Printf.printf
1328 "%s: %d typechecks - %f ± %f (s)\n"
1329 (Relative_path.to_absolute fn |> Filename.basename)
1330 n_of_times_to_typecheck
1331 mean
1332 (standard_deviation mean samples)
1334 if profiling then Relative_path.Map.iter timings ~f:print_elapsed_cpu_time;
1335 Option.iter tracer ~f:Memtrace.stop_tracing;
1336 errors
1338 let create_nasts ctx files_info =
1339 let build_nast fn _ =
1340 let (syntax_errors, ast) =
1341 Ast_provider.get_ast_with_error ~full:true ctx fn
1343 let error_list = Errors.get_sorted_error_list syntax_errors in
1344 List.iter error_list ~f:Errors.add_error;
1345 Naming.program ctx ast
1347 Relative_path.Map.mapi ~f:build_nast files_info
1349 (** This is an almost-pure function which returns what we get out of parsing.
1350 The only side-effect it has is on the global errors list. *)
1351 let parse_and_name ctx files_contents =
1352 Relative_path.Map.mapi files_contents ~f:(fun fn contents ->
1353 (* Get parse errors. *)
1354 let () =
1355 Errors.run_in_context fn Errors.Parsing (fun () ->
1356 let popt = Provider_context.get_tcopt ctx in
1357 let parsed_file =
1358 Full_fidelity_ast.defensive_program popt fn contents
1360 let ast =
1361 let { Parser_return.ast; _ } = parsed_file in
1362 if ParserOptions.deregister_php_stdlib popt then
1363 Nast.deregister_ignored_attributes ast
1364 else
1367 Ast_provider.provide_ast_hint fn ast Ast_provider.Full;
1370 match Direct_decl_utils.direct_decl_parse ctx fn with
1371 | None -> failwith "no file contents"
1372 | Some decls -> Direct_decl_utils.decls_to_fileinfo fn decls)
1374 (** This function is used for gathering naming and parsing errors,
1375 and the side-effect of updating the global reverse naming table (and
1376 picking up duplicate-name errors along the way), and for the side effect
1377 of updating the decl heap (and picking up decling errors along the way). *)
1378 let parse_name_and_decl ctx files_contents =
1379 Errors.do_ (fun () ->
1380 let files_info = parse_and_name ctx files_contents in
1381 Relative_path.Map.iter files_info ~f:(fun fn fileinfo ->
1382 let (errors, _failed_naming_fns) =
1383 Naming_global.ndecl_file_error_if_already_bound ctx fn fileinfo
1385 Errors.merge_into_current errors);
1386 Relative_path.Map.iter files_info ~f:(fun fn _ ->
1387 Errors.run_in_context fn Errors.Decl (fun () ->
1388 Decl.make_env ~sh:SharedMem.Uses ctx fn));
1390 files_info)
1392 (** This function is used solely for its side-effect of putting decls into shared-mem *)
1393 let add_decls_to_heap ctx files_contents =
1394 Errors.ignore_ (fun () ->
1395 let files_info = parse_and_name ctx files_contents in
1396 Relative_path.Map.iter files_info ~f:(fun fn _ ->
1397 Errors.run_in_context fn Errors.Decl (fun () ->
1398 Decl.make_env ~sh:SharedMem.Uses ctx fn)));
1401 (** This function doesn't have side-effects. Its sole job is to return shallow decls. *)
1402 let get_shallow_decls ctx filename file_contents :
1403 Shallow_decl_defs.shallow_class SMap.t =
1404 let popt = Provider_context.get_popt ctx in
1405 let opts = DeclParserOptions.from_parser_options popt in
1406 (Direct_decl_parser.parse_decls opts filename file_contents)
1407 .Direct_decl_parser.pf_decls
1408 |> List.fold ~init:SMap.empty ~f:(fun acc (name, decl) ->
1409 match decl with
1410 | Shallow_decl_defs.Class c -> SMap.add name c acc
1411 | _ -> acc)
1413 let test_shallow_class_diff popt filename =
1414 let filename_after = Relative_path.to_absolute filename ^ ".after" in
1415 let contents1 = Sys_utils.cat (Relative_path.to_absolute filename) in
1416 let contents2 = Sys_utils.cat filename_after in
1417 let decls1 = get_shallow_decls popt filename contents1 in
1418 let decls2 = get_shallow_decls popt filename contents2 in
1419 let decls =
1420 SMap.merge (fun _ a b -> Some (a, b)) decls1 decls2 |> SMap.bindings
1422 let diffs =
1423 List.map decls ~f:(fun (cid, old_and_new) ->
1424 ( Utils.strip_ns cid,
1425 match old_and_new with
1426 | (Some c1, Some c2) -> Shallow_class_diff.diff_class c1 c2
1427 | (None, None) -> ClassDiff.(Major_change MajorChange.Unknown)
1428 | (None, Some _) -> ClassDiff.(Major_change MajorChange.Added)
1429 | (Some _, None) -> ClassDiff.(Major_change MajorChange.Removed) ))
1431 List.iter diffs ~f:(fun (cid, diff) ->
1432 Format.printf "%s: %a@." cid ClassDiff.pp diff)
1434 let add_newline contents =
1435 (* this is used for incremental mode to change all the positions, so we
1436 basically want a prepend; there's a few cases we need to handle:
1437 - empty file
1438 - header line: apppend after header
1439 - shebang and header: append after header
1440 - shebang only, no header (e.g. .hack file): append after shebang
1441 - no header or shebang (e.g. .hack file): prepend
1443 let after_shebang =
1444 if string_starts_with contents "#!" then
1445 String.index_exn contents '\n' + 1
1446 else
1449 let after_header =
1451 String.length contents > after_shebang + 2
1452 && String.equal (String.sub contents ~pos:after_shebang ~len:2) "<?"
1453 then
1454 String.index_from_exn contents after_shebang '\n' + 1
1455 else
1456 after_shebang
1458 String.sub contents ~pos:0 ~len:after_header
1459 ^ "\n"
1460 ^ String.sub
1461 contents
1462 ~pos:after_header
1463 ~len:(String.length contents - after_header)
1465 (* Might raise because of Option.value_exn *)
1466 let get_decls defs =
1467 ( SSet.fold
1468 (fun x acc ->
1469 Option.value_exn ~message:"Decl not found" (Decl_heap.Typedefs.get x)
1470 :: acc)
1471 defs.FileInfo.n_types
1473 SSet.fold
1474 (fun x acc ->
1475 Option.value_exn ~message:"Decl not found" (Decl_heap.Funs.get x) :: acc)
1476 defs.FileInfo.n_funs
1478 SSet.fold
1479 (fun x acc ->
1480 Option.value_exn ~message:"Decl not found" (Decl_heap.Classes.get x)
1481 :: acc)
1482 defs.FileInfo.n_classes
1483 [] )
1485 let fail_comparison s =
1486 raise
1487 (Failure
1488 (Printf.sprintf "Comparing %s failed!\n" s
1489 ^ "It's likely that you added new positions to decl types "
1490 ^ "without updating Decl_pos_utils.NormalizeSig\n"))
1492 let compare_typedefs t1 t2 =
1493 let t1 = Decl_pos_utils.NormalizeSig.typedef t1 in
1494 let t2 = Decl_pos_utils.NormalizeSig.typedef t2 in
1495 if Poly.(t1 <> t2) then fail_comparison "typedefs"
1497 let compare_funs f1 f2 =
1498 let f1 = Decl_pos_utils.NormalizeSig.fun_elt f1 in
1499 let f2 = Decl_pos_utils.NormalizeSig.fun_elt f2 in
1500 if Poly.(f1 <> f2) then fail_comparison "funs"
1502 let compare_classes mode c1 c2 =
1503 if Decl_compare.class_big_diff c1 c2 then fail_comparison "class_big_diff";
1505 let c1 = Decl_pos_utils.NormalizeSig.class_type c1 in
1506 let c2 = Decl_pos_utils.NormalizeSig.class_type c2 in
1507 let (_, is_unchanged) =
1508 Decl_compare.ClassDiff.compare mode c1.Decl_defs.dc_name c1 c2
1510 if not is_unchanged then fail_comparison "ClassDiff";
1512 let (_, is_unchanged) = Decl_compare.ClassEltDiff.compare mode c1 c2 in
1513 match is_unchanged with
1514 | `Changed -> fail_comparison "ClassEltDiff"
1515 | _ -> ()
1517 let test_decl_compare ctx filenames builtins files_contents files_info =
1518 (* skip some edge cases that we don't handle now... ugly! *)
1519 if String.equal (Relative_path.suffix filenames) "capitalization3.php" then
1521 else if String.equal (Relative_path.suffix filenames) "capitalization4.php"
1522 then
1524 else
1525 (* do not analyze builtins over and over *)
1526 let files_info =
1527 Relative_path.Map.fold
1528 builtins
1530 begin
1531 (fun k _ acc -> Relative_path.Map.remove acc k)
1533 ~init:files_info
1535 let files =
1536 Relative_path.Map.fold
1537 files_info
1538 ~f:(fun k _ acc -> Relative_path.Set.add acc k)
1539 ~init:Relative_path.Set.empty
1541 let defs =
1542 Relative_path.Map.fold
1543 files_info
1545 begin
1546 fun _ names1 names2 ->
1547 FileInfo.(merge_names (simplify names1) names2)
1549 ~init:FileInfo.empty_names
1551 let (typedefs1, funs1, classes1) = get_decls defs in
1552 (* For the purpose of this test, we can ignore other heaps *)
1553 Ast_provider.remove_batch files;
1555 let get_classes path =
1556 match Relative_path.Map.find_opt files_info path with
1557 | None -> SSet.empty
1558 | Some info ->
1559 SSet.of_list @@ List.map info.FileInfo.classes ~f:(fun (_, x, _) -> x)
1561 (* We need to oldify, not remove, for ClassEltDiff to work *)
1562 Decl_redecl_service.oldify_type_decl
1564 None
1565 get_classes
1566 ~bucket_size:1
1567 ~defs
1568 ~collect_garbage:false;
1570 let files_contents = Relative_path.Map.map files_contents ~f:add_newline in
1571 add_decls_to_heap ctx files_contents;
1572 let (typedefs2, funs2, classes2) = get_decls defs in
1573 let deps_mode = Provider_context.get_deps_mode ctx in
1574 List.iter2_exn typedefs1 typedefs2 ~f:compare_typedefs;
1575 List.iter2_exn funs1 funs2 ~f:compare_funs;
1576 List.iter2_exn classes1 classes2 ~f:(compare_classes deps_mode);
1579 (* Returns a list of Tast defs, along with associated type environments. *)
1580 let compute_tasts ?(drop_fixmed = true) ctx files_info interesting_files :
1581 Errors.t
1582 * (Tast.program Relative_path.Map.t
1583 * Typing_inference_env.t_global_with_pos list) =
1584 let _f _k nast x =
1585 match (nast, x) with
1586 | (Some nast, Some _) -> Some nast
1587 | _ -> None
1589 Errors.do_ ~drop_fixmed (fun () ->
1590 let nasts = create_nasts ctx files_info in
1591 (* Interesting files are usually the non hhi ones. *)
1592 let filter_non_interesting nasts =
1593 Relative_path.Map.merge nasts interesting_files ~f:(fun _k nast x ->
1594 match (nast, x) with
1595 | (Some nast, Some _) -> Some nast
1596 | _ -> None)
1598 let nasts = filter_non_interesting nasts in
1599 let tasts_envs =
1600 Relative_path.Map.map
1601 nasts
1602 ~f:(Typing_toplevel.nast_to_tast_gienv ~do_tast_checks:true ctx)
1604 let tasts = Relative_path.Map.map tasts_envs ~f:fst in
1605 let genvs =
1606 List.concat
1607 @@ Relative_path.Map.values
1608 @@ Relative_path.Map.map tasts_envs ~f:snd
1610 (tasts, genvs))
1612 let merge_global_inference_env_in_tast gienv tast =
1613 let env_merger =
1614 object
1615 inherit Tast_visitor.endo
1617 method! on_'en _ env =
1619 env with
1620 Tast.inference_env =
1621 Typing_inference_env.simple_merge
1622 env.Tast.inference_env
1623 gienv.inference_env;
1627 env_merger#go tast
1629 (* Given source code containing the string "^ hover-at-caret", return
1630 the line and column of the position indicated. *)
1631 let hover_at_caret_pos (src : string) : int * int =
1632 let lines = String.split_lines src in
1633 match
1634 List.findi lines ~f:(fun _ line ->
1635 String.is_substring line ~substring:"^ hover-at-caret")
1636 with
1637 | Some (line_num, line_src) ->
1638 let col_num =
1639 String.lfindi line_src ~f:(fun _ c ->
1640 match c with
1641 | '^' -> true
1642 | _ -> false)
1644 (line_num, Option.value_exn col_num + 1)
1645 | None ->
1646 failwith "Could not find any occurrence of ^ hover-at-caret in source code"
1649 * Compute TASTs for some files, then expand all type variables.
1651 let compute_tasts_expand_types ctx ~verbosity files_info interesting_files =
1652 let (errors, (tasts, gienvs)) =
1653 compute_tasts ctx files_info interesting_files
1655 let subconstraints =
1656 Typing_global_inference.StateSubConstraintGraphs.build
1658 (List.concat (Relative_path.Map.values tasts))
1659 gienvs
1661 let (tasts, gi_solved) =
1662 match global_inference_merge_and_solve ctx ~verbosity subconstraints with
1663 | None -> (tasts, None)
1664 | Some ((gienv, _, _) as gi_solved) ->
1665 let tasts =
1666 Relative_path.Map.map
1667 tasts
1668 ~f:(merge_global_inference_env_in_tast gienv ctx)
1670 (tasts, Some gi_solved)
1672 let tasts = Relative_path.Map.map tasts ~f:(Tast_expand.expand_program ctx) in
1673 (errors, tasts, gi_solved)
1675 let decl_parse_typecheck_and_then ~verbosity ctx files_contents f =
1676 let (parse_errors, files_info) = parse_name_and_decl ctx files_contents in
1677 let parse_errors = Errors.get_sorted_error_list parse_errors in
1678 let (errors, _tasts, _gi_solved) =
1679 compute_tasts_expand_types ctx ~verbosity files_info files_contents
1681 let errors = parse_errors @ Errors.get_sorted_error_list errors in
1682 if List.is_empty errors then
1683 f files_info
1684 else
1685 print_errors_if_present errors
1687 let print_nasts ~should_print_position nasts filenames =
1688 List.iter filenames ~f:(fun filename ->
1689 match Relative_path.Map.find_opt nasts filename with
1690 | None ->
1691 Printf.eprintf
1692 "Could not find nast for file %s\n"
1693 (Relative_path.show filename);
1694 Printf.eprintf "Available nasts:\n";
1695 Relative_path.Map.iter nasts ~f:(fun path _ ->
1696 Printf.eprintf " %s\n" (Relative_path.show path))
1697 | Some nast ->
1698 if should_print_position then
1699 Naming_ast_print.print_nast nast
1700 else
1701 Naming_ast_print.print_nast_without_position nast)
1703 let print_tasts ~should_print_position tasts ctx =
1704 Relative_path.Map.iter tasts ~f:(fun _k (tast : Tast.program) ->
1705 if should_print_position then
1706 Typing_ast_print.print_tast ctx tast
1707 else
1708 Typing_ast_print.print_tast_without_position ctx tast)
1710 let typecheck_tasts tasts tcopt (filename : Relative_path.t) =
1711 let env = Typing_env_types.empty tcopt filename ~droot:None in
1712 let tasts = Relative_path.Map.values tasts in
1713 let typecheck_tast tast =
1714 Errors.get_sorted_error_list (Tast_typecheck.check env tast)
1716 List.concat_map tasts ~f:typecheck_tast
1718 let pp_debug_deps fmt entries =
1719 Format.fprintf fmt "@[<v>";
1720 ignore
1721 @@ List.fold_left entries ~init:false ~f:(fun sep (obj, roots) ->
1722 if sep then Format.fprintf fmt "@;";
1723 Format.fprintf fmt "%s -> " obj;
1724 Format.fprintf fmt "@[<hv>";
1725 ignore
1726 @@ List.fold_left roots ~init:false ~f:(fun sep root ->
1727 if sep then Format.fprintf fmt ",@ ";
1728 Format.pp_print_string fmt root;
1729 true);
1730 Format.fprintf fmt "@]";
1731 true);
1732 Format.fprintf fmt "@]"
1734 let show_debug_deps = Format.asprintf "%a" pp_debug_deps
1736 let sort_debug_deps deps =
1737 Hashtbl.fold deps ~init:[] ~f:(fun ~key:obj ~data:set acc ->
1738 (obj, set) :: acc)
1739 |> List.sort ~compare:(fun (a, _) (b, _) -> String.compare a b)
1740 |> List.map ~f:(fun (obj, roots) ->
1741 let roots =
1742 HashSet.fold roots ~init:[] ~f:List.cons
1743 |> List.sort ~compare:String.compare
1745 (obj, roots))
1747 (* Note: this prints dependency graph edges in the same direction as the mapping
1748 which is actually stored in the shared memory table. The line "X -> Y" can be
1749 read, "X is used by Y", or "X is a dependency of Y", or "when X changes, Y
1750 must be rechecked". *)
1751 let dump_debug_deps dbg_deps =
1752 dbg_deps |> sort_debug_deps |> show_debug_deps |> Printf.printf "%s\n"
1754 let dump_debug_glean_deps
1755 (deps :
1756 (Typing_deps.Dep.dependency Typing_deps.Dep.variant
1757 * Typing_deps.Dep.dependent Typing_deps.Dep.variant)
1758 HashSet.t) =
1759 let json_opt = Glean_dependency_graph_convert.convert_deps_to_json ~deps in
1760 match json_opt with
1761 | Some json_obj ->
1762 Printf.printf "%s\n" (Hh_json.json_to_string ~pretty:true json_obj)
1763 | None -> Printf.printf "No dependencies\n"
1765 let handle_constraint_mode
1766 ~do_
1767 name
1768 opts
1770 error_format
1771 ~iter_over_files
1772 ~profile_type_check_multi
1773 ~memtrace =
1774 (* Process a single typechecked file *)
1775 let process_file path info =
1776 match info.FileInfo.file_mode with
1777 | Some FileInfo.Mstrict ->
1778 let (ctx, entry) = Provider_context.add_entry_if_missing ~ctx ~path in
1779 let { Tast_provider.Compute_tast.tast; _ } =
1780 Tast_provider.compute_tast_unquarantined ~ctx ~entry
1782 do_ opts ctx tast
1783 | _ ->
1784 (* We are not interested in partial files and there is nothing in HHI
1785 files to analyse *)
1788 let print_errors = List.iter ~f:(print_error ~oc:stdout error_format) in
1789 (* Process a multifile that is not typechecked *)
1790 let process_multifile filename =
1791 Printf.printf
1792 "=== %s analysis results for %s\n%!"
1793 name
1794 (Relative_path.to_absolute filename);
1795 let files_contents = Multifile.file_to_files filename in
1796 let (parse_errors, file_info) = parse_name_and_decl ctx files_contents in
1797 let error_list = Errors.get_sorted_error_list parse_errors in
1798 let check_errors =
1799 check_file ctx error_list file_info ~profile_type_check_multi ~memtrace
1801 if not (List.is_empty check_errors) then
1802 print_errors check_errors
1803 else
1804 Relative_path.Map.iter file_info ~f:process_file
1806 let process_multifile filename =
1807 Provider_utils.respect_but_quarantine_unsaved_changes ~ctx ~f:(fun () ->
1808 process_multifile filename)
1810 iter_over_files process_multifile
1812 let scrape_class_names (ast : Nast.program) : SSet.t =
1813 let names = ref SSet.empty in
1814 let visitor =
1815 object
1816 (* It would look less clumsy to use Aast.reduce, but would use set union which has higher complexity. *)
1817 inherit [_] Aast.iter
1819 method! on_class_name _ (_p, id) = names := SSet.add id !names
1822 visitor#on_program () ast;
1823 !names
1825 (** Scrape names in file and return the files where those names are defined. *)
1826 let get_some_file_dependencies ctx (file : Relative_path.t) :
1827 Relative_path.Set.t =
1828 let open Hh_prelude in
1829 let nast = Ast_provider.get_ast ctx ~full:true file in
1830 let names =
1831 Errors.ignore_ (fun () -> Naming.program ctx nast) |> scrape_class_names
1832 (* TODO: scape other defs too *)
1834 SSet.fold
1835 (fun class_name files ->
1836 match Naming_provider.get_class_path ctx class_name with
1837 | None -> files
1838 | Some file -> Relative_path.Set.add files file)
1839 names
1840 Relative_path.Set.empty
1842 (** Recursively scrape names in files and return the
1843 files where those names are defined. *)
1844 let traverse_file_dependencies ctx (files : Relative_path.t list) ~(depth : int)
1845 : Relative_path.Set.t =
1846 let rec traverse
1847 (files : Relative_path.Set.t)
1848 depth
1849 (visited : Relative_path.Set.t)
1850 (results : Relative_path.Set.t) =
1851 if Int.( <= ) depth 0 then
1852 Relative_path.Set.union files results
1853 else
1854 let (next_files, visited, results) =
1855 Relative_path.Set.fold
1856 files
1857 ~init:(Relative_path.Set.empty, visited, results)
1858 ~f:(fun file (next_files, visited, results) ->
1859 if Relative_path.Set.mem visited file then
1860 (next_files, visited, results)
1861 else
1862 let visited = Relative_path.Set.add visited file in
1863 let dependencies = get_some_file_dependencies ctx file in
1864 let next_files =
1865 Relative_path.Set.union dependencies next_files
1867 let results = Relative_path.Set.add results file in
1868 (next_files, visited, results))
1870 traverse next_files (depth - 1) visited results
1872 traverse
1873 (Relative_path.Set.of_list files)
1874 depth
1875 Relative_path.Set.empty
1876 Relative_path.Set.empty
1878 let apply_patches files_contents patches =
1879 if List.length patches <= 0 then
1880 print_endline "No patches"
1881 else
1882 ServerRefactorTypes.apply_patches_to_file_contents files_contents patches
1883 |> Multifile.print_files_as_multifile
1885 let handle_mode
1886 mode
1887 filenames
1889 builtins
1890 files_contents
1891 files_info
1892 parse_errors
1893 max_errors
1894 error_format
1895 batch_mode
1896 out_extension
1897 dbg_deps
1898 dbg_glean_deps
1899 ~should_print_position
1900 ~profile_type_check_multi
1901 ~memtrace
1902 ~verbosity =
1903 let expect_single_file () : Relative_path.t =
1904 match filenames with
1905 | [x] -> x
1906 | _ -> die "Only single file expected"
1908 let iter_over_files f : unit = List.iter filenames ~f in
1909 match mode with
1910 | Refactor_sound_dynamic (analysis_mode, refactor_mode, element_name) ->
1911 let opts =
1912 match
1913 ( Refactor_sd_options.parse_analysis_mode analysis_mode,
1914 Refactor_sd_options.parse_refactor_mode refactor_mode )
1915 with
1916 | (Some analysis_mode, Some refactor_mode) ->
1917 Refactor_sd_options.mk ~analysis_mode ~refactor_mode
1918 | (None, _) -> die "invalid refactor_sd analysis mode"
1919 | (_, None) -> die "invalid refactor_sd refactor mode"
1921 handle_constraint_mode
1922 ~do_:(Refactor_sd.do_ element_name)
1923 "Sound Dynamic"
1924 opts
1926 error_format
1927 ~iter_over_files
1928 ~profile_type_check_multi
1929 ~memtrace
1930 | SDT_analysis command ->
1931 let opts =
1932 let command =
1933 match Sdt_analysis_options.parse_command command with
1934 | Some command -> command
1935 | None -> die "invalid SDT analysis mode"
1937 Sdt_analysis_options.mk ~verbosity ~command
1939 handle_constraint_mode
1940 ~do_:Sdt_analysis.do_
1941 "SDT"
1942 opts
1944 error_format
1945 ~iter_over_files
1946 ~profile_type_check_multi
1947 ~memtrace
1948 | Shape_analysis mode ->
1949 let opts =
1950 match Shape_analysis_options.parse_mode mode with
1951 | Some (command, mode) ->
1952 Shape_analysis_options.mk ~command ~mode ~verbosity
1953 | None -> die "invalid shape analysis mode"
1955 handle_constraint_mode
1956 ~do_:Shape_analysis.do_
1957 "Shape"
1958 opts
1960 error_format
1961 ~iter_over_files
1962 ~profile_type_check_multi
1963 ~memtrace
1964 | Ifc (mode, lattice) ->
1965 (* Timing mode is same as check except we print out the time it takes to
1966 analyse the file. *)
1967 let (mode, should_time) =
1968 if String.equal mode "time" then
1969 ("check", true)
1970 else
1971 (mode, false)
1973 let ifc_opts =
1974 match Ifc_options.parse ~mode ~lattice with
1975 | Ok opts -> opts
1976 | Error e -> die ("could not parse IFC options: " ^ e)
1978 let time f =
1979 let start_time = Unix.gettimeofday () in
1980 let result = Lazy.force f in
1981 let elapsed_time = Unix.gettimeofday () -. start_time in
1982 if should_time then Printf.printf "Duration: %f\n" elapsed_time;
1983 result
1985 let print_errors = List.iter ~f:(print_error ~oc:stdout error_format) in
1986 let process_file filename =
1987 Printf.printf
1988 "=== IFC analysis results for %s\n%!"
1989 (Relative_path.to_absolute filename);
1990 let files_contents = Multifile.file_to_files filename in
1991 let (parse_errors, file_info) = parse_name_and_decl ctx files_contents in
1992 let check_errors =
1993 let error_list = Errors.get_sorted_error_list parse_errors in
1994 check_file ctx error_list file_info ~profile_type_check_multi ~memtrace
1996 if not (List.is_empty check_errors) then
1997 print_errors check_errors
1998 else
2000 let ifc_errors = time @@ lazy (Ifc_main.do_ ifc_opts file_info ctx) in
2001 if not (List.is_empty ifc_errors) then print_errors ifc_errors
2002 with
2003 | exn ->
2004 let e = Exception.wrap exn in
2005 Stdlib.Printexc.register_printer (function
2006 | Ifc_types.IFCError err ->
2007 Some
2008 (Printf.sprintf "IFCError(%s)"
2009 @@ Ifc_types.show_ifc_error_ty err)
2010 | _ -> None);
2011 Printf.printf "Uncaught exception: %s" (Exception.to_string e)
2013 iter_over_files (fun filename ->
2014 Provider_utils.respect_but_quarantine_unsaved_changes ~ctx ~f:(fun () ->
2015 process_file filename))
2016 | Color ->
2017 Relative_path.Map.iter files_info ~f:(fun fn fileinfo ->
2018 if Relative_path.Map.mem builtins fn then
2020 else
2021 let (tast, _) = Typing_check_utils.type_file ctx fn fileinfo in
2022 let result = Coverage_level.get_levels ctx tast fn in
2023 match result with
2024 | Ok result -> print_colored fn result
2025 | Error () ->
2026 failwith
2027 ("HH_FIXMEs not found for path " ^ Relative_path.to_absolute fn))
2028 | Coverage ->
2029 Relative_path.Map.iter files_info ~f:(fun fn fileinfo ->
2030 if Relative_path.Map.mem builtins fn then
2032 else
2033 let (tast, _) = Typing_check_utils.type_file ctx fn fileinfo in
2034 let type_acc =
2035 ServerCoverageMetricUtils.accumulate_types ctx tast fn
2037 print_coverage type_acc)
2038 | Cst_search ->
2039 let path = expect_single_file () in
2040 let (ctx, entry) = Provider_context.add_entry_if_missing ~ctx ~path in
2041 let result =
2042 let open Result.Monad_infix in
2043 Sys_utils.read_stdin_to_string ()
2044 |> Hh_json.json_of_string
2045 |> CstSearchService.compile_pattern ctx
2046 >>| CstSearchService.search ctx entry
2047 >>| CstSearchService.result_to_json ~sort_results:true
2048 >>| Hh_json.json_to_string ~pretty:true
2050 begin
2051 match result with
2052 | Ok result -> Printf.printf "%s\n" result
2053 | Error message ->
2054 Printf.printf "%s\n" message;
2055 exit 1
2057 | Dump_symbol_info ->
2058 iter_over_files (fun filename ->
2059 match Relative_path.Map.find_opt files_info filename with
2060 | Some _fileinfo ->
2061 let raw_result = SymbolInfoServiceUtils.helper ctx [] [filename] in
2062 let result = SymbolInfoServiceUtils.format_result raw_result in
2063 let result_json =
2064 ServerCommandTypes.Symbol_info_service.to_json result
2066 print_endline (Hh_json.json_to_multiline result_json)
2067 | None -> ())
2068 | Glean_index out_dir ->
2070 (not (Disk.is_directory out_dir))
2071 || Array.length (Sys.readdir out_dir) > 0
2072 then (
2073 Printf.printf "%s should be an empty dir\n" out_dir;
2074 exit 1
2075 ) else
2076 Symbol_entrypoint.index_files ctx ~out_dir ~files:filenames
2077 | Lint ->
2078 let lint_errors =
2079 Relative_path.Map.fold
2080 files_contents
2081 ~init:[]
2082 ~f:(fun fn content lint_errors ->
2083 lint_errors
2084 @ fst (Lints_core.do_ (fun () -> Linting_main.lint ctx fn content)))
2086 if not (List.is_empty lint_errors) then (
2087 let lint_errors =
2088 List.sort
2089 ~compare:
2090 begin
2091 fun x y ->
2092 Pos.compare (Lints_core.get_pos x) (Lints_core.get_pos y)
2094 lint_errors
2096 let lint_errors = List.map ~f:Lints_core.to_absolute lint_errors in
2097 ServerLintTypes.output_text stdout lint_errors error_format;
2098 exit 2
2099 ) else
2100 Printf.printf "No lint errors\n"
2101 | Lint_json ->
2102 let json_errors =
2103 Relative_path.Map.fold
2104 files_contents
2105 ~init:[]
2106 ~f:(fun fn content json_errors ->
2107 json_errors
2108 @ fst (Lints_core.do_ (fun () -> Linting_main.lint ctx fn content)))
2110 let json_errors =
2111 List.sort
2112 ~compare:
2113 begin
2114 fun x y ->
2115 Pos.compare (Lints_core.get_pos x) (Lints_core.get_pos y)
2117 json_errors
2119 let json_errors = List.map ~f:Lints_core.to_absolute json_errors in
2120 ServerLintTypes.output_json ~pretty:true stdout json_errors;
2121 exit 2
2122 | Dump_deps ->
2123 Relative_path.Map.iter files_info ~f:(fun fn fileinfo ->
2124 ignore @@ Typing_check_utils.check_defs ctx fn fileinfo);
2125 if Hashtbl.length dbg_deps > 0 then dump_debug_deps dbg_deps
2126 | Dump_dep_hashes ->
2127 iter_over_files (fun _ ->
2128 let nasts = create_nasts ctx files_info in
2129 Relative_path.Map.iter nasts ~f:(fun _ nast ->
2130 Dep_hash_to_symbol.dump nast))
2131 | Dump_glean_deps ->
2132 Relative_path.Map.iter files_info ~f:(fun fn fileinfo ->
2133 ignore @@ Typing_check_utils.check_defs ctx fn fileinfo);
2134 dump_debug_glean_deps dbg_glean_deps
2135 | Get_some_file_deps depth ->
2136 let file_deps = traverse_file_dependencies ctx filenames ~depth in
2137 Relative_path.Set.iter file_deps ~f:(fun file ->
2138 Printf.printf "%s\n" (Relative_path.to_absolute file))
2139 | Dump_inheritance ->
2140 let open ServerCommandTypes.Method_jumps in
2141 let naming_table = Naming_table.create files_info in
2142 Naming_table.iter naming_table ~f:(fun fn fileinfo ->
2143 if Relative_path.Map.mem builtins fn then
2145 else (
2146 List.iter fileinfo.FileInfo.classes ~f:(fun (_p, class_, _) ->
2147 Printf.printf
2148 "Ancestors of %s and their overridden methods:\n"
2149 class_;
2150 let ancestors =
2151 (* Might raise {!Naming_table.File_info_not_found} *)
2152 MethodJumps.get_inheritance
2154 class_
2155 ~filter:No_filter
2156 ~find_children:false
2157 naming_table
2158 None
2160 ServerCommandTypes.Method_jumps.print_readable
2161 ancestors
2162 ~find_children:false;
2163 Printf.printf "\n");
2164 Printf.printf "\n";
2165 List.iter fileinfo.FileInfo.classes ~f:(fun (_p, class_, _) ->
2166 Printf.printf
2167 "Children of %s and the methods they override:\n"
2168 class_;
2169 let children =
2170 (* Might raise {!Naming_table.File_info_not_found} *)
2171 MethodJumps.get_inheritance
2173 class_
2174 ~filter:No_filter
2175 ~find_children:true
2176 naming_table
2177 None
2179 ServerCommandTypes.Method_jumps.print_readable
2180 children
2181 ~find_children:true;
2182 Printf.printf "\n")
2184 | Identify_symbol (line, column) ->
2185 let path = expect_single_file () in
2186 let (ctx, entry) = Provider_context.add_entry_if_missing ~ctx ~path in
2187 (* TODO(ljw): surely this doesn't need quarantine? *)
2188 let result =
2189 Provider_utils.respect_but_quarantine_unsaved_changes ~ctx ~f:(fun () ->
2190 ServerIdentifyFunction.go_quarantined_absolute
2191 ~ctx
2192 ~entry
2193 ~line
2194 ~column)
2196 begin
2197 match result with
2198 | [] -> print_endline "None"
2199 | result -> ClientGetDefinition.print_readable ~short_pos:true result
2201 | Find_local (line, char) ->
2202 let filename = expect_single_file () in
2203 let (ctx, entry) =
2204 Provider_context.add_entry_if_missing ~ctx ~path:filename
2206 let result = ServerFindLocals.go ~ctx ~entry ~line ~char in
2207 let print pos = Printf.printf "%s\n" (Pos.string_no_file pos) in
2208 List.iter result ~f:print
2209 | Outline ->
2210 iter_over_files (fun filename ->
2211 let file = cat (Relative_path.to_absolute filename) in
2212 let results =
2213 FileOutline.outline (Provider_context.get_popt ctx) file
2215 FileOutline.print ~short_pos:true results)
2216 | Dump_nast ->
2217 let (errors, nasts) = Errors.do_ (fun () -> create_nasts ctx files_info) in
2218 print_errors_if_present (Errors.get_sorted_error_list errors);
2220 print_nasts
2221 ~should_print_position
2222 nasts
2223 (Relative_path.Map.keys files_contents)
2224 | Dump_tast ->
2225 let (errors, tasts, _gi_solved) =
2226 compute_tasts_expand_types ctx ~verbosity files_info files_contents
2228 print_errors_if_present (parse_errors @ Errors.get_sorted_error_list errors);
2229 print_tasts ~should_print_position tasts ctx
2230 | Check_tast ->
2231 iter_over_files (fun filename ->
2232 let files_contents =
2233 Relative_path.Map.filter files_contents ~f:(fun k _v ->
2234 Relative_path.equal k filename)
2236 let (errors, tasts, _gi_solved) =
2237 compute_tasts_expand_types ctx ~verbosity files_info files_contents
2239 print_tasts ~should_print_position tasts ctx;
2240 if not @@ Errors.is_empty errors then (
2241 print_errors error_format errors max_errors;
2242 Printf.printf "Did not typecheck the TAST as there are typing errors.";
2243 exit 2
2244 ) else
2245 let tast_check_errors = typecheck_tasts tasts ctx filename in
2246 print_error_list error_format tast_check_errors max_errors;
2247 if not (List.is_empty tast_check_errors) then exit 2)
2248 | Dump_stripped_tast ->
2249 iter_over_files (fun filename ->
2250 let files_contents =
2251 Relative_path.Map.filter files_contents ~f:(fun k _v ->
2252 Relative_path.equal k filename)
2254 let (_, (tasts, _gienvs)) =
2255 compute_tasts ctx files_info files_contents
2257 let tast = Relative_path.Map.find tasts filename in
2258 let nast = Tast.to_nast tast in
2259 Printf.printf "%s\n" (Nast.show_program nast))
2260 | RewriteGlobalInference ->
2261 let (errors, _tasts, gi_solved) =
2262 compute_tasts_expand_types ctx ~verbosity files_info files_contents
2264 print_errors_if_present (parse_errors @ Errors.get_sorted_error_list errors);
2265 (match gi_solved with
2266 | None ->
2267 prerr_endline
2268 ("error: no patches generated as global"
2269 ^ " inference is turend off (use --global-inference)");
2270 exit 1
2271 | Some gi_solved ->
2272 ServerGlobalInference.Mode_rewrite.get_patches ~files_contents gi_solved
2273 |> apply_patches files_contents)
2274 | RemoveDeadUnsafeCasts ->
2275 let ctx =
2276 Provider_context.map_tcopt ctx ~f:(fun tcopt ->
2277 GlobalOptions.{ tcopt with tco_populate_dead_unsafe_cast_heap = true })
2279 let decl_parse_typecheck_and_then =
2280 decl_parse_typecheck_and_then ~verbosity ctx
2282 let backend = Provider_context.get_backend ctx in
2283 (* Because we repeatedly apply the codemod, positions change. So we need to
2284 re-parse, decl, and typecheck the file to generated accurate patches as
2285 well as amend the patched test files in memory. This involves
2286 invalidating a number of shared heaps and providing in memory
2287 replacements after patching files. *)
2288 let invalidate_heaps_and_update_files files_info files_contents =
2289 let paths_to_purge =
2290 Relative_path.Map.keys files_contents |> Relative_path.Set.of_list
2292 (* Purge the file, then provide its replacement, otherwise the
2293 replacement is dropped on the floor. *)
2294 File_provider.remove_batch paths_to_purge;
2295 Relative_path.Map.iter
2296 ~f:File_provider.provide_file_for_tests
2297 files_contents;
2298 Ast_provider.remove_batch paths_to_purge;
2299 Relative_path.Map.iter
2300 ~f:(fun path file_info ->
2301 (* Don't invalidate builtins, otherwise, we can't find them. *)
2302 if not (Relative_path.prefix path |> Relative_path.is_hhi) then
2303 Naming_global.remove_decls_using_file_info backend file_info)
2304 files_info
2306 (* Repeatedly apply dead unsafe cast removal. We can't do this at once as
2307 removing one unsafe cast might have a bearing on another. *)
2308 let rec go files_info files_contents =
2309 invalidate_heaps_and_update_files files_info files_contents;
2310 decl_parse_typecheck_and_then files_contents @@ fun files_info ->
2311 let patches =
2312 Remove_dead_unsafe_casts.get_patches
2313 ~is_test:true
2314 ~files_info
2315 ~fold:Relative_path.Map.fold
2317 let files_contents =
2318 ServerRefactorTypes.apply_patches_to_file_contents
2319 files_contents
2320 patches
2322 if List.is_empty patches then
2323 Multifile.print_files_as_multifile files_contents
2324 else
2325 go files_info files_contents
2327 go files_info files_contents;
2329 (* Typecheck after the codemod is fully applied to confirm that what we
2330 produce is not garbage. *)
2331 Printf.printf
2332 "\nTypechecking after the codemod... (no output after this is good news)\n";
2333 invalidate_heaps_and_update_files files_info files_contents;
2334 decl_parse_typecheck_and_then files_contents @@ fun _ -> ()
2335 | Find_refs (line, column) ->
2336 let path = expect_single_file () in
2337 let naming_table = Naming_table.create files_info in
2338 let genv = ServerEnvBuild.default_genv in
2339 let init_id = Random_id.short_string () in
2340 let env =
2342 (ServerEnvBuild.make_env
2343 ~init_id
2344 ~deps_mode:(Typing_deps_mode.InMemoryMode None)
2345 genv.ServerEnv.config)
2346 with
2347 ServerEnv.naming_table;
2348 ServerEnv.tcopt = Provider_context.get_tcopt ctx;
2351 let include_defs = true in
2352 let (ctx, entry) =
2353 Provider_context.add_entry_if_missing
2354 ~ctx:(Provider_utils.ctx_from_server_env env)
2355 ~path
2357 let open Option.Monad_infix in
2358 let open ServerCommandTypes.Done_or_retry in
2359 let results =
2360 Provider_utils.respect_but_quarantine_unsaved_changes ~ctx ~f:(fun () ->
2361 ServerFindRefs.(
2362 go_from_file_ctx ~ctx ~entry ~line ~column >>= fun (name, action) ->
2363 go ctx action include_defs genv env
2364 |> map_env ~f:(to_ide name)
2365 |> snd
2366 |> function
2367 | Done r -> r
2368 | Retry ->
2369 failwith
2370 @@ "should only happen with prechecked files "
2371 ^ "which are not a thing in hh_single_type_check"))
2373 ClientFindRefsPrint.print_ide_readable results
2374 | Go_to_impl (line, column) ->
2375 let filename = expect_single_file () in
2376 let naming_table = Naming_table.create files_info in
2377 let genv = ServerEnvBuild.default_genv in
2378 let init_id = Random_id.short_string () in
2379 let env =
2381 (ServerEnvBuild.make_env
2382 ~init_id
2383 ~deps_mode:(Typing_deps_mode.InMemoryMode None)
2384 genv.ServerEnv.config)
2385 with
2386 ServerEnv.naming_table;
2387 ServerEnv.tcopt = Provider_context.get_tcopt ctx;
2390 let filename = Relative_path.to_absolute filename in
2391 let contents = cat filename in
2392 let (ctx, entry) =
2393 Provider_context.add_or_overwrite_entry_contents
2394 ~ctx:(Provider_utils.ctx_from_server_env env)
2395 ~path:(Relative_path.create_detect_prefix filename)
2396 ~contents
2398 Option.Monad_infix.(
2399 ServerCommandTypes.Done_or_retry.(
2400 let results =
2401 ServerFindRefs.go_from_file_ctx ~ctx ~entry ~line ~column
2402 >>= fun (name, action) ->
2403 ServerGoToImpl.go ~action ~genv ~env
2404 |> map_env ~f:(ServerFindRefs.to_ide name)
2405 |> snd
2406 |> function
2407 | Done r -> r
2408 | Retry ->
2409 failwith
2410 @@ "should only happen with prechecked files "
2411 ^ "which are not a thing in hh_single_type_check"
2413 ClientFindRefsPrint.print_ide_readable results))
2414 | Highlight_refs (line, column) ->
2415 let path = expect_single_file () in
2416 let (ctx, entry) = Provider_context.add_entry_if_missing ~ctx ~path in
2417 let results =
2418 ServerHighlightRefs.go_quarantined ~ctx ~entry ~line ~column
2420 ClientHighlightRefs.go results ~output_json:false
2421 | Errors when batch_mode ->
2422 (* For each file in our batch, run typechecking serially.
2423 Reset the heaps every time in between. *)
2424 iter_over_files (fun filename ->
2425 let oc =
2426 Out_channel.create (Relative_path.to_absolute filename ^ out_extension)
2428 (* This means builtins had errors, so lets just print those if we see them *)
2429 if not (List.is_empty parse_errors) then
2430 (* This closes the out channel *)
2431 write_error_list error_format parse_errors oc max_errors
2432 else (
2433 Typing_log.out_channel := oc;
2434 Provider_utils.respect_but_quarantine_unsaved_changes
2435 ~ctx
2436 ~f:(fun () ->
2437 let files_contents = Multifile.file_to_files filename in
2438 Relative_path.Map.iter files_contents ~f:(fun filename contents ->
2439 File_provider.(provide_file_for_tests filename contents));
2440 let (parse_errors, individual_file_info) =
2441 parse_name_and_decl ctx files_contents
2443 let errors =
2444 check_file
2446 (Errors.get_sorted_error_list parse_errors)
2447 individual_file_info
2448 ~profile_type_check_multi
2449 ~memtrace
2451 write_error_list error_format errors oc max_errors)
2453 | Decl_compare when batch_mode ->
2454 (* For each file in our batch, run typechecking serially.
2455 Reset the heaps every time in between. *)
2456 iter_over_files (fun filename ->
2457 let oc =
2458 Out_channel.create (Relative_path.to_absolute filename ^ ".decl_out")
2460 Provider_utils.respect_but_quarantine_unsaved_changes ~ctx ~f:(fun () ->
2461 let files_contents =
2462 Relative_path.Map.filter files_contents ~f:(fun k _v ->
2463 Relative_path.equal k filename)
2465 let (_, individual_file_info) =
2466 parse_name_and_decl ctx files_contents
2469 test_decl_compare
2471 filename
2472 builtins
2473 files_contents
2474 individual_file_info;
2475 Out_channel.output_string oc ""
2476 with
2477 | e ->
2478 let msg = Exn.to_string e in
2479 Out_channel.output_string oc msg);
2480 Out_channel.close oc)
2481 | Errors ->
2482 (* Don't typecheck builtins *)
2483 let errors =
2484 check_file ctx parse_errors files_info ~profile_type_check_multi ~memtrace
2486 print_error_list error_format errors max_errors;
2487 if not (List.is_empty errors) then exit 2
2488 | Type ->
2489 let path_stream =
2490 match filenames with
2491 | [] ->
2492 Stream.from (fun _ ->
2494 Some
2495 (let path = Caml.input_line Caml.stdin |> String.strip in
2496 Relative_path.(create Dummy path))
2497 with
2498 | End_of_file -> None)
2499 | filenames -> Stream.of_list filenames
2501 let process path =
2502 let (errors, tasts, _gi_solved) =
2503 compute_tasts_expand_types ctx ~verbosity files_info files_contents
2505 let errors = Errors.get_error_list errors in
2506 if (not (List.is_empty parse_errors)) || not (List.is_empty errors) then begin
2507 List.iter ~f:(print_error error_format) (parse_errors @ errors);
2508 exit 2
2509 end else
2510 let tast = Relative_path.Map.find tasts path in
2511 Typing_preorder_ser.encode_tys_as_stdout_lines tast
2513 Stream.iter process path_stream
2514 | Decl_compare ->
2515 let filename = expect_single_file () in
2516 (* Might raise because of Option.value_exn *)
2517 test_decl_compare ctx filename builtins files_contents files_info
2518 | Shallow_class_diff ->
2519 print_errors_if_present parse_errors;
2520 let filename = expect_single_file () in
2521 test_shallow_class_diff ctx filename
2522 | Get_member class_and_member_id ->
2523 let (cid, mid) =
2524 match Str.split (Str.regexp "::") class_and_member_id with
2525 | [cid; mid] -> (cid, mid)
2526 | _ ->
2527 failwith
2528 (Printf.sprintf "Invalid --get-member ID: %S" class_and_member_id)
2530 let cid = Utils.add_ns cid in
2531 (match Decl_provider.get_class ctx cid with
2532 | None -> Printf.printf "No class named %s\n" cid
2533 | Some cls ->
2534 let ty_to_string ty =
2535 let env =
2536 Typing_env_types.empty ctx Relative_path.default ~droot:None
2538 Typing_print.full_strip_ns_decl env ty
2540 let print_class_element member_type get mid =
2541 match get cls mid with
2542 | None -> ()
2543 | Some ce ->
2544 let abstract =
2545 if Typing_defs.get_ce_abstract ce then
2546 "abstract "
2547 else
2550 let origin = ce.Typing_defs.ce_origin in
2551 let from =
2552 if String.equal origin cid then
2554 else
2555 Printf.sprintf " from %s" (Utils.strip_ns origin)
2557 Printf.printf
2558 " %s%s%s: %s\n"
2559 abstract
2560 member_type
2561 from
2562 (ty_to_string (Lazy.force ce.Typing_defs.ce_type))
2564 Printf.printf "%s::%s\n" cid mid;
2565 print_class_element "method" Cls.get_method mid;
2566 print_class_element "static method" Cls.get_smethod mid;
2567 print_class_element "property" Cls.get_prop mid;
2568 print_class_element "static property" Cls.get_sprop mid;
2569 print_class_element "static property" Cls.get_sprop ("$" ^ mid);
2570 (match Cls.get_const cls mid with
2571 | None -> ()
2572 | Some cc ->
2573 let abstract =
2574 Typing_defs.(
2575 match cc.cc_abstract with
2576 | CCAbstract _ -> "abstract "
2577 | CCConcrete -> "")
2579 let origin = cc.Typing_defs.cc_origin in
2580 let from =
2581 if String.equal origin cid then
2583 else
2584 Printf.sprintf " from %s" (Utils.strip_ns origin)
2586 let ty = ty_to_string cc.Typing_defs.cc_type in
2587 Printf.printf " %sconst%s: %s\n" abstract from ty);
2588 (match Cls.get_typeconst cls mid with
2589 | None -> ()
2590 | Some ttc ->
2591 let origin = ttc.Typing_defs.ttc_origin in
2592 let from =
2593 if String.equal origin cid then
2595 else
2596 Printf.sprintf " from %s" (Utils.strip_ns origin)
2598 let ty =
2599 let open Typing_defs in
2600 match ttc.ttc_kind with
2601 | TCConcrete { tc_type = ty } -> "= " ^ ty_to_string ty
2602 | TCAbstract
2604 atc_as_constraint = as_cstr;
2605 atc_super_constraint = _;
2606 atc_default = default;
2607 } ->
2608 String.concat
2609 ~sep:" "
2610 (List.filter_map
2612 Option.map as_cstr ~f:(fun ty -> "as " ^ ty_to_string ty);
2613 Option.map default ~f:(fun ty -> "= " ^ ty_to_string ty);
2615 ~f:(fun x -> x))
2617 let abstract =
2618 Typing_defs.(
2619 match ttc.ttc_kind with
2620 | TCConcrete _ -> ""
2621 | TCAbstract _ -> "abstract ")
2623 Printf.printf " %stypeconst%s: %s %s\n" abstract from mid ty);
2625 | Hover pos_given ->
2626 let filename = expect_single_file () in
2627 let (ctx, entry) =
2628 Provider_context.add_entry_if_missing ~ctx ~path:filename
2630 let (line, column) =
2631 match pos_given with
2632 | Some (line, column) -> (line, column)
2633 | None ->
2634 let src = Provider_context.read_file_contents_exn entry in
2635 hover_at_caret_pos src
2637 let results = ServerHover.go_quarantined ~ctx ~entry ~line ~column in
2638 let formatted_results =
2639 List.map
2640 ~f:(fun r ->
2641 let open HoverService in
2642 String.concat ~sep:"\n" (r.snippet :: r.addendum))
2643 results
2645 Printf.printf
2646 "%s\n"
2647 (String.concat ~sep:"\n-------------\n" formatted_results)
2648 | Apply_quickfixes ->
2649 let path = expect_single_file () in
2650 let (ctx, entry) = Provider_context.add_entry_if_missing ~ctx ~path in
2651 let (errors, _) =
2652 compute_tasts ~drop_fixmed:false ctx files_info files_contents
2654 let src = Relative_path.Map.find files_contents path in
2656 let quickfixes =
2657 Errors.get_error_list ~drop_fixmed:false errors
2658 |> List.map ~f:(fun e ->
2659 (* If an error has multiple possible quickfixes, take the first. *)
2660 List.hd (User_error.quickfixes e))
2661 |> List.filter_opt
2664 let cst = Ast_provider.compute_cst ~ctx ~entry in
2665 let tree = Provider_context.PositionedSyntaxTree.root cst in
2667 let classish_starts =
2668 match entry.Provider_context.source_text with
2669 | Some source_text ->
2670 Quickfix_ffp.classish_starts
2671 tree
2672 source_text
2673 entry.Provider_context.path
2674 | None -> SMap.empty
2677 (* Print the title of each quickfix, so we can see text changes in tests. *)
2678 List.iter quickfixes ~f:(fun qf ->
2679 Printf.printf "%s\n" (Quickfix.get_title qf));
2681 (* Print the source code after applying all these quickfixes. *)
2682 Printf.printf "\n%s" (Quickfix.apply_all src classish_starts quickfixes)
2683 | CountImpreciseTypes ->
2684 let (errors, tasts, _gi_solved) =
2685 compute_tasts_expand_types ctx ~verbosity files_info files_contents
2687 if not @@ Errors.is_empty errors then (
2688 print_errors error_format errors max_errors;
2689 Printf.printf
2690 "Did not count imprecise types because there are typing errors.";
2691 exit 2
2692 ) else
2693 let tasts = Relative_path.Map.values tasts in
2694 let results =
2695 List.map ~f:(Count_imprecise_types.count ctx) tasts
2696 |> List.fold
2697 ~f:(SMap.union ~combine:(fun id _ -> failwith ("Clash at " ^ id)))
2698 ~init:SMap.empty
2700 let json = Count_imprecise_types.json_of_results results in
2701 Printf.printf "%s" (Hh_json.json_to_string json)
2703 (*****************************************************************************)
2704 (* Main entry point *)
2705 (*****************************************************************************)
2707 let decl_and_run_mode
2709 files;
2710 extra_builtins;
2711 mode;
2712 error_format;
2713 no_builtins;
2714 tcopt;
2715 max_errors;
2716 batch_mode;
2717 out_extension;
2718 verbosity;
2719 should_print_position;
2720 custom_hhi_path;
2721 profile_type_check_multi;
2722 memtrace;
2723 pessimise_builtins;
2724 rust_provider_backend;
2726 (popt : TypecheckerOptions.t)
2727 (hhi_root : Path.t)
2728 (naming_table_path : string option) : unit =
2729 Ident.track_names := true;
2730 let builtins =
2731 if no_builtins then
2732 Relative_path.Map.empty
2733 else
2734 let extra_builtins =
2735 let add_file_content map filename =
2736 Relative_path.create Relative_path.Dummy filename
2737 |> Multifile.file_to_file_list
2738 |> List.map ~f:(fun (path, contents) ->
2739 (Filename.basename (Relative_path.suffix path), contents))
2740 |> List.unordered_append map
2742 extra_builtins
2743 |> List.fold ~f:add_file_content ~init:[]
2744 |> Array.of_list
2746 let magic_builtins =
2747 if pessimise_builtins then
2748 pessimised_magic_builtins
2749 else
2750 magic_builtins
2752 let magic_builtins = Array.append magic_builtins extra_builtins in
2753 let hhi_builtins =
2754 match custom_hhi_path with
2755 | None -> hhi_builtins
2756 | Some path -> Array.of_list (Hhi_get.get_hhis_in_dir path)
2758 (* Check that magic_builtin filenames are unique *)
2759 let () =
2760 let n_of_builtins = Array.length magic_builtins in
2761 let n_of_unique_builtins =
2762 Array.to_list magic_builtins
2763 |> List.map ~f:fst
2764 |> SSet.of_list
2765 |> SSet.cardinal
2767 if n_of_builtins <> n_of_unique_builtins then
2768 die "Multiple magic builtins share the same base name.\n"
2770 Array.iter magic_builtins ~f:(fun (file_name, file_contents) ->
2771 let file_path = Path.concat hhi_root file_name in
2772 let file = Path.to_string file_path in
2773 Sys_utils.try_touch
2774 (Sys_utils.Touch_existing { follow_symlinks = true })
2775 file;
2776 Sys_utils.write_file ~file file_contents);
2778 (* Take the builtins (file, contents) array and create relative paths *)
2779 Array.fold
2780 (Array.append magic_builtins hhi_builtins)
2781 ~init:Relative_path.Map.empty
2782 ~f:(fun acc (f, src) ->
2783 let f = Path.concat hhi_root f |> Path.to_string in
2784 Relative_path.Map.add
2786 ~key:(Relative_path.create Relative_path.Hhi f)
2787 ~data:src)
2789 let files =
2790 if use_canonical_filenames () then
2791 files
2792 |> List.map ~f:Sys_utils.realpath
2793 |> List.map ~f:(fun s -> Option.value_exn s)
2794 |> List.map ~f:Relative_path.create_detect_prefix
2795 else
2796 files |> List.map ~f:(Relative_path.create Relative_path.Dummy)
2798 let files_contents =
2799 List.fold
2800 files
2801 ~f:(fun acc filename ->
2802 let files_contents = Multifile.file_to_files filename in
2803 Relative_path.Map.union acc files_contents)
2804 ~init:Relative_path.Map.empty
2806 (* Merge in builtins *)
2807 let files_contents_with_builtins =
2808 Relative_path.Map.fold
2809 builtins
2811 begin
2812 (fun k src acc -> Relative_path.Map.add acc ~key:k ~data:src)
2814 ~init:files_contents
2816 Relative_path.Map.iter files_contents ~f:(fun filename contents ->
2817 File_provider.(provide_file_for_tests filename contents));
2818 (* Don't declare all the filenames in batch_errors mode *)
2819 let to_decl =
2820 if batch_mode then
2821 builtins
2822 else
2823 files_contents_with_builtins
2825 let dbg_deps = Hashtbl.Poly.create () in
2826 (match mode with
2827 | Dump_deps ->
2828 (* In addition to actually recording the dependencies in shared memory,
2829 we build a non-hashed respresentation of the dependency graph
2830 for printing. *)
2831 let get_debug_trace root obj =
2832 let root = Typing_deps.Dep.variant_to_string root in
2833 let obj = Typing_deps.Dep.variant_to_string obj in
2834 match Hashtbl.find dbg_deps obj with
2835 | Some set -> HashSet.add set root
2836 | None ->
2837 let set = HashSet.create () in
2838 HashSet.add set root;
2839 Hashtbl.set dbg_deps ~key:obj ~data:set
2841 Typing_deps.add_dependency_callback ~name:"get_debug_trace" get_debug_trace
2842 | _ -> ());
2843 let dbg_glean_deps = HashSet.create () in
2844 (match mode with
2845 | Dump_glean_deps ->
2846 (* In addition to actually recording the dependencies in shared memory,
2847 we build a non-hashed respresentation of the dependency graph
2848 for printing. In the callback we receive this as dep_right uses dep_left. *)
2849 let get_debug_trace dep_right dep_left =
2850 HashSet.add dbg_glean_deps (dep_left, dep_right)
2852 Typing_deps.add_dependency_callback ~name:"get_debug_trace" get_debug_trace
2853 | _ -> ());
2854 let ctx =
2855 if rust_provider_backend then (
2856 Provider_backend.set_rust_backend popt;
2857 Provider_context.empty_for_tool
2858 ~popt
2859 ~tcopt
2860 ~backend:(Provider_backend.get ())
2861 ~deps_mode:(Typing_deps_mode.InMemoryMode None)
2862 ) else
2863 Provider_context.empty_for_test
2864 ~popt
2865 ~tcopt
2866 ~deps_mode:(Typing_deps_mode.InMemoryMode None)
2868 (* We make the following call for the side-effect of updating ctx's "naming-table fallback"
2869 so it will look in the sqlite database for names it doesn't know.
2870 This function returns the forward naming table. *)
2871 let naming_table_for_root : Naming_table.t option =
2872 Option.map naming_table_path ~f:(fun path ->
2873 Naming_table.load_from_sqlite ctx path)
2875 (* If run in naming-table mode, we first have to remove any old names from the files we're about to redeclare --
2876 otherwise when we declare them it'd count as a duplicate definition! *)
2877 Option.iter naming_table_for_root ~f:(fun naming_table_for_root ->
2878 Relative_path.Map.iter files_contents ~f:(fun file _content ->
2879 let file_info =
2880 Naming_table.get_file_info naming_table_for_root file
2882 Option.iter file_info ~f:(fun file_info ->
2883 let ids_to_strings ids =
2884 List.map ids ~f:(fun (_, name, _) -> name)
2886 Naming_global.remove_decls
2887 ~backend:(Provider_context.get_backend ctx)
2888 ~funs:(ids_to_strings file_info.FileInfo.funs)
2889 ~classes:(ids_to_strings file_info.FileInfo.classes)
2890 ~typedefs:(ids_to_strings file_info.FileInfo.typedefs)
2891 ~consts:(ids_to_strings file_info.FileInfo.consts)
2892 ~modules:(ids_to_strings file_info.FileInfo.modules))));
2894 let (errors, files_info) = parse_name_and_decl ctx to_decl in
2895 handle_mode
2896 mode
2897 files
2899 builtins
2900 files_contents
2901 files_info
2902 (Errors.get_sorted_error_list errors)
2903 max_errors
2904 error_format
2905 batch_mode
2906 out_extension
2907 dbg_deps
2908 dbg_glean_deps
2909 ~should_print_position
2910 ~profile_type_check_multi
2911 ~memtrace
2912 ~verbosity
2914 let main_hack
2915 ({ tcopt; _ } as opts)
2916 (root : Path.t)
2917 (naming_table : string option)
2918 (sharedmem_config : SharedMem.config) : unit =
2919 (* TODO: We should have a per file config *)
2920 Sys_utils.signal Sys.sigusr1 (Sys.Signal_handle Typing.debug_print_last_pos);
2921 EventLogger.init_fake ();
2922 Measure.push_global ();
2924 let (_handle : SharedMem.handle) =
2925 SharedMem.init ~num_workers:0 sharedmem_config
2927 let process custom hhi_root =
2928 if custom then
2929 let hhi_root_s = Path.to_string hhi_root in
2930 if Disk.file_exists hhi_root_s && Disk.is_directory hhi_root_s then
2931 Hhi.set_custom_hhi_root hhi_root
2932 else
2933 die ("Custom hhi directory " ^ hhi_root_s ^ " not found")
2934 else
2935 Hhi.set_hhi_root_for_unit_test hhi_root;
2936 Relative_path.set_path_prefix Relative_path.Root root;
2937 Relative_path.set_path_prefix Relative_path.Hhi hhi_root;
2938 Relative_path.set_path_prefix Relative_path.Tmp (Path.make "tmp");
2939 decl_and_run_mode opts tcopt hhi_root naming_table;
2940 TypingLogger.flush_buffers ()
2942 match opts.custom_hhi_path with
2943 | Some hhi_root -> process true (Path.make hhi_root)
2944 | None -> Tempfile.with_tempdir (fun hhi_root -> process false hhi_root)
2946 (* command line driver *)
2947 let () =
2948 if !Sys.interactive then
2950 else
2951 (* On windows, setting 'binary mode' avoids to output CRLF on
2952 stdout. The 'text mode' would not hurt the user in general, but
2953 it breaks the testsuite where the output is compared to the
2954 expected one (i.e. in given file without CRLF). *)
2955 Out_channel.set_binary_mode stdout true;
2956 let (options, root, naming_table, sharedmem_config) = parse_options () in
2957 Unix.handle_unix_error main_hack options root naming_table sharedmem_config