2 * Copyright (c) 2015, Facebook, Inc.
5 * This source code is licensed under the MIT license found in the
6 * LICENSE file in the "hack" directory of this source tree.
12 open SearchServiceRunner
14 open Reordered_argument_collections
17 module SLC
= ServerLocalConfig
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
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
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. *)
47 type check_results
= {
49 total_rechecked_count
: int;
52 (*****************************************************************************)
54 (*****************************************************************************)
56 let print_defs prefix defs
=
57 List.iter defs
begin fun (_
, fname
) ->
58 Printf.printf
" %s %s\n" prefix fname
;
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
;
68 Out_channel.flush stdout
;
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");
78 Out_channel.flush stdout
;
81 let debug_print_path_set genv name set
=
82 ServerDebug.log genv
begin fun () ->
84 let files = Relative_path.Set.fold set ~init
:[] ~f
:begin fun k acc
->
85 JSON_String
(Relative_path.suffix k
) :: acc
88 "type", JSON_String
"incremental_files";
89 "name", JSON_String name
;
90 "files", JSON_Array
files;
94 let debug_print_fast_keys genv name fast
=
95 ServerDebug.log genv
begin fun () ->
97 let files = Relative_path.Map.fold fast ~init
:[] ~f
:begin fun k _v acc
->
98 JSON_String
(Relative_path.suffix k
) :: acc
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
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 (*****************************************************************************)
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
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
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
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
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
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. *)
185 (* A subset of files which errors we want to update, or None if we want
186 * to update entire error list. *)
188 (* Consider errors only coming from those phases *)
190 (* Current global error list *)
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
))
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
)
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
();
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
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
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
)
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
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
319 ~f
:begin fun k
acc ->
320 match Relative_path.Map.get
acc k
with
321 | Some _
-> acc (* the file was re-parsed already *)
323 (* The file was not re-parsed, so it's correct to look up its contents
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
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
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
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
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
:
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
->
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
->
385 FileInfo.fast * FileInfo.fast
387 val get_to_recheck2_approximation
:
388 to_redecl_phase2_deps
:Typing_deps.DepSet.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
->
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
->
409 (* Update the global state based on resuts of typing *)
410 val get_env_after_typing
:
411 old_env
:ServerEnv.env
->
413 needs_phase2_redecl
:Relative_path.Set.t
->
414 needs_recheck
:Relative_path.Set.t
->
415 diag_subscribe
:Diagnostic_subscription.t
option ->
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
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
436 ~phases
:[Errors.Decl
]
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
459 ~phases
:[Errors.Decl
; Errors.Typing
]
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
473 ide_needs_parsing
= Relative_path.Set.empty
;
474 disk_needs_parsing
= Relative_path.Set.empty
;
477 let get_env_after_typing
480 ~needs_phase2_redecl
:_
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
489 needs_phase2_redecl
= Relative_path.Set.empty
;
492 init_env
= { old_env
.init_env
with needs_full_init };
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
516 ~filter
:(Some
(ide_error_sources env
))
517 ~phases
:[Errors.Decl
]
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
)
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
555 ~filter
:(Some
(ide_error_sources env
))
556 ~phases
:[Errors.Decl
; Errors.Typing
]
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
571 ide_needs_parsing
= Relative_path.Set.empty
;
574 let get_env_after_typing
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
587 ide_needs_parsing
= Relative_path.Set.empty
;
597 module Make
: functor(CheckKind
:CheckKindType
) -> sig
598 val type_check_core
:
601 ServerEnv.env
* check_results
602 end = functor(CheckKind
:CheckKindType
) -> struct
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
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
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
])
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
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
651 Relative_path.Set.choose
|>
652 Relative_path.to_absolute
|>
653 Hh_logger.log
"Filename: %s";
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
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
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;
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
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 *)
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
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
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
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
();
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 *)
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
838 ~global_errors
:errors
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
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
) &&
857 reparse_count = 0 (* Ignore incremental updates *)
859 TypingLogger.log_lambda_counts
();
862 ServerDebug.info genv
"incremental_done";
864 let new_env = CheckKind.get_env_after_typing
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
=
890 | Lazy_check
-> HackEventLogger.set_lazy_incremental
()
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;
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
;
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
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
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 (*****************************************************************************)
927 Printf.printf
"****************************************\n";
928 Printf.printf
"Start Check\n";
929 Out_channel.flush stdout
;