11(* * Reactive dead code solver.
22
33 Reactive pipeline: decls + live + annotations → dead_decls, live_decls, dead_modules,
4- dead_decls_by_file, issues_by_file, incorrect_dead_decls
4+ dead_decls_by_file, issues_by_file, incorrect_dead_decls, dead_module_issues
55
66 Current status:
77 - All collections are reactive (zero recomputation on cache hit for unchanged files)
1111 - value_refs_from_by_file = refs grouped by source file (reactive flatMap with merge)
1212 - issues_by_file = per-file issue generation (reactive flatMap)
1313 - incorrect_dead_decls = live decls with @dead annotation (reactive join)
14+ - dead_module_issues = dead_modules joined with modules_with_reported (reactive join)
1415 - is_pos_live uses reactive live collection (no resolvedDead mutation)
1516 - shouldReport callback replaces report field mutation (no mutation needed)
1617 - isInsideReportedValue is per-file only, so files are independent
1718 - hasRefBelow uses per-file refs: O(file_refs) per dead decl (was O(total_refs))
18- - Module issues generated in collect_issues from dead_modules + modules_with_reported_values
1919
2020 All issues now match between reactive and non-reactive modes (380 on deadcode test):
2121 - Dead code issues: 362 (Exception:2, Module:31, Type:87, Value:233, ValueWithSideEffects:8)
@@ -39,6 +39,8 @@ type t = {
3939 Second component: modules with at least one reported value (for module issue generation). *)
4040 incorrect_dead_decls : (Lexing .position , Decl .t ) Reactive .t ;
4141 (* * Live declarations with @dead annotation. Reactive join of live_decls + annotations. *)
42+ dead_module_issues : (Name .t , Issue .t ) Reactive .t ;
43+ (* * Dead module issues. Reactive join of dead_modules + modules_with_reported. *)
4244 config : DceConfig .t ;
4345}
4446
@@ -193,6 +195,35 @@ let create ~(decls : (Lexing.position, Decl.t) Reactive.t)
193195 ()
194196 in
195197
198+ (* Reactive modules_with_reported: modules that have at least one reported dead value *)
199+ let modules_with_reported =
200+ Reactive. flatMap issues_by_file
201+ ~f: (fun _file (_issues , modules_list ) ->
202+ List. map (fun m -> (m, () )) modules_list)
203+ ()
204+ in
205+
206+ (* Reactive dead module issues: dead_modules joined with modules_with_reported *)
207+ let dead_module_issues =
208+ Reactive. join dead_modules modules_with_reported
209+ ~key_of: (fun moduleName _loc -> moduleName)
210+ ~f: (fun moduleName loc has_reported_opt ->
211+ match has_reported_opt with
212+ | Some () ->
213+ let loc =
214+ if loc.Location. loc_ghost then
215+ let pos_fname = loc.loc_start.pos_fname in
216+ let pos =
217+ {Lexing. pos_fname; pos_lnum = 0 ; pos_bol = 0 ; pos_cnum = 0 }
218+ in
219+ {Location. loc_start = pos; loc_end = pos; loc_ghost = false }
220+ else loc
221+ in
222+ [(moduleName, AnalysisResult. make_dead_module_issue ~loc ~module Name)]
223+ | None -> [] )
224+ ()
225+ in
226+
196227 {
197228 decls;
198229 live;
@@ -204,6 +235,7 @@ let create ~(decls : (Lexing.position, Decl.t) Reactive.t)
204235 dead_decls_by_file;
205236 issues_by_file;
206237 incorrect_dead_decls;
238+ dead_module_issues;
207239 config;
208240 }
209241
@@ -260,44 +292,18 @@ let collect_issues ~(t : t) ~(config : DceConfig.t)
260292 (* Collect issues from reactive issues_by_file *)
261293 let num_files = ref 0 in
262294 let dead_issues = ref [] in
263- (* Track modules that have at least one reported value (for module issue generation) *)
264- let modules_with_reported_values : (Name.t, unit) Hashtbl.t =
265- Hashtbl. create 64
266- in
267295 Reactive. iter
268- (fun _file (file_issues , modules_list ) ->
296+ (fun _file (file_issues , _modules_list ) ->
269297 incr num_files;
270- dead_issues := file_issues @ ! dead_issues;
271- (* Collect modules that have reported values *)
272- List. iter
273- (fun moduleName ->
274- Hashtbl. replace modules_with_reported_values moduleName () )
275- modules_list)
298+ dead_issues := file_issues @ ! dead_issues)
276299 t.issues_by_file;
277300 let t2 = Unix. gettimeofday () in
278301
279- (* Generate module issues: only for modules that are dead AND have a reported value *)
302+ (* Collect module issues from reactive dead_module_issues *)
280303 let module_issues = ref [] in
281- let reported_modules : (Name.t, unit) Hashtbl.t = Hashtbl. create 64 in
282304 Reactive. iter
283- (fun moduleName loc ->
284- (* Only report if module has at least one reported dead value *)
285- if Hashtbl. mem modules_with_reported_values moduleName then
286- if not (Hashtbl. mem reported_modules moduleName) then (
287- Hashtbl. replace reported_modules moduleName () ;
288- let loc =
289- if loc.Location. loc_ghost then
290- let pos_fname = loc.loc_start.pos_fname in
291- let pos =
292- {Lexing. pos_fname; pos_lnum = 0 ; pos_bol = 0 ; pos_cnum = 0 }
293- in
294- {Location. loc_start = pos; loc_end = pos; loc_ghost = false }
295- else loc
296- in
297- module_issues :=
298- AnalysisResult. make_dead_module_issue ~loc ~module Name
299- :: ! module_issues))
300- t.dead_modules;
305+ (fun _moduleName issue -> module_issues := issue :: ! module_issues)
306+ t.dead_module_issues;
301307 let t3 = Unix. gettimeofday () in
302308
303309 if ! Cli. timing then
0 commit comments