77 - dead_decls, live_decls = decls partitioned by liveness (reactive join)
88 - dead_modules = modules with dead decls but no live decls (reactive anti-join)
99 - dead_decls_by_file = dead decls grouped by file (reactive flatMap with merge)
10+ - value_refs_from_by_file = refs grouped by source file (reactive flatMap with merge)
1011 - issues_by_file = per-file issue generation (reactive flatMap)
1112 - is_pos_live uses reactive live collection (no resolvedDead mutation)
1213 - shouldReport callback replaces report field mutation (no mutation needed)
1314 - isInsideReportedValue is per-file only, so files are independent
15+ - hasRefBelow uses per-file refs: O(file_refs) per dead decl (was O(total_refs))
1416 - Module issues generated in collect_issues from dead_modules + modules_with_reported_values
1517
16- TODO for further optimization:
17- - hasRefBelow: uses O(total_refs) linear scan of refs_from per dead decl;
18- could use reactive refs_to index for O(1) lookup per decl
19-
2018 All issues now match between reactive and non-reactive modes (380 on deadcode test):
2119 - Dead code issues: 362 (Exception:2, Module:31, Type:87, Value:233, ValueWithSideEffects:8)
2220 - Incorrect @dead: 1
@@ -108,20 +106,26 @@ let create ~(decls : (Lexing.position, Decl.t) Reactive.t)
108106 ()
109107 in
110108
111- (* hasRefBelow callback - captured once, uses current state of value_refs_from *)
109+ (* Reactive per-file grouping of value refs (for hasRefBelow optimization) *)
112110 let transitive = config.DceConfig. run.transitive in
113- let hasRefBelow =
111+ let value_refs_from_by_file =
114112 match value_refs_from with
115- | None -> fun _ -> false
113+ | None -> None
116114 | Some refs_from ->
117- DeadCommon. make_hasRefBelow ~transitive ~iter_value_refs_from: (fun f ->
118- Reactive. iter f refs_from)
115+ Some (
116+ Reactive. flatMap refs_from
117+ ~f: (fun posFrom posToSet -> [(posFrom.Lexing. pos_fname, [(posFrom, posToSet)])])
118+ ~merge: (fun refs1 refs2 -> refs1 @ refs2)
119+ ()
120+ )
119121 in
120122
121123 (* Reactive per-file issues - recomputed when dead_decls_by_file changes.
122124 Returns (file, (value_issues, modules_with_reported_values)) where
123125 modules_with_reported_values are modules that have at least one reported dead value.
124- Module issues are generated separately in collect_issues using dead_modules. *)
126+ Module issues are generated separately in collect_issues using dead_modules.
127+
128+ hasRefBelow now uses per-file refs: O(file_refs) instead of O(total_refs). *)
125129 let issues_by_file =
126130 Reactive. flatMap dead_decls_by_file
127131 ~f: (fun file decls ->
@@ -140,6 +144,23 @@ let create ~(decls : (Lexing.position, Decl.t) Reactive.t)
140144 Hashtbl. replace modules_with_values moduleName () ;
141145 None (* Module issues generated separately *)
142146 in
147+ (* Per-file hasRefBelow: only scan refs from this file *)
148+ let hasRefBelow =
149+ if transitive then fun _ -> false
150+ else
151+ match value_refs_from_by_file with
152+ | None -> fun _ -> false
153+ | Some refs_by_file ->
154+ let file_refs = Reactive. get refs_by_file file in
155+ fun decl ->
156+ match file_refs with
157+ | None -> false
158+ | Some refs_list ->
159+ List. exists (fun (posFrom , posToSet ) ->
160+ PosSet. mem decl.Decl. pos posToSet &&
161+ DeadCommon. refIsBelow decl posFrom
162+ ) refs_list
163+ in
143164 (* Sort within file and generate issues *)
144165 let sorted = decls |> List. fast_sort Decl. compareForReporting in
145166 let reporting_ctx = DeadCommon.ReportingContext. create () in
0 commit comments