Skip to content

Commit 597f13f

Browse files
avikchaudhurifacebook-github-bot
authored andcommitted
propagating opts from merge phase to check phase of types-first
Summary: In types-first, the merge phase is repurposed to merge dependencies. Some of those dependencies discover that their signatures have not changed. However, this information was not previously propagated to the (new) check phase, which caused all files that were merged to be checked again. (To add insult to injury, we were actually also checking unchanged but previously checked files again, but count that in the clowniness bucket.) This diff fixes this situation. In particular, it collects information about which files had their signatures unchanged from Merge_stream, and uses this information to filter out dependents whose dependencies have not changed their signatures. This has to be done carefully so that errors for the skipped files are preserved. Reviewed By: nmote Differential Revision: D15197843 fbshipit-source-id: f979db1358f7ada2bba5cb65e27ab5daa37ef88f
1 parent 836a579 commit 597f13f

File tree

7 files changed

+117
-39
lines changed

7 files changed

+117
-39
lines changed

src/services/inference/merge_service.ml

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,14 @@ type 'a merge_job =
1818
File_key.t Nel.t ->
1919
'a merge_job_result
2020

21-
type 'a merge_results = 'a merge_job_results * int (* skipped count *)
21+
type sig_opts_data = {
22+
skipped_count: int;
23+
sig_new_or_changed: FilenameSet.t;
24+
}
25+
26+
type 'a merge_results =
27+
'a merge_job_results *
28+
sig_opts_data
2229

2330
type merge_strict_context_result = {
2431
cx: Context.t;
@@ -402,10 +409,11 @@ let merge_runner
402409
~next:(Merge_stream.next stream)
403410
in
404411
let total_files = Merge_stream.total_files stream in
405-
let skipped_files = Merge_stream.skipped_files stream in
406-
Hh_logger.info "Merge skipped %d of %d modules" skipped_files total_files;
412+
let skipped_count = Merge_stream.skipped_count stream in
413+
let sig_new_or_changed = Merge_stream.sig_new_or_changed master_mutator in
414+
Hh_logger.info "Merge skipped %d of %d modules" skipped_count total_files;
407415
let elapsed = Unix.gettimeofday () -. start_time in
408416
if Options.should_profile options then Hh_logger.info "merged (strict) in %f" elapsed;
409-
Lwt.return (ret, skipped_files)
417+
Lwt.return (ret, { skipped_count; sig_new_or_changed })
410418

411419
let merge_strict = merge_runner ~job:merge_strict_component

src/services/inference/merge_service.mli

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,14 @@ type 'a merge_job =
1616
File_key.t Nel.t ->
1717
'a merge_job_result
1818

19-
type 'a merge_results = 'a merge_job_results * int (* skipped count *)
19+
type sig_opts_data = {
20+
skipped_count: int;
21+
sig_new_or_changed: FilenameSet.t;
22+
}
23+
24+
type 'a merge_results =
25+
'a merge_job_results *
26+
sig_opts_data
2027

2128
type merge_strict_context_result = {
2229
cx: Context.t;

src/services/inference/merge_stream.ml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -232,5 +232,9 @@ let merge ~master_mutator ~reader stream =
232232
update_server_status stream;
233233
List.rev_append merged acc
234234

235+
(* NOTE: call these functions only at the end of merge, not during. *)
235236
let total_files stream = stream.total_files
236-
let skipped_files stream = stream.skipped_files
237+
let skipped_count stream = stream.skipped_files
238+
(* See explanation in Context_heaps for why calling this function at the end of merge returns files
239+
whose signatures are new or have changed. *)
240+
let sig_new_or_changed = Context_heaps.Merge_context_mutator.unrevived_files

src/services/inference/merge_stream.mli

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,4 +33,5 @@ val merge:
3333
'a merge_result -> 'a merge_result -> 'a merge_result
3434

3535
val total_files: 'a t -> int
36-
val skipped_files: 'a t -> int
36+
val skipped_count: 'a t -> int
37+
val sig_new_or_changed: Context_heaps.Merge_context_mutator.master_mutator -> FilenameSet.t

src/services/inference/types_js.ml

Lines changed: 77 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -323,7 +323,7 @@ let run_merge_service
323323
acc
324324
=
325325
with_timer_lwt ~options "Merge" profiling (fun () ->
326-
let%lwt merged, skipped_count = Merge_service.merge_strict
326+
let%lwt merged, { Merge_service.skipped_count; sig_new_or_changed } = Merge_service.merge_strict
327327
~master_mutator ~worker_mutator ~reader ~intermediate_result_callback ~options ~workers
328328
dependency_graph component_map recheck_map
329329
in
@@ -349,7 +349,7 @@ let run_merge_service
349349
update_errset errors file new_errors, warnings, suppressions, coverage
350350
) acc merged
351351
in
352-
Lwt.return (errs, warnings, suppressions, coverage, skipped_count)
352+
Lwt.return (errs, warnings, suppressions, coverage, skipped_count, sig_new_or_changed)
353353
)
354354

355355
(* This function does some last minute preparation and then calls into the merge service, which
@@ -362,7 +362,6 @@ let merge
362362
~profiling
363363
~workers
364364
~errors
365-
~unchanged_checked
366365
~to_merge
367366
~components
368367
~recheck_map
@@ -371,6 +370,7 @@ let merge
371370
~persistent_connections
372371
~prep_merge =
373372
let { ServerEnv.local_errors; merge_errors; warnings; suppressions; } = errors in
373+
let coverage = FilenameMap.empty in
374374

375375
let%lwt send_errors_over_connection =
376376
match persistent_connections with
@@ -451,15 +451,14 @@ let merge
451451
initially.
452452
*)
453453
Hh_logger.info "to_merge: %s" (CheckedSet.debug_counts_to_string to_merge);
454-
Hh_logger.info "unchanged_checked: %s" (CheckedSet.debug_counts_to_string unchanged_checked);
455454
Hh_logger.info "Calculating dependencies";
456455
MonitorRPC.status_update ~event:ServerStatus.Calculating_dependencies_progress;
457456
let files_to_merge = CheckedSet.all to_merge in
458457
let%lwt dependency_graph, component_map =
459458
calc_deps ~options ~profiling ~dependency_graph ~components files_to_merge in
460459

461460
Hh_logger.info "Merging";
462-
let%lwt merge_errors, warnings, suppressions, coverage, skipped_count =
461+
let%lwt merge_errors, warnings, suppressions, coverage, skipped_count, sig_new_or_changed =
463462
let intermediate_result_callback results =
464463
let errors = lazy (
465464
Core_list.map ~f:(fun (file, result) ->
@@ -486,7 +485,7 @@ let merge
486485

487486
let merge_start_time = Unix.gettimeofday () in
488487

489-
let%lwt merge_errors, warnings, suppressions, coverage, skipped_count =
488+
let%lwt result =
490489
run_merge_service
491490
~master_mutator
492491
~worker_mutator
@@ -498,7 +497,7 @@ let merge
498497
dependency_graph
499498
component_map
500499
recheck_map
501-
(merge_errors, warnings, suppressions, FilenameMap.empty)
500+
(merge_errors, warnings, suppressions, coverage)
502501
in
503502
let%lwt () =
504503
if Options.should_profile options
@@ -515,24 +514,44 @@ let merge
515514
in
516515

517516
Hh_logger.info "Done";
518-
Lwt.return (merge_errors, warnings, suppressions, coverage, skipped_count)
517+
Lwt.return result
519518
in
520519

521-
let checked = CheckedSet.union unchanged_checked to_merge in
522-
Hh_logger.info "Checked set: %s" (CheckedSet.debug_counts_to_string checked);
523-
524520
let errors = { ServerEnv.local_errors; merge_errors; warnings; suppressions } in
525521
let cycle_leaders = component_map
526522
|> Utils_js.FilenameMap.elements
527523
|> Core_list.map ~f:(fun (leader, members) -> (leader, Nel.length members))
528524
|> Core_list.filter ~f:(fun (_, member_count) -> member_count > 1) in
529-
Lwt.return (checked, cycle_leaders, errors, coverage, skipped_count)
525+
Lwt.return (cycle_leaders, errors, coverage, skipped_count, sig_new_or_changed)
530526

531-
let check_files ~options ~reader ~workers errors coverage checked_files =
532-
let files = FilenameSet.union (CheckedSet.focused checked_files) (CheckedSet.dependents checked_files) in
527+
let check_files
528+
~reader
529+
~options
530+
~workers
531+
~errors
532+
~updated_errors
533+
~coverage
534+
~merged_files
535+
~sig_new_or_changed
536+
~dependency_info =
533537
match options.Options.opt_arch with
534-
| Options.Classic -> Lwt.return (errors, coverage)
538+
| Options.Classic -> Lwt.return (updated_errors, coverage)
535539
| Options.TypesFirst ->
540+
Hh_logger.info "Check prep";
541+
Hh_logger.info "new or changed signatures: %d" (FilenameSet.cardinal sig_new_or_changed);
542+
let focused_to_check = CheckedSet.focused merged_files in
543+
let merged_dependents = CheckedSet.dependents merged_files in
544+
let skipped_count = ref 0 in
545+
let all_dependency_graph = Dependency_info.all_dependency_graph dependency_info in
546+
(* skip dependents whenever none of their dependencies have new or changed signatures *)
547+
let dependents_to_check =
548+
FilenameSet.filter (fun f ->
549+
(FilenameSet.exists (fun f' -> FilenameSet.mem f' sig_new_or_changed)
550+
@@ FilenameMap.find_unsafe f all_dependency_graph)
551+
|| (incr skipped_count; false)
552+
) @@ merged_dependents in
553+
Hh_logger.info "Check will skip %d files" !skipped_count;
554+
let files = FilenameSet.union focused_to_check dependents_to_check in
536555
let job = List.fold_left (fun (errors, warnings, suppressions, coverage) file ->
537556
let new_errors, new_warnings, new_suppressions, new_coverage =
538557
Merge_service.check_file options ~reader file in
@@ -547,27 +566,32 @@ let check_files ~options ~reader ~workers errors coverage checked_files =
547566
Error_suppressions.empty,
548567
FilenameMap.empty in
549568
let merge
550-
(errors, warnings, suppressions, coverage)
551-
(new_errors, new_warnings, new_suppressions, new_coverage) =
552-
FilenameMap.union errors new_errors,
553-
FilenameMap.union warnings new_warnings,
569+
(new_errors, new_warnings, new_suppressions, new_coverage)
570+
(errors, warnings, suppressions, coverage) =
571+
FilenameMap.union new_errors errors, (* WARNING! order matters (new wins over old) *)
572+
FilenameMap.union new_warnings warnings, (* WARNING! order matters (new wins over old) *)
554573
Error_suppressions.update_suppressions suppressions new_suppressions,
555-
FilenameMap.union coverage new_coverage
574+
FilenameMap.union new_coverage coverage (* WARNING! order matters (new wins over old) *)
556575
in
557576

558577
Hh_logger.info "Checking files";
559578
let progress_fn ~total ~start ~length:_ =
560579
MonitorRPC.status_update
561580
ServerStatus.(Checking_progress { total = Some total; finished = start })
562581
in
563-
let%lwt merge_errors, warnings, suppressions, coverage = MultiWorkerLwt.call
582+
let%lwt new_errors, new_warnings, new_suppressions, new_coverage = MultiWorkerLwt.call
564583
workers
565584
~job
566585
~neutral
567586
~merge
568587
~next:(MultiWorkerLwt.next ~progress_fn ~max_size:100 workers
569588
(FilenameSet.elements files))
570589
in
590+
let merge_errors, warnings, suppressions, coverage =
591+
merge
592+
(* NOTE: Order matters when there is overlap. We want new stuff to overwrite old stuff. *)
593+
(new_errors, new_warnings, new_suppressions, new_coverage)
594+
ServerEnv.(errors.merge_errors, errors.warnings, errors.suppressions, coverage) in
571595
Hh_logger.info "Done";
572596
let errors = { errors with
573597
ServerEnv.merge_errors;
@@ -626,12 +650,12 @@ let ensure_checked_dependencies ~options ~reader ~env file file_sig =
626650
else acc
627651
| None -> acc (* complain elsewhere about required module not found *)
628652
) resolved_requires CheckedSet.empty in
629-
let unchanged_checked = env.ServerEnv.checked_files in
653+
let checked = env.ServerEnv.checked_files in
630654

631655
(* Often, all dependencies have already been checked, so infer_input contains no unchecked files.
632656
* In that case, let's short-circuit typecheck, since a no-op typecheck still takes time on
633657
* large repos *)
634-
let unchecked_dependencies = CheckedSet.diff infer_input unchanged_checked in
658+
let unchecked_dependencies = CheckedSet.diff infer_input checked in
635659
if CheckedSet.is_empty unchecked_dependencies
636660
then Lwt.return_unit
637661
else begin
@@ -1466,14 +1490,13 @@ end = struct
14661490

14671491
let dependency_graph = Dependency_info.dependency_graph dependency_info in
14681492
(* recheck *)
1469-
let%lwt checked_files, cycle_leaders, errors, coverage, skipped_count = merge
1493+
let%lwt cycle_leaders, updated_errors, coverage, skipped_count, sig_new_or_changed = merge
14701494
~transaction
14711495
~reader
14721496
~options
14731497
~profiling
14741498
~workers
14751499
~errors
1476-
~unchanged_checked
14771500
~to_merge
14781501
~components
14791502
~recheck_map
@@ -1493,7 +1516,20 @@ end = struct
14931516
))
14941517
in
14951518

1496-
let%lwt errors, coverage = check_files ~options ~reader ~workers errors coverage checked_files in
1519+
let merged_files = to_merge in
1520+
let%lwt errors, coverage = check_files
1521+
~reader
1522+
~options
1523+
~workers
1524+
~errors
1525+
~updated_errors
1526+
~coverage
1527+
~merged_files
1528+
~sig_new_or_changed
1529+
~dependency_info in
1530+
1531+
let checked_files = CheckedSet.union unchanged_checked merged_files in
1532+
Hh_logger.info "Checked set: %s" (CheckedSet.debug_counts_to_string checked_files);
14971533

14981534
(* NOTE: unused fields are left in their initial empty state *)
14991535
env.ServerEnv.collated_errors := None;
@@ -2073,12 +2109,10 @@ let full_check ~profiling ~options ~workers ?focus_targets env =
20732109
let%lwt infer_input = files_to_infer
20742110
~options ~reader ?focus_targets ~profiling ~parsed ~dependency_info in
20752111

2076-
let unchanged_checked = CheckedSet.empty in
2077-
20782112
let%lwt to_merge, components, recheck_map =
20792113
include_dependencies_and_dependents
20802114
~options ~profiling
2081-
~unchanged_checked
2115+
~unchanged_checked:CheckedSet.empty
20822116
~infer_input
20832117
~dependency_info
20842118
~all_dependent_files:FilenameSet.empty
@@ -2090,21 +2124,32 @@ let full_check ~profiling ~options ~workers ?focus_targets env =
20902124
let%lwt () = ensure_parsed ~options ~profiling ~workers ~reader to_merge in
20912125

20922126
let dependency_graph = Dependency_info.dependency_graph dependency_info in
2093-
let%lwt (checked_files, _, errors, coverage, _) = merge
2127+
let%lwt _, updated_errors, coverage, _, sig_new_or_changed = merge
20942128
~transaction
20952129
~reader
20962130
~options
20972131
~profiling
20982132
~workers
20992133
~errors
2100-
~unchanged_checked
21012134
~to_merge
21022135
~components
21032136
~recheck_map
21042137
~dependency_graph
21052138
~deleted:FilenameSet.empty
21062139
~persistent_connections:None
21072140
~prep_merge:None in
2108-
let%lwt errors, coverage = check_files ~options ~reader ~workers errors coverage checked_files in
2141+
let merged_files = to_merge in
2142+
let%lwt errors, coverage = check_files
2143+
~reader
2144+
~options
2145+
~workers
2146+
~errors
2147+
~updated_errors
2148+
~coverage
2149+
~merged_files
2150+
~sig_new_or_changed
2151+
~dependency_info in
2152+
let checked_files = merged_files in
2153+
Hh_logger.info "Checked set: %s" (CheckedSet.debug_counts_to_string checked_files);
21092154
Lwt.return { env with ServerEnv.checked_files; errors; coverage }
21102155
)

src/state/heaps/context/context_heaps.ml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ module Merge_context_mutator: sig
6969
val add_merge_on_exn:
7070
(worker_mutator -> options:Options.t -> File_key.t Nel.t -> unit) Expensive.t
7171
val revive_files: master_mutator -> Utils_js.FilenameSet.t -> unit
72+
val unrevived_files: master_mutator -> Utils_js.FilenameSet.t
7273
end = struct
7374
type master_mutator = Utils_js.FilenameSet.t ref
7475
type worker_mutator = unit
@@ -149,6 +150,17 @@ end = struct
149150
assert (FilenameSet.is_empty (FilenameSet.diff files (!oldified_files)));
150151
oldified_files := FilenameSet.diff (!oldified_files) files;
151152
revive_merge_batch files
153+
154+
(* WARNING: Only call this function at the end of merge!!! Calling it during merge will return
155+
meaningless results.
156+
157+
Initially, `oldified_files` contains the set of files to be merged (see Merge_stream). During
158+
merge, we call `revive_files` for files whose signatures have not changed. So the remaining
159+
`oldified_files` at the end of merge must contain the set of files whose signatures are new or
160+
have changed. In principle, we could maintain this state separately in Merge_stream, but it
161+
seems wasteful to do so. *)
162+
let unrevived_files oldified_files =
163+
!oldified_files
152164
end
153165

154166
module type READER = sig

src/state/heaps/context/context_heaps.mli

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,4 +36,5 @@ module Merge_context_mutator: sig
3636
val add_merge_on_exn:
3737
(worker_mutator -> options:Options.t -> File_key.t Nel.t -> unit) Expensive.t
3838
val revive_files: master_mutator -> Utils_js.FilenameSet.t -> unit
39+
val unrevived_files: master_mutator -> Utils_js.FilenameSet.t
3940
end

0 commit comments

Comments
 (0)