Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 57 additions & 16 deletions analysis/reanalyze/DEADCODE_REFACTOR_PLAN.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,11 +80,11 @@ you can swap one file's data without affecting others.

**Impact**: Can't analyze a subset of files without reanalyzing everything. Can't clear state between test runs without module reloading.

### P3: Delayed/deferred processing queues
### P3: Cross-file processing queues
**Problem**: Several analyses use global queues that get "flushed" later:
- `DeadOptionalArgs.delayedItems` - deferred optional arg analysis
- `DeadException.delayedItems` - deferred exception checks
- `DeadType.TypeDependencies.delayedItems` - deferred type deps
- `DeadOptionalArgs.delayedItems` - cross-file optional arg analysis → DELETED (now `CrossFileItems`)
- `DeadException.delayedItems` - cross-file exception checks → DELETED (now `CrossFileItems`)
- `DeadType.TypeDependencies.delayedItems` - per-file type deps (already handled per-file)
- `ProcessDeadAnnotations.positionsAnnotated` - annotation tracking

**Additional problem**: `positionsAnnotated` mixes **input** (source annotations from AST) with **output** (positions the solver determines are dead). The solver mutates this during analysis, violating purity.
Expand Down Expand Up @@ -346,29 +346,39 @@ val is_annotated_* : t -> ... -> bool
**Pattern**: Same as Task 3/4.

**Changes**:
- [ ] Create `References` module with `builder` and `t` types
- [ ] `process_cmt_file` returns `References.builder` for both value and type refs
- [ ] `References.merge_all : builder list -> t`
- [ ] Delete global `ValueReferences.table` and `TypeReferences.table`
- [x] Create `References` module with `builder` and `t` types
- [x] Thread `~refs:References.builder` through `addValueReference`, `addTypeReference`
- [x] `process_cmt_file` returns `References.builder` in `file_data`
- [x] Merge refs into builder, process delayed items, then freeze
- [x] Solver uses `References.t` via `find_value_refs` and `find_type_refs`
- [x] Delete global `ValueReferences.table` and `TypeReferences.table`

**Status**: Complete ✅

**Test**: Process files in different orders - results should be identical.

**Estimated effort**: Medium (similar to Task 4)

### Task 6: Delayed items use map → list → merge pattern (P3)
### Task 6: Cross-file items use map → list → merge pattern (P3)

**Value**: No global queues. Delayed items are per-file immutable data.
**Value**: No global queues. Cross-file items are per-file immutable data.

**Pattern**: Same as Task 3/4/5.

**Changes**:
- [ ] Create `DelayedItems` module with `builder` and `t` types
- [ ] `process_cmt_file` returns `DelayedItems.builder`
- [ ] `DelayedItems.merge_all : builder list -> t`
- [ ] `forceDelayedItems` is pure function on `DelayedItems.t`
- [ ] Delete global `delayedItems` refs
- [x] Create `CrossFileItems` module with `builder` and `t` types
- [x] Thread `~cross_file:CrossFileItems.builder` through AST processing
- [x] `process_cmt_file` returns `CrossFileItems.builder` in `file_data`
- [x] `CrossFileItems.merge_all : builder list -> t`
- [x] `process_exception_refs` and `process_optional_args` are pure functions on merged `t`
- [x] Delete global `delayedItems` refs from `DeadException` and `DeadOptionalArgs`

**Status**: Complete ✅

**Key insight**: "Delayed" items are just per-file data collected during AST processing.
**Note**: `DeadType.TypeDependencies` was already per-file (processed within `process_cmt_file`),
so it didn't need to be included.

**Key insight**: Cross-file items are references that span file boundaries.
They should follow the same pattern as everything else.

**Test**: Process files in different orders - results should be identical.
Expand Down Expand Up @@ -496,6 +506,37 @@ This enables parallelization, caching, and incremental recomputation.

---

## Optional Future Tasks

### Optional Task: Make OptionalArgs tracking immutable

**Value**: Currently `CrossFileItems.process_optional_args` mutates `optionalArgs` inside declarations.
Making this immutable would complete the pure pipeline.

**Current state**:
- `OptionalArgs.t` inside `decl.declKind = Value {optionalArgs}` is mutable
- `OptionalArgs.call` and `OptionalArgs.combine` mutate the record
- This happens after merge but before solver

**Why it's acceptable now**:
- Mutation happens in a well-defined phase (after merge, before solver)
- Solver sees effectively immutable data
- Order independence is maintained (calls accumulate, order doesn't matter)

**Changes needed**:
- [ ] Make `OptionalArgs.t` an immutable data structure
- [ ] Collect call info during AST processing as `OptionalArgCalls.builder`
- [ ] Return calls from `process_cmt_file` in `file_data`
- [ ] Merge all calls after file processing
- [ ] Build final `OptionalArgs` state from merged calls (pure)
- [ ] Store immutable `OptionalArgs` in declarations

**Estimated effort**: Medium-High (touches core data structures)

**Priority**: Low (current design works, just not fully pure)

---

## Success Criteria

After all tasks:
Expand Down
92 changes: 92 additions & 0 deletions analysis/reanalyze/src/CrossFileItems.ml
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
(** Cross-file items collected during AST processing.

These are references that span file boundaries and need to be resolved
after all files are processed. *)

open Common

(** {2 Item types} *)

type exception_ref = {exception_path: Path.t; loc_from: Location.t}

type optional_arg_call = {
pos_to: Lexing.position;
arg_names: string list;
arg_names_maybe: string list;
}

type function_ref = {pos_from: Lexing.position; pos_to: Lexing.position}

(** {2 Types} *)

type t = {
exception_refs: exception_ref list;
optional_arg_calls: optional_arg_call list;
function_refs: function_ref list;
}

type builder = {
mutable exception_refs: exception_ref list;
mutable optional_arg_calls: optional_arg_call list;
mutable function_refs: function_ref list;
}

(** {2 Builder API} *)

let create_builder () : builder =
{exception_refs = []; optional_arg_calls = []; function_refs = []}

let add_exception_ref (b : builder) ~exception_path ~loc_from =
b.exception_refs <- {exception_path; loc_from} :: b.exception_refs

let add_optional_arg_call (b : builder) ~pos_to ~arg_names ~arg_names_maybe =
b.optional_arg_calls <-
{pos_to; arg_names; arg_names_maybe} :: b.optional_arg_calls

let add_function_reference (b : builder) ~pos_from ~pos_to =
b.function_refs <- {pos_from; pos_to} :: b.function_refs

(** {2 Merge API} *)

let merge_all (builders : builder list) : t =
let exception_refs =
builders |> List.concat_map (fun b -> b.exception_refs)
in
let optional_arg_calls =
builders |> List.concat_map (fun b -> b.optional_arg_calls)
in
let function_refs = builders |> List.concat_map (fun b -> b.function_refs) in
{exception_refs; optional_arg_calls; function_refs}

(** {2 Processing API} *)

let process_exception_refs (t : t) ~refs ~find_exception ~config =
t.exception_refs
|> List.iter (fun {exception_path; loc_from} ->
match find_exception exception_path with
| None -> ()
| Some loc_to ->
DeadCommon.addValueReference ~config ~refs ~binding:Location.none
~addFileReference:true ~locFrom:loc_from ~locTo:loc_to)

let process_optional_args (t : t) ~decls =
(* Process optional arg calls *)
t.optional_arg_calls
|> List.iter (fun {pos_to; arg_names; arg_names_maybe} ->
match Declarations.find_opt decls pos_to with
| Some {declKind = Value r} ->
r.optionalArgs
|> OptionalArgs.call ~argNames:arg_names
~argNamesMaybe:arg_names_maybe
| _ -> ());
(* Process function references *)
t.function_refs
|> List.iter (fun {pos_from; pos_to} ->
match
( Declarations.find_opt decls pos_from,
Declarations.find_opt decls pos_to )
with
| Some {declKind = Value rFrom}, Some {declKind = Value rTo}
when not (OptionalArgs.isEmpty rTo.optionalArgs) ->
OptionalArgs.combine rFrom.optionalArgs rTo.optionalArgs
| _ -> ())
52 changes: 52 additions & 0 deletions analysis/reanalyze/src/CrossFileItems.mli
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
(** Cross-file items collected during AST processing.

These are references that span file boundaries and need to be resolved
after all files are processed. Two types are provided:
- [builder] - mutable, for AST processing
- [t] - immutable, for processing after merge *)

(** {2 Types} *)

type t
(** Immutable cross-file items - for processing after merge *)

type builder
(** Mutable builder - for AST processing *)

(** {2 Builder API - for AST processing} *)

val create_builder : unit -> builder

val add_exception_ref :
builder -> exception_path:Common.Path.t -> loc_from:Location.t -> unit
(** Add a cross-file exception reference (defined in another file). *)

val add_optional_arg_call :
builder ->
pos_to:Lexing.position ->
arg_names:string list ->
arg_names_maybe:string list ->
unit
(** Add a cross-file optional argument call. *)

val add_function_reference :
builder -> pos_from:Lexing.position -> pos_to:Lexing.position -> unit
(** Add a cross-file function reference (for optional args combining). *)

(** {2 Merge API} *)

val merge_all : builder list -> t
(** Merge all builders into one immutable result. Order doesn't matter. *)

(** {2 Processing API - for after merge} *)

val process_exception_refs :
t ->
refs:References.builder ->
find_exception:(Common.Path.t -> Location.t option) ->
config:DceConfig.t ->
unit
(** Process cross-file exception references. *)

val process_optional_args : t -> decls:Declarations.t -> unit
(** Process cross-file optional argument calls and function references. *)
12 changes: 8 additions & 4 deletions analysis/reanalyze/src/DceFileProcessing.ml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ let processSignature ~config ~decls ~(file : file_context) ~doValues ~doTypes
type file_data = {
annotations: FileAnnotations.builder;
decls: Declarations.builder;
refs: References.builder;
cross_file: CrossFileItems.builder;
}

let process_cmt_file ~config ~(file : file_context) ~cmtFilePath
Expand All @@ -55,6 +57,8 @@ let process_cmt_file ~config ~(file : file_context) ~cmtFilePath
(* Mutable builders for AST processing *)
let annotations = FileAnnotations.create_builder () in
let decls = Declarations.create_builder () in
let refs = References.create_builder () in
let cross_file = CrossFileItems.create_builder () in
(match cmt_infos.cmt_annots with
| Interface signature ->
CollectAnnotations.signature ~state:annotations ~config signature;
Expand All @@ -69,11 +73,11 @@ let process_cmt_file ~config ~(file : file_context) ~cmtFilePath
processSignature ~config ~decls ~file ~doValues:true ~doTypes:false
structure.str_type;
let doExternals = false in
DeadValue.processStructure ~config ~decls ~file:dead_common_file
~doTypes:true ~doExternals
DeadValue.processStructure ~config ~decls ~refs ~cross_file
~file:dead_common_file ~doTypes:true ~doExternals
~cmt_value_dependencies:cmt_infos.cmt_value_dependencies structure
| _ -> ());
DeadType.TypeDependencies.forceDelayedItems ~config;
DeadType.TypeDependencies.forceDelayedItems ~config ~refs;
DeadType.TypeDependencies.clear ();
(* Return builders - caller will merge and freeze *)
{annotations; decls}
{annotations; decls; refs; cross_file}
4 changes: 3 additions & 1 deletion analysis/reanalyze/src/DceFileProcessing.mli
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,10 @@ type file_context = {
type file_data = {
annotations: FileAnnotations.builder;
decls: Declarations.builder;
refs: References.builder;
cross_file: CrossFileItems.builder;
}
(** Result of processing a cmt file - both annotations and declarations *)
(** Result of processing a cmt file - annotations, declarations, references, and delayed items *)

val process_cmt_file :
config:DceConfig.t ->
Expand Down
Loading