Add type annotations to `clientConnect.ml`
[hiphop-php.git] / hphp / hack / src / server / serverTypeCheck.ml
blob6968f1e20eee5e9eb198b1da4f85e21935fb8a5e
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 Core_kernel
11 open ServerCheckUtils
12 open SearchServiceRunner
13 open ServerEnv
14 open Reordered_argument_collections
15 open Utils
17 module SLC = ServerLocalConfig
19 type check_kind =
20 (* Lazy check is a check limited to the files open in IDE. It:
21 * - produces push diagnostics for those files
22 * - updates their parsing / naming / decl definitions on heap
23 * - updates their parsing level indexes, like HackSearchService or
24 * ServerEnv.files_info
25 * - invalidates their declaration dependencies, by removing them from the
26 * heap and depending on lazy declaration to redeclare them on
27 * as-needed basis later
28 * - stores the information about what it skipped doing to be finished later
29 * by Full_check
31 * It does not do the "full" expensive fanout:
32 * - does not re-declare dependencies ("phase 2 decl")
33 * - does not fan out to all typing dependencies
34 * - because of that, it does not update structures depending on global state,
35 * like global error list, dependency table or the lists of files that
36 * failed parsing / declaration / checking
38 * Any operation that need the global state to be up to date and cannot get
39 * the data that they need through lazy decl, need to be preceded by
40 * Full_check. *)
41 | Lazy_check
42 (* Full check brings the global state of the server to consistency by
43 * executing all the re-checks that lazy checks delayed. It processes the
44 * disk updates and typechecks the full fanout of accumulated changes. *)
45 | Full_check
47 type check_results = {
48 reparse_count: int;
49 total_rechecked_count: int;
52 (*****************************************************************************)
53 (* Debugging *)
54 (*****************************************************************************)
56 let print_defs prefix defs =
57 List.iter defs begin fun (_, fname) ->
58 Printf.printf " %s %s\n" prefix fname;
59 end
61 let print_fast_pos fast_pos =
62 SMap.iter fast_pos begin fun x (funs, classes) ->
63 Printf.printf "File: %s\n" x;
64 print_defs "Fun" funs;
65 print_defs "Class" classes;
66 end;
67 Printf.printf "\n";
68 Out_channel.flush stdout;
71 let print_fast fast =
72 SMap.iter fast begin fun x (funs, classes) ->
73 Printf.printf "File: %s\n" x;
74 SSet.iter funs (Printf.printf " Fun %s\n");
75 SSet.iter classes (Printf.printf " Class %s\n");
76 end;
77 Printf.printf "\n";
78 Out_channel.flush stdout;
81 let debug_print_path_set genv name set =
82 ServerDebug.log genv begin fun () ->
83 let open Hh_json in
84 let files = Relative_path.Set.fold set ~init:[] ~f:begin fun k acc ->
85 JSON_String (Relative_path.suffix k) :: acc
86 end in
87 JSON_Object [
88 "type", JSON_String "incremental_files";
89 "name", JSON_String name;
90 "files", JSON_Array files;
92 end
94 let debug_print_fast_keys genv name fast =
95 ServerDebug.log genv begin fun () ->
96 let open Hh_json in
97 let files = Relative_path.Map.fold fast ~init:[] ~f:begin fun k _v acc ->
98 JSON_String (Relative_path.suffix k) :: acc
99 end in
100 let decls = Relative_path.Map.fold fast ~init:[] ~f:begin fun _k v acc ->
101 let {FileInfo.n_funs; n_classes; n_types; n_consts} = v in
102 let prepend_json_strings decls acc =
103 SSet.fold decls ~init:acc ~f:(fun n acc -> JSON_String n :: acc) in
104 let acc = prepend_json_strings n_funs acc in
105 let acc = prepend_json_strings n_classes acc in
106 let acc = prepend_json_strings n_types acc in
107 let acc = prepend_json_strings n_consts acc in
109 end in
110 JSON_Object [
111 "type", JSON_String "incremental_files";
112 "name", JSON_String name;
113 "files", JSON_Array files;
114 "decls", JSON_Array decls;
118 (*****************************************************************************)
119 (* Given a set of Ast.id list produce a SSet.t (got rid of the positions) *)
120 (*****************************************************************************)
122 let set_of_idl l =
123 List.fold_left l ~f:(fun acc (_, x) -> SSet.add acc x) ~init:SSet.empty
125 (*****************************************************************************)
126 (* We want add all the declarations that were present in a file *before* the
127 * current modification. The scenario:
128 * File foo.php was defining the class A.
129 * The user gets rid of class A (in foo.php)
130 * In general, the type-checker determines what must be re-declared or
131 * re-typechecked, by comparing the old and the new type-definitions.
132 * That's why we are adding the 'old' definitions to the file.
133 * In this case, the redecl phase (typing/typing_redecl_service.ml) is going
134 * to compare the 'old' definition of A with the new one. It will realize that
135 * the new one is missing, and go ahead and retype everything that depends
136 * on A.
137 * Without a call to add_old_decls, the class A wouldn't appear anywhere,
138 * and we wouldn't realize that we have to re-check the types that depend
139 * on A.
141 (*****************************************************************************)
143 let add_old_decls old_files_info fast =
144 Relative_path.Map.fold fast ~f:begin fun filename info_names acc ->
145 match Relative_path.Map.get old_files_info filename with
146 | None -> acc
147 | Some old_info ->
148 let old_info_names = FileInfo.simplify old_info in
149 let info_names = FileInfo.merge_names old_info_names info_names in
150 Relative_path.Map.add acc ~key:filename ~data:info_names
151 end ~init:fast
153 (*****************************************************************************)
154 (* Removes the names that were defined in the files *)
155 (*****************************************************************************)
157 let remove_decls env fast_parsed =
158 Relative_path.Map.iter fast_parsed begin fun fn _ ->
159 match Relative_path.Map.get env.files_info fn with
160 | None -> ()
161 | Some {FileInfo.
162 funs = funl;
163 classes = classel;
164 typedefs = typel;
165 consts = constl;
166 file_mode = _;
167 comments = _;
168 hash = _;
169 } ->
170 let funs = set_of_idl funl in
171 let classes = set_of_idl classel in
172 let typedefs = set_of_idl typel in
173 let consts = set_of_idl constl in
174 NamingGlobal.remove_decls ~funs ~classes ~typedefs ~consts
177 (* If the only things that would change about file analysis are positions,
178 * we're not going to recheck it, and positions in its error list might
179 * become stale. Look if any of those positions refer to files that have
180 * actually changed and add them to files to recheck. *)
181 let get_files_with_stale_errors
182 (* Set of files that were reparsed (so their ASTs and positions
183 * in them could have changed. *)
184 ~reparsed
185 (* A subset of files which errors we want to update, or None if we want
186 * to update entire error list. *)
187 ~filter
188 (* Consider errors only coming from those phases *)
189 ~phases
190 (* Current global error list *)
191 ~errors =
192 let fold = match filter with
193 | None -> begin fun phase init f ->
194 (* Looking at global files *)
195 Errors.fold_errors errors ~phase ~init
196 ~f:(fun source error acc -> f source error acc)
198 | Some sources -> begin fun phase init f ->
199 (* Looking only at subset of error sources *)
200 Relative_path.Set.fold sources ~init ~f:begin fun source acc ->
201 Errors.fold_errors_in errors
202 ~source ~phase ~init:acc ~f:(fun error acc -> f source error acc)
206 List.fold phases ~init:Relative_path.Set.empty ~f:begin fun acc phase ->
207 fold phase acc begin fun source error acc ->
208 if List.exists (Errors.to_list error) ~f:begin fun e ->
209 Relative_path.Set.mem reparsed (fst e |> Pos.filename)
211 then Relative_path.Set.add acc source else acc
215 (*****************************************************************************)
216 (* Parses the set of modified files *)
217 (*****************************************************************************)
219 (* Even when we remove an IDE file that failed after parsing stage, it might
220 * appear again in later stages - we need to filter it every time we extend
221 * the set of files to process *)
222 let remove_failed_parsing fast ~stop_at_errors env failed_parsing =
223 if stop_at_errors then Relative_path.Map.filter fast
224 ~f:(fun k _ -> not @@ Relative_path.(Set.mem failed_parsing k &&
225 Set.mem env.editor_open_files k))
226 else fast
228 let parsing genv env to_check ~stop_at_errors =
230 let ide_files, disk_files =
231 Relative_path.Set.partition (Relative_path.Set.mem env.editor_open_files)
232 to_check in
234 File_heap.FileHeap.remove_batch disk_files;
235 Parser_heap.ParserHeap.remove_batch disk_files;
236 Fixmes.HH_FIXMES.remove_batch disk_files;
237 Fixmes.DECL_HH_FIXMES.remove_batch disk_files;
239 if stop_at_errors then begin
240 File_heap.FileHeap.LocalChanges.push_stack ();
241 Parser_heap.ParserHeap.LocalChanges.push_stack ();
242 Fixmes.HH_FIXMES.LocalChanges.push_stack ();
243 Fixmes.DECL_HH_FIXMES.LocalChanges.push_stack ();
245 end;
246 (* Do not remove ide files from file heap *)
247 Parser_heap.ParserHeap.remove_batch ide_files;
248 Fixmes.HH_FIXMES.remove_batch ide_files;
249 Fixmes.DECL_HH_FIXMES.remove_batch ide_files;
251 HackSearchService.MasterApi.clear_shared_memory to_check;
252 SharedMem.collect `gentle;
253 let get_next = MultiWorker.next
254 genv.workers (Relative_path.Set.elements disk_files) in
255 let (fast, errors, failed_parsing) as res =
256 Parsing_service.go genv.workers ide_files ~get_next env.popt ~trace:true in
258 SearchServiceRunner.update_fileinfo_map fast;
259 (* During integration tests, we want to pretend that search is run
260 synchronously *)
261 if SearchServiceRunner.should_run_completely genv
262 then SearchServiceRunner.run_completely genv;
264 if stop_at_errors then begin
265 (* Revert changes and ignore results for IDE files that failed parsing *)
266 let ide_failed_parsing =
267 Relative_path.Set.inter failed_parsing ide_files in
268 let fast =
269 remove_failed_parsing fast stop_at_errors env ide_failed_parsing in
270 let ide_success_parsing =
271 Relative_path.Set.diff ide_files ide_failed_parsing in
273 File_heap.FileHeap.LocalChanges.revert_batch failed_parsing;
274 Parser_heap.ParserHeap.LocalChanges.revert_batch ide_failed_parsing;
275 Fixmes.HH_FIXMES.LocalChanges.revert_batch ide_failed_parsing;
276 Fixmes.DECL_HH_FIXMES.LocalChanges.revert_batch ide_failed_parsing;
279 File_heap.FileHeap.LocalChanges.commit_batch ide_success_parsing;
280 Parser_heap.ParserHeap.LocalChanges.commit_batch ide_success_parsing;
281 Fixmes.HH_FIXMES.LocalChanges.commit_batch ide_success_parsing;
282 Fixmes.DECL_HH_FIXMES.LocalChanges.commit_batch ide_success_parsing;
283 Parser_heap.ParserHeap.LocalChanges.commit_batch disk_files;
284 Fixmes.HH_FIXMES.LocalChanges.commit_batch disk_files;
285 Fixmes.DECL_HH_FIXMES.LocalChanges.commit_batch disk_files;
287 File_heap.FileHeap.LocalChanges.pop_stack ();
288 Parser_heap.ParserHeap.LocalChanges.pop_stack ();
289 Fixmes.HH_FIXMES.LocalChanges.pop_stack ();
290 Fixmes.DECL_HH_FIXMES.LocalChanges.pop_stack ();
292 (fast, errors, failed_parsing)
293 end else res
295 (*****************************************************************************)
296 (* At any given point in time, we want to know what each file defines.
297 * The datastructure that maintains this information is called file_info.
298 * This code updates the file information.
300 (*****************************************************************************)
302 let update_file_info env fast_parsed =
303 Typing_deps.update_files fast_parsed;
304 let files_info = Relative_path.Map.union fast_parsed env.files_info in
305 files_info
307 (*****************************************************************************)
308 (* Defining the global naming environment.
309 * Defines an environment with the names of all the globals (classes/funs).
311 (*****************************************************************************)
313 let declare_names env fast_parsed =
314 (* We need to do naming phase for files that failed naming before, even
315 * if they were not re-parsed in this iteration, so we are extending
316 * fast_parsed with them. *)
317 let fast_parsed = Relative_path.Set.fold env.failed_naming
318 ~init:fast_parsed
319 ~f:begin fun k acc ->
320 match Relative_path.Map.get acc k with
321 | Some _ -> acc (* the file was re-parsed already *)
322 | None ->
323 (* The file was not re-parsed, so it's correct to look up its contents
324 * in (old) env. *)
325 match Relative_path.Map.get env.files_info k with
326 | None -> acc (* this should not happen - failed_naming should be
327 a subset of keys in files_info *)
328 | Some v -> Relative_path.Map.add acc k v
331 remove_decls env fast_parsed;
332 let errorl, failed_naming =
333 Relative_path.Map.fold fast_parsed ~f:begin fun k v (errorl, failed) ->
334 let errorl', failed'= NamingGlobal.ndecl_file env.tcopt k v in
335 let errorl = Errors.merge errorl' errorl in
336 let failed = Relative_path.Set.union failed' failed in
337 errorl, failed
338 end ~init:(Errors.empty, Relative_path.Set.empty) in
339 let fast = FileInfo.simplify_fast fast_parsed in
340 errorl, failed_naming, fast
342 let diff_set_and_map_keys set map =
343 Relative_path.Map.fold map
344 ~init:set
345 ~f:(fun k _ acc -> Relative_path.Set.remove acc k)
347 let union_set_and_map_keys set map =
348 Relative_path.Map.fold map
349 ~init:set
350 ~f:(fun k _ acc -> Relative_path.Set.add acc k)
352 let get_interrupt_config genv env =
353 MultiThreadedCall.{handlers = env.interrupt_handlers genv ; env;}
355 (*****************************************************************************)
356 (* Where the action is! *)
357 (*****************************************************************************)
359 module type CheckKindType = sig
360 (* Parsing treats files open in IDE and files coming from disk differently:
362 * - for IDE files, we need to look up their contents in the map in env,
363 * instead of reading from disk (duh)
364 * - we parse IDE files in master process (to avoid passing env to the
365 * workers)
366 * - to make the IDE more responsive, we try to shortcut the typechecking at
367 * the parsing level if there were parsing errors
369 val get_files_to_parse :
370 ServerEnv.env ->
371 Relative_path.Set.t * bool
372 (* files to parse, should we stop if there are parsing errors *)
374 val get_defs_to_redecl :
375 reparsed:Relative_path.Set.t ->
376 env:ServerEnv.env ->
377 Relative_path.Set.t
379 (* Returns a tuple: files to redecl now, files to redecl later *)
380 val get_defs_to_redecl_phase2 :
381 decl_defs:FileInfo.fast ->
382 files_info:FileInfo.t Relative_path.Map.t ->
383 to_redecl_phase2:Relative_path.Set.t ->
384 env:ServerEnv.env ->
385 FileInfo.fast * FileInfo.fast
387 val get_to_recheck2_approximation :
388 to_redecl_phase2_deps:Typing_deps.DepSet.t ->
389 env:ServerEnv.env ->
390 Relative_path.Set.t
392 (* Which files to typecheck, based on results of declaration phase *)
393 val get_defs_to_recheck :
394 reparsed:Relative_path.Set.t ->
395 phase_2_decl_defs:FileInfo.fast ->
396 files_info:FileInfo.t Relative_path.Map.t ->
397 to_recheck:Relative_path.Set.t ->
398 env:ServerEnv.env ->
399 FileInfo.fast * Relative_path.Set.t
402 (* Update the global state based on resuts of parsing, naming and decl *)
403 val get_env_after_decl :
404 old_env:ServerEnv.env ->
405 files_info:FileInfo.t Relative_path.Map.t ->
406 failed_naming:Relative_path.Set.t ->
407 ServerEnv.env
409 (* Update the global state based on resuts of typing *)
410 val get_env_after_typing :
411 old_env:ServerEnv.env ->
412 errorl:Errors.t ->
413 needs_phase2_redecl:Relative_path.Set.t ->
414 needs_recheck:Relative_path.Set.t ->
415 diag_subscribe:Diagnostic_subscription.t option ->
416 ServerEnv.env
418 val is_full : bool
421 module FullCheckKind : CheckKindType = struct
422 let get_files_to_parse env =
423 let files_to_parse = Relative_path.Set.(
424 env.ide_needs_parsing |> union
425 env.disk_needs_parsing
426 ) in
427 files_to_parse, false
429 let get_defs_to_redecl ~reparsed ~env =
430 (* Besides the files that actually changed, we want to also redeclare
431 * those that have decl errors referring to files that were
432 * reparsed, since positions in those errors can be now stale *)
433 get_files_with_stale_errors
434 ~reparsed
435 ~filter:None
436 ~phases:[Errors.Decl]
437 ~errors:env.errorl
439 let get_defs_to_redecl_phase2 ~decl_defs ~files_info ~to_redecl_phase2 ~env =
440 let fast = extend_fast decl_defs files_info to_redecl_phase2 in
441 (* Add decl fanout that was delayed by previous lazy checks to phase 2 *)
442 let fast = extend_fast fast files_info env.needs_phase2_redecl in
443 fast, Relative_path.Map.empty
445 let get_to_recheck2_approximation ~to_redecl_phase2_deps:_ ~env:_ =
446 (* Full check is computing to_recheck2 set accurately, so there is no need
447 * to approximate anything *)
448 Relative_path.Set.empty
450 let get_defs_to_recheck ~reparsed ~phase_2_decl_defs ~files_info ~to_recheck ~env =
451 (* Besides the files that actually changed, we want to also recheck
452 * those that have typing errors referring to files that were
453 * reparsed, since positions in those errors can be now stale. TODO: do we
454 * really also need to add decl errors? We always did, but I don't know why.
456 let stale_errors = get_files_with_stale_errors
457 ~reparsed
458 ~filter:None
459 ~phases:[Errors.Decl; Errors.Typing]
460 ~errors:env.errorl
462 let to_recheck = Relative_path.Set.union stale_errors to_recheck in
463 let to_recheck = Relative_path.Set.union env.needs_recheck to_recheck in
464 extend_fast phase_2_decl_defs files_info to_recheck, Relative_path.Set.empty
466 let get_env_after_decl
467 ~old_env
468 ~files_info
469 ~failed_naming =
470 { old_env with
471 files_info;
472 failed_naming;
473 ide_needs_parsing = Relative_path.Set.empty;
474 disk_needs_parsing = Relative_path.Set.empty;
477 let get_env_after_typing
478 ~old_env
479 ~errorl
480 ~needs_phase2_redecl:_
481 ~needs_recheck
482 ~diag_subscribe =
483 let full_check = if Relative_path.Set.is_empty needs_recheck then
484 Full_check_done else old_env.full_check in
485 let needs_full_init =
486 old_env.init_env.needs_full_init && full_check <> Full_check_done in
487 { old_env with
488 errorl;
489 needs_phase2_redecl = Relative_path.Set.empty;
490 needs_recheck;
491 full_check;
492 init_env = { old_env.init_env with needs_full_init };
493 diag_subscribe;
496 let is_full = true
499 module LazyCheckKind : CheckKindType = struct
500 let get_files_to_parse env =
501 env.ide_needs_parsing, true
503 let ide_error_sources env = match env.diag_subscribe with
504 | Some ds -> Diagnostic_subscription.error_sources ds
505 | None -> Relative_path.Set.empty
507 let is_ide_file env x =
508 Relative_path.Set.mem (ide_error_sources env) x ||
509 Relative_path.Set.mem (env.editor_open_files) x
511 let get_defs_to_redecl ~reparsed ~env =
512 (* Same as FullCheckKind.get_defs_to_redecl, but we limit returned set only
513 * to files that are relevant to IDE *)
514 get_files_with_stale_errors
515 ~reparsed
516 ~filter:(Some (ide_error_sources env))
517 ~phases:[Errors.Decl]
518 ~errors:env.errorl
520 let get_defs_to_redecl_phase2
521 ~decl_defs ~files_info ~to_redecl_phase2 ~env =
522 (* Do phase2 only for IDE files, delay the fanout until next full check *)
523 let to_redecl_phase2_now, to_redecl_phase2_later =
524 Relative_path.Set.partition (is_ide_file env) to_redecl_phase2
526 extend_fast decl_defs files_info to_redecl_phase2_now,
527 extend_fast decl_defs files_info to_redecl_phase2_later
530 let get_related_files dep =
531 Typing_deps.get_ideps_from_hash dep |> Typing_deps.get_files
533 let get_to_recheck2_approximation ~to_redecl_phase2_deps ~env =
534 (* We didn't do the full fan-out from to_redecl_phase2_deps, so the
535 * to_recheck2 set might not be complete. We would recompute it during next
536 * full check, but if it contains files open in editor, we would like to
537 * recheck them sooner than that. We approximate it by taking all the
538 * possible dependencies of dependencies and preemptively rechecking them
539 * if they are open in the editor *)
540 if Typing_deps.DepSet.cardinal to_redecl_phase2_deps > 1000 then
541 (* inspecting tons of dependencies would take more time that just
542 * rechecking all relevant files. *)
543 Relative_path.Set.union env.editor_open_files (ide_error_sources env)
544 else
545 Typing_deps.DepSet.fold to_redecl_phase2_deps
546 ~init:Relative_path.Set.empty
547 ~f:(fun x acc -> Relative_path.Set.union acc @@ get_related_files x)
548 |> Relative_path.Set.filter ~f:(is_ide_file env)
550 let get_defs_to_recheck ~reparsed ~phase_2_decl_defs ~files_info ~to_recheck ~env =
551 (* Same as FullCheckKind.get_defs_to_recheck, but we limit returned set only
552 * to files that are relevant to IDE *)
553 let stale_errors = get_files_with_stale_errors
554 ~reparsed
555 ~filter:(Some (ide_error_sources env))
556 ~phases:[Errors.Decl; Errors.Typing]
557 ~errors:env.errorl
559 let to_recheck = Relative_path.Set.union to_recheck stale_errors in
560 let to_recheck_now, to_recheck_later =
561 Relative_path.Set.partition (is_ide_file env) to_recheck in
562 extend_fast phase_2_decl_defs files_info to_recheck_now, to_recheck_later
564 let get_env_after_decl
565 ~old_env
566 ~files_info
567 ~failed_naming =
568 { old_env with
569 files_info;
570 failed_naming;
571 ide_needs_parsing = Relative_path.Set.empty;
574 let get_env_after_typing
575 ~old_env
576 ~errorl
577 ~needs_phase2_redecl
578 ~needs_recheck
579 ~diag_subscribe =
580 (* If it was started, it's still started, otherwise it needs starting *)
581 let full_check = match old_env.full_check with
582 | Full_check_started -> Full_check_started
583 | _ -> Full_check_needed
585 { old_env with
586 errorl;
587 ide_needs_parsing = Relative_path.Set.empty;
588 needs_phase2_redecl;
589 needs_recheck;
590 full_check;
591 diag_subscribe;
594 let is_full = false
597 module Make: functor(CheckKind:CheckKindType) -> sig
598 val type_check_core :
599 ServerEnv.genv ->
600 ServerEnv.env ->
601 ServerEnv.env * check_results
602 end = functor(CheckKind:CheckKindType) -> struct
604 let get_defs fast =
605 Relative_path.Map.fold fast ~f:begin fun _ names1 names2 ->
606 FileInfo.merge_names names1 names2
607 end ~init:FileInfo.empty_names
609 let get_oldified_defs env =
610 Relative_path.Set.fold env.needs_phase2_redecl ~f:begin fun path acc ->
611 match Relative_path.Map.get env.files_info path with
612 | None -> acc
613 | Some names -> FileInfo.(merge_names (simplify names) acc)
614 end ~init:FileInfo.empty_names
616 let clear_failed_parsing errors failed_parsing =
617 (* In most cases, set of files processed in a phase is a superset
618 * of files from previous phase - i.e if we run decl on file A, we'll also
619 * run its typing.
620 * In few cases we might choose not to run further stages for files that
621 * failed parsing (see ~stop_at_errors). We need to manually clear out
622 * error lists for those files. *)
623 Relative_path.Set.fold failed_parsing ~init:errors ~f:begin fun path acc ->
624 let path = Relative_path.Set.singleton path in
625 List.fold_left Errors.([Naming; Decl; Typing])
626 ~init:acc
627 ~f:begin fun acc phase ->
628 Errors.(incremental_update_set acc empty path phase)
632 let type_check_core genv env =
633 let env = if CheckKind.is_full
634 then { env with full_check = Full_check_started } else env in
635 let start_t = Unix.gettimeofday () in
636 let t = start_t in
637 (* Files in env.needs_decl contain declarations which were not finished.
638 * They were only oldified, but we didn't run phase2 redeclarations for them
639 * which would compute new versions, compare them with old ones and remove
640 * the old ones. We'll use oldified_defs sets to track what is in the old
641 * heap as we progress with redeclaration *)
642 let oldified_defs = get_oldified_defs env in
644 let files_to_parse, stop_at_errors =
645 CheckKind.get_files_to_parse env in
647 let reparse_count = Relative_path.Set.cardinal files_to_parse in
648 Hh_logger.log "Files to recompute: %d" reparse_count;
649 if reparse_count = 1 then
650 files_to_parse |>
651 Relative_path.Set.choose |>
652 Relative_path.to_absolute |>
653 Hh_logger.log "Filename: %s";
655 (* PARSING *)
657 debug_print_path_set genv "files_to_parse" files_to_parse;
658 (* log line basically says the same as previous, but easier to parse for
659 * client *)
660 let logstring = Printf.sprintf "Parsing %d files" reparse_count in
661 Hh_logger.log "Begin %s" logstring;
662 let fast_parsed, errorl, failed_parsing =
663 parsing genv env files_to_parse ~stop_at_errors in
665 let errors = env.errorl in
666 let errors =
667 Errors.(incremental_update_set errors errorl files_to_parse Parsing) in
668 let errors = clear_failed_parsing errors failed_parsing in
669 let hs = SharedMem.heap_size () in
670 Hh_logger.log "Heap size: %d" hs;
671 HackEventLogger.parsing_end t hs ~parsed_count:reparse_count;
672 let t = Hh_logger.log_duration logstring t in
674 (* UPDATE FILE INFO *)
675 let logstring = "Updating deps" in
676 Hh_logger.log "Begin %s" logstring;
677 let old_env = env in
678 let files_info = update_file_info env fast_parsed in
679 HackEventLogger.updating_deps_end t;
680 let t = Hh_logger.log_duration logstring t in
682 (* NAMING *)
683 let logstring = "Naming" in
684 Hh_logger.log "Begin %s" logstring;
685 let errorl', failed_naming, fast = declare_names env fast_parsed in
686 let errors = Errors.(incremental_update_map errors errorl' fast Naming) in
687 (* failed_naming can be a superset of keys in fast - see comment in
688 * NamingGlobal.ndecl_file *)
689 let fast = extend_fast fast files_info failed_naming in
691 (* COMPUTES WHAT MUST BE REDECLARED *)
692 let deptable_unlocked =
693 Typing_deps.allow_dependency_table_reads true in
694 let failed_decl = CheckKind.get_defs_to_redecl files_to_parse env in
695 let fast = extend_fast fast files_info failed_decl in
696 let fast = add_old_decls env.files_info fast in
697 let fast = remove_failed_parsing fast stop_at_errors env failed_parsing in
699 HackEventLogger.naming_end t;
700 let t = Hh_logger.log_duration logstring t in
702 let bucket_size = genv.local_config.SLC.type_decl_bucket_size in
703 debug_print_fast_keys genv "to_redecl_phase1" fast;
704 let defs_to_redecl = get_defs fast in
705 let _, changes, to_redecl_phase2_deps, to_recheck1 =
706 Decl_redecl_service.redo_type_decl
707 ~conservative_redecl:(not genv.local_config.ServerLocalConfig.disable_conservative_redecl)
708 ~bucket_size genv.workers env.tcopt oldified_defs fast in
710 (* Things that were redeclared are no longer in old heap, so we substract
711 * defs_ro_redecl from oldified_defs *)
712 let oldified_defs =
713 snd @@ Decl_utils.split_defs oldified_defs defs_to_redecl in
714 let to_redecl_phase2 = Typing_deps.get_files to_redecl_phase2_deps in
715 let to_recheck1 = Typing_deps.get_files to_recheck1 in
716 let hs = SharedMem.heap_size () in
717 Hh_logger.log "Heap size: %d" hs;
718 HackEventLogger.first_redecl_end t hs;
719 ServerRevisionTracker.decl_changed genv.ServerEnv.local_config
720 (Relative_path.Set.cardinal to_redecl_phase2);
721 let t = Hh_logger.log_duration "Determining changes" t in
723 (* DECLARING TYPES: Phase2 *)
724 let fast_redecl_phase2_now, lazy_decl_later =
725 CheckKind.get_defs_to_redecl_phase2 fast files_info to_redecl_phase2 env
728 let fast_redecl_phase2_now = remove_failed_parsing
729 fast_redecl_phase2_now stop_at_errors env failed_parsing in
730 let count = Relative_path.Map.cardinal fast_redecl_phase2_now in
731 let logstring = Printf.sprintf "Type-decl %d files" count in
732 Hh_logger.log "Begin %s" logstring;
733 Hh_logger.log "Invalidate declarations in %d files"
734 (Relative_path.Map.cardinal lazy_decl_later);
736 debug_print_fast_keys genv "to_redecl_phase2" fast_redecl_phase2_now;
737 debug_print_fast_keys genv "lazy_decl_later" lazy_decl_later;
739 let get_classes path =
740 match Relative_path.Map.get files_info path with
741 | None -> SSet.empty
742 | Some info -> SSet.of_list @@ List.map info.FileInfo.classes snd
744 let defs_to_oldify = get_defs lazy_decl_later in
745 Decl_redecl_service.oldify_type_decl ~bucket_size
746 genv.workers get_classes oldified_defs defs_to_oldify;
747 let oldified_defs = FileInfo.merge_names oldified_defs defs_to_oldify in
749 let errorl', _changes, _to_redecl2, to_recheck2 =
750 Decl_redecl_service.redo_type_decl
751 ~conservative_redecl:(not genv.local_config.ServerLocalConfig.disable_conservative_redecl)
752 ~bucket_size genv.workers
753 env.tcopt oldified_defs fast_redecl_phase2_now in
755 let errors = Errors.(incremental_update_map errors
756 errorl' fast_redecl_phase2_now Decl) in
758 let needs_phase2_redecl = diff_set_and_map_keys
759 (* Redaclaration delayed before and now. *)
760 (union_set_and_map_keys env.needs_phase2_redecl lazy_decl_later)
761 (* Redeclarations completed now. *)
762 fast_redecl_phase2_now
765 let to_recheck2 = Typing_deps.get_files to_recheck2 in
766 let to_recheck2 = Relative_path.Set.union to_recheck2
767 (CheckKind.get_to_recheck2_approximation to_redecl_phase2_deps env) in
768 (* We have changed declarations, which means that typed ASTs could have
769 * changed too. *)
770 Ide_tast_cache.invalidate ();
772 (* DECLARING TYPES: merging results of the 2 phases *)
773 let fast = Relative_path.Map.union fast fast_redecl_phase2_now in
774 let to_recheck = Relative_path.Set.union to_recheck1 to_recheck2 in
775 let to_recheck = Relative_path.Set.union to_recheck to_redecl_phase2 in
776 let hs = SharedMem.heap_size () in
777 Hh_logger.log "Heap size: %d" hs;
778 HackEventLogger.second_redecl_end t hs;
779 ServerRevisionTracker.typing_changed genv.local_config
780 (Relative_path.Set.cardinal to_recheck);
781 let t = Hh_logger.log_duration logstring t in
782 let env = CheckKind.get_env_after_decl
783 ~old_env:env ~files_info ~failed_naming in
784 Hh_logger.log "Begin evaluating prechecked changes";
785 let env = ServerPrecheckedFiles.update_after_local_changes genv env changes in
786 let t = Hh_logger.log_duration "Evaluating prechecked changes" t in
788 let _ : bool = Typing_deps.allow_dependency_table_reads
789 deptable_unlocked in
791 (* Checking this before starting typechecking because we want to attribtue
792 * big rechecks to rebases, even when restarting is disabled *)
793 if genv.local_config.ServerLocalConfig.hg_aware_recheck_restart_threshold = 0 then
794 ServerRevisionTracker.check_blocking ();
796 (* TYPE CHECKING *)
797 let fast, lazy_check_later = CheckKind.get_defs_to_recheck
798 files_to_parse fast files_info to_recheck env in
799 let fast = remove_failed_parsing fast stop_at_errors env failed_parsing in
800 let to_recheck_count = Relative_path.Map.cardinal fast in
801 let logstring = Printf.sprintf "Type-check %d files" in
802 Hh_logger.log "Begin %s" (logstring to_recheck_count);
803 ServerCheckpoint.process_updates fast;
804 debug_print_fast_keys genv "to_recheck" fast;
805 debug_print_path_set genv "lazy_check_later" lazy_check_later;
806 if Relative_path.(Map.mem fast default) then
807 Hh_logger.log "WARNING: recheking defintion in a dummy file";
808 let dynamic_view_files = if ServerDynamicView.dynamic_view_on ()
809 then env.editor_open_files
810 else Relative_path.Set.empty in
811 let interrupt = get_interrupt_config genv env in
812 let memory_cap = genv.local_config.ServerLocalConfig.max_typechecker_worker_memory_mb in
813 let errorl', env , cancelled = Typing_check_service.go_with_interrupt
814 genv.workers env.tcopt dynamic_view_files fast ~interrupt ~memory_cap in
815 (* Add new things that need to be rechecked *)
816 let needs_recheck =
817 Relative_path.Set.union env.needs_recheck lazy_check_later in
818 (* Remove things that were cancelled from things we started rechecking... *)
819 let fast, needs_recheck = List.fold cancelled ~init:(fast, needs_recheck)
820 ~f:begin fun (fast, needs_recheck) (path, _) ->
821 Relative_path.Map.remove fast path,
822 Relative_path.Set.add needs_recheck path
825 (* ... leaving only things that we actually checked, and which can be
826 * removed from needs_recheck *)
827 let needs_recheck = diff_set_and_map_keys needs_recheck fast in
829 let errors = Errors.(incremental_update_map errors errorl' fast Typing) in
831 let full_check_done =
832 CheckKind.is_full && Relative_path.Set.is_empty needs_recheck in
833 let diag_subscribe = Option.map old_env.diag_subscribe ~f:begin fun x ->
834 Diagnostic_subscription.update x
835 ~priority_files:env.editor_open_files
836 ~reparsed:files_to_parse
837 ~rechecked:fast
838 ~global_errors:errors
839 ~full_check_done
840 end in
842 let total_rechecked_count = Relative_path.Map.cardinal fast in
843 HackEventLogger.type_check_end to_recheck_count total_rechecked_count t;
844 let t = Hh_logger.log_duration (logstring total_rechecked_count) t in
846 Hh_logger.log "Total: %f\n%!" (t -. start_t);
848 SharedMem.hh_log_level() > 0 ||
849 GlobalOptions.tco_language_feature_logging env.tcopt
850 then begin
851 Measure.print_stats ();
852 Measure.print_distributions ();
853 (* Log lambda counts for full checks where we don't load from a saved state *)
855 (genv.ServerEnv.options |> ServerArgs.no_load) &&
856 full_check_done &&
857 reparse_count = 0 (* Ignore incremental updates *)
858 then begin
859 TypingLogger.log_lambda_counts ();
860 end;
861 end;
862 ServerDebug.info genv "incremental_done";
864 let new_env = CheckKind.get_env_after_typing
866 errors
867 needs_phase2_redecl
868 needs_recheck
869 diag_subscribe
871 let deptable_unlocked = Typing_deps.allow_dependency_table_reads true in
872 let new_env = ServerPrecheckedFiles.update_after_recheck genv new_env fast in
873 (* We might have completed a full check, which might mean that a rebase was
874 * successfully processed. *)
875 ServerRevisionTracker.check_non_blocking new_env;
876 let _ : bool = Typing_deps.allow_dependency_table_reads deptable_unlocked in
878 new_env, {reparse_count; total_rechecked_count;}
881 let check_kind_to_string = function
882 | Full_check -> "Full_check"
883 | Lazy_check -> "Lazy_check"
885 module FC = Make(FullCheckKind)
886 module LC = Make(LazyCheckKind)
888 let type_check_unsafe genv env kind =
889 (match kind with
890 | Lazy_check -> HackEventLogger.set_lazy_incremental ()
891 | Full_check -> ());
892 let check_kind = check_kind_to_string kind in
893 HackEventLogger.with_check_kind check_kind @@ begin fun () ->
894 Printf.eprintf "******************************************\n";
895 Hh_logger.log "Check kind: %s" check_kind;
896 match kind with
897 | Lazy_check ->
898 ServerBusyStatus.send env ServerCommandTypes.Doing_local_typecheck;
899 let res = LC.type_check_core genv env in
900 ServerBusyStatus.send env ServerCommandTypes.Done_local_typecheck;
902 | Full_check ->
903 ServerBusyStatus.send env
904 (ServerCommandTypes.Doing_global_typecheck env.can_interrupt);
905 let (env, _) as res = FC.type_check_core genv env in
906 if env.full_check = Full_check_done then begin
907 let total = Errors.count env.ServerEnv.errorl in
908 let is_truncated, shown = match env.ServerEnv.diag_subscribe with
909 | None -> false, 0
910 | Some ds -> Diagnostic_subscription.get_pushed_error_length ds in
911 let msg = ServerCommandTypes.Done_global_typecheck {is_truncated; shown; total} in
912 ServerBusyStatus.send env msg
913 end;
917 let type_check genv env kind =
918 ServerUtils.with_exit_on_exception @@ fun () ->
919 type_check_unsafe genv env kind
921 (*****************************************************************************)
922 (* Checks that the working directory is clean *)
923 (*****************************************************************************)
925 let check genv env =
926 if !debug then begin
927 Printf.printf "****************************************\n";
928 Printf.printf "Start Check\n";
929 Out_channel.flush stdout;
930 end;
931 type_check genv env