66 - dead_decls, live_decls, dead_modules are reactive (zero recomputation on cache hit)
77 - dead_modules = modules with dead decls but no live decls (reactive anti-join)
88 - is_pos_live uses reactive live collection (no resolvedDead mutation needed)
9- - collect_issues still iterates dead_decls + live_decls for annotations + sorting
10- - Uses DeadCommon.reportDeclaration for isInsideReportedValue and hasRefBelow
9+ - Per-file issue generation: group by file, sort within file, fresh ReportingContext per file
10+ - isInsideReportedValue is per-file only, so files are independent
1111
1212 TODO for fully reactive issues:
13- - isInsideReportedValue: needs reactive tracking of reported positions
14- (currently relies on sequential iteration order via ReportingContext )
13+ - Make dead_decls_by_file a reactive collection (per-file grouping)
14+ - Make per-file issues reactive (only recompute changed files )
1515 - hasRefBelow: uses O(total_refs) linear scan of refs_from per dead decl;
1616 could use reactive refs_to index for O(1) lookup per decl
1717 - report field: still mutated to suppress annotated decls; could check in reportDeclaration
18- - Sorting: O(n log n) for isInsideReportedValue ordering; fundamentally sequential
1918
2019 All issues now match between reactive and non-reactive modes (380 on deadcode test):
2120 - Dead code issues: 362 (Exception:2, Module:31, Type:87, Value:233, ValueWithSideEffects:8)
@@ -164,11 +163,18 @@ let collect_issues ~(t : t) ~(config : DceConfig.t)
164163 t.live_decls;
165164 let t2 = Unix. gettimeofday () in
166165
167- (* Sort dead declarations for isInsideReportedValue ordering *)
168- let sorted_dead = ! dead_list |> List. fast_sort Decl. compareForReporting in
166+ (* Group dead declarations by file *)
167+ let by_file : (string, Decl.t list) Hashtbl.t = Hashtbl. create 64 in
168+ List. iter
169+ (fun (decl : Decl.t ) ->
170+ let file = decl.pos.pos_fname in
171+ let existing = Hashtbl. find_opt by_file file |> Option. value ~default: [] in
172+ Hashtbl. replace by_file file (decl :: existing))
173+ ! dead_list;
169174 let t3 = Unix. gettimeofday () in
170175
171- (* Generate issues - use reactive dead_modules via callback *)
176+ (* Generate issues per-file with independent ReportingContext.
177+ isInsideReportedValue only checks within same file, so files are independent. *)
172178 let transitive = config.DceConfig. run.transitive in
173179 let hasRefBelow =
174180 match t.value_refs_from with
@@ -182,23 +188,33 @@ let collect_issues ~(t : t) ~(config : DceConfig.t)
182188 check_module_dead ~dead_modules: t.dead_modules ~reported_modules ~file Name
183189 moduleName
184190 in
185- let reporting_ctx = DeadCommon.ReportingContext. create () in
186191 let dead_issues =
187- sorted_dead
188- |> List. concat_map (fun decl ->
189- DeadCommon. reportDeclaration ~config ~has RefBelow ~check ModuleDead
190- reporting_ctx decl)
192+ Hashtbl. fold
193+ (fun _file decls acc ->
194+ (* Sort within file for isInsideReportedValue *)
195+ let sorted = decls |> List. fast_sort Decl. compareForReporting in
196+ (* Fresh ReportingContext per file *)
197+ let reporting_ctx = DeadCommon.ReportingContext. create () in
198+ let file_issues =
199+ sorted
200+ |> List. concat_map (fun decl ->
201+ DeadCommon. reportDeclaration ~config ~has RefBelow ~check ModuleDead
202+ reporting_ctx decl)
203+ in
204+ file_issues @ acc)
205+ by_file []
191206 in
192207 let t4 = Unix. gettimeofday () in
193208
194209 if ! Cli. timing then
195210 Printf. eprintf
196- " collect_issues: iter_dead=%.2fms iter_live=%.2fms sort =%.2fms \
197- report=%.2fms\n "
211+ " collect_issues: iter_dead=%.2fms iter_live=%.2fms group =%.2fms \
212+ report=%.2fms (%d files) \n "
198213 ((t1 -. t0) *. 1000.0 )
199214 ((t2 -. t1) *. 1000.0 )
200215 ((t3 -. t2) *. 1000.0 )
201- ((t4 -. t3) *. 1000.0 );
216+ ((t4 -. t3) *. 1000.0 )
217+ (Hashtbl. length by_file);
202218
203219 List. rev ! incorrect_dead_issues @ dead_issues
204220
0 commit comments