From 4f2bf79622ed8fb8601be0335ab034ad9e865138 Mon Sep 17 00:00:00 2001 From: Corentin De Souza <9597216+fantazio@users.noreply.github.com> Date: Wed, 19 Aug 2026 11:28:52 +0200 Subject: [PATCH 1/5] [src][state][file_infos] unify `signature` and `cmt_struct` They are now merged into the new `annots` fields, which expliclty states whether the content of the compilation unit is a structure, a signature or both. The initial case is Neither (no compilation unit), as is the case when the content of a .cmt or .cmti file is not handled (e.g. a Partial_implementation) --- src/deadCode.ml | 31 +++++++++------- src/deadSign.ml | 6 +-- src/state/file_infos.ml | 80 ++++++++++++++++++++++++++-------------- src/state/file_infos.mli | 15 +++++--- 4 files changed, 82 insertions(+), 50 deletions(-) diff --git a/src/deadCode.ml b/src/deadCode.ml index c4a8dd4..71adeae 100644 --- a/src/deadCode.ml +++ b/src/deadCode.ml @@ -327,7 +327,8 @@ let regabs state = hashtbl_add_unique_to_list main_files (Utils.Filepath.unit fn) () -let read_interface fn (sign : State.File_infos.signature) state = +(* annots must not be Neither *) +let read_interface fn (annots : State.File_infos.annots) state = regabs state; if Config.must_report_main state.config then let comp_unit = @@ -341,11 +342,14 @@ let read_interface fn (sign : State.File_infos.signature) state = |> Ident.create_persistent in let path = [module_id] in - begin match sign with - | Structure structure -> - DeadSign.collect_export_from_structure ~path ~comp_unit structure - | Signature signature -> - DeadSign.collect_export_from_signature ~path ~comp_unit signature + begin match annots with + | Structure strc -> + DeadSign.collect_export_from_structure ~path ~comp_unit strc + | Signature sign | Both {sign; _} -> + DeadSign.collect_export_from_signature ~path ~comp_unit sign + | Neither -> + (* TODO: better error handling *) + assert false end; last_loc := Lexing.dummy_pos @@ -432,10 +436,9 @@ let load_file fn state = if state.State.config.verbose then Printf.eprintf "Scanning interface from %s\n%!" fn; init_and_continue state fn (fun state -> - match state.file_infos.signature with - | None -> report_error (fn ^ ": missing signature") - | Some sign -> - read_interface fn sign state + match state.file_infos.annots with + | Neither -> report_error (fn ^ ": missing signature") + | annots -> read_interface fn annots state ) in let process_implementation fn = @@ -443,16 +446,16 @@ let load_file fn state = if state.State.config.verbose then Printf.eprintf "Scanning implementation from %s\n%!" fn; init_and_continue state fn (fun state -> - match state.file_infos.cmt_struct with - | None -> report_error (fn ^ ": missing cmt_struct") - | Some structure -> + match state.file_infos.annots with + | Neither | Signature _ -> report_error (fn ^ ": missing structure") + | Structure strc | Both {strc; _} -> regabs state; let prepare (loc1, loc2) = DeadObj.add_equal loc1 loc2; VdNode.merge_locs ~force:true loc2 loc1 in List.iter prepare state.file_infos.location_dependencies; - collect_references.Tast_mapper.structure collect_references structure + collect_references.Tast_mapper.structure collect_references strc |> ignore; let loc_dep = if Config.must_report_section state.config.sections.exported_values then diff --git a/src/deadSign.ml b/src/deadSign.ml index 36fa4ec..bb8157c 100644 --- a/src/deadSign.ml +++ b/src/deadSign.ml @@ -258,8 +258,8 @@ let rec collect_export_from_include ~path sig_item = let correct_export sig_item = let state = State.get_current () in - match state.file_infos.signature with - | Some (Signature _) -> + match state.file_infos.annots with + | Structure _ -> correct_export sig_item + | _ -> (* Typedtree signatures found in .cmti files do not need correction *) () - | _ -> correct_export sig_item diff --git a/src/state/file_infos.ml b/src/state/file_infos.ml index 4ac88b6..f469fe6 100644 --- a/src/state/file_infos.ml +++ b/src/state/file_infos.ml @@ -1,12 +1,21 @@ -type signature = +type annots = | Structure of Typedtree.structure | Signature of Typedtree.signature + | Both of { sign: Typedtree.signature; strc: Typedtree.structure } + | Neither + +let merge_annots sign strc = + match sign, strc with + | Structure _, _ -> Result.error "expected a signature first" + | _, Signature _ -> Result.error "expected a structure second" + | Neither, annots | annots, Neither -> Result.ok annots + | (Signature sign | Both {sign; _}), (Structure strc | Both {strc; _}) -> + Result.ok (Both {sign; strc}) type t = { builddir : string; cm_file : string; - signature : signature option; - cmt_struct : Typedtree.structure option; + annots : annots; cmti_uid_to_decl : Location_dependencies.uid_to_decl option; location_dependencies : Location_dependencies.t; modname : string; @@ -16,8 +25,7 @@ type t = { let empty = { builddir = "!!UNKNOWN_BUILDDIR!!"; cm_file = ""; - signature = None; - cmt_struct = None; + annots = Neither; cmti_uid_to_decl = None; location_dependencies = Location_dependencies.empty; modname = "!!UNKNOWN_MODNAME!!"; @@ -27,7 +35,7 @@ let empty = { (** [init_from_all_cm_infos ~cm_file cmt_infos] creates a [t] with: - information from [cmt_infos] : [builddir], [modname], [sourcepath]; - [cm_file]; - - [signature] is extracted from [cmt_infos.cmt_annots] + - [annots] is extracted from [cmt_infos.cmt_annots] *) let init_from_all_cm_infos ~cm_file cmt_infos = let builddir = cmt_infos.Cmt_format.cmt_builddir in @@ -36,15 +44,15 @@ let init_from_all_cm_infos ~cm_file cmt_infos = |> Option.map (Filename.concat builddir) in let modname = cmt_infos.cmt_modname in - let signature = + let annots = match cmt_infos.cmt_annots with - | Interface sign -> Some (Signature sign) - | Implementation str -> Some (Structure str) - | _ -> None + | Interface sign -> Signature sign + | Implementation strc -> Structure strc + | _ -> Neither in {empty with builddir; cm_file; - signature; + annots; modname; sourcepath} @@ -69,32 +77,43 @@ let ( let* ) x f = Result.bind x f let ( let+ ) x f = Result.map f x let init_from_cmti_file cmti_file = - let+ file_infos, cmt_infos = init_from_cm_file cmti_file in + let* file_infos, cmt_infos = init_from_cm_file cmti_file in let cmti_uid_to_decl = Some cmt_infos.cmt_uid_to_decl in - {file_infos with cmti_uid_to_decl} + match file_infos.annots with + | Signature _ | Both _ -> + let file_infos = {file_infos with cmti_uid_to_decl} in + Result.ok file_infos + | _ -> Result.error (cmti_file ^ ": does not contain an interface") let init_from_cmt_file ~comp_unit_to_path cmt_file = let* file_infos, cmt_infos = init_from_cm_file cmt_file in - let* cmt_struct = - match cmt_infos.cmt_annots with - | Implementation structure -> Result.ok structure + let* annots = + match file_infos.annots with + | Structure _ | Both _ -> Result.ok file_infos.annots | _ -> Result.error (cmt_file ^ ": does not contain an implementation") in - let cmt_struct = Some cmt_struct in (* Read the cmti if it exists. We always want to do it in case a user specified the cmt before the cmti to ensure the location_dependencies are idempotent. *) - let cmti_uid_to_decl = + let cmti_uid_to_decl, annots = let cmti_file = Filename.remove_extension cmt_file ^ ".cmti" in match init_from_cmti_file cmti_file with - | Error _ -> None - | Ok file_infos -> file_infos.cmti_uid_to_decl + | Error _ -> None, annots + | Ok file_infos -> + let annots = + match merge_annots file_infos.annots annots with + | Error _ -> + (* TODO: better error handling *) + assert false + | Ok annots -> annots + in + file_infos.cmti_uid_to_decl, annots in let+ location_dependencies = Location_dependencies.init ~comp_unit_to_path cmt_infos cmti_uid_to_decl in let file_infos = - {file_infos with cmt_struct; cmti_uid_to_decl; location_dependencies} + {file_infos with annots; cmti_uid_to_decl; location_dependencies} in file_infos, cmt_infos @@ -115,9 +134,9 @@ let init ~comp_unit_to_path cm_file = {file_infos with location_dependencies} in match filled_with_cmt_infos with - | Ok {cmt_struct; cmti_uid_to_decl; location_dependencies; _} -> + | Ok {annots; cmti_uid_to_decl; location_dependencies; _} -> let+ res, _ = init_from_cm_file cm_file in - {res with cmt_struct; cmti_uid_to_decl; location_dependencies} + {res with annots; cmti_uid_to_decl; location_dependencies} | Error _ -> init_from_cmti_file cm_file ) | _ -> Result.error (cm_file ^ ": not a .cmti or .cmt file") @@ -126,7 +145,7 @@ let change_file ~comp_unit_to_path file_infos cm_file = let no_ext = Filename.remove_extension cm_file in assert(no_ext = Filename.remove_extension file_infos.cm_file); match Filename.extension cm_file, file_infos with - | ".cmt", {cmt_struct = (Some _ as cs); signature; cmti_uid_to_decl; _} -> + | ".cmt", {annots; cmti_uid_to_decl; _} -> let* res, cmt_infos = init_from_cm_file cm_file in let+ location_dependencies = match file_infos.location_dependencies with @@ -134,10 +153,17 @@ let change_file ~comp_unit_to_path file_infos cm_file = | loc_dep -> (* They have already been computed *) Result.ok loc_dep in - {res with cmt_struct = cs; signature; cmti_uid_to_decl; location_dependencies} - | ".cmti", {cmti_uid_to_decl = (Some _ as cutd); cmt_struct; location_dependencies; _} -> + let annots = + match merge_annots annots res.annots with + | Error _ -> + (* TODO: better error handling *) + assert false + | Ok annots -> annots + in + {res with annots; cmti_uid_to_decl; location_dependencies} + | ".cmti", {cmti_uid_to_decl = (Some _ as cutd); annots; location_dependencies; _} -> let+ res, _ = init_from_cm_file cm_file in - {res with cmti_uid_to_decl = cutd; cmt_struct; location_dependencies} + {res with cmti_uid_to_decl = cutd; annots; location_dependencies} | _ -> (* invalid extension or the corresponding info is None *) init ~comp_unit_to_path cm_file diff --git a/src/state/file_infos.mli b/src/state/file_infos.mli index 8c50705..a946583 100644 --- a/src/state/file_infos.mli +++ b/src/state/file_infos.mli @@ -1,16 +1,19 @@ (** Information about a analyzable file ([.cmti] or [.cmt] file) *) -type signature = +(** binary_annots of the compialtion unit *) +type annots = | Structure of Typedtree.structure - | Signature of Typedtree.signature + (* only a .cmt was read (i.e. no .cmti available) *) + | Signature of Typedtree.signature (* only a .cmti was read *) + | Both of { sign: Typedtree.signature; strc: Typedtree.structure } + (* both the .cmti and .cmt were read *) + | Neither (* no file read or the content was discarded *) type t = { builddir : string; (** The [cmt_builddir] *) cm_file : string; (** The filepath currently analyzed *) - signature : signature option; - (** Extracted from [cmt_infos] in cmti files and [cmi_infos] in cmt files *) - cmt_struct : Typedtree.structure option; - (** Extracted from a cmt's [cmt_infos.cmt_annots] *) + annots : annots; + (** Extracted from [cmt_infos.cmt_annots] in .cmti and .cmt files. *) cmti_uid_to_decl : Location_dependencies.uid_to_decl option; (** Extracted from a cmti's [cmt_infos] *) location_dependencies : Location_dependencies.t; From 71545d7d7bf9a83f5a3b1ffe24b611330b2c18c0 Mon Sep 17 00:00:00 2001 From: Corentin De Souza <9597216+fantazio@users.noreply.github.com> Date: Wed, 19 Aug 2026 11:58:19 +0200 Subject: [PATCH 2/5] [src][state][file_infos] simplify code Remove a lot of anticipated initialization (reading the .cmt for .cmti and vice-versa). This results in simpler init functions. Also errors when changing from .cmt to .cmti file of the same compilation unit (the order must be .cmti then .cmt). --- src/state/file_infos.ml | 96 +++++++++++------------------------------ 1 file changed, 25 insertions(+), 71 deletions(-) diff --git a/src/state/file_infos.ml b/src/state/file_infos.ml index f469fe6..bfad1d7 100644 --- a/src/state/file_infos.ml +++ b/src/state/file_infos.ml @@ -1,3 +1,5 @@ +module Locdep = Location_dependencies + type annots = | Structure of Typedtree.structure | Signature of Typedtree.signature @@ -6,18 +8,19 @@ type annots = let merge_annots sign strc = match sign, strc with - | Structure _, _ -> Result.error "expected a signature first" - | _, Signature _ -> Result.error "expected a structure second" - | Neither, annots | annots, Neither -> Result.ok annots + | Structure _, _ | _, Signature _ -> + (* TODO: better error handling *) + assert false + | Neither, annots | annots, Neither -> annots | (Signature sign | Both {sign; _}), (Structure strc | Both {strc; _}) -> - Result.ok (Both {sign; strc}) + Both {sign; strc} type t = { builddir : string; cm_file : string; annots : annots; - cmti_uid_to_decl : Location_dependencies.uid_to_decl option; - location_dependencies : Location_dependencies.t; + cmti_uid_to_decl : Locdep.uid_to_decl option; + location_dependencies : Locdep.t; modname : string; sourcepath : string option; } @@ -27,7 +30,7 @@ let empty = { cm_file = ""; annots = Neither; cmti_uid_to_decl = None; - location_dependencies = Location_dependencies.empty; + location_dependencies = Locdep.empty; modname = "!!UNKNOWN_MODNAME!!"; sourcepath = None; } @@ -80,65 +83,25 @@ let init_from_cmti_file cmti_file = let* file_infos, cmt_infos = init_from_cm_file cmti_file in let cmti_uid_to_decl = Some cmt_infos.cmt_uid_to_decl in match file_infos.annots with - | Signature _ | Both _ -> + | Signature _ -> let file_infos = {file_infos with cmti_uid_to_decl} in Result.ok file_infos | _ -> Result.error (cmti_file ^ ": does not contain an interface") let init_from_cmt_file ~comp_unit_to_path cmt_file = let* file_infos, cmt_infos = init_from_cm_file cmt_file in - let* annots = - match file_infos.annots with - | Structure _ | Both _ -> Result.ok file_infos.annots - | _ -> Result.error (cmt_file ^ ": does not contain an implementation") - in - (* Read the cmti if it exists. We always want to do it in case a user - specified the cmt before the cmti to ensure the location_dependencies - are idempotent. *) - let cmti_uid_to_decl, annots = - let cmti_file = Filename.remove_extension cmt_file ^ ".cmti" in - match init_from_cmti_file cmti_file with - | Error _ -> None, annots - | Ok file_infos -> - let annots = - match merge_annots file_infos.annots annots with - | Error _ -> - (* TODO: better error handling *) - assert false - | Ok annots -> annots - in - file_infos.cmti_uid_to_decl, annots - in - let+ location_dependencies = - Location_dependencies.init ~comp_unit_to_path cmt_infos cmti_uid_to_decl - in - let file_infos = - {file_infos with annots; cmti_uid_to_decl; location_dependencies} - in - file_infos, cmt_infos + match file_infos.annots with + | Structure _ -> + let+ location_dependencies = + Locdep.init ~comp_unit_to_path cmt_infos None + in + {file_infos with location_dependencies} + | _ -> Result.error (cmt_file ^ ": does not contain an implementation") let init ~comp_unit_to_path cm_file = match Filename.extension cm_file with - | ".cmt" -> - let+ file_infos, _ = init_from_cmt_file ~comp_unit_to_path cm_file in - file_infos - | ".cmti" -> ( - (* Using cmt_infos is not critical. The intent is to mirror the behavior - on cmt files, where both cmt and cmti are read. *) - let filled_with_cmt_infos = - let cmt_file = Filename.remove_extension cm_file ^ ".cmt" in - let* file_infos, cmt_infos = init_from_cmt_file ~comp_unit_to_path cmt_file in - let+ location_dependencies = - Location_dependencies.init ~comp_unit_to_path cmt_infos file_infos.cmti_uid_to_decl - in - {file_infos with location_dependencies} - in - match filled_with_cmt_infos with - | Ok {annots; cmti_uid_to_decl; location_dependencies; _} -> - let+ res, _ = init_from_cm_file cm_file in - {res with annots; cmti_uid_to_decl; location_dependencies} - | Error _ -> init_from_cmti_file cm_file - ) + | ".cmt" -> init_from_cmt_file ~comp_unit_to_path cm_file + | ".cmti" -> init_from_cmti_file cm_file | _ -> Result.error (cm_file ^ ": not a .cmti or .cmt file") let change_file ~comp_unit_to_path file_infos cm_file = @@ -148,22 +111,13 @@ let change_file ~comp_unit_to_path file_infos cm_file = | ".cmt", {annots; cmti_uid_to_decl; _} -> let* res, cmt_infos = init_from_cm_file cm_file in let+ location_dependencies = - match file_infos.location_dependencies with - | [] -> Location_dependencies.init ~comp_unit_to_path cmt_infos cmti_uid_to_decl - | loc_dep -> (* They have already been computed *) - Result.ok loc_dep - in - let annots = - match merge_annots annots res.annots with - | Error _ -> - (* TODO: better error handling *) - assert false - | Ok annots -> annots + Locdep.init ~comp_unit_to_path cmt_infos cmti_uid_to_decl in + let annots = merge_annots annots res.annots in {res with annots; cmti_uid_to_decl; location_dependencies} - | ".cmti", {cmti_uid_to_decl = (Some _ as cutd); annots; location_dependencies; _} -> - let+ res, _ = init_from_cm_file cm_file in - {res with cmti_uid_to_decl = cutd; annots; location_dependencies} + | ".cmti", _ -> + (* .cmti files are alwasy read before the correpsonding.cmt *) + Result.error (cm_file ^ ": must be read before its correpsonding .cmt") | _ -> (* invalid extension or the corresponding info is None *) init ~comp_unit_to_path cm_file From 76cc84880800362bbaf43cee287bf565afa1589c Mon Sep 17 00:00:00 2001 From: Corentin De Souza <9597216+fantazio@users.noreply.github.com> Date: Wed, 19 Aug 2026 12:12:17 +0200 Subject: [PATCH 3/5] [src][state][cmt] only read cmt_infos Both the cache and the `read` function used to store/return a tuple containg the cmi_infos and the cmt_infos. Cmi_infos are not used anymore anywhere, thus we can reduce this tuple to only the cmt_infos. --- src/state/cmt.ml | 25 ++++++++++++++----------- src/state/cmt.mli | 8 +++----- src/state/file_infos.ml | 2 +- src/state/location_dependencies.ml | 3 +-- 4 files changed, 19 insertions(+), 19 deletions(-) diff --git a/src/state/cmt.ml b/src/state/cmt.ml index ce9c002..256f8ee 100644 --- a/src/state/cmt.ml +++ b/src/state/cmt.ml @@ -34,9 +34,8 @@ module Cache = struct end -type cmi_cmt_infos = Cmi_format.cmi_infos option * Cmt_format.cmt_infos - -let cache_cmt : ((string * string), (string * cmi_cmt_infos)) Cache.t = Cache.create 64 +let cache_cmt : ((string * string), (string * Cmt_format.cmt_infos)) Cache.t = + Cache.create 64 let set_cache_size capacity = Cache.update_capacity cache_cmt capacity @@ -51,17 +50,21 @@ let read_no_cache filepath = Option.map (( ^ ) " Tip: ") tip |> Option.value ~default:"" in - Printf.sprintf "%s: %s.%s" filepath msg tip + Printf.sprintf "%s: %s. %s" filepath msg tip |> Result.error in - match Cmt_format.read filepath with + match Cmt_format.read_cmt filepath with | exception Cmi_format.(Error (Not_an_interface _)) -> let tip = - "the file must be compiled with the same OCaml version as the dead_code_analyzer." + "The file must be compiled with the same OCaml version as the dead_code_analyzer." in error ~tip "invalid magic number" - | _, None -> error "missing cmt_infos" - | cmi_infos, Some cmt_infos -> Result.ok (cmi_infos, cmt_infos) + | exception Cmt_format.(Error (Not_a_typedtree _)) -> + let tip = + "The file must be a .cmt or .cmti file compiled with the same OCaml version as the dead_code_analyzer." + in + error ~tip "does not contain a typedtree" + | cmt_infos -> Result.ok cmt_infos let read filepath = let comp_unit = Utils.Filepath.unit filepath in @@ -70,9 +73,9 @@ let read filepath = | Some (fp, res) when String.equal fp filepath -> Result.ok res | _ -> read_no_cache filepath - |> Result.map (fun cmi_cmt_infos -> - Cache.add cache_cmt (ext, comp_unit) (filepath, cmi_cmt_infos); - cmi_cmt_infos) + |> Result.map (fun cmt_infos -> + Cache.add cache_cmt (ext, comp_unit) (filepath, cmt_infos); + cmt_infos) let find_cached_from_comp_unit comp_unit ext = Cache.find_opt cache_cmt (ext, comp_unit) diff --git a/src/state/cmt.mli b/src/state/cmt.mli index 49432e9..dcc06ae 100644 --- a/src/state/cmt.mli +++ b/src/state/cmt.mli @@ -1,10 +1,8 @@ -type cmi_cmt_infos = Cmi_format.cmi_infos option * Cmt_format.cmt_infos +val read : string -> (Cmt_format.cmt_infos, string) Result.t -val read : string -> (cmi_cmt_infos, string) Result.t +val cached_cmti : string -> Cmt_format.cmt_infos option -val cached_cmti : string -> cmi_cmt_infos option - -val cached_cmt : string -> cmi_cmt_infos option +val cached_cmt : string -> Cmt_format.cmt_infos option val set_cache_size : int -> unit diff --git a/src/state/file_infos.ml b/src/state/file_infos.ml index bfad1d7..d8267f2 100644 --- a/src/state/file_infos.ml +++ b/src/state/file_infos.ml @@ -70,7 +70,7 @@ let init_from_cm_file cm_file = else match Cmt.read cm_file with | Error _ as err -> err - | Ok (_, cmt_infos) -> + | Ok cmt_infos -> let file_infos = init_from_all_cm_infos ~cm_file cmt_infos in diff --git a/src/state/location_dependencies.ml b/src/state/location_dependencies.ml index e31078c..d951af2 100644 --- a/src/state/location_dependencies.ml +++ b/src/state/location_dependencies.ml @@ -42,12 +42,11 @@ let find_opt_external_uid_loc ~comp_unit_to_path = function in Cmt.read cm_path |> Result.to_option in - let* cmi_cmt_infos = + let* cmt_infos = match cached with | Some _ as some -> some | None -> read_from_path () in - let cmt_infos = snd cmi_cmt_infos in let cmt_uid_to_decl = cmt_infos.cmt_uid_to_decl in let* item_decl = UidTbl.find_opt cmt_uid_to_decl uid in loc_opt_of_item_decl item_decl From cd3627926d61a235a734fa8658964a1c9cfaebae Mon Sep 17 00:00:00 2001 From: Corentin De Souza <9597216+fantazio@users.noreply.github.com> Date: Wed, 19 Aug 2026 15:12:02 +0200 Subject: [PATCH 4/5] [src][state][file_infos] unify cmti_uid_to_decl and location_dependencies The former is an intermediate result to produce the latter. They are now represented via the same field `location_dependencies` of the new type `loc_dep`. --- src/deadCode.ml | 11 +++++++++-- src/state/file_infos.ml | 23 ++++++++++++++--------- src/state/file_infos.mli | 16 +++++++++++++--- 3 files changed, 36 insertions(+), 14 deletions(-) diff --git a/src/deadCode.ml b/src/deadCode.ml index 71adeae..6e2c5ab 100644 --- a/src/deadCode.ml +++ b/src/deadCode.ml @@ -454,7 +454,14 @@ let load_file fn state = DeadObj.add_equal loc1 loc2; VdNode.merge_locs ~force:true loc2 loc1 in - List.iter prepare state.file_infos.location_dependencies; + let location_dependencies = + match state.file_infos.location_dependencies with + | Set location_dependencies -> location_dependencies + | _ -> + (* TODO: better error handling *) + assert false + in + List.iter prepare location_dependencies; collect_references.Tast_mapper.structure collect_references strc |> ignore; let loc_dep = @@ -471,7 +478,7 @@ let load_file fn state = Some (loc1, loc2) else None ) - state.file_infos.location_dependencies + location_dependencies else [] in eof loc_dep diff --git a/src/state/file_infos.ml b/src/state/file_infos.ml index d8267f2..43dbc49 100644 --- a/src/state/file_infos.ml +++ b/src/state/file_infos.ml @@ -15,12 +15,16 @@ let merge_annots sign strc = | (Signature sign | Both {sign; _}), (Structure strc | Both {strc; _}) -> Both {sign; strc} +type loc_dep = + | In_progress of Location_dependencies.uid_to_decl + | Set of Location_dependencies.t + | Unset + type t = { builddir : string; cm_file : string; annots : annots; - cmti_uid_to_decl : Locdep.uid_to_decl option; - location_dependencies : Locdep.t; + location_dependencies : loc_dep; modname : string; sourcepath : string option; } @@ -29,8 +33,7 @@ let empty = { builddir = "!!UNKNOWN_BUILDDIR!!"; cm_file = ""; annots = Neither; - cmti_uid_to_decl = None; - location_dependencies = Locdep.empty; + location_dependencies = Unset; modname = "!!UNKNOWN_MODNAME!!"; sourcepath = None; } @@ -81,10 +84,10 @@ let ( let+ ) x f = Result.map f x let init_from_cmti_file cmti_file = let* file_infos, cmt_infos = init_from_cm_file cmti_file in - let cmti_uid_to_decl = Some cmt_infos.cmt_uid_to_decl in + let location_dependencies = In_progress cmt_infos.cmt_uid_to_decl in match file_infos.annots with | Signature _ -> - let file_infos = {file_infos with cmti_uid_to_decl} in + let file_infos = {file_infos with location_dependencies} in Result.ok file_infos | _ -> Result.error (cmti_file ^ ": does not contain an interface") @@ -95,6 +98,7 @@ let init_from_cmt_file ~comp_unit_to_path cmt_file = let+ location_dependencies = Locdep.init ~comp_unit_to_path cmt_infos None in + let location_dependencies = Set location_dependencies in {file_infos with location_dependencies} | _ -> Result.error (cmt_file ^ ": does not contain an implementation") @@ -108,13 +112,14 @@ let change_file ~comp_unit_to_path file_infos cm_file = let no_ext = Filename.remove_extension cm_file in assert(no_ext = Filename.remove_extension file_infos.cm_file); match Filename.extension cm_file, file_infos with - | ".cmt", {annots; cmti_uid_to_decl; _} -> + | ".cmt", {annots; location_dependencies = In_progress uid_to_decl; _} -> let* res, cmt_infos = init_from_cm_file cm_file in let+ location_dependencies = - Locdep.init ~comp_unit_to_path cmt_infos cmti_uid_to_decl + Locdep.init ~comp_unit_to_path cmt_infos (Some uid_to_decl) in + let location_dependencies = Set location_dependencies in let annots = merge_annots annots res.annots in - {res with annots; cmti_uid_to_decl; location_dependencies} + {res with annots; location_dependencies} | ".cmti", _ -> (* .cmti files are alwasy read before the correpsonding.cmt *) Result.error (cm_file ^ ": must be read before its correpsonding .cmt") diff --git a/src/state/file_infos.mli b/src/state/file_infos.mli index a946583..531a3f3 100644 --- a/src/state/file_infos.mli +++ b/src/state/file_infos.mli @@ -9,14 +9,24 @@ type annots = (* both the .cmti and .cmt were read *) | Neither (* no file read or the content was discarded *) +(** Dependencies similar to [cmt_infos.cmt_value_dependencies] in OCaml 5.2. + Because the .cmti file is processed before its correpsonding .cmt file, + they are In_progress when processing the .cmti, and Set when processing + the .cmt. +*) +type loc_dep = + | In_progress of Location_dependencies.uid_to_decl + (** Extracted from a .cmti's [cmt_infos.cmt_uid_to_decl]. + Temporary storage before processing the corresponding .cmt file.*) + | Set of Location_dependencies.t + | Unset (* no file read *) + type t = { builddir : string; (** The [cmt_builddir] *) cm_file : string; (** The filepath currently analyzed *) annots : annots; (** Extracted from [cmt_infos.cmt_annots] in .cmti and .cmt files. *) - cmti_uid_to_decl : Location_dependencies.uid_to_decl option; - (** Extracted from a cmti's [cmt_infos] *) - location_dependencies : Location_dependencies.t; + location_dependencies : loc_dep; (** Dependencies similar to [cmt_infos.cmt_value_dependencies] in OCaml 5.2 *) modname : string; (** Either [cmti_name] or [cmt_modname] *) sourcepath : string option; (** The path to the associated source file *) From 8268157256dcce5c1da5b0f9c960d7fd65bca84f Mon Sep 17 00:00:00 2001 From: Corentin De Souza <9597216+fantazio@users.noreply.github.com> Date: Wed, 19 Aug 2026 16:02:25 +0200 Subject: [PATCH 5/5] [src][state][file_infos] group annots and location_dependencies The shape of location_dependenices depends on the file being processed, as does annots. Therefore, they are now grouped under the new field and type `cm_infos` which binds them to their reprensentation depending on the file type (.cmt or .cmti). This helps simplifying the code a little. This includes a small refactor of the interface processing in SeadCode. --- src/deadCode.ml | 39 ++++++--------- src/deadSign.ml | 4 +- src/state/file_infos.ml | 101 ++++++++++++++++++--------------------- src/state/file_infos.mli | 44 ++++++++--------- 4 files changed, 81 insertions(+), 107 deletions(-) diff --git a/src/deadCode.ml b/src/deadCode.ml index 6e2c5ab..6181baf 100644 --- a/src/deadCode.ml +++ b/src/deadCode.ml @@ -327,8 +327,7 @@ let regabs state = hashtbl_add_unique_to_list main_files (Utils.Filepath.unit fn) () -(* annots must not be Neither *) -let read_interface fn (annots : State.File_infos.annots) state = +let read_interface fn export_collector state = regabs state; if Config.must_report_main state.config then let comp_unit = @@ -342,15 +341,7 @@ let read_interface fn (annots : State.File_infos.annots) state = |> Ident.create_persistent in let path = [module_id] in - begin match annots with - | Structure strc -> - DeadSign.collect_export_from_structure ~path ~comp_unit strc - | Signature sign | Both {sign; _} -> - DeadSign.collect_export_from_signature ~path ~comp_unit sign - | Neither -> - (* TODO: better error handling *) - assert false - end; + export_collector ~path ~comp_unit; last_loc := Lexing.dummy_pos @@ -433,12 +424,17 @@ let load_file fn state = in let process_interface fn = last_loc := Lexing.dummy_pos; - if state.State.config.verbose then - Printf.eprintf "Scanning interface from %s\n%!" fn; + if state.State.config.verbose then + Printf.eprintf "Scanning interface from %s\n%!" fn; init_and_continue state fn (fun state -> - match state.file_infos.annots with + match state.file_infos.cm_infos with | Neither -> report_error (fn ^ ": missing signature") - | annots -> read_interface fn annots state + | Cmti {sign; _} | Cmt {sign = Some sign; _} -> + let export_collector = DeadSign.collect_export_from_signature sign in + read_interface fn export_collector state + | Cmt {strc; _} -> + let export_collector = DeadSign.collect_export_from_structure strc in + read_interface fn export_collector state ) in let process_implementation fn = @@ -446,21 +442,14 @@ let load_file fn state = if state.State.config.verbose then Printf.eprintf "Scanning implementation from %s\n%!" fn; init_and_continue state fn (fun state -> - match state.file_infos.annots with - | Neither | Signature _ -> report_error (fn ^ ": missing structure") - | Structure strc | Both {strc; _} -> + match state.file_infos.cm_infos with + | Neither | Cmti _ -> report_error (fn ^ ": missing structure") + | Cmt {strc; location_dependencies; _} -> regabs state; let prepare (loc1, loc2) = DeadObj.add_equal loc1 loc2; VdNode.merge_locs ~force:true loc2 loc1 in - let location_dependencies = - match state.file_infos.location_dependencies with - | Set location_dependencies -> location_dependencies - | _ -> - (* TODO: better error handling *) - assert false - in List.iter prepare location_dependencies; collect_references.Tast_mapper.structure collect_references strc |> ignore; diff --git a/src/deadSign.ml b/src/deadSign.ml index bb8157c..0f5a5d4 100644 --- a/src/deadSign.ml +++ b/src/deadSign.ml @@ -258,8 +258,8 @@ let rec collect_export_from_include ~path sig_item = let correct_export sig_item = let state = State.get_current () in - match state.file_infos.annots with - | Structure _ -> correct_export sig_item + match state.file_infos.cm_infos with + | Cmt {sign = None; _} -> correct_export sig_item | _ -> (* Typedtree signatures found in .cmti files do not need correction *) () diff --git a/src/state/file_infos.ml b/src/state/file_infos.ml index 43dbc49..76e0386 100644 --- a/src/state/file_infos.ml +++ b/src/state/file_infos.ml @@ -1,30 +1,21 @@ module Locdep = Location_dependencies -type annots = - | Structure of Typedtree.structure - | Signature of Typedtree.signature - | Both of { sign: Typedtree.signature; strc: Typedtree.structure } +type cm_infos = + | Cmti of { + sign : Typedtree.signature; + cmti_uid_to_decl : Location_dependencies.uid_to_decl; + } + | Cmt of { + strc : Typedtree.structure; + sign : Typedtree.signature option; + location_dependencies : Location_dependencies.t; + } | Neither -let merge_annots sign strc = - match sign, strc with - | Structure _, _ | _, Signature _ -> - (* TODO: better error handling *) - assert false - | Neither, annots | annots, Neither -> annots - | (Signature sign | Both {sign; _}), (Structure strc | Both {strc; _}) -> - Both {sign; strc} - -type loc_dep = - | In_progress of Location_dependencies.uid_to_decl - | Set of Location_dependencies.t - | Unset - type t = { builddir : string; cm_file : string; - annots : annots; - location_dependencies : loc_dep; + cm_infos : cm_infos; modname : string; sourcepath : string option; } @@ -32,8 +23,7 @@ type t = { let empty = { builddir = "!!UNKNOWN_BUILDDIR!!"; cm_file = ""; - annots = Neither; - location_dependencies = Unset; + cm_infos = Neither; modname = "!!UNKNOWN_MODNAME!!"; sourcepath = None; } @@ -41,7 +31,8 @@ let empty = { (** [init_from_all_cm_infos ~cm_file cmt_infos] creates a [t] with: - information from [cmt_infos] : [builddir], [modname], [sourcepath]; - [cm_file]; - - [annots] is extracted from [cmt_infos.cmt_annots] + - [cm_infos] is built with [cmt_infos.cmt_annots] and + [cmt_infos.cmt_uid_to_decl] *) let init_from_all_cm_infos ~cm_file cmt_infos = let builddir = cmt_infos.Cmt_format.cmt_builddir in @@ -50,17 +41,17 @@ let init_from_all_cm_infos ~cm_file cmt_infos = |> Option.map (Filename.concat builddir) in let modname = cmt_infos.cmt_modname in - let annots = + let cm_infos = match cmt_infos.cmt_annots with - | Interface sign -> Signature sign - | Implementation strc -> Structure strc + | Interface sign -> + let cmti_uid_to_decl = cmt_infos.cmt_uid_to_decl in + Cmti {sign; cmti_uid_to_decl} + | Implementation strc -> + let location_dependencies = Location_dependencies.empty in + Cmt {strc; sign = None; location_dependencies} | _ -> Neither in - {empty with builddir; - cm_file; - annots; - modname; - sourcepath} + {builddir; cm_file; cm_infos; modname; sourcepath} (** [init_from_cm_file cm_file] returns an [Ok t] with [t] filled with general info expected for both cmt and cmti files, using the [cm_file] (see @@ -83,23 +74,25 @@ let ( let* ) x f = Result.bind x f let ( let+ ) x f = Result.map f x let init_from_cmti_file cmti_file = - let* file_infos, cmt_infos = init_from_cm_file cmti_file in - let location_dependencies = In_progress cmt_infos.cmt_uid_to_decl in - match file_infos.annots with - | Signature _ -> - let file_infos = {file_infos with location_dependencies} in - Result.ok file_infos + let* file_infos, _ = init_from_cm_file cmti_file in + match file_infos.cm_infos with + | Cmti _ -> Result.ok file_infos | _ -> Result.error (cmti_file ^ ": does not contain an interface") -let init_from_cmt_file ~comp_unit_to_path cmt_file = +let init_from_cmt_file ~comp_unit_to_path ?(cmi_infos = Neither) cmt_file = let* file_infos, cmt_infos = init_from_cm_file cmt_file in - match file_infos.annots with - | Structure _ -> + match file_infos.cm_infos with + | Cmt cmt -> + let sign, cmti_uid_to_decl = + match cmi_infos with + | Cmti {sign; cmti_uid_to_decl} -> Some sign, Some cmti_uid_to_decl + | _ -> None, None + in let+ location_dependencies = - Locdep.init ~comp_unit_to_path cmt_infos None + Locdep.init ~comp_unit_to_path cmt_infos cmti_uid_to_decl in - let location_dependencies = Set location_dependencies in - {file_infos with location_dependencies} + let cm_infos = Cmt {cmt with sign; location_dependencies} in + {file_infos with cm_infos} | _ -> Result.error (cmt_file ^ ": does not contain an implementation") let init ~comp_unit_to_path cm_file = @@ -112,20 +105,18 @@ let change_file ~comp_unit_to_path file_infos cm_file = let no_ext = Filename.remove_extension cm_file in assert(no_ext = Filename.remove_extension file_infos.cm_file); match Filename.extension cm_file, file_infos with - | ".cmt", {annots; location_dependencies = In_progress uid_to_decl; _} -> - let* res, cmt_infos = init_from_cm_file cm_file in - let+ location_dependencies = - Locdep.init ~comp_unit_to_path cmt_infos (Some uid_to_decl) - in - let location_dependencies = Set location_dependencies in - let annots = merge_annots annots res.annots in - {res with annots; location_dependencies} + | ".cmt", {cm_infos = Cmti _ as cmi_infos; _} -> + init_from_cmt_file ~comp_unit_to_path ~cmi_infos cm_file + | ".cmt", _ -> + (* The only other possible file read before a .cmt is the + corresponding .cmti *) + Result.error (cm_file ^ ": must be read after its corresponding .cmti") | ".cmti", _ -> - (* .cmti files are alwasy read before the correpsonding.cmt *) - Result.error (cm_file ^ ": must be read before its correpsonding .cmt") + (* .cmti files are always read before the corresponding .cmt *) + Result.error (cm_file ^ ": must be read before its corresponding .cmt") | _ -> - (* invalid extension or the corresponding info is None *) - init ~comp_unit_to_path cm_file + (* invalid extension *) + Result.error (cm_file ^ ": not a .cmti or .cmt file") let has_sourcepath file_infos = Option.is_some file_infos.sourcepath diff --git a/src/state/file_infos.mli b/src/state/file_infos.mli index 531a3f3..8b17458 100644 --- a/src/state/file_infos.mli +++ b/src/state/file_infos.mli @@ -1,33 +1,27 @@ -(** Information about a analyzable file ([.cmti] or [.cmt] file) *) - -(** binary_annots of the compialtion unit *) -type annots = - | Structure of Typedtree.structure - (* only a .cmt was read (i.e. no .cmti available) *) - | Signature of Typedtree.signature (* only a .cmti was read *) - | Both of { sign: Typedtree.signature; strc: Typedtree.structure } - (* both the .cmti and .cmt were read *) +(** Information about an analyzable file ([.cmti] or [.cmt] file) *) + +(** Data specific to the file type (.cmt or .cmti) *) +type cm_infos = + | Cmti of { + sign : Typedtree.signature; + cmti_uid_to_decl : Location_dependencies.uid_to_decl; + (** Extracted from [cmt_infos.cmt_uid_to_decl] *) + } (* only a .cmti was read *) + | Cmt of { + strc : Typedtree.structure; + sign : Typedtree.signature option; + (** signature read in the corresponding .cmti (if available) *) + location_dependencies : Location_dependencies.t; + (** Dependencies similar to [cmt_infos.cmt_value_dependencies] + in OCaml 5.2. *) + } (* infos coming from a .cmt *) | Neither (* no file read or the content was discarded *) -(** Dependencies similar to [cmt_infos.cmt_value_dependencies] in OCaml 5.2. - Because the .cmti file is processed before its correpsonding .cmt file, - they are In_progress when processing the .cmti, and Set when processing - the .cmt. -*) -type loc_dep = - | In_progress of Location_dependencies.uid_to_decl - (** Extracted from a .cmti's [cmt_infos.cmt_uid_to_decl]. - Temporary storage before processing the corresponding .cmt file.*) - | Set of Location_dependencies.t - | Unset (* no file read *) - type t = { builddir : string; (** The [cmt_builddir] *) cm_file : string; (** The filepath currently analyzed *) - annots : annots; - (** Extracted from [cmt_infos.cmt_annots] in .cmti and .cmt files. *) - location_dependencies : loc_dep; - (** Dependencies similar to [cmt_infos.cmt_value_dependencies] in OCaml 5.2 *) + cm_infos : cm_infos; + (** Data specific to the file type (.cmt or .cmti) *) modname : string; (** Either [cmti_name] or [cmt_modname] *) sourcepath : string option; (** The path to the associated source file *) }