From 762c6ce01d0c9ee137a46718ee5e011138f12721 Mon Sep 17 00:00:00 2001 From: Christoph Knittel Date: Tue, 31 Mar 2026 13:30:08 +0200 Subject: [PATCH 01/10] Fix incorrect analysis report for optional function args --- analysis/reanalyze/src/dead_common.ml | 8 ++- analysis/reanalyze/src/dead_optional_args.ml | 22 +++++-- analysis/reanalyze/src/dead_value.ml | 28 +++++++-- analysis/reanalyze/src/decl.ml | 1 + .../deadcode/src/ContextOptionalArgs.res | 60 +++++++++++++++++++ 5 files changed, 107 insertions(+), 12 deletions(-) create mode 100644 tests/analysis_tests/tests-reanalyze/deadcode/src/ContextOptionalArgs.res diff --git a/analysis/reanalyze/src/dead_common.ml b/analysis/reanalyze/src/dead_common.ml index a843d68606b..a3f39096f8e 100644 --- a/analysis/reanalyze/src/dead_common.ml +++ b/analysis/reanalyze/src/dead_common.ml @@ -124,11 +124,13 @@ let addDeclaration_ ~config ~decls ~(file : File_context.t) ?pos_end ?pos_start Declarations.add decls pos decl) let add_value_declaration ~config ~decls ~file ?(is_toplevel = true) - ~(loc : Location.t) ~module_loc ?(optional_args = Optional_args.empty) ~path - ~side_effects name = + ?(reports_optional_args = false) ~(loc : Location.t) ~module_loc + ?(optional_args = Optional_args.empty) ~path ~side_effects name = name |> addDeclaration_ ~config ~decls ~file - ~decl_kind:(Value {is_toplevel; optional_args; side_effects}) + ~decl_kind: + (Value + {is_toplevel; reports_optional_args; optional_args; side_effects}) ~loc ~module_loc ~path (** Create a dead code issue. Pure - no side effects. *) diff --git a/analysis/reanalyze/src/dead_optional_args.ml b/analysis/reanalyze/src/dead_optional_args.ml index 21311815398..cd6e0686e5f 100644 --- a/analysis/reanalyze/src/dead_optional_args.ml +++ b/analysis/reanalyze/src/dead_optional_args.ml @@ -7,13 +7,15 @@ let add_function_reference ~config ~decls ~cross_file ~(loc_from : Location.t) if active () then let pos_to = loc_to.loc_start in let pos_from = loc_from.loc_start in - (* Check if target has optional args - for filtering and debug logging *) - let should_add = - match Declarations.find_opt_builder decls pos_to with + let has_optional_arg_state pos = + match Declarations.find_opt_builder decls pos with | Some {decl_kind = Value {optional_args}} -> not (Optional_args.is_empty optional_args) | _ -> false in + let should_add = + has_optional_arg_state pos_from && has_optional_arg_state pos_to + in if should_add then ( if config.Dce_config.cli.debug then Log_.item "OptionalArgs.addFunctionReference %s %s@." @@ -39,6 +41,18 @@ let rec from_type_expr (texpr : Types.type_expr) = | Tsubst t -> from_type_expr t | _ -> [] +let rec from_type_expr_with_arity (texpr : Types.type_expr) arity = + if arity <= 0 then [] + else + match texpr.desc with + | _ when not (active ()) -> [] + | Tarrow ({lbl = Optional {txt = s}}, t_to, _, _) -> + s :: from_type_expr_with_arity t_to (arity - 1) + | Tarrow (_, t_to, _, _) -> from_type_expr_with_arity t_to (arity - 1) + | Tlink t -> from_type_expr_with_arity t arity + | Tsubst t -> from_type_expr_with_arity t arity + | _ -> [] + let add_references ~config ~cross_file ~(loc_from : Location.t) ~(loc_to : Location.t) ~(binding : Location.t) ~path (arg_names, arg_names_maybe) = @@ -61,7 +75,7 @@ let add_references ~config ~cross_file ~(loc_from : Location.t) Uses optional_args_state map for final computed state. *) let check ~optional_args_state ~ann_store ~config:_ decl : Issue.t list = match decl with - | {Decl.decl_kind = Value {optional_args}} + | {Decl.decl_kind = Value {reports_optional_args = true; optional_args}} when active () && not (Annotation_store.is_annotated_gentype_or_live ann_store decl.pos) diff --git a/analysis/reanalyze/src/dead_value.ml b/analysis/reanalyze/src/dead_value.ml index 3fd1c343b79..c2438a38dff 100644 --- a/analysis/reanalyze/src/dead_value.ml +++ b/analysis/reanalyze/src/dead_value.ml @@ -27,13 +27,27 @@ let collect_value_binding ~config ~decls ~file ~(current_binding : Location.t) ({pat_desc = Tpat_any}, id, {loc = {loc_start; loc_ghost} as loc}) when (not loc_ghost) && not vb.vb_loc.loc_ghost -> let name = Ident.name id |> Name.create ~is_interface:false in - let optional_args = - vb.vb_expr.exp_type |> Dead_optional_args.from_type_expr - |> Optional_args.from_list + let optional_args, reports_optional_args = + match vb.vb_expr.exp_desc with + | Texp_function {arity = Some arity; _} -> + ( vb.vb_expr.exp_type + |> (fun texpr -> + Dead_optional_args.from_type_expr_with_arity texpr arity) + |> Optional_args.from_list, + true ) + | Texp_function _ -> + ( vb.vb_expr.exp_type |> Dead_optional_args.from_type_expr + |> Optional_args.from_list, + true ) + | _ -> + ( vb.vb_expr.exp_type |> Dead_optional_args.from_type_expr + |> Optional_args.from_list, + false ) in let exists = match Declarations.find_opt_builder decls loc_start with | Some {decl_kind = Value r} -> + r.reports_optional_args <- reports_optional_args; r.optional_args <- optional_args; true | _ -> false @@ -50,7 +64,8 @@ let collect_value_binding ~config ~decls ~file ~(current_binding : Location.t) let side_effects = Side_effects.check_expr vb.vb_expr in name |> add_value_declaration ~config ~decls ~file ~is_toplevel ~loc - ~module_loc:module_path.loc ~optional_args ~path ~side_effects); + ~module_loc:module_path.loc ~reports_optional_args + ~optional_args ~path ~side_effects); (match Declarations.find_opt_builder decls loc_start with | None -> () | Some decl -> @@ -351,13 +366,16 @@ let rec process_signature_item ~config ~decls ~file ~do_types ~do_values val_type |> Dead_optional_args.from_type_expr |> Optional_args.from_list in + let reports_optional_args = + not (Optional_args.is_empty optional_args) + in (* if Ident.name id = "someValue" then Printf.printf "XXX %s\n" (Ident.name id); *) Ident.name id |> Name.create ~is_interface:false |> add_value_declaration ~config ~decls ~file ~loc ~module_loc - ~optional_args ~path ~side_effects:false + ~reports_optional_args ~optional_args ~path ~side_effects:false | Sig_module (id, {Types.md_type = module_type; md_loc = module_loc}, _) | Sig_modtype (id, {Types.mtd_type = Some module_type; mtd_loc = module_loc}) -> diff --git a/analysis/reanalyze/src/decl.ml b/analysis/reanalyze/src/decl.ml index aa457b3f5b8..9ddaad60ac8 100644 --- a/analysis/reanalyze/src/decl.ml +++ b/analysis/reanalyze/src/decl.ml @@ -7,6 +7,7 @@ module Kind = struct | VariantCase | Value of { is_toplevel: bool; + mutable reports_optional_args: bool; mutable optional_args: Optional_args.t; side_effects: bool; } diff --git a/tests/analysis_tests/tests-reanalyze/deadcode/src/ContextOptionalArgs.res b/tests/analysis_tests/tests-reanalyze/deadcode/src/ContextOptionalArgs.res new file mode 100644 index 00000000000..f9c4075e9d1 --- /dev/null +++ b/tests/analysis_tests/tests-reanalyze/deadcode/src/ContextOptionalArgs.res @@ -0,0 +1,60 @@ +module NotificationProvider = { + let dispatchNotificationContext = React.createContext(( + ~action as _: option=?, + _message: string, + ) => ()) + + module Provider = { + let make = React.Context.provider(dispatchNotificationContext) + } + + @react.component + let make = (~children) => { + let dispatchNotification = (~action=?, message) => + switch action { + | Some(action) => Console.log2(message, action) + | None => Console.log(message) + } + + {children} + } + + let useNotification = () => React.useContext(dispatchNotificationContext) +} + +module ComponentUsingAction = { + @react.component + let make = () => { + let dispatchNotification = NotificationProvider.useNotification() + + React.useEffect(() => { + dispatchNotification(~action="Some action", "This is a notification message") + None + }, []) + + React.null + } +} + +module ComponentNotUsingAction = { + @react.component + let make = () => { + let dispatchNotification = NotificationProvider.useNotification() + + React.useEffect(() => { + dispatchNotification("This is a notification message") + None + }, []) + + React.null + } +} + +@live +@react.component +let make = () => { + + + + +} From d9c6e7758142cbdab87d1bbbf6253919278beba7 Mon Sep 17 00:00:00 2001 From: Christoph Knittel Date: Wed, 1 Apr 2026 11:01:56 +0200 Subject: [PATCH 02/10] Fix --- .../deadcode/src/TopLevelOptionalArgValueUse.res | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 tests/analysis_tests/tests-reanalyze/deadcode/src/TopLevelOptionalArgValueUse.res diff --git a/tests/analysis_tests/tests-reanalyze/deadcode/src/TopLevelOptionalArgValueUse.res b/tests/analysis_tests/tests-reanalyze/deadcode/src/TopLevelOptionalArgValueUse.res new file mode 100644 index 00000000000..8ed461de7b4 --- /dev/null +++ b/tests/analysis_tests/tests-reanalyze/deadcode/src/TopLevelOptionalArgValueUse.res @@ -0,0 +1,14 @@ +/* Repro for the optional-arg ownership regression on fix-reanalyze. + Passing a function with optional args as a first-class value in a top-level + eval should not suppress warnings on the function declaration itself. */ + +let formatDate = (~fmt=?, s) => s + +let takesFn = _ => () + +takesFn(formatDate) + +let liveCaller = () => formatDate("2024-01-01") + +let _ = liveCaller() + From 187f6174252f46f0010098044d530bd92c44edff Mon Sep 17 00:00:00 2001 From: Christoph Knittel Date: Sat, 20 Jun 2026 16:39:07 +0700 Subject: [PATCH 03/10] Fix optional arg analysis for function values Signed-off-by: Christoph Knittel --- .../reanalyze/src/cross_file_items_store.ml | 19 +++ .../reanalyze/src/cross_file_items_store.mli | 7 + analysis/reanalyze/src/dead_common.ml | 8 +- analysis/reanalyze/src/dead_optional_args.ml | 22 ++- analysis/reanalyze/src/dead_value.ml | 124 ++++++++++++--- analysis/reanalyze/src/decl.ml | 7 +- analysis/reanalyze/src/reanalyze.ml | 20 ++- .../deadcode/expected/deadcode.txt | 143 +++++++++++++++++- 8 files changed, 309 insertions(+), 41 deletions(-) diff --git a/analysis/reanalyze/src/cross_file_items_store.ml b/analysis/reanalyze/src/cross_file_items_store.ml index 3a07beb11dc..5273d049c32 100644 --- a/analysis/reanalyze/src/cross_file_items_store.ml +++ b/analysis/reanalyze/src/cross_file_items_store.ml @@ -65,3 +65,22 @@ let compute_optional_args_state (store : t) ~find_decl ~is_live : set_state pos_from updated_from; set_state pos_to updated_to)); state + +let compute_live_direct_optional_arg_calls (store : t) ~find_decl ~is_live : + Pos_set.t = + let direct_calls = ref Pos_set.empty in + iter_optional_arg_calls store (fun {Cross_file_items.pos_from; pos_to; _} -> + if is_live pos_from then + match find_decl pos_to with + | Some + { + Decl.decl_kind = + Value + { + optional_args_report = + Decl.Kind.ReportOptionalArgsIfDirectCall; + }; + } -> + direct_calls := Pos_set.add pos_to !direct_calls + | _ -> ()); + !direct_calls diff --git a/analysis/reanalyze/src/cross_file_items_store.mli b/analysis/reanalyze/src/cross_file_items_store.mli index f8da5db43c8..359a8e0f5fa 100644 --- a/analysis/reanalyze/src/cross_file_items_store.mli +++ b/analysis/reanalyze/src/cross_file_items_store.mli @@ -28,3 +28,10 @@ val compute_optional_args_state : is_live:(Lexing.position -> bool) -> Optional_args_state.t (** Compute optional args state from calls and function references *) + +val compute_live_direct_optional_arg_calls : + t -> + find_decl:(Lexing.position -> Decl.t option) -> + is_live:(Lexing.position -> bool) -> + Pos_set.t +(** Compute live optional-arg call targets that hit reporting declarations directly *) diff --git a/analysis/reanalyze/src/dead_common.ml b/analysis/reanalyze/src/dead_common.ml index a3f39096f8e..25190d18678 100644 --- a/analysis/reanalyze/src/dead_common.ml +++ b/analysis/reanalyze/src/dead_common.ml @@ -124,13 +124,13 @@ let addDeclaration_ ~config ~decls ~(file : File_context.t) ?pos_end ?pos_start Declarations.add decls pos decl) let add_value_declaration ~config ~decls ~file ?(is_toplevel = true) - ?(reports_optional_args = false) ~(loc : Location.t) ~module_loc - ?(optional_args = Optional_args.empty) ~path ~side_effects name = + ?(optional_args_report = Decl.Kind.NoOptionalArgReport) ~(loc : Location.t) + ~module_loc ?(optional_args = Optional_args.empty) ~path ~side_effects name + = name |> addDeclaration_ ~config ~decls ~file ~decl_kind: - (Value - {is_toplevel; reports_optional_args; optional_args; side_effects}) + (Value {is_toplevel; optional_args_report; optional_args; side_effects}) ~loc ~module_loc ~path (** Create a dead code issue. Pure - no side effects. *) diff --git a/analysis/reanalyze/src/dead_optional_args.ml b/analysis/reanalyze/src/dead_optional_args.ml index cd6e0686e5f..0e9240c4280 100644 --- a/analysis/reanalyze/src/dead_optional_args.ml +++ b/analysis/reanalyze/src/dead_optional_args.ml @@ -14,7 +14,12 @@ let add_function_reference ~config ~decls ~cross_file ~(loc_from : Location.t) | _ -> false in let should_add = - has_optional_arg_state pos_from && has_optional_arg_state pos_to + if + pos_to.pos_fname <> pos_from.pos_fname + && (file_is_implementation_of pos_to.pos_fname pos_from.pos_fname + || file_is_implementation_of pos_from.pos_fname pos_to.pos_fname) + then has_optional_arg_state pos_to + else has_optional_arg_state pos_from && has_optional_arg_state pos_to in if should_add then ( if config.Dce_config.cli.debug then @@ -73,10 +78,19 @@ let add_references ~config ~cross_file ~(loc_from : Location.t) (** Check for optional args issues. Returns issues instead of logging. Uses optional_args_state map for final computed state. *) -let check ~optional_args_state ~ann_store ~config:_ decl : Issue.t list = +let check ~optional_args_state ~direct_optional_arg_calls ~ann_store ~config:_ + decl : Issue.t list = + let should_report_optional_args = + let open Decl.Kind in + match decl.Decl.decl_kind with + | Value {optional_args_report = ReportOptionalArgs} -> true + | Value {optional_args_report = ReportOptionalArgsIfDirectCall} -> + Pos_set.mem decl.pos direct_optional_arg_calls + | _ -> false + in match decl with - | {Decl.decl_kind = Value {reports_optional_args = true; optional_args}} - when active () + | {Decl.decl_kind = Value {optional_args}} + when active () && should_report_optional_args && not (Annotation_store.is_annotated_gentype_or_live ann_store decl.pos) -> diff --git a/analysis/reanalyze/src/dead_value.ml b/analysis/reanalyze/src/dead_value.ml index c2438a38dff..4421caccc45 100644 --- a/analysis/reanalyze/src/dead_value.ml +++ b/analysis/reanalyze/src/dead_value.ml @@ -27,27 +27,28 @@ let collect_value_binding ~config ~decls ~file ~(current_binding : Location.t) ({pat_desc = Tpat_any}, id, {loc = {loc_start; loc_ghost} as loc}) when (not loc_ghost) && not vb.vb_loc.loc_ghost -> let name = Ident.name id |> Name.create ~is_interface:false in - let optional_args, reports_optional_args = + let optional_args, optional_args_report = + let open Decl.Kind in match vb.vb_expr.exp_desc with | Texp_function {arity = Some arity; _} -> ( vb.vb_expr.exp_type |> (fun texpr -> - Dead_optional_args.from_type_expr_with_arity texpr arity) + Dead_optional_args.from_type_expr_with_arity texpr arity) |> Optional_args.from_list, - true ) + ReportOptionalArgs ) | Texp_function _ -> ( vb.vb_expr.exp_type |> Dead_optional_args.from_type_expr |> Optional_args.from_list, - true ) + ReportOptionalArgs ) | _ -> ( vb.vb_expr.exp_type |> Dead_optional_args.from_type_expr |> Optional_args.from_list, - false ) + NoOptionalArgReport ) in let exists = match Declarations.find_opt_builder decls loc_start with | Some {decl_kind = Value r} -> - r.reports_optional_args <- reports_optional_args; + r.optional_args_report <- optional_args_report; r.optional_args <- optional_args; true | _ -> false @@ -64,8 +65,8 @@ let collect_value_binding ~config ~decls ~file ~(current_binding : Location.t) let side_effects = Side_effects.check_expr vb.vb_expr in name |> add_value_declaration ~config ~decls ~file ~is_toplevel ~loc - ~module_loc:module_path.loc ~reports_optional_args - ~optional_args ~path ~side_effects); + ~module_loc:module_path.loc ~optional_args_report ~optional_args + ~path ~side_effects); (match Declarations.find_opt_builder decls loc_start with | None -> () | Some decl -> @@ -124,10 +125,76 @@ let process_optional_args ~config ~cross_file ~exp_type ~(loc_from : Location.t) |> Dead_optional_args.add_references ~config ~cross_file ~loc_from ~loc_to ~binding ~path) -let rec collect_expr ~config ~refs ~file_deps ~cross_file +let value_use_target_pos (e : Typedtree.expression) = + match e.exp_desc with + | Texp_ident (_, _, {Types.val_loc = {loc_ghost = false; _} as loc_to}) -> + Some loc_to.loc_start + | Texp_let + ( Nonrecursive, + [ + { + vb_pat = {pat_desc = Tpat_var (id_arg, _)}; + vb_expr = + { + Typedtree.exp_desc = + Texp_ident + (_, _, {Types.val_loc = {loc_ghost = false; _} as loc_to}); + _; + }; + _; + }; + ], + { + Typedtree.exp_desc = + Texp_function + { + case = + { + c_lhs = {pat_desc = Tpat_var (eta_arg, _)}; + c_rhs = + { + Typedtree.exp_desc = + Texp_apply + {funct = {exp_desc = Texp_ident (id_arg2, _, _)}; _}; + _; + }; + _; + }; + _; + }; + _; + } ) + when Ident.name id_arg = "arg" + && Ident.name eta_arg = "eta" + && Path.name id_arg2 = "arg" -> + Some loc_to.loc_start + | _ -> None + +let rec collect_expr ~config ~decls ~refs ~file_deps ~cross_file ~(last_binding : Location.t) super self (e : Typedtree.expression) = let loc_from = e.exp_loc in let binding = last_binding in + let require_direct_optional_arg_call pos = + match Declarations.find_opt_builder decls pos with + | Some + ({ + decl_kind = + Value + ({optional_args_report = Decl.Kind.ReportOptionalArgs} as + value_kind); + } as decl) -> + Declarations.replace_builder decls pos + { + decl with + decl_kind = + Value + { + value_kind with + optional_args_report = Decl.Kind.ReportOptionalArgsIfDirectCall; + }; + } + | _ -> () + in (match e.exp_desc with | Texp_ident (_path, _, {Types.val_loc = {loc_ghost = false; _} as loc_to}) -> (* if Path.name _path = "rc" then assert false; *) @@ -157,7 +224,14 @@ let rec collect_expr ~config ~refs ~file_deps ~cross_file args |> process_optional_args ~config ~cross_file ~exp_type ~loc_from:(loc_from : Location.t) - ~binding:last_binding ~loc_to ~path + ~binding:last_binding ~loc_to ~path; + args + |> List.iter (fun (_, arg) -> + match arg with + | Some arg -> + arg |> value_use_target_pos + |> Option.iter require_direct_optional_arg_call + | _ -> ()) | Texp_let ( (* generated for functions with optional args *) Nonrecursive, @@ -224,13 +298,17 @@ let rec collect_expr ~config ~refs ~file_deps ~cross_file fields |> Array.iter (fun (_, record_label_definition, _) -> match record_label_definition with - | Typedtree.Overridden (_, ({exp_loc} as e)) when exp_loc.loc_ghost - -> - (* Punned field in OCaml projects has ghost location in expression *) - let e = {e with exp_loc = {exp_loc with loc_ghost = false}} in - collect_expr ~config ~refs ~file_deps ~cross_file ~last_binding - super self e - |> ignore + | Typedtree.Overridden (_, e) -> ( + e |> value_use_target_pos + |> Option.iter require_direct_optional_arg_call; + match e with + | {exp_loc; _} when exp_loc.loc_ghost -> + (* Punned field in OCaml projects has ghost location in expression *) + let e = {e with exp_loc = {exp_loc with loc_ghost = false}} in + collect_expr ~config ~decls ~refs ~file_deps ~cross_file + ~last_binding super self e + |> ignore + | _ -> ()) | _ -> ()) | _ -> ()); super.Tast_mapper.expr self e @@ -366,8 +444,10 @@ let rec process_signature_item ~config ~decls ~file ~do_types ~do_values val_type |> Dead_optional_args.from_type_expr |> Optional_args.from_list in - let reports_optional_args = - not (Optional_args.is_empty optional_args) + let optional_args_report = + if Optional_args.is_empty optional_args then + Decl.Kind.NoOptionalArgReport + else Decl.Kind.ReportOptionalArgs in (* if Ident.name id = "someValue" then @@ -375,7 +455,7 @@ let rec process_signature_item ~config ~decls ~file ~do_types ~do_values Ident.name id |> Name.create ~is_interface:false |> add_value_declaration ~config ~decls ~file ~loc ~module_loc - ~reports_optional_args ~optional_args ~path ~side_effects:false + ~optional_args_report ~optional_args ~path ~side_effects:false | Sig_module (id, {Types.md_type = module_type; md_loc = module_loc}, _) | Sig_modtype (id, {Types.mtd_type = Some module_type; mtd_loc = module_loc}) -> @@ -409,8 +489,8 @@ let traverse_structure ~config ~decls ~refs ~file_deps ~cross_file ~file expr = (fun _self e -> e - |> collect_expr ~config ~refs ~file_deps ~cross_file ~last_binding - super mapper); + |> collect_expr ~config ~decls ~refs ~file_deps ~cross_file + ~last_binding super mapper); pat = (fun _self p -> p diff --git a/analysis/reanalyze/src/decl.ml b/analysis/reanalyze/src/decl.ml index 9ddaad60ac8..e3ababece6e 100644 --- a/analysis/reanalyze/src/decl.ml +++ b/analysis/reanalyze/src/decl.ml @@ -1,13 +1,18 @@ (** Declaration types for dead code analysis. *) module Kind = struct + type optional_args_report = + | NoOptionalArgReport + | ReportOptionalArgs + | ReportOptionalArgsIfDirectCall + type t = | Exception | RecordLabel | VariantCase | Value of { is_toplevel: bool; - mutable reports_optional_args: bool; + mutable optional_args_report: optional_args_report; mutable optional_args: Optional_args.t; side_effects: bool; } diff --git a/analysis/reanalyze/src/reanalyze.ml b/analysis/reanalyze/src/reanalyze.ml index 062df5c6eec..f0b3aaabbdc 100644 --- a/analysis/reanalyze/src/reanalyze.ml +++ b/analysis/reanalyze/src/reanalyze.ml @@ -347,12 +347,17 @@ let run_analysis ~dce_config ~cmt_root ~reactive_collection ~reactive_merge Cross_file_items_store.compute_optional_args_state cross_file_store ~find_decl ~is_live in + let direct_optional_arg_calls = + Cross_file_items_store.compute_live_direct_optional_arg_calls + cross_file_store ~find_decl ~is_live + in (* Iterate live declarations and check for optional args issues *) let issues = ref [] in Reactive_solver.iter_live_decls ~t:solver (fun decl -> let decl_issues = - Dead_optional_args.check ~optional_args_state ~ann_store - ~config:dce_config decl + Dead_optional_args.check ~optional_args_state + ~direct_optional_arg_calls ~ann_store ~config:dce_config + decl in issues := List.rev_append decl_issues !issues); List.rev !issues @@ -399,14 +404,21 @@ let run_analysis ~dce_config ~cmt_root ~reactive_collection ~reactive_merge ~find_decl:(Declaration_store.find_opt decl_store) ~is_live in + let direct_optional_arg_calls = + Cross_file_items_store.compute_live_direct_optional_arg_calls + cross_file_store + ~find_decl:(Declaration_store.find_opt decl_store) + ~is_live + in (* Collect optional args issues only for live declarations *) let optional_args_issues = Declaration_store.fold (fun _pos decl acc -> if Decl.is_live decl then let issues = - Dead_optional_args.check ~optional_args_state ~ann_store - ~config:dce_config decl + Dead_optional_args.check ~optional_args_state + ~direct_optional_arg_calls ~ann_store ~config:dce_config + decl in List.rev_append issues acc else acc) diff --git a/tests/analysis_tests/tests-reanalyze/deadcode/expected/deadcode.txt b/tests/analysis_tests/tests-reanalyze/deadcode/expected/deadcode.txt index 10c70d7cd19..e2e6708e0e7 100644 --- a/tests/analysis_tests/tests-reanalyze/deadcode/expected/deadcode.txt +++ b/tests/analysis_tests/tests-reanalyze/deadcode/expected/deadcode.txt @@ -32,6 +32,52 @@ addTypeReference _none_:1:-1 --> ComponentAsProp.res:6:20 addTypeReference _none_:1:-1 --> ComponentAsProp.res:6:34 addValueReference ComponentAsProp.res:6:4 --> React.res:16:0 + Scanning ContextOptionalArgs.cmt Source:ContextOptionalArgs.res + addValueDeclaration +dispatchNotificationContext ContextOptionalArgs.res:2:6 path:+ContextOptionalArgs.NotificationProvider + addValueDeclaration +make ContextOptionalArgs.res:8:8 path:+ContextOptionalArgs.NotificationProvider.Provider + addValueDeclaration +make ContextOptionalArgs.res:12:6 path:+ContextOptionalArgs.NotificationProvider + addValueDeclaration +useNotification ContextOptionalArgs.res:22:6 path:+ContextOptionalArgs.NotificationProvider + addValueDeclaration +make ContextOptionalArgs.res:27:6 path:+ContextOptionalArgs.ComponentUsingAction + addValueDeclaration +make ContextOptionalArgs.res:41:6 path:+ContextOptionalArgs.ComponentNotUsingAction + addValueDeclaration +make ContextOptionalArgs.res:55:4 path:+ContextOptionalArgs + addRecordLabelDeclaration children ContextOptionalArgs.res:12:14 path:+ContextOptionalArgs.NotificationProvider.props + addValueReference ContextOptionalArgs.res:2:6 --> React.res:81:0 + addValueReference ContextOptionalArgs.res:8:8 --> ContextOptionalArgs.res:2:6 + addValueReference ContextOptionalArgs.res:8:8 --> React.res:77:2 + addRecordLabelDeclaration children ContextOptionalArgs.res:12:14 path:+ContextOptionalArgs.NotificationProvider.props + addValueDeclaration +dispatchNotification ContextOptionalArgs.res:13:8 path:+ContextOptionalArgs.NotificationProvider + addValueReference ContextOptionalArgs.res:13:8 --> ContextOptionalArgs.res:13:43 + addValueReference ContextOptionalArgs.res:13:8 --> ContextOptionalArgs.res:15:13 + addValueReference ContextOptionalArgs.res:13:8 --> ContextOptionalArgs.res:13:43 + addValueReference ContextOptionalArgs.res:13:8 --> ContextOptionalArgs.res:13:32 + addValueReference ContextOptionalArgs.res:19:5 --> ContextOptionalArgs.res:8:8 + addValueReference ContextOptionalArgs.res:19:20 --> ContextOptionalArgs.res:13:8 + addValueReference ContextOptionalArgs.res:19:43 --> ContextOptionalArgs.res:12:14 + addTypeReference _none_:1:-1 --> ContextOptionalArgs.res:12:14 + addValueReference ContextOptionalArgs.res:12:6 --> React.res:16:0 + DeadOptionalArgs.addReferences React.useContext called with optional argNames: argNamesMaybe: ContextOptionalArgs.res:22:30 + addValueReference ContextOptionalArgs.res:22:6 --> ContextOptionalArgs.res:2:6 + addValueReference ContextOptionalArgs.res:22:6 --> React.res:250:0 + addValueDeclaration +dispatchNotification ContextOptionalArgs.res:28:8 path:+ContextOptionalArgs.ComponentUsingAction + DeadOptionalArgs.addReferences NotificationProvider.useNotification called with optional argNames: argNamesMaybe: ContextOptionalArgs.res:28:31 + addValueReference ContextOptionalArgs.res:28:8 --> ContextOptionalArgs.res:22:6 + addValueReference ContextOptionalArgs.res:35:4 --> React.res:3:0 + DeadOptionalArgs.addReferences dispatchNotification called with optional argNames:action argNamesMaybe: ContextOptionalArgs.res:31:6 + addValueReference ContextOptionalArgs.res:31:6 --> ContextOptionalArgs.res:28:8 + addValueReference ContextOptionalArgs.res:30:4 --> React.res:150:0 + addValueReference ContextOptionalArgs.res:27:6 --> React.res:16:0 + addValueDeclaration +dispatchNotification ContextOptionalArgs.res:42:8 path:+ContextOptionalArgs.ComponentNotUsingAction + DeadOptionalArgs.addReferences NotificationProvider.useNotification called with optional argNames: argNamesMaybe: ContextOptionalArgs.res:42:31 + addValueReference ContextOptionalArgs.res:42:8 --> ContextOptionalArgs.res:22:6 + addValueReference ContextOptionalArgs.res:49:4 --> React.res:3:0 + DeadOptionalArgs.addReferences dispatchNotification called with optional argNames: argNamesMaybe: ContextOptionalArgs.res:45:6 + addValueReference ContextOptionalArgs.res:45:6 --> ContextOptionalArgs.res:42:8 + addValueReference ContextOptionalArgs.res:44:4 --> React.res:150:0 + addValueReference ContextOptionalArgs.res:41:6 --> React.res:16:0 + addValueReference ContextOptionalArgs.res:56:3 --> ContextOptionalArgs.res:12:6 + addValueReference ContextOptionalArgs.res:57:5 --> ContextOptionalArgs.res:27:6 + addValueReference ContextOptionalArgs.res:58:5 --> ContextOptionalArgs.res:41:6 + addValueReference ContextOptionalArgs.res:55:4 --> React.res:16:0 Scanning CreateErrorHandler1.cmt Source:CreateErrorHandler1.res addValueDeclaration +notification CreateErrorHandler1.res:3:6 path:+CreateErrorHandler1.Error1 addValueReference CreateErrorHandler1.res:3:6 --> CreateErrorHandler1.res:3:21 @@ -1455,6 +1501,16 @@ addTypeReference TestPromise.res:14:32 --> TestPromise.res:7:2 Scanning ToSuppress.cmt Source:ToSuppress.res addValueDeclaration +toSuppress ToSuppress.res:1:4 path:+ToSuppress + Scanning TopLevelOptionalArgValueUse.cmt Source:TopLevelOptionalArgValueUse.res + addValueDeclaration +formatDate TopLevelOptionalArgValueUse.res:5:4 path:+TopLevelOptionalArgValueUse + addValueDeclaration +takesFn TopLevelOptionalArgValueUse.res:7:4 path:+TopLevelOptionalArgValueUse + addValueDeclaration +liveCaller TopLevelOptionalArgValueUse.res:11:4 path:+TopLevelOptionalArgValueUse + addValueReference TopLevelOptionalArgValueUse.res:5:4 --> TopLevelOptionalArgValueUse.res:5:26 + addValueReference TopLevelOptionalArgValueUse.res:9:8 --> TopLevelOptionalArgValueUse.res:5:4 + addValueReference TopLevelOptionalArgValueUse.res:9:0 --> TopLevelOptionalArgValueUse.res:7:4 + DeadOptionalArgs.addReferences formatDate called with optional argNames: argNamesMaybe: TopLevelOptionalArgValueUse.res:11:23 + addValueReference TopLevelOptionalArgValueUse.res:11:4 --> TopLevelOptionalArgValueUse.res:5:4 + addValueReference TopLevelOptionalArgValueUse.res:13:8 --> TopLevelOptionalArgValueUse.res:11:4 Scanning TransitiveType1.cmt Source:TransitiveType1.res addValueDeclaration +convert TransitiveType1.res:2:4 path:+TransitiveType1 addValueDeclaration +convertAlias TransitiveType1.res:5:4 path:+TransitiveType1 @@ -1958,9 +2014,9 @@ Forward Liveness Analysis - decls: 702 - roots(external targets): 137 - decl-deps: decls_with_out=411 edges_to_decls=289 + decls: 712 + roots(external targets): 147 + decl-deps: decls_with_out=422 edges_to_decls=302 Root (external ref): Value +FirstClassModules.M.InnerModule2.+k Root (external ref): VariantCase DeadRT.moduleAccessPath.Root @@ -1997,7 +2053,9 @@ Forward Liveness Analysis Root (external ref): Value +Newton.+f Root (external ref): RecordLabel +Records.record.v Root (external ref): VariantCase +DeadTest.VariantUsedOnlyInImplementation.t.A + Root (external ref): Value +ContextOptionalArgs.ComponentUsingAction.+make Root (external ref): RecordLabel +Records.person.address + Root (external ref): RecordLabel +ContextOptionalArgs.NotificationProvider.props.children Root (annotated): Value +Variants.+testConvert2 Root (annotated): Value +Tuples.+coord2d Root (external ref): Value +CreateErrorHandler1.Error1.+notification @@ -2034,6 +2092,7 @@ Forward Liveness Analysis Root (external ref): RecordLabel +DeadTest.props.s Root (annotated): Value +Hooks.+default Root (external ref): VariantCase +Unison.break_.Never + Root (external ref): Value +ContextOptionalArgs.NotificationProvider.+make Root (annotated): Value +TestEmitInnerModules.Inner.+y Root (external ref): VariantCase InnerModuleTypes.I.t.Foo Root (annotated): Value +Types.+selfRecursiveConverter @@ -2047,12 +2106,14 @@ Forward Liveness Analysis Root (annotated): Value +Variants.+restResult3 Root (external ref): RecordLabel +Tuples.person.name Root (external ref): Value +FirstClassModules.M.InnerModule3.+k3 + Root (external ref): Value +ContextOptionalArgs.ComponentNotUsingAction.+dispatchNotification Root (external ref): RecordLabel +Records.coord.x Root (annotated): RecordLabel +DeadTypeTest.record.y Root (annotated): Value +TestImport.+defaultValue Root (external ref): Value +OptArg.+threeArgs Root (annotated): Value +Types.+setMatch Root (external ref): Value +DeadTest.+deadIncorrect + Root (external ref): Value +TopLevelOptionalArgValueUse.+takesFn Root (external ref): RecordLabel +TypeReexport.UseReexported.reexportedType.usedField Root (annotated): Value +Docstrings.+signMessage Root (external ref): RecordLabel +Hooks.Inner.Inner2.props.vehicle @@ -2077,6 +2138,7 @@ Forward Liveness Analysis Root (annotated): Value +Docstrings.+unitArgWithConversionU Root (annotated): Value +Tuples.+computeArea Root (annotated): Value +References.+get + Root (external ref): Value +TopLevelOptionalArgValueUse.+formatDate Root (annotated): Value +ModuleAliases.+testNested Root (external ref): Value +FirstClassModules.SomeFunctor.+ww Root (annotated): Value +Hooks.Inner.Inner2.+make @@ -2101,12 +2163,14 @@ Forward Liveness Analysis Root (annotated): Value +Variants.+id2 Root (annotated): Value +Uncurried.+sumU Root (external ref): Value +TestOptArg.+bar + Root (external ref): Value +TopLevelOptionalArgValueUse.+liveCaller Root (external ref): RecordLabel +DeadTest.record.yyy Root (annotated): Value +Docstrings.+one Root (external ref): Value +OptArg.+twoArgs Root (annotated): Value +OcamlWarningSuppressToplevel.+suppressed1 Root (external ref): RecordLabel +Records.business2.address2 Root (annotated): Value +Tuples.+testTuple + Root (external ref): Value +ContextOptionalArgs.NotificationProvider.+dispatchNotification Root (annotated): Value +Records.+testMyObj2 Root (annotated): Value +Uncurried.+callback2 Root (external ref): Value +DeadTest.VariantUsedOnlyInImplementation.+a @@ -2117,6 +2181,7 @@ Forward Liveness Analysis Root (annotated): Value +TestEmitInnerModules.Inner.+x Root (external ref): Value +OptArg.+wrapOneArg Root (external ref): RecordLabel +ComponentAsProp.props.title + Root (external ref): Value +ContextOptionalArgs.ComponentUsingAction.+dispatchNotification Root (annotated): Value +Records.+findAddress Root (annotated): Value +Uncurried.+callback Root (annotated): Value +VariantsWithPayload.+printVariantWithPayload @@ -2125,6 +2190,7 @@ Forward Liveness Analysis Root (annotated): Value +Docstrings.+unnamed2 Root (annotated): Value +LetPrivate.local_1.+x Root (annotated): Value +TestImport.+make + Root (external ref): Value +ContextOptionalArgs.NotificationProvider.Provider.+make Root (annotated): Value +DeadTest.GloobLive.+globallyLive1 Root (annotated): Value +Docstrings.+grouped Root (annotated): Value +OcamlWarningSuppressToplevel.M.+suppressed4 @@ -2166,8 +2232,10 @@ Forward Liveness Analysis Root (annotated): RecordLabel +DeadTypeTest.record.x Root (external ref): RecordLabel +Records.myRec.type_ Root (annotated): Value +TestOptArg.+liveSuppressesOptArgs + Root (external ref): Value +ContextOptionalArgs.ComponentNotUsingAction.+make Root (annotated): Value NestedModulesInSignature.Universe.+theAnswer Root (annotated): Value +Docstrings.+unitArgWithoutConversion + Root (annotated): Value +ContextOptionalArgs.+make Root (annotated): Value +References.+create Root (annotated): Value +Types.+currentTime Root (annotated): Value +Records.+someBusiness2 @@ -2288,7 +2356,7 @@ Forward Liveness Analysis Root (annotated): Value +UseImportJsValue.+useGetProp Root (annotated): Value +Hooks.+functionWithRenamedArgs - 325 roots found + 334 roots found Propagate: DeadRT.moduleAccessPath.Root -> +DeadRT.moduleAccessPath.Root Propagate: +TypeReexportCrossFileB.reexportedRecord.usedField -> +TypeReexportCrossFileA.originalRecord.usedField @@ -2298,6 +2366,7 @@ Forward Liveness Analysis Propagate: +Newton.+f -> +Newton.++ Propagate: +Newton.+f -> +Newton.+* Propagate: +DeadTest.VariantUsedOnlyInImplementation.t.A -> +DeadTest.VariantUsedOnlyInImplementation.t.A + Propagate: +ContextOptionalArgs.ComponentUsingAction.+make -> +ContextOptionalArgs.NotificationProvider.+useNotification Propagate: +DeadTest.+thisIsMarkedLive -> +DeadTest.+thisIsKeptAlive Propagate: +TypeReexport.UseOriginal.reexportedType.directlyUsed -> +TypeReexport.UseOriginal.originalType.directlyUsed Propagate: +Hooks.+default -> +Hooks.+make @@ -2315,6 +2384,7 @@ Forward Liveness Analysis Propagate: +DeadTest.VariantUsedOnlyInImplementation.+a -> +DeadTest.VariantUsedOnlyInImplementation.+a Propagate: +OptArg.+wrapOneArg -> +OptArg.+oneArg Propagate: +TestImmutableArray.+testImmutableArrayGet -> ImmutableArray.Array.+get + Propagate: +ContextOptionalArgs.NotificationProvider.Provider.+make -> +ContextOptionalArgs.NotificationProvider.+dispatchNotificationContext Propagate: +TypeReexport.VariantUseReexported.reexportedType.A -> +TypeReexport.VariantUseReexported.originalType.A Propagate: +Unison.+toString -> +Unison.+fits Propagate: +References.+set -> +References.R.+set @@ -2344,7 +2414,7 @@ Forward Liveness Analysis Propagate: +DeadTest.MM.+x -> +DeadTest.MM.+y Propagate: +ImportJsValue.AbsoluteValue.+getAbs -> +ImportJsValue.AbsoluteValue.+getAbs - 53 declarations marked live via propagation + 55 declarations marked live via propagation Dead VariantCase +AutoAnnotate.variant.R Dead RecordLabel +AutoAnnotate.record.variant @@ -2373,6 +2443,56 @@ Forward Liveness Analysis Live (external ref) RecordLabel +ComponentAsProp.props.button deps: in=1 (live=1 dead=0) out=0 <- +ComponentAsProp.+make (live) + Live (propagated) Value +ContextOptionalArgs.NotificationProvider.+dispatchNotificationContext + deps: in=2 (live=2 dead=0) out=0 + <- +ContextOptionalArgs.NotificationProvider.Provider.+make (live) + <- +ContextOptionalArgs.NotificationProvider.+useNotification (live) + Live (external ref) Value +ContextOptionalArgs.NotificationProvider.Provider.+make + deps: in=1 (live=1 dead=0) out=1 + <- +ContextOptionalArgs.NotificationProvider.+make (live) + -> +ContextOptionalArgs.NotificationProvider.+dispatchNotificationContext + Live (external ref) Value +ContextOptionalArgs.NotificationProvider.+make + deps: in=1 (live=1 dead=0) out=3 + <- +ContextOptionalArgs.+make (live) + -> +ContextOptionalArgs.NotificationProvider.Provider.+make + -> +ContextOptionalArgs.NotificationProvider.props.children + -> +ContextOptionalArgs.NotificationProvider.+dispatchNotification + Live (external ref) RecordLabel +ContextOptionalArgs.NotificationProvider.props.children + deps: in=1 (live=1 dead=0) out=0 + <- +ContextOptionalArgs.NotificationProvider.+make (live) + Live (external ref) Value +ContextOptionalArgs.NotificationProvider.+dispatchNotification + deps: in=1 (live=1 dead=0) out=0 + <- +ContextOptionalArgs.NotificationProvider.+make (live) + Live (propagated) Value +ContextOptionalArgs.NotificationProvider.+useNotification + deps: in=4 (live=4 dead=0) out=1 + <- +ContextOptionalArgs.ComponentUsingAction.+make (live) + <- +ContextOptionalArgs.ComponentUsingAction.+dispatchNotification (live) + <- +ContextOptionalArgs.ComponentNotUsingAction.+make (live) + <- ... (1 more) + -> +ContextOptionalArgs.NotificationProvider.+dispatchNotificationContext + Live (external ref) Value +ContextOptionalArgs.ComponentUsingAction.+make + deps: in=1 (live=1 dead=0) out=2 + <- +ContextOptionalArgs.+make (live) + -> +ContextOptionalArgs.NotificationProvider.+useNotification + -> +ContextOptionalArgs.ComponentUsingAction.+dispatchNotification + Live (external ref) Value +ContextOptionalArgs.ComponentUsingAction.+dispatchNotification + deps: in=1 (live=1 dead=0) out=1 + <- +ContextOptionalArgs.ComponentUsingAction.+make (live) + -> +ContextOptionalArgs.NotificationProvider.+useNotification + Live (external ref) Value +ContextOptionalArgs.ComponentNotUsingAction.+make + deps: in=1 (live=1 dead=0) out=2 + <- +ContextOptionalArgs.+make (live) + -> +ContextOptionalArgs.NotificationProvider.+useNotification + -> +ContextOptionalArgs.ComponentNotUsingAction.+dispatchNotification + Live (external ref) Value +ContextOptionalArgs.ComponentNotUsingAction.+dispatchNotification + deps: in=1 (live=1 dead=0) out=1 + <- +ContextOptionalArgs.ComponentNotUsingAction.+make (live) + -> +ContextOptionalArgs.NotificationProvider.+useNotification + Live (annotated) Value +ContextOptionalArgs.+make + deps: in=0 (live=0 dead=0) out=3 + -> +ContextOptionalArgs.NotificationProvider.+make + -> +ContextOptionalArgs.ComponentUsingAction.+make + -> +ContextOptionalArgs.ComponentNotUsingAction.+make Live (external ref) Value +CreateErrorHandler1.Error1.+notification Live (external ref) Value +CreateErrorHandler2.Error2.+notification Live (external ref) Value +DeadCodeImplementation.M.+x @@ -3669,6 +3789,13 @@ Forward Liveness Analysis deps: in=0 (live=0 dead=0) out=1 -> +TestPromise.fromPayload.s Dead Value +ToSuppress.+toSuppress + Live (external ref) Value +TopLevelOptionalArgValueUse.+formatDate + deps: in=1 (live=1 dead=0) out=0 + <- +TopLevelOptionalArgValueUse.+liveCaller (live) + Live (external ref) Value +TopLevelOptionalArgValueUse.+takesFn + Live (external ref) Value +TopLevelOptionalArgValueUse.+liveCaller + deps: in=0 (live=0 dead=0) out=1 + -> +TopLevelOptionalArgValueUse.+formatDate Live (annotated) Value +TransitiveType1.+convert Live (annotated) Value +TransitiveType1.+convertAlias Dead Value +TransitiveType2.+convertT2 @@ -5249,6 +5376,10 @@ Forward Liveness Analysis OptionalArgsLiveDead.res:1:1-33 optional argument fmt of function formatDate is never used + Warning Unused Argument + TopLevelOptionalArgValueUse.res:5:1-33 + optional argument fmt of function formatDate is never used + Warning Unused Argument OptArg.res:14:1-42 optional argument a of function twoArgs is never used @@ -5273,4 +5404,4 @@ Forward Liveness Analysis OptArg.res:26:1-70 optional argument c of function wrapfourArgs is always supplied (2 calls) - Analysis reported 319 issues (Incorrect Dead Annotation:1, Warning Dead Exception:2, Warning Dead Module:22, Warning Dead Type:94, Warning Dead Value:177, Warning Dead Value With Side Effects:5, Warning Redundant Optional Argument:6, Warning Unused Argument:12) + Analysis reported 319 issues (Incorrect Dead Annotation:1, Warning Dead Exception:2, Warning Dead Module:22, Warning Dead Type:93, Warning Dead Value:177, Warning Dead Value With Side Effects:5, Warning Redundant Optional Argument:6, Warning Unused Argument:13) From 2d7736437a894ba4c0f3d60c0ae2cec801bfb361 Mon Sep 17 00:00:00 2001 From: Christoph Knittel Date: Sat, 20 Jun 2026 16:54:57 +0700 Subject: [PATCH 04/10] Suppress optional arg warnings for escaped functions Signed-off-by: Christoph Knittel --- .../reanalyze/src/cross_file_items_store.ml | 19 --------- .../reanalyze/src/cross_file_items_store.mli | 7 ---- analysis/reanalyze/src/dead_common.ml | 8 ++-- analysis/reanalyze/src/dead_optional_args.ml | 15 ++----- analysis/reanalyze/src/dead_value.ml | 42 +++++++------------ analysis/reanalyze/src/decl.ml | 7 +--- analysis/reanalyze/src/reanalyze.ml | 20 ++------- .../deadcode/expected/deadcode.txt | 30 ++++++------- .../src/TopLevelOptionalArgValueUse.res | 6 +-- 9 files changed, 42 insertions(+), 112 deletions(-) diff --git a/analysis/reanalyze/src/cross_file_items_store.ml b/analysis/reanalyze/src/cross_file_items_store.ml index 5273d049c32..3a07beb11dc 100644 --- a/analysis/reanalyze/src/cross_file_items_store.ml +++ b/analysis/reanalyze/src/cross_file_items_store.ml @@ -65,22 +65,3 @@ let compute_optional_args_state (store : t) ~find_decl ~is_live : set_state pos_from updated_from; set_state pos_to updated_to)); state - -let compute_live_direct_optional_arg_calls (store : t) ~find_decl ~is_live : - Pos_set.t = - let direct_calls = ref Pos_set.empty in - iter_optional_arg_calls store (fun {Cross_file_items.pos_from; pos_to; _} -> - if is_live pos_from then - match find_decl pos_to with - | Some - { - Decl.decl_kind = - Value - { - optional_args_report = - Decl.Kind.ReportOptionalArgsIfDirectCall; - }; - } -> - direct_calls := Pos_set.add pos_to !direct_calls - | _ -> ()); - !direct_calls diff --git a/analysis/reanalyze/src/cross_file_items_store.mli b/analysis/reanalyze/src/cross_file_items_store.mli index 359a8e0f5fa..f8da5db43c8 100644 --- a/analysis/reanalyze/src/cross_file_items_store.mli +++ b/analysis/reanalyze/src/cross_file_items_store.mli @@ -28,10 +28,3 @@ val compute_optional_args_state : is_live:(Lexing.position -> bool) -> Optional_args_state.t (** Compute optional args state from calls and function references *) - -val compute_live_direct_optional_arg_calls : - t -> - find_decl:(Lexing.position -> Decl.t option) -> - is_live:(Lexing.position -> bool) -> - Pos_set.t -(** Compute live optional-arg call targets that hit reporting declarations directly *) diff --git a/analysis/reanalyze/src/dead_common.ml b/analysis/reanalyze/src/dead_common.ml index 25190d18678..a3f39096f8e 100644 --- a/analysis/reanalyze/src/dead_common.ml +++ b/analysis/reanalyze/src/dead_common.ml @@ -124,13 +124,13 @@ let addDeclaration_ ~config ~decls ~(file : File_context.t) ?pos_end ?pos_start Declarations.add decls pos decl) let add_value_declaration ~config ~decls ~file ?(is_toplevel = true) - ?(optional_args_report = Decl.Kind.NoOptionalArgReport) ~(loc : Location.t) - ~module_loc ?(optional_args = Optional_args.empty) ~path ~side_effects name - = + ?(reports_optional_args = false) ~(loc : Location.t) ~module_loc + ?(optional_args = Optional_args.empty) ~path ~side_effects name = name |> addDeclaration_ ~config ~decls ~file ~decl_kind: - (Value {is_toplevel; optional_args_report; optional_args; side_effects}) + (Value + {is_toplevel; reports_optional_args; optional_args; side_effects}) ~loc ~module_loc ~path (** Create a dead code issue. Pure - no side effects. *) diff --git a/analysis/reanalyze/src/dead_optional_args.ml b/analysis/reanalyze/src/dead_optional_args.ml index 0e9240c4280..cf4cde404c7 100644 --- a/analysis/reanalyze/src/dead_optional_args.ml +++ b/analysis/reanalyze/src/dead_optional_args.ml @@ -78,19 +78,10 @@ let add_references ~config ~cross_file ~(loc_from : Location.t) (** Check for optional args issues. Returns issues instead of logging. Uses optional_args_state map for final computed state. *) -let check ~optional_args_state ~direct_optional_arg_calls ~ann_store ~config:_ - decl : Issue.t list = - let should_report_optional_args = - let open Decl.Kind in - match decl.Decl.decl_kind with - | Value {optional_args_report = ReportOptionalArgs} -> true - | Value {optional_args_report = ReportOptionalArgsIfDirectCall} -> - Pos_set.mem decl.pos direct_optional_arg_calls - | _ -> false - in +let check ~optional_args_state ~ann_store ~config:_ decl : Issue.t list = match decl with - | {Decl.decl_kind = Value {optional_args}} - when active () && should_report_optional_args + | {Decl.decl_kind = Value {reports_optional_args = true; optional_args}} + when active () && not (Annotation_store.is_annotated_gentype_or_live ann_store decl.pos) -> diff --git a/analysis/reanalyze/src/dead_value.ml b/analysis/reanalyze/src/dead_value.ml index 4421caccc45..019f3fc79e7 100644 --- a/analysis/reanalyze/src/dead_value.ml +++ b/analysis/reanalyze/src/dead_value.ml @@ -27,28 +27,27 @@ let collect_value_binding ~config ~decls ~file ~(current_binding : Location.t) ({pat_desc = Tpat_any}, id, {loc = {loc_start; loc_ghost} as loc}) when (not loc_ghost) && not vb.vb_loc.loc_ghost -> let name = Ident.name id |> Name.create ~is_interface:false in - let optional_args, optional_args_report = - let open Decl.Kind in + let optional_args, reports_optional_args = match vb.vb_expr.exp_desc with | Texp_function {arity = Some arity; _} -> ( vb.vb_expr.exp_type |> (fun texpr -> Dead_optional_args.from_type_expr_with_arity texpr arity) |> Optional_args.from_list, - ReportOptionalArgs ) + true ) | Texp_function _ -> ( vb.vb_expr.exp_type |> Dead_optional_args.from_type_expr |> Optional_args.from_list, - ReportOptionalArgs ) + true ) | _ -> ( vb.vb_expr.exp_type |> Dead_optional_args.from_type_expr |> Optional_args.from_list, - NoOptionalArgReport ) + false ) in let exists = match Declarations.find_opt_builder decls loc_start with | Some {decl_kind = Value r} -> - r.optional_args_report <- optional_args_report; + r.reports_optional_args <- reports_optional_args; r.optional_args <- optional_args; true | _ -> false @@ -65,7 +64,7 @@ let collect_value_binding ~config ~decls ~file ~(current_binding : Location.t) let side_effects = Side_effects.check_expr vb.vb_expr in name |> add_value_declaration ~config ~decls ~file ~is_toplevel ~loc - ~module_loc:module_path.loc ~optional_args_report ~optional_args + ~module_loc:module_path.loc ~reports_optional_args ~optional_args ~path ~side_effects); (match Declarations.find_opt_builder decls loc_start with | None -> () @@ -174,24 +173,15 @@ let rec collect_expr ~config ~decls ~refs ~file_deps ~cross_file ~(last_binding : Location.t) super self (e : Typedtree.expression) = let loc_from = e.exp_loc in let binding = last_binding in - let require_direct_optional_arg_call pos = + let suppress_optional_arg_report pos = match Declarations.find_opt_builder decls pos with | Some - ({ - decl_kind = - Value - ({optional_args_report = Decl.Kind.ReportOptionalArgs} as - value_kind); - } as decl) -> + ({decl_kind = Value ({reports_optional_args = true} as value_kind)} as + decl) -> Declarations.replace_builder decls pos { decl with - decl_kind = - Value - { - value_kind with - optional_args_report = Decl.Kind.ReportOptionalArgsIfDirectCall; - }; + decl_kind = Value {value_kind with reports_optional_args = false}; } | _ -> () in @@ -230,7 +220,7 @@ let rec collect_expr ~config ~decls ~refs ~file_deps ~cross_file match arg with | Some arg -> arg |> value_use_target_pos - |> Option.iter require_direct_optional_arg_call + |> Option.iter suppress_optional_arg_report | _ -> ()) | Texp_let ( (* generated for functions with optional args *) @@ -300,7 +290,7 @@ let rec collect_expr ~config ~decls ~refs ~file_deps ~cross_file match record_label_definition with | Typedtree.Overridden (_, e) -> ( e |> value_use_target_pos - |> Option.iter require_direct_optional_arg_call; + |> Option.iter suppress_optional_arg_report; match e with | {exp_loc; _} when exp_loc.loc_ghost -> (* Punned field in OCaml projects has ghost location in expression *) @@ -444,10 +434,8 @@ let rec process_signature_item ~config ~decls ~file ~do_types ~do_values val_type |> Dead_optional_args.from_type_expr |> Optional_args.from_list in - let optional_args_report = - if Optional_args.is_empty optional_args then - Decl.Kind.NoOptionalArgReport - else Decl.Kind.ReportOptionalArgs + let reports_optional_args = + not (Optional_args.is_empty optional_args) in (* if Ident.name id = "someValue" then @@ -455,7 +443,7 @@ let rec process_signature_item ~config ~decls ~file ~do_types ~do_values Ident.name id |> Name.create ~is_interface:false |> add_value_declaration ~config ~decls ~file ~loc ~module_loc - ~optional_args_report ~optional_args ~path ~side_effects:false + ~reports_optional_args ~optional_args ~path ~side_effects:false | Sig_module (id, {Types.md_type = module_type; md_loc = module_loc}, _) | Sig_modtype (id, {Types.mtd_type = Some module_type; mtd_loc = module_loc}) -> diff --git a/analysis/reanalyze/src/decl.ml b/analysis/reanalyze/src/decl.ml index e3ababece6e..9ddaad60ac8 100644 --- a/analysis/reanalyze/src/decl.ml +++ b/analysis/reanalyze/src/decl.ml @@ -1,18 +1,13 @@ (** Declaration types for dead code analysis. *) module Kind = struct - type optional_args_report = - | NoOptionalArgReport - | ReportOptionalArgs - | ReportOptionalArgsIfDirectCall - type t = | Exception | RecordLabel | VariantCase | Value of { is_toplevel: bool; - mutable optional_args_report: optional_args_report; + mutable reports_optional_args: bool; mutable optional_args: Optional_args.t; side_effects: bool; } diff --git a/analysis/reanalyze/src/reanalyze.ml b/analysis/reanalyze/src/reanalyze.ml index f0b3aaabbdc..062df5c6eec 100644 --- a/analysis/reanalyze/src/reanalyze.ml +++ b/analysis/reanalyze/src/reanalyze.ml @@ -347,17 +347,12 @@ let run_analysis ~dce_config ~cmt_root ~reactive_collection ~reactive_merge Cross_file_items_store.compute_optional_args_state cross_file_store ~find_decl ~is_live in - let direct_optional_arg_calls = - Cross_file_items_store.compute_live_direct_optional_arg_calls - cross_file_store ~find_decl ~is_live - in (* Iterate live declarations and check for optional args issues *) let issues = ref [] in Reactive_solver.iter_live_decls ~t:solver (fun decl -> let decl_issues = - Dead_optional_args.check ~optional_args_state - ~direct_optional_arg_calls ~ann_store ~config:dce_config - decl + Dead_optional_args.check ~optional_args_state ~ann_store + ~config:dce_config decl in issues := List.rev_append decl_issues !issues); List.rev !issues @@ -404,21 +399,14 @@ let run_analysis ~dce_config ~cmt_root ~reactive_collection ~reactive_merge ~find_decl:(Declaration_store.find_opt decl_store) ~is_live in - let direct_optional_arg_calls = - Cross_file_items_store.compute_live_direct_optional_arg_calls - cross_file_store - ~find_decl:(Declaration_store.find_opt decl_store) - ~is_live - in (* Collect optional args issues only for live declarations *) let optional_args_issues = Declaration_store.fold (fun _pos decl acc -> if Decl.is_live decl then let issues = - Dead_optional_args.check ~optional_args_state - ~direct_optional_arg_calls ~ann_store ~config:dce_config - decl + Dead_optional_args.check ~optional_args_state ~ann_store + ~config:dce_config decl in List.rev_append issues acc else acc) diff --git a/tests/analysis_tests/tests-reanalyze/deadcode/expected/deadcode.txt b/tests/analysis_tests/tests-reanalyze/deadcode/expected/deadcode.txt index e2e6708e0e7..bcce26a3990 100644 --- a/tests/analysis_tests/tests-reanalyze/deadcode/expected/deadcode.txt +++ b/tests/analysis_tests/tests-reanalyze/deadcode/expected/deadcode.txt @@ -1502,15 +1502,15 @@ Scanning ToSuppress.cmt Source:ToSuppress.res addValueDeclaration +toSuppress ToSuppress.res:1:4 path:+ToSuppress Scanning TopLevelOptionalArgValueUse.cmt Source:TopLevelOptionalArgValueUse.res - addValueDeclaration +formatDate TopLevelOptionalArgValueUse.res:5:4 path:+TopLevelOptionalArgValueUse - addValueDeclaration +takesFn TopLevelOptionalArgValueUse.res:7:4 path:+TopLevelOptionalArgValueUse - addValueDeclaration +liveCaller TopLevelOptionalArgValueUse.res:11:4 path:+TopLevelOptionalArgValueUse - addValueReference TopLevelOptionalArgValueUse.res:5:4 --> TopLevelOptionalArgValueUse.res:5:26 - addValueReference TopLevelOptionalArgValueUse.res:9:8 --> TopLevelOptionalArgValueUse.res:5:4 - addValueReference TopLevelOptionalArgValueUse.res:9:0 --> TopLevelOptionalArgValueUse.res:7:4 - DeadOptionalArgs.addReferences formatDate called with optional argNames: argNamesMaybe: TopLevelOptionalArgValueUse.res:11:23 - addValueReference TopLevelOptionalArgValueUse.res:11:4 --> TopLevelOptionalArgValueUse.res:5:4 - addValueReference TopLevelOptionalArgValueUse.res:13:8 --> TopLevelOptionalArgValueUse.res:11:4 + addValueDeclaration +formatDate TopLevelOptionalArgValueUse.res:4:4 path:+TopLevelOptionalArgValueUse + addValueDeclaration +takesFn TopLevelOptionalArgValueUse.res:6:4 path:+TopLevelOptionalArgValueUse + addValueDeclaration +liveCaller TopLevelOptionalArgValueUse.res:10:4 path:+TopLevelOptionalArgValueUse + addValueReference TopLevelOptionalArgValueUse.res:4:4 --> TopLevelOptionalArgValueUse.res:4:26 + addValueReference TopLevelOptionalArgValueUse.res:8:8 --> TopLevelOptionalArgValueUse.res:4:4 + addValueReference TopLevelOptionalArgValueUse.res:8:0 --> TopLevelOptionalArgValueUse.res:6:4 + DeadOptionalArgs.addReferences formatDate called with optional argNames: argNamesMaybe: TopLevelOptionalArgValueUse.res:10:23 + addValueReference TopLevelOptionalArgValueUse.res:10:4 --> TopLevelOptionalArgValueUse.res:4:4 + addValueReference TopLevelOptionalArgValueUse.res:12:8 --> TopLevelOptionalArgValueUse.res:10:4 Scanning TransitiveType1.cmt Source:TransitiveType1.res addValueDeclaration +convert TransitiveType1.res:2:4 path:+TransitiveType1 addValueDeclaration +convertAlias TransitiveType1.res:5:4 path:+TransitiveType1 @@ -2086,6 +2086,7 @@ Forward Liveness Analysis Root (annotated): Value +UseImportJsValue.+useTypeImportedInOtherModule Root (annotated): Value +Hooks.Inner.+make Root (external ref): Value +OptArg.+foo + Root (external ref): Value +TopLevelOptionalArgValueUse.+formatDate Root (annotated): Value +Variants.+fortytwoOK Root (external ref): Value OptArg.+bar Root (annotated): Value +Records.+payloadValue @@ -2113,7 +2114,6 @@ Forward Liveness Analysis Root (external ref): Value +OptArg.+threeArgs Root (annotated): Value +Types.+setMatch Root (external ref): Value +DeadTest.+deadIncorrect - Root (external ref): Value +TopLevelOptionalArgValueUse.+takesFn Root (external ref): RecordLabel +TypeReexport.UseReexported.reexportedType.usedField Root (annotated): Value +Docstrings.+signMessage Root (external ref): RecordLabel +Hooks.Inner.Inner2.props.vehicle @@ -2138,7 +2138,6 @@ Forward Liveness Analysis Root (annotated): Value +Docstrings.+unitArgWithConversionU Root (annotated): Value +Tuples.+computeArea Root (annotated): Value +References.+get - Root (external ref): Value +TopLevelOptionalArgValueUse.+formatDate Root (annotated): Value +ModuleAliases.+testNested Root (external ref): Value +FirstClassModules.SomeFunctor.+ww Root (annotated): Value +Hooks.Inner.Inner2.+make @@ -2163,7 +2162,6 @@ Forward Liveness Analysis Root (annotated): Value +Variants.+id2 Root (annotated): Value +Uncurried.+sumU Root (external ref): Value +TestOptArg.+bar - Root (external ref): Value +TopLevelOptionalArgValueUse.+liveCaller Root (external ref): RecordLabel +DeadTest.record.yyy Root (annotated): Value +Docstrings.+one Root (external ref): Value +OptArg.+twoArgs @@ -2242,6 +2240,7 @@ Forward Liveness Analysis Root (external ref): Value +DeadTest.+ira Root (annotated): Value +FirstClassModules.+testConvert Root (external ref): RecordLabel +Records.coord.z + Root (external ref): Value +TopLevelOptionalArgValueUse.+liveCaller Root (annotated): Value +Types.+someIntList Root (annotated): Value +Types.+jsString2T Root (external ref): VariantCase +Unison.stack.Empty @@ -2289,6 +2288,7 @@ Forward Liveness Analysis Root (annotated): Value +ScopedAnnotationsOverride.M.NestedInLive.+nestedLive Root (external ref): Value +FirstClassModules.M.+y Root (external ref): RecordLabel +Uncurried.authU.loginU + Root (external ref): Value +TopLevelOptionalArgValueUse.+takesFn Root (annotated): Value +ModuleAliases.+testInner2 Root (annotated): RecordLabel +ImportHooks.props.person Root (external ref): Value DeadValueTest.+valueAlive @@ -5376,10 +5376,6 @@ Forward Liveness Analysis OptionalArgsLiveDead.res:1:1-33 optional argument fmt of function formatDate is never used - Warning Unused Argument - TopLevelOptionalArgValueUse.res:5:1-33 - optional argument fmt of function formatDate is never used - Warning Unused Argument OptArg.res:14:1-42 optional argument a of function twoArgs is never used @@ -5404,4 +5400,4 @@ Forward Liveness Analysis OptArg.res:26:1-70 optional argument c of function wrapfourArgs is always supplied (2 calls) - Analysis reported 319 issues (Incorrect Dead Annotation:1, Warning Dead Exception:2, Warning Dead Module:22, Warning Dead Type:93, Warning Dead Value:177, Warning Dead Value With Side Effects:5, Warning Redundant Optional Argument:6, Warning Unused Argument:13) + Analysis reported 318 issues (Incorrect Dead Annotation:1, Warning Dead Exception:2, Warning Dead Module:22, Warning Dead Type:93, Warning Dead Value:177, Warning Dead Value With Side Effects:5, Warning Redundant Optional Argument:6, Warning Unused Argument:12) diff --git a/tests/analysis_tests/tests-reanalyze/deadcode/src/TopLevelOptionalArgValueUse.res b/tests/analysis_tests/tests-reanalyze/deadcode/src/TopLevelOptionalArgValueUse.res index 8ed461de7b4..73d28038cae 100644 --- a/tests/analysis_tests/tests-reanalyze/deadcode/src/TopLevelOptionalArgValueUse.res +++ b/tests/analysis_tests/tests-reanalyze/deadcode/src/TopLevelOptionalArgValueUse.res @@ -1,6 +1,5 @@ -/* Repro for the optional-arg ownership regression on fix-reanalyze. - Passing a function with optional args as a first-class value in a top-level - eval should not suppress warnings on the function declaration itself. */ +/* Passing a function with optional args as a first-class value must suppress + optional-arg warnings even when the function also has a direct call. */ let formatDate = (~fmt=?, s) => s @@ -11,4 +10,3 @@ takesFn(formatDate) let liveCaller = () => formatDate("2024-01-01") let _ = liveCaller() - From ee7490d8f4c9743ed69d6e971c50863f8593b48e Mon Sep 17 00:00:00 2001 From: Christoph Knittel Date: Sat, 20 Jun 2026 17:01:41 +0700 Subject: [PATCH 05/10] Make optional arg value escapes liveness-aware Signed-off-by: Christoph Knittel --- analysis/reanalyze/src/cross_file_items.ml | 29 +++++++- analysis/reanalyze/src/cross_file_items.mli | 11 ++- .../reanalyze/src/cross_file_items_store.ml | 16 +++++ .../reanalyze/src/cross_file_items_store.mli | 8 +++ analysis/reanalyze/src/dead_optional_args.ml | 4 +- analysis/reanalyze/src/dead_value.ml | 29 ++++---- analysis/reanalyze/src/reactive_merge.ml | 9 ++- analysis/reanalyze/src/reanalyze.ml | 14 +++- .../deadcode/expected/deadcode.txt | 72 ++++++++++++++----- .../src/TopLevelOptionalArgValueUse.res | 13 +++- 10 files changed, 161 insertions(+), 44 deletions(-) diff --git a/analysis/reanalyze/src/cross_file_items.ml b/analysis/reanalyze/src/cross_file_items.ml index 81ccc951119..6d0554a12dc 100644 --- a/analysis/reanalyze/src/cross_file_items.ml +++ b/analysis/reanalyze/src/cross_file_items.ml @@ -16,24 +16,36 @@ type optional_arg_call = { type function_ref = {pos_from: Lexing.position; pos_to: Lexing.position} +type optional_arg_value_escape = { + 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; + optional_arg_value_escapes: optional_arg_value_escape list; } type builder = { mutable exception_refs: exception_ref list; mutable optional_arg_calls: optional_arg_call list; mutable function_refs: function_ref list; + mutable optional_arg_value_escapes: optional_arg_value_escape list; } (** {2 Builder API} *) let create_builder () : builder = - {exception_refs = []; optional_arg_calls = []; function_refs = []} + { + exception_refs = []; + optional_arg_calls = []; + function_refs = []; + optional_arg_value_escapes = []; + } let add_exception_ref (b : builder) ~exception_path ~loc_from = b.exception_refs <- {exception_path; loc_from} :: b.exception_refs @@ -46,6 +58,10 @@ let add_optional_arg_call (b : builder) ~pos_from ~pos_to ~arg_names let add_function_reference (b : builder) ~pos_from ~pos_to = b.function_refs <- {pos_from; pos_to} :: b.function_refs +let add_optional_arg_value_escape (b : builder) ~pos_from ~pos_to = + b.optional_arg_value_escapes <- + {pos_from; pos_to} :: b.optional_arg_value_escapes + (** {2 Merge API} *) let merge_all (builders : builder list) : t = @@ -56,7 +72,15 @@ let merge_all (builders : builder list) : t = 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} + let optional_arg_value_escapes = + builders |> List.concat_map (fun b -> b.optional_arg_value_escapes) + in + { + exception_refs; + optional_arg_calls; + function_refs; + optional_arg_value_escapes; + } (** {2 Builder extraction for reactive merge} *) @@ -65,6 +89,7 @@ let builder_to_t (builder : builder) : t = exception_refs = builder.exception_refs; optional_arg_calls = builder.optional_arg_calls; function_refs = builder.function_refs; + optional_arg_value_escapes = builder.optional_arg_value_escapes; } (** {2 Processing API} *) diff --git a/analysis/reanalyze/src/cross_file_items.mli b/analysis/reanalyze/src/cross_file_items.mli index 1fb9b92cbf1..4863b92e73a 100644 --- a/analysis/reanalyze/src/cross_file_items.mli +++ b/analysis/reanalyze/src/cross_file_items.mli @@ -18,12 +18,18 @@ type optional_arg_call = { type function_ref = {pos_from: Lexing.position; pos_to: Lexing.position} +type optional_arg_value_escape = { + 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; + optional_arg_value_escapes: optional_arg_value_escape list; } (** Immutable cross-file items - for processing after merge *) @@ -49,7 +55,10 @@ val add_optional_arg_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). *) + +val add_optional_arg_value_escape : + builder -> pos_from:Lexing.position -> pos_to:Lexing.position -> unit +(** Record an optional-arg function used as a first-class value. *) (** {2 Merge API} *) diff --git a/analysis/reanalyze/src/cross_file_items_store.ml b/analysis/reanalyze/src/cross_file_items_store.ml index 3a07beb11dc..2fe61c74574 100644 --- a/analysis/reanalyze/src/cross_file_items_store.ml +++ b/analysis/reanalyze/src/cross_file_items_store.ml @@ -28,6 +28,15 @@ let iter_function_refs t f = (fun _path items -> List.iter f items.Cross_file_items.function_refs) r +let iter_optional_arg_value_escapes t f = + match t with + | Frozen cfi -> List.iter f cfi.Cross_file_items.optional_arg_value_escapes + | Reactive r -> + Reactive.iter + (fun _path items -> + List.iter f items.Cross_file_items.optional_arg_value_escapes) + r + (** Compute optional args state from calls and function references. Returns a map from position to final OptionalArgs.t state. Pure function - does not mutate declarations. *) @@ -65,3 +74,10 @@ let compute_optional_args_state (store : t) ~find_decl ~is_live : set_state pos_from updated_from; set_state pos_to updated_to)); state + +let compute_live_optional_arg_value_escapes (store : t) ~is_live : Pos_set.t = + let escapes = ref Pos_set.empty in + iter_optional_arg_value_escapes store + (fun {Cross_file_items.pos_from; pos_to} -> + if is_live pos_from then escapes := Pos_set.add pos_to !escapes); + !escapes diff --git a/analysis/reanalyze/src/cross_file_items_store.mli b/analysis/reanalyze/src/cross_file_items_store.mli index f8da5db43c8..d3729d6339f 100644 --- a/analysis/reanalyze/src/cross_file_items_store.mli +++ b/analysis/reanalyze/src/cross_file_items_store.mli @@ -22,9 +22,17 @@ val iter_optional_arg_calls : val iter_function_refs : t -> (Cross_file_items.function_ref -> unit) -> unit (** Iterate over all function refs *) +val iter_optional_arg_value_escapes : + t -> (Cross_file_items.optional_arg_value_escape -> unit) -> unit +(** Iterate over optional-arg functions used as first-class values *) + val compute_optional_args_state : t -> find_decl:(Lexing.position -> Decl.t option) -> is_live:(Lexing.position -> bool) -> Optional_args_state.t (** Compute optional args state from calls and function references *) + +val compute_live_optional_arg_value_escapes : + t -> is_live:(Lexing.position -> bool) -> Pos_set.t +(** Compute optional-arg declarations with live first-class value escapes *) diff --git a/analysis/reanalyze/src/dead_optional_args.ml b/analysis/reanalyze/src/dead_optional_args.ml index cf4cde404c7..8e7012cefe5 100644 --- a/analysis/reanalyze/src/dead_optional_args.ml +++ b/analysis/reanalyze/src/dead_optional_args.ml @@ -78,10 +78,12 @@ let add_references ~config ~cross_file ~(loc_from : Location.t) (** Check for optional args issues. Returns issues instead of logging. Uses optional_args_state map for final computed state. *) -let check ~optional_args_state ~ann_store ~config:_ decl : Issue.t list = +let check ~optional_args_state ~optional_arg_value_escapes ~ann_store ~config:_ + decl : Issue.t list = match decl with | {Decl.decl_kind = Value {reports_optional_args = true; optional_args}} when active () + && (not (Pos_set.mem decl.pos optional_arg_value_escapes)) && not (Annotation_store.is_annotated_gentype_or_live ann_store decl.pos) -> diff --git a/analysis/reanalyze/src/dead_value.ml b/analysis/reanalyze/src/dead_value.ml index 019f3fc79e7..ecb31109ddc 100644 --- a/analysis/reanalyze/src/dead_value.ml +++ b/analysis/reanalyze/src/dead_value.ml @@ -173,18 +173,20 @@ let rec collect_expr ~config ~decls ~refs ~file_deps ~cross_file ~(last_binding : Location.t) super self (e : Typedtree.expression) = let loc_from = e.exp_loc in let binding = last_binding in - let suppress_optional_arg_report pos = - match Declarations.find_opt_builder decls pos with - | Some - ({decl_kind = Value ({reports_optional_args = true} as value_kind)} as - decl) -> - Declarations.replace_builder decls pos - { - decl with - decl_kind = Value {value_kind with reports_optional_args = false}; - } + let add_optional_arg_value_escape pos_from pos_to = + match Declarations.find_opt_builder decls pos_to with + | Some {decl_kind = Value {reports_optional_args = true}} -> + Cross_file_items.add_optional_arg_value_escape cross_file ~pos_from + ~pos_to | _ -> () in + let add_optional_arg_value_escape_from_expr arg = + let pos_from = + if binding = Location.none then loc_from.loc_start else binding.loc_start + in + arg |> value_use_target_pos + |> Option.iter (add_optional_arg_value_escape pos_from) + in (match e.exp_desc with | Texp_ident (_path, _, {Types.val_loc = {loc_ghost = false; _} as loc_to}) -> (* if Path.name _path = "rc" then assert false; *) @@ -218,9 +220,7 @@ let rec collect_expr ~config ~decls ~refs ~file_deps ~cross_file args |> List.iter (fun (_, arg) -> match arg with - | Some arg -> - arg |> value_use_target_pos - |> Option.iter suppress_optional_arg_report + | Some arg -> add_optional_arg_value_escape_from_expr arg | _ -> ()) | Texp_let ( (* generated for functions with optional args *) @@ -289,8 +289,7 @@ let rec collect_expr ~config ~decls ~refs ~file_deps ~cross_file |> Array.iter (fun (_, record_label_definition, _) -> match record_label_definition with | Typedtree.Overridden (_, e) -> ( - e |> value_use_target_pos - |> Option.iter suppress_optional_arg_report; + add_optional_arg_value_escape_from_expr e; match e with | {exp_loc; _} when exp_loc.loc_ghost -> (* Punned field in OCaml projects has ghost location in expression *) diff --git a/analysis/reanalyze/src/reactive_merge.ml b/analysis/reanalyze/src/reactive_merge.ml index 6c376e0efa6..14dc222d719 100644 --- a/analysis/reanalyze/src/reactive_merge.ml +++ b/analysis/reanalyze/src/reactive_merge.ml @@ -88,6 +88,8 @@ let create (source : (string, Dce_file_processing.file_data option) Reactive.t) exception_refs = a.exception_refs @ b.exception_refs; optional_arg_calls = a.optional_arg_calls @ b.optional_arg_calls; function_refs = a.function_refs @ b.function_refs; + optional_arg_value_escapes = + a.optional_arg_value_escapes @ b.optional_arg_value_escapes; }) () in @@ -225,17 +227,22 @@ let collect_cross_file_items (t : t) : Cross_file_items.t = let exception_refs = ref [] in let optional_arg_calls = ref [] in let function_refs = ref [] in + let optional_arg_value_escapes = ref [] in Reactive.iter (fun _path items -> exception_refs := items.Cross_file_items.exception_refs @ !exception_refs; optional_arg_calls := items.Cross_file_items.optional_arg_calls @ !optional_arg_calls; - function_refs := items.Cross_file_items.function_refs @ !function_refs) + function_refs := items.Cross_file_items.function_refs @ !function_refs; + optional_arg_value_escapes := + items.Cross_file_items.optional_arg_value_escapes + @ !optional_arg_value_escapes) t.cross_file_items; { Cross_file_items.exception_refs = !exception_refs; optional_arg_calls = !optional_arg_calls; function_refs = !function_refs; + optional_arg_value_escapes = !optional_arg_value_escapes; } (** Convert reactive file deps to FileDeps.t for solver. diff --git a/analysis/reanalyze/src/reanalyze.ml b/analysis/reanalyze/src/reanalyze.ml index 062df5c6eec..b96ad7a7938 100644 --- a/analysis/reanalyze/src/reanalyze.ml +++ b/analysis/reanalyze/src/reanalyze.ml @@ -347,11 +347,16 @@ let run_analysis ~dce_config ~cmt_root ~reactive_collection ~reactive_merge Cross_file_items_store.compute_optional_args_state cross_file_store ~find_decl ~is_live in + let optional_arg_value_escapes = + Cross_file_items_store.compute_live_optional_arg_value_escapes + cross_file_store ~is_live + in (* Iterate live declarations and check for optional args issues *) let issues = ref [] in Reactive_solver.iter_live_decls ~t:solver (fun decl -> let decl_issues = - Dead_optional_args.check ~optional_args_state ~ann_store + Dead_optional_args.check ~optional_args_state + ~optional_arg_value_escapes ~ann_store ~config:dce_config decl in issues := List.rev_append decl_issues !issues); @@ -399,13 +404,18 @@ let run_analysis ~dce_config ~cmt_root ~reactive_collection ~reactive_merge ~find_decl:(Declaration_store.find_opt decl_store) ~is_live in + let optional_arg_value_escapes = + Cross_file_items_store.compute_live_optional_arg_value_escapes + cross_file_store ~is_live + in (* Collect optional args issues only for live declarations *) let optional_args_issues = Declaration_store.fold (fun _pos decl acc -> if Decl.is_live decl then let issues = - Dead_optional_args.check ~optional_args_state ~ann_store + Dead_optional_args.check ~optional_args_state + ~optional_arg_value_escapes ~ann_store ~config:dce_config decl in List.rev_append issues acc diff --git a/tests/analysis_tests/tests-reanalyze/deadcode/expected/deadcode.txt b/tests/analysis_tests/tests-reanalyze/deadcode/expected/deadcode.txt index bcce26a3990..f47bfce6318 100644 --- a/tests/analysis_tests/tests-reanalyze/deadcode/expected/deadcode.txt +++ b/tests/analysis_tests/tests-reanalyze/deadcode/expected/deadcode.txt @@ -1503,14 +1503,23 @@ addValueDeclaration +toSuppress ToSuppress.res:1:4 path:+ToSuppress Scanning TopLevelOptionalArgValueUse.cmt Source:TopLevelOptionalArgValueUse.res addValueDeclaration +formatDate TopLevelOptionalArgValueUse.res:4:4 path:+TopLevelOptionalArgValueUse - addValueDeclaration +takesFn TopLevelOptionalArgValueUse.res:6:4 path:+TopLevelOptionalArgValueUse - addValueDeclaration +liveCaller TopLevelOptionalArgValueUse.res:10:4 path:+TopLevelOptionalArgValueUse + addValueDeclaration +formatDateEscapes TopLevelOptionalArgValueUse.res:5:4 path:+TopLevelOptionalArgValueUse + addValueDeclaration +takesFn TopLevelOptionalArgValueUse.res:7:4 path:+TopLevelOptionalArgValueUse + addValueDeclaration +deadEscape TopLevelOptionalArgValueUse.res:9:4 path:+TopLevelOptionalArgValueUse + addValueDeclaration +liveCaller TopLevelOptionalArgValueUse.res:11:4 path:+TopLevelOptionalArgValueUse + addValueDeclaration +liveEscapeCaller TopLevelOptionalArgValueUse.res:13:4 path:+TopLevelOptionalArgValueUse addValueReference TopLevelOptionalArgValueUse.res:4:4 --> TopLevelOptionalArgValueUse.res:4:26 - addValueReference TopLevelOptionalArgValueUse.res:8:8 --> TopLevelOptionalArgValueUse.res:4:4 - addValueReference TopLevelOptionalArgValueUse.res:8:0 --> TopLevelOptionalArgValueUse.res:6:4 - DeadOptionalArgs.addReferences formatDate called with optional argNames: argNamesMaybe: TopLevelOptionalArgValueUse.res:10:23 - addValueReference TopLevelOptionalArgValueUse.res:10:4 --> TopLevelOptionalArgValueUse.res:4:4 - addValueReference TopLevelOptionalArgValueUse.res:12:8 --> TopLevelOptionalArgValueUse.res:10:4 + addValueReference TopLevelOptionalArgValueUse.res:5:4 --> TopLevelOptionalArgValueUse.res:5:33 + addValueReference TopLevelOptionalArgValueUse.res:9:4 --> TopLevelOptionalArgValueUse.res:4:4 + addValueReference TopLevelOptionalArgValueUse.res:9:4 --> TopLevelOptionalArgValueUse.res:7:4 + DeadOptionalArgs.addReferences formatDate called with optional argNames: argNamesMaybe: TopLevelOptionalArgValueUse.res:11:23 + addValueReference TopLevelOptionalArgValueUse.res:11:4 --> TopLevelOptionalArgValueUse.res:4:4 + DeadOptionalArgs.addReferences formatDateEscapes called with optional argNames: argNamesMaybe: TopLevelOptionalArgValueUse.res:15:2 + addValueReference TopLevelOptionalArgValueUse.res:13:4 --> TopLevelOptionalArgValueUse.res:5:4 + addValueReference TopLevelOptionalArgValueUse.res:13:4 --> TopLevelOptionalArgValueUse.res:5:4 + addValueReference TopLevelOptionalArgValueUse.res:13:4 --> TopLevelOptionalArgValueUse.res:7:4 + addValueReference TopLevelOptionalArgValueUse.res:18:8 --> TopLevelOptionalArgValueUse.res:11:4 + addValueReference TopLevelOptionalArgValueUse.res:19:8 --> TopLevelOptionalArgValueUse.res:13:4 Scanning TransitiveType1.cmt Source:TransitiveType1.res addValueDeclaration +convert TransitiveType1.res:2:4 path:+TransitiveType1 addValueDeclaration +convertAlias TransitiveType1.res:5:4 path:+TransitiveType1 @@ -2014,9 +2023,9 @@ Forward Liveness Analysis - decls: 712 - roots(external targets): 147 - decl-deps: decls_with_out=422 edges_to_decls=302 + decls: 715 + roots(external targets): 146 + decl-deps: decls_with_out=425 edges_to_decls=306 Root (external ref): Value +FirstClassModules.M.InnerModule2.+k Root (external ref): VariantCase DeadRT.moduleAccessPath.Root @@ -2039,6 +2048,7 @@ Forward Liveness Analysis Root (external ref): Value +OptionalArgsLiveDead.+liveCaller Root (annotated): RecordLabel +ImportHookDefault.props.renderMe Root (annotated): Value +TypeParams3.+test + Root (external ref): Value +TopLevelOptionalArgValueUse.+liveCaller Root (annotated): Value +Variants.+sunday Root (annotated): Value +NestedModules.Universe.Nested2.Nested3.+nested3Value Root (external ref): RecordLabel +Unison.t.doc @@ -2052,6 +2062,7 @@ Forward Liveness Analysis Root (annotated): Value +Uncurried.+uncurried3 Root (external ref): Value +Newton.+f Root (external ref): RecordLabel +Records.record.v + Root (external ref): Value +TopLevelOptionalArgValueUse.+liveEscapeCaller Root (external ref): VariantCase +DeadTest.VariantUsedOnlyInImplementation.t.A Root (external ref): Value +ContextOptionalArgs.ComponentUsingAction.+make Root (external ref): RecordLabel +Records.person.address @@ -2086,7 +2097,6 @@ Forward Liveness Analysis Root (annotated): Value +UseImportJsValue.+useTypeImportedInOtherModule Root (annotated): Value +Hooks.Inner.+make Root (external ref): Value +OptArg.+foo - Root (external ref): Value +TopLevelOptionalArgValueUse.+formatDate Root (annotated): Value +Variants.+fortytwoOK Root (external ref): Value OptArg.+bar Root (annotated): Value +Records.+payloadValue @@ -2240,7 +2250,6 @@ Forward Liveness Analysis Root (external ref): Value +DeadTest.+ira Root (annotated): Value +FirstClassModules.+testConvert Root (external ref): RecordLabel +Records.coord.z - Root (external ref): Value +TopLevelOptionalArgValueUse.+liveCaller Root (annotated): Value +Types.+someIntList Root (annotated): Value +Types.+jsString2T Root (external ref): VariantCase +Unison.stack.Empty @@ -2288,7 +2297,6 @@ Forward Liveness Analysis Root (annotated): Value +ScopedAnnotationsOverride.M.NestedInLive.+nestedLive Root (external ref): Value +FirstClassModules.M.+y Root (external ref): RecordLabel +Uncurried.authU.loginU - Root (external ref): Value +TopLevelOptionalArgValueUse.+takesFn Root (annotated): Value +ModuleAliases.+testInner2 Root (annotated): RecordLabel +ImportHooks.props.person Root (external ref): Value DeadValueTest.+valueAlive @@ -2356,15 +2364,18 @@ Forward Liveness Analysis Root (annotated): Value +UseImportJsValue.+useGetProp Root (annotated): Value +Hooks.+functionWithRenamedArgs - 334 roots found + 333 roots found Propagate: DeadRT.moduleAccessPath.Root -> +DeadRT.moduleAccessPath.Root Propagate: +TypeReexportCrossFileB.reexportedRecord.usedField -> +TypeReexportCrossFileA.originalRecord.usedField Propagate: +DeadTypeTest.deadType.OnlyInImplementation -> DeadTypeTest.deadType.OnlyInImplementation Propagate: +OptionalArgsLiveDead.+liveCaller -> +OptionalArgsLiveDead.+formatDate + Propagate: +TopLevelOptionalArgValueUse.+liveCaller -> +TopLevelOptionalArgValueUse.+formatDate Propagate: +Newton.+f -> +Newton.+- Propagate: +Newton.+f -> +Newton.++ Propagate: +Newton.+f -> +Newton.+* + Propagate: +TopLevelOptionalArgValueUse.+liveEscapeCaller -> +TopLevelOptionalArgValueUse.+formatDateEscapes + Propagate: +TopLevelOptionalArgValueUse.+liveEscapeCaller -> +TopLevelOptionalArgValueUse.+takesFn Propagate: +DeadTest.VariantUsedOnlyInImplementation.t.A -> +DeadTest.VariantUsedOnlyInImplementation.t.A Propagate: +ContextOptionalArgs.ComponentUsingAction.+make -> +ContextOptionalArgs.NotificationProvider.+useNotification Propagate: +DeadTest.+thisIsMarkedLive -> +DeadTest.+thisIsKeptAlive @@ -2414,7 +2425,7 @@ Forward Liveness Analysis Propagate: +DeadTest.MM.+x -> +DeadTest.MM.+y Propagate: +ImportJsValue.AbsoluteValue.+getAbs -> +ImportJsValue.AbsoluteValue.+getAbs - 55 declarations marked live via propagation + 58 declarations marked live via propagation Dead VariantCase +AutoAnnotate.variant.R Dead RecordLabel +AutoAnnotate.record.variant @@ -3789,13 +3800,28 @@ Forward Liveness Analysis deps: in=0 (live=0 dead=0) out=1 -> +TestPromise.fromPayload.s Dead Value +ToSuppress.+toSuppress - Live (external ref) Value +TopLevelOptionalArgValueUse.+formatDate - deps: in=1 (live=1 dead=0) out=0 + Live (propagated) Value +TopLevelOptionalArgValueUse.+formatDate + deps: in=2 (live=1 dead=1) out=0 + <- +TopLevelOptionalArgValueUse.+deadEscape (dead) <- +TopLevelOptionalArgValueUse.+liveCaller (live) - Live (external ref) Value +TopLevelOptionalArgValueUse.+takesFn + Live (propagated) Value +TopLevelOptionalArgValueUse.+formatDateEscapes + deps: in=1 (live=1 dead=0) out=0 + <- +TopLevelOptionalArgValueUse.+liveEscapeCaller (live) + Live (propagated) Value +TopLevelOptionalArgValueUse.+takesFn + deps: in=2 (live=1 dead=1) out=0 + <- +TopLevelOptionalArgValueUse.+deadEscape (dead) + <- +TopLevelOptionalArgValueUse.+liveEscapeCaller (live) + Dead Value +TopLevelOptionalArgValueUse.+deadEscape + deps: in=0 (live=0 dead=0) out=2 + -> +TopLevelOptionalArgValueUse.+formatDate + -> +TopLevelOptionalArgValueUse.+takesFn Live (external ref) Value +TopLevelOptionalArgValueUse.+liveCaller deps: in=0 (live=0 dead=0) out=1 -> +TopLevelOptionalArgValueUse.+formatDate + Live (external ref) Value +TopLevelOptionalArgValueUse.+liveEscapeCaller + deps: in=0 (live=0 dead=0) out=2 + -> +TopLevelOptionalArgValueUse.+formatDateEscapes + -> +TopLevelOptionalArgValueUse.+takesFn Live (annotated) Value +TransitiveType1.+convert Live (annotated) Value +TransitiveType1.+convertAlias Dead Value +TransitiveType2.+convertT2 @@ -5156,6 +5182,10 @@ Forward Liveness Analysis TestPromise.res:11:19-32 toPayload.result is a record label never used to read a value + Warning Dead Value + TopLevelOptionalArgValueUse.res:9:1-42 + deadEscape is never used + Warning Dead Module TransitiveType2.res:0:1 TransitiveType2 is a dead module as all its items are dead. @@ -5396,8 +5426,12 @@ Forward Liveness Analysis Unison.res:17:1-55 optional argument break_ of function group is always supplied (2 calls) + Warning Unused Argument + TopLevelOptionalArgValueUse.res:4:1-33 + optional argument fmt of function formatDate is never used + Warning Redundant Optional Argument OptArg.res:26:1-70 optional argument c of function wrapfourArgs is always supplied (2 calls) - Analysis reported 318 issues (Incorrect Dead Annotation:1, Warning Dead Exception:2, Warning Dead Module:22, Warning Dead Type:93, Warning Dead Value:177, Warning Dead Value With Side Effects:5, Warning Redundant Optional Argument:6, Warning Unused Argument:12) + Analysis reported 320 issues (Incorrect Dead Annotation:1, Warning Dead Exception:2, Warning Dead Module:22, Warning Dead Type:93, Warning Dead Value:178, Warning Dead Value With Side Effects:5, Warning Redundant Optional Argument:6, Warning Unused Argument:13) diff --git a/tests/analysis_tests/tests-reanalyze/deadcode/src/TopLevelOptionalArgValueUse.res b/tests/analysis_tests/tests-reanalyze/deadcode/src/TopLevelOptionalArgValueUse.res index 73d28038cae..e69ad5f1c71 100644 --- a/tests/analysis_tests/tests-reanalyze/deadcode/src/TopLevelOptionalArgValueUse.res +++ b/tests/analysis_tests/tests-reanalyze/deadcode/src/TopLevelOptionalArgValueUse.res @@ -1,12 +1,19 @@ -/* Passing a function with optional args as a first-class value must suppress - optional-arg warnings even when the function also has a direct call. */ +/* Dead first-class escapes should not suppress optional-arg warnings, but live + first-class escapes should suppress them even when there is also a direct call. */ let formatDate = (~fmt=?, s) => s +let formatDateEscapes = (~fmt=?, s) => s let takesFn = _ => () -takesFn(formatDate) +let deadEscape = () => takesFn(formatDate) let liveCaller = () => formatDate("2024-01-01") +let liveEscapeCaller = () => { + takesFn(formatDateEscapes) + formatDateEscapes("2024-01-01") +} + let _ = liveCaller() +let _ = liveEscapeCaller() From 3851c543451740fa04ac5b9e958116a5610823c7 Mon Sep 17 00:00:00 2001 From: Christoph Knittel Date: Sat, 20 Jun 2026 17:08:26 +0700 Subject: [PATCH 06/10] Track optional arg escapes from value references Signed-off-by: Christoph Knittel --- analysis/reanalyze/src/dead_value.ml | 93 ++++++------------- .../deadcode/expected/deadcode.txt | 75 ++++++++++----- .../src/TopLevelOptionalArgValueUse.res | 12 +++ 3 files changed, 92 insertions(+), 88 deletions(-) diff --git a/analysis/reanalyze/src/dead_value.ml b/analysis/reanalyze/src/dead_value.ml index ecb31109ddc..5619810727f 100644 --- a/analysis/reanalyze/src/dead_value.ml +++ b/analysis/reanalyze/src/dead_value.ml @@ -124,52 +124,7 @@ let process_optional_args ~config ~cross_file ~exp_type ~(loc_from : Location.t) |> Dead_optional_args.add_references ~config ~cross_file ~loc_from ~loc_to ~binding ~path) -let value_use_target_pos (e : Typedtree.expression) = - match e.exp_desc with - | Texp_ident (_, _, {Types.val_loc = {loc_ghost = false; _} as loc_to}) -> - Some loc_to.loc_start - | Texp_let - ( Nonrecursive, - [ - { - vb_pat = {pat_desc = Tpat_var (id_arg, _)}; - vb_expr = - { - Typedtree.exp_desc = - Texp_ident - (_, _, {Types.val_loc = {loc_ghost = false; _} as loc_to}); - _; - }; - _; - }; - ], - { - Typedtree.exp_desc = - Texp_function - { - case = - { - c_lhs = {pat_desc = Tpat_var (eta_arg, _)}; - c_rhs = - { - Typedtree.exp_desc = - Texp_apply - {funct = {exp_desc = Texp_ident (id_arg2, _, _)}; _}; - _; - }; - _; - }; - _; - }; - _; - } ) - when Ident.name id_arg = "arg" - && Ident.name eta_arg = "eta" - && Path.name id_arg2 = "arg" -> - Some loc_to.loc_start - | _ -> None - -let rec collect_expr ~config ~decls ~refs ~file_deps ~cross_file +let rec collect_expr ~config ~decls ~refs ~file_deps ~cross_file ~callee_locs ~(last_binding : Location.t) super self (e : Typedtree.expression) = let loc_from = e.exp_loc in let binding = last_binding in @@ -180,13 +135,18 @@ let rec collect_expr ~config ~decls ~refs ~file_deps ~cross_file ~pos_to | _ -> () in - let add_optional_arg_value_escape_from_expr arg = - let pos_from = - if binding = Location.none then loc_from.loc_start else binding.loc_start - in - arg |> value_use_target_pos - |> Option.iter (add_optional_arg_value_escape pos_from) + let rec remove_first target = function + | [] -> [] + | x :: xs when x = target -> xs + | x :: xs -> x :: remove_first target xs + in + let callee_loc_opt = + match e.exp_desc with + | Texp_apply {funct = {exp_desc = Texp_ident (_, _, _); exp_loc}; _} -> + Some exp_loc + | _ -> None in + Option.iter (fun loc -> callee_locs := loc :: !callee_locs) callee_loc_opt; (match e.exp_desc with | Texp_ident (_path, _, {Types.val_loc = {loc_ghost = false; _} as loc_to}) -> (* if Path.name _path = "rc" then assert false; *) @@ -199,9 +159,15 @@ let rec collect_expr ~config ~decls ~refs ~file_deps ~cross_file (loc_to.loc_start |> Pos.to_string); References.add_value_ref refs ~pos_to:loc_to.loc_start ~pos_from:Location.none.loc_start) - else + else ( add_value_reference ~config ~refs ~file_deps ~binding - ~add_file_reference:true ~loc_from ~loc_to + ~add_file_reference:true ~loc_from ~loc_to; + if not (List.mem loc_from !callee_locs) then + let pos_from = + if binding = Location.none then loc_from.loc_start + else binding.loc_start + in + add_optional_arg_value_escape pos_from loc_to.loc_start) | Texp_apply { funct = @@ -216,12 +182,7 @@ let rec collect_expr ~config ~decls ~refs ~file_deps ~cross_file args |> process_optional_args ~config ~cross_file ~exp_type ~loc_from:(loc_from : Location.t) - ~binding:last_binding ~loc_to ~path; - args - |> List.iter (fun (_, arg) -> - match arg with - | Some arg -> add_optional_arg_value_escape_from_expr arg - | _ -> ()) + ~binding:last_binding ~loc_to ~path | Texp_let ( (* generated for functions with optional args *) Nonrecursive, @@ -289,18 +250,21 @@ let rec collect_expr ~config ~decls ~refs ~file_deps ~cross_file |> Array.iter (fun (_, record_label_definition, _) -> match record_label_definition with | Typedtree.Overridden (_, e) -> ( - add_optional_arg_value_escape_from_expr e; match e with | {exp_loc; _} when exp_loc.loc_ghost -> (* Punned field in OCaml projects has ghost location in expression *) let e = {e with exp_loc = {exp_loc with loc_ghost = false}} in collect_expr ~config ~decls ~refs ~file_deps ~cross_file - ~last_binding super self e + ~callee_locs ~last_binding super self e |> ignore | _ -> ()) | _ -> ()) | _ -> ()); - super.Tast_mapper.expr self e + let result = super.Tast_mapper.expr self e in + Option.iter + (fun loc -> callee_locs := remove_first loc !callee_locs) + callee_loc_opt; + result (* type k. is a locally abstract type @@ -467,6 +431,7 @@ let rec process_signature_item ~config ~decls ~file ~do_types ~do_values (* Traverse the AST *) let traverse_structure ~config ~decls ~refs ~file_deps ~cross_file ~file ~do_types ~do_externals (structure : Typedtree.structure) : unit = + let callee_locs = ref [] in let rec create_mapper (last_binding : Location.t) (module_path : Module_path.t) = let super = Tast_mapper.default in @@ -477,7 +442,7 @@ let traverse_structure ~config ~decls ~refs ~file_deps ~cross_file ~file (fun _self e -> e |> collect_expr ~config ~decls ~refs ~file_deps ~cross_file - ~last_binding super mapper); + ~callee_locs ~last_binding super mapper); pat = (fun _self p -> p diff --git a/tests/analysis_tests/tests-reanalyze/deadcode/expected/deadcode.txt b/tests/analysis_tests/tests-reanalyze/deadcode/expected/deadcode.txt index f47bfce6318..e69f5069a11 100644 --- a/tests/analysis_tests/tests-reanalyze/deadcode/expected/deadcode.txt +++ b/tests/analysis_tests/tests-reanalyze/deadcode/expected/deadcode.txt @@ -1504,22 +1504,33 @@ Scanning TopLevelOptionalArgValueUse.cmt Source:TopLevelOptionalArgValueUse.res addValueDeclaration +formatDate TopLevelOptionalArgValueUse.res:4:4 path:+TopLevelOptionalArgValueUse addValueDeclaration +formatDateEscapes TopLevelOptionalArgValueUse.res:5:4 path:+TopLevelOptionalArgValueUse - addValueDeclaration +takesFn TopLevelOptionalArgValueUse.res:7:4 path:+TopLevelOptionalArgValueUse - addValueDeclaration +deadEscape TopLevelOptionalArgValueUse.res:9:4 path:+TopLevelOptionalArgValueUse - addValueDeclaration +liveCaller TopLevelOptionalArgValueUse.res:11:4 path:+TopLevelOptionalArgValueUse - addValueDeclaration +liveEscapeCaller TopLevelOptionalArgValueUse.res:13:4 path:+TopLevelOptionalArgValueUse + addValueDeclaration +formatDateReturn TopLevelOptionalArgValueUse.res:6:4 path:+TopLevelOptionalArgValueUse + addValueDeclaration +formatDateTuple TopLevelOptionalArgValueUse.res:7:4 path:+TopLevelOptionalArgValueUse + addValueDeclaration +takesFn TopLevelOptionalArgValueUse.res:9:4 path:+TopLevelOptionalArgValueUse + addValueDeclaration +deadEscape TopLevelOptionalArgValueUse.res:11:4 path:+TopLevelOptionalArgValueUse + addValueDeclaration +liveCaller TopLevelOptionalArgValueUse.res:13:4 path:+TopLevelOptionalArgValueUse + addValueDeclaration +liveEscapeCaller TopLevelOptionalArgValueUse.res:15:4 path:+TopLevelOptionalArgValueUse + addValueDeclaration +liveReturnCaller TopLevelOptionalArgValueUse.res:20:4 path:+TopLevelOptionalArgValueUse + addValueDeclaration +liveTupleCaller TopLevelOptionalArgValueUse.res:24:4 path:+TopLevelOptionalArgValueUse addValueReference TopLevelOptionalArgValueUse.res:4:4 --> TopLevelOptionalArgValueUse.res:4:26 addValueReference TopLevelOptionalArgValueUse.res:5:4 --> TopLevelOptionalArgValueUse.res:5:33 - addValueReference TopLevelOptionalArgValueUse.res:9:4 --> TopLevelOptionalArgValueUse.res:4:4 - addValueReference TopLevelOptionalArgValueUse.res:9:4 --> TopLevelOptionalArgValueUse.res:7:4 - DeadOptionalArgs.addReferences formatDate called with optional argNames: argNamesMaybe: TopLevelOptionalArgValueUse.res:11:23 + addValueReference TopLevelOptionalArgValueUse.res:6:4 --> TopLevelOptionalArgValueUse.res:6:32 + addValueReference TopLevelOptionalArgValueUse.res:7:4 --> TopLevelOptionalArgValueUse.res:7:31 addValueReference TopLevelOptionalArgValueUse.res:11:4 --> TopLevelOptionalArgValueUse.res:4:4 - DeadOptionalArgs.addReferences formatDateEscapes called with optional argNames: argNamesMaybe: TopLevelOptionalArgValueUse.res:15:2 - addValueReference TopLevelOptionalArgValueUse.res:13:4 --> TopLevelOptionalArgValueUse.res:5:4 - addValueReference TopLevelOptionalArgValueUse.res:13:4 --> TopLevelOptionalArgValueUse.res:5:4 - addValueReference TopLevelOptionalArgValueUse.res:13:4 --> TopLevelOptionalArgValueUse.res:7:4 - addValueReference TopLevelOptionalArgValueUse.res:18:8 --> TopLevelOptionalArgValueUse.res:11:4 - addValueReference TopLevelOptionalArgValueUse.res:19:8 --> TopLevelOptionalArgValueUse.res:13:4 + addValueReference TopLevelOptionalArgValueUse.res:11:4 --> TopLevelOptionalArgValueUse.res:9:4 + DeadOptionalArgs.addReferences formatDate called with optional argNames: argNamesMaybe: TopLevelOptionalArgValueUse.res:13:23 + addValueReference TopLevelOptionalArgValueUse.res:13:4 --> TopLevelOptionalArgValueUse.res:4:4 + DeadOptionalArgs.addReferences formatDateEscapes called with optional argNames: argNamesMaybe: TopLevelOptionalArgValueUse.res:17:2 + addValueReference TopLevelOptionalArgValueUse.res:15:4 --> TopLevelOptionalArgValueUse.res:5:4 + addValueReference TopLevelOptionalArgValueUse.res:15:4 --> TopLevelOptionalArgValueUse.res:5:4 + addValueReference TopLevelOptionalArgValueUse.res:15:4 --> TopLevelOptionalArgValueUse.res:9:4 + addValueReference TopLevelOptionalArgValueUse.res:20:4 --> TopLevelOptionalArgValueUse.res:6:4 + addValueReference TopLevelOptionalArgValueUse.res:24:4 --> TopLevelOptionalArgValueUse.res:7:4 + addValueReference TopLevelOptionalArgValueUse.res:28:8 --> TopLevelOptionalArgValueUse.res:13:4 + addValueReference TopLevelOptionalArgValueUse.res:29:8 --> TopLevelOptionalArgValueUse.res:15:4 + DeadOptionalArgs.addReferences liveReturnCaller called with optional argNames: argNamesMaybe: TopLevelOptionalArgValueUse.res:30:8 + addValueReference TopLevelOptionalArgValueUse.res:30:8 --> TopLevelOptionalArgValueUse.res:20:4 + addValueReference TopLevelOptionalArgValueUse.res:31:8 --> TopLevelOptionalArgValueUse.res:24:4 Scanning TransitiveType1.cmt Source:TransitiveType1.res addValueDeclaration +convert TransitiveType1.res:2:4 path:+TransitiveType1 addValueDeclaration +convertAlias TransitiveType1.res:5:4 path:+TransitiveType1 @@ -2023,9 +2034,9 @@ Forward Liveness Analysis - decls: 715 - roots(external targets): 146 - decl-deps: decls_with_out=425 edges_to_decls=306 + decls: 719 + roots(external targets): 148 + decl-deps: decls_with_out=429 edges_to_decls=308 Root (external ref): Value +FirstClassModules.M.InnerModule2.+k Root (external ref): VariantCase DeadRT.moduleAccessPath.Root @@ -2048,7 +2059,6 @@ Forward Liveness Analysis Root (external ref): Value +OptionalArgsLiveDead.+liveCaller Root (annotated): RecordLabel +ImportHookDefault.props.renderMe Root (annotated): Value +TypeParams3.+test - Root (external ref): Value +TopLevelOptionalArgValueUse.+liveCaller Root (annotated): Value +Variants.+sunday Root (annotated): Value +NestedModules.Universe.Nested2.Nested3.+nested3Value Root (external ref): RecordLabel +Unison.t.doc @@ -2062,7 +2072,6 @@ Forward Liveness Analysis Root (annotated): Value +Uncurried.+uncurried3 Root (external ref): Value +Newton.+f Root (external ref): RecordLabel +Records.record.v - Root (external ref): Value +TopLevelOptionalArgValueUse.+liveEscapeCaller Root (external ref): VariantCase +DeadTest.VariantUsedOnlyInImplementation.t.A Root (external ref): Value +ContextOptionalArgs.ComponentUsingAction.+make Root (external ref): RecordLabel +Records.person.address @@ -2119,6 +2128,7 @@ Forward Liveness Analysis Root (external ref): Value +FirstClassModules.M.InnerModule3.+k3 Root (external ref): Value +ContextOptionalArgs.ComponentNotUsingAction.+dispatchNotification Root (external ref): RecordLabel +Records.coord.x + Root (external ref): Value +TopLevelOptionalArgValueUse.+liveCaller Root (annotated): RecordLabel +DeadTypeTest.record.y Root (annotated): Value +TestImport.+defaultValue Root (external ref): Value +OptArg.+threeArgs @@ -2138,6 +2148,7 @@ Forward Liveness Analysis Root (external ref): Value +DeadTest.+make Root (annotated): Value +Records.+testMyRecBsAs Root (external ref): RecordLabel +DeadTest.inlineRecord.IR.c + Root (external ref): Value +TopLevelOptionalArgValueUse.+liveEscapeCaller Root (annotated): Value +Records.+origin Root (annotated): Value +Variants.+onlySunday Root (annotated): Value +Docstrings.+treeU @@ -2315,10 +2326,12 @@ Forward Liveness Analysis Root (annotated): Value +Variants.+monday Root (annotated): Value +VariantsWithPayload.+printVariantWithPayloads Root (annotated): Value +Unboxed.+r2Test + Root (external ref): Value +TopLevelOptionalArgValueUse.+liveTupleCaller Root (external ref): RecordLabel +Records.coord.y Root (external ref): RecordLabel +DeadTest.record.xxx Root (annotated): Value +FirstClassModules.+firstClassModule Root (external ref): RecordLabel +Hooks.props.vehicle + Root (external ref): Value +TopLevelOptionalArgValueUse.+liveReturnCaller Root (annotated): Value +ImportJsValue.+useGetAbs Root (external ref): Value +JsxV4.C.+make Root (external ref): RecordLabel +Types.selfRecursive.self @@ -2364,18 +2377,15 @@ Forward Liveness Analysis Root (annotated): Value +UseImportJsValue.+useGetProp Root (annotated): Value +Hooks.+functionWithRenamedArgs - 333 roots found + 335 roots found Propagate: DeadRT.moduleAccessPath.Root -> +DeadRT.moduleAccessPath.Root Propagate: +TypeReexportCrossFileB.reexportedRecord.usedField -> +TypeReexportCrossFileA.originalRecord.usedField Propagate: +DeadTypeTest.deadType.OnlyInImplementation -> DeadTypeTest.deadType.OnlyInImplementation Propagate: +OptionalArgsLiveDead.+liveCaller -> +OptionalArgsLiveDead.+formatDate - Propagate: +TopLevelOptionalArgValueUse.+liveCaller -> +TopLevelOptionalArgValueUse.+formatDate Propagate: +Newton.+f -> +Newton.+- Propagate: +Newton.+f -> +Newton.++ Propagate: +Newton.+f -> +Newton.+* - Propagate: +TopLevelOptionalArgValueUse.+liveEscapeCaller -> +TopLevelOptionalArgValueUse.+formatDateEscapes - Propagate: +TopLevelOptionalArgValueUse.+liveEscapeCaller -> +TopLevelOptionalArgValueUse.+takesFn Propagate: +DeadTest.VariantUsedOnlyInImplementation.t.A -> +DeadTest.VariantUsedOnlyInImplementation.t.A Propagate: +ContextOptionalArgs.ComponentUsingAction.+make -> +ContextOptionalArgs.NotificationProvider.+useNotification Propagate: +DeadTest.+thisIsMarkedLive -> +DeadTest.+thisIsKeptAlive @@ -2383,7 +2393,10 @@ Forward Liveness Analysis Propagate: +Hooks.+default -> +Hooks.+make Propagate: InnerModuleTypes.I.t.Foo -> +InnerModuleTypes.I.t.Foo Propagate: DeadTypeTest.deadType.OnlyInInterface -> +DeadTypeTest.deadType.OnlyInInterface + Propagate: +TopLevelOptionalArgValueUse.+liveCaller -> +TopLevelOptionalArgValueUse.+formatDate Propagate: +TypeReexport.UseReexported.reexportedType.usedField -> +TypeReexport.UseReexported.originalType.usedField + Propagate: +TopLevelOptionalArgValueUse.+liveEscapeCaller -> +TopLevelOptionalArgValueUse.+formatDateEscapes + Propagate: +TopLevelOptionalArgValueUse.+liveEscapeCaller -> +TopLevelOptionalArgValueUse.+takesFn Propagate: +References.+get -> +References.R.+get Propagate: ErrorHandler.Make.+notify -> +ErrorHandler.Make.+notify Propagate: +References.+make -> +References.R.+make @@ -2406,6 +2419,8 @@ Forward Liveness Analysis Propagate: +OptArg.+wrapfourArgs -> +OptArg.+fourArgs Propagate: +DeadRT.moduleAccessPath.Kaboom -> DeadRT.moduleAccessPath.Kaboom Propagate: DeadValueTest.+valueAlive -> +DeadValueTest.+valueAlive + Propagate: +TopLevelOptionalArgValueUse.+liveTupleCaller -> +TopLevelOptionalArgValueUse.+formatDateTuple + Propagate: +TopLevelOptionalArgValueUse.+liveReturnCaller -> +TopLevelOptionalArgValueUse.+formatDateReturn Propagate: +ImportJsValue.+useGetAbs -> +ImportJsValue.AbsoluteValue.+getAbs Propagate: +TypeReexport.VariantUseOriginal.reexportedType.A -> +TypeReexport.VariantUseOriginal.originalType.A Propagate: +TypeReexport.OnlyReexportedDead.reexportedType.usedField -> +TypeReexport.OnlyReexportedDead.originalType.usedField @@ -2425,7 +2440,7 @@ Forward Liveness Analysis Propagate: +DeadTest.MM.+x -> +DeadTest.MM.+y Propagate: +ImportJsValue.AbsoluteValue.+getAbs -> +ImportJsValue.AbsoluteValue.+getAbs - 58 declarations marked live via propagation + 60 declarations marked live via propagation Dead VariantCase +AutoAnnotate.variant.R Dead RecordLabel +AutoAnnotate.record.variant @@ -3807,6 +3822,12 @@ Forward Liveness Analysis Live (propagated) Value +TopLevelOptionalArgValueUse.+formatDateEscapes deps: in=1 (live=1 dead=0) out=0 <- +TopLevelOptionalArgValueUse.+liveEscapeCaller (live) + Live (propagated) Value +TopLevelOptionalArgValueUse.+formatDateReturn + deps: in=1 (live=1 dead=0) out=0 + <- +TopLevelOptionalArgValueUse.+liveReturnCaller (live) + Live (propagated) Value +TopLevelOptionalArgValueUse.+formatDateTuple + deps: in=1 (live=1 dead=0) out=0 + <- +TopLevelOptionalArgValueUse.+liveTupleCaller (live) Live (propagated) Value +TopLevelOptionalArgValueUse.+takesFn deps: in=2 (live=1 dead=1) out=0 <- +TopLevelOptionalArgValueUse.+deadEscape (dead) @@ -3822,6 +3843,12 @@ Forward Liveness Analysis deps: in=0 (live=0 dead=0) out=2 -> +TopLevelOptionalArgValueUse.+formatDateEscapes -> +TopLevelOptionalArgValueUse.+takesFn + Live (external ref) Value +TopLevelOptionalArgValueUse.+liveReturnCaller + deps: in=0 (live=0 dead=0) out=1 + -> +TopLevelOptionalArgValueUse.+formatDateReturn + Live (external ref) Value +TopLevelOptionalArgValueUse.+liveTupleCaller + deps: in=0 (live=0 dead=0) out=1 + -> +TopLevelOptionalArgValueUse.+formatDateTuple Live (annotated) Value +TransitiveType1.+convert Live (annotated) Value +TransitiveType1.+convertAlias Dead Value +TransitiveType2.+convertT2 @@ -5183,7 +5210,7 @@ Forward Liveness Analysis toPayload.result is a record label never used to read a value Warning Dead Value - TopLevelOptionalArgValueUse.res:9:1-42 + TopLevelOptionalArgValueUse.res:11:1-42 deadEscape is never used Warning Dead Module diff --git a/tests/analysis_tests/tests-reanalyze/deadcode/src/TopLevelOptionalArgValueUse.res b/tests/analysis_tests/tests-reanalyze/deadcode/src/TopLevelOptionalArgValueUse.res index e69ad5f1c71..225e43bcd53 100644 --- a/tests/analysis_tests/tests-reanalyze/deadcode/src/TopLevelOptionalArgValueUse.res +++ b/tests/analysis_tests/tests-reanalyze/deadcode/src/TopLevelOptionalArgValueUse.res @@ -3,6 +3,8 @@ let formatDate = (~fmt=?, s) => s let formatDateEscapes = (~fmt=?, s) => s +let formatDateReturn = (~fmt=?, s) => s +let formatDateTuple = (~fmt=?, s) => s let takesFn = _ => () @@ -15,5 +17,15 @@ let liveEscapeCaller = () => { formatDateEscapes("2024-01-01") } +let liveReturnCaller = () => { + formatDateReturn +} + +let liveTupleCaller = () => { + (formatDateTuple, "tuple") +} + let _ = liveCaller() let _ = liveEscapeCaller() +let _ = liveReturnCaller() +let _ = liveTupleCaller() From cd9fb64502b5d7453743d4fac713055c19fbbd72 Mon Sep 17 00:00:00 2001 From: Christoph Knittel Date: Sat, 20 Jun 2026 17:51:50 +0700 Subject: [PATCH 07/10] Track cross-file optional arg value escapes Signed-off-by: Christoph Knittel --- analysis/reanalyze/src/dead_value.ml | 12 ++++----- .../deadcode/expected/deadcode.txt | 26 +++++++++++++++---- .../src/CrossFileOptionalArgProvider.res | 1 + .../src/CrossFileOptionalArgValueUse.res | 5 ++++ 4 files changed, 33 insertions(+), 11 deletions(-) create mode 100644 tests/analysis_tests/tests-reanalyze/deadcode/src/CrossFileOptionalArgProvider.res create mode 100644 tests/analysis_tests/tests-reanalyze/deadcode/src/CrossFileOptionalArgValueUse.res diff --git a/analysis/reanalyze/src/dead_value.ml b/analysis/reanalyze/src/dead_value.ml index 5619810727f..49cff7ad7ec 100644 --- a/analysis/reanalyze/src/dead_value.ml +++ b/analysis/reanalyze/src/dead_value.ml @@ -128,12 +128,10 @@ let rec collect_expr ~config ~decls ~refs ~file_deps ~cross_file ~callee_locs ~(last_binding : Location.t) super self (e : Typedtree.expression) = let loc_from = e.exp_loc in let binding = last_binding in - let add_optional_arg_value_escape pos_from pos_to = - match Declarations.find_opt_builder decls pos_to with - | Some {decl_kind = Value {reports_optional_args = true}} -> + let add_optional_arg_value_escape ~val_type pos_from pos_to = + if Dead_optional_args.has_optional_args val_type then Cross_file_items.add_optional_arg_value_escape cross_file ~pos_from ~pos_to - | _ -> () in let rec remove_first target = function | [] -> [] @@ -148,7 +146,9 @@ let rec collect_expr ~config ~decls ~refs ~file_deps ~cross_file ~callee_locs in Option.iter (fun loc -> callee_locs := loc :: !callee_locs) callee_loc_opt; (match e.exp_desc with - | Texp_ident (_path, _, {Types.val_loc = {loc_ghost = false; _} as loc_to}) -> + | Texp_ident + (_path, _, {Types.val_loc = {loc_ghost = false; _} as loc_to; val_type}) + -> (* if Path.name _path = "rc" then assert false; *) if loc_from = loc_to && _path |> Path.name = "emptyArray" then ( (* Work around lowercase jsx with no children producing an artifact `emptyArray` @@ -167,7 +167,7 @@ let rec collect_expr ~config ~decls ~refs ~file_deps ~cross_file ~callee_locs if binding = Location.none then loc_from.loc_start else binding.loc_start in - add_optional_arg_value_escape pos_from loc_to.loc_start) + add_optional_arg_value_escape ~val_type pos_from loc_to.loc_start) | Texp_apply { funct = diff --git a/tests/analysis_tests/tests-reanalyze/deadcode/expected/deadcode.txt b/tests/analysis_tests/tests-reanalyze/deadcode/expected/deadcode.txt index e69f5069a11..3d7e7b3eaf0 100644 --- a/tests/analysis_tests/tests-reanalyze/deadcode/expected/deadcode.txt +++ b/tests/analysis_tests/tests-reanalyze/deadcode/expected/deadcode.txt @@ -88,6 +88,14 @@ addValueDeclaration +notification CreateErrorHandler2.res:3:6 path:+CreateErrorHandler2.Error2 addValueReference CreateErrorHandler2.res:3:6 --> CreateErrorHandler2.res:3:21 addValueReference ErrorHandler.resi:3:2 --> CreateErrorHandler2.res:3:6 + Scanning CrossFileOptionalArgProvider.cmt Source:CrossFileOptionalArgProvider.res + addValueDeclaration +formatDate CrossFileOptionalArgProvider.res:1:4 path:+CrossFileOptionalArgProvider + addValueReference CrossFileOptionalArgProvider.res:1:4 --> CrossFileOptionalArgProvider.res:1:26 + Scanning CrossFileOptionalArgValueUse.cmt Source:CrossFileOptionalArgValueUse.res + addValueDeclaration +liveReturnCaller CrossFileOptionalArgValueUse.res:1:4 path:+CrossFileOptionalArgValueUse + addValueReference CrossFileOptionalArgValueUse.res:1:4 --> CrossFileOptionalArgProvider.res:1:4 + DeadOptionalArgs.addReferences liveReturnCaller called with optional argNames: argNamesMaybe: CrossFileOptionalArgValueUse.res:5:8 + addValueReference CrossFileOptionalArgValueUse.res:5:8 --> CrossFileOptionalArgValueUse.res:1:4 Scanning DeadCodeImplementation.cmt Source:DeadCodeImplementation.res addValueDeclaration +x DeadCodeImplementation.res:2:6 path:+DeadCodeImplementation.M addValueReference DeadCodeInterface.res:2:2 --> DeadCodeImplementation.res:2:6 @@ -2034,9 +2042,9 @@ Forward Liveness Analysis - decls: 719 - roots(external targets): 148 - decl-deps: decls_with_out=429 edges_to_decls=308 + decls: 721 + roots(external targets): 149 + decl-deps: decls_with_out=431 edges_to_decls=309 Root (external ref): Value +FirstClassModules.M.InnerModule2.+k Root (external ref): VariantCase DeadRT.moduleAccessPath.Root @@ -2143,6 +2151,7 @@ Forward Liveness Analysis Root (annotated): Value +TestImport.+innerStuffContents Root (external ref): Value +ModuleExceptionBug.+ddjdj Root (annotated): Value +TransitiveType1.+convert + Root (external ref): Value +CrossFileOptionalArgValueUse.+liveReturnCaller Root (annotated): Value +ImportHooks.+make Root (external ref): VariantCase +Unison.break_.IfNeed Root (external ref): Value +DeadTest.+make @@ -2377,7 +2386,7 @@ Forward Liveness Analysis Root (annotated): Value +UseImportJsValue.+useGetProp Root (annotated): Value +Hooks.+functionWithRenamedArgs - 335 roots found + 336 roots found Propagate: DeadRT.moduleAccessPath.Root -> +DeadRT.moduleAccessPath.Root Propagate: +TypeReexportCrossFileB.reexportedRecord.usedField -> +TypeReexportCrossFileA.originalRecord.usedField @@ -2395,6 +2404,7 @@ Forward Liveness Analysis Propagate: DeadTypeTest.deadType.OnlyInInterface -> +DeadTypeTest.deadType.OnlyInInterface Propagate: +TopLevelOptionalArgValueUse.+liveCaller -> +TopLevelOptionalArgValueUse.+formatDate Propagate: +TypeReexport.UseReexported.reexportedType.usedField -> +TypeReexport.UseReexported.originalType.usedField + Propagate: +CrossFileOptionalArgValueUse.+liveReturnCaller -> +CrossFileOptionalArgProvider.+formatDate Propagate: +TopLevelOptionalArgValueUse.+liveEscapeCaller -> +TopLevelOptionalArgValueUse.+formatDateEscapes Propagate: +TopLevelOptionalArgValueUse.+liveEscapeCaller -> +TopLevelOptionalArgValueUse.+takesFn Propagate: +References.+get -> +References.R.+get @@ -2440,7 +2450,7 @@ Forward Liveness Analysis Propagate: +DeadTest.MM.+x -> +DeadTest.MM.+y Propagate: +ImportJsValue.AbsoluteValue.+getAbs -> +ImportJsValue.AbsoluteValue.+getAbs - 60 declarations marked live via propagation + 61 declarations marked live via propagation Dead VariantCase +AutoAnnotate.variant.R Dead RecordLabel +AutoAnnotate.record.variant @@ -2521,6 +2531,12 @@ Forward Liveness Analysis -> +ContextOptionalArgs.ComponentNotUsingAction.+make Live (external ref) Value +CreateErrorHandler1.Error1.+notification Live (external ref) Value +CreateErrorHandler2.Error2.+notification + Live (propagated) Value +CrossFileOptionalArgProvider.+formatDate + deps: in=1 (live=1 dead=0) out=0 + <- +CrossFileOptionalArgValueUse.+liveReturnCaller (live) + Live (external ref) Value +CrossFileOptionalArgValueUse.+liveReturnCaller + deps: in=0 (live=0 dead=0) out=1 + -> +CrossFileOptionalArgProvider.+formatDate Live (external ref) Value +DeadCodeImplementation.M.+x Live (external ref) Exception +DeadExn.Etoplevel deps: in=1 (live=0 dead=1) out=0 diff --git a/tests/analysis_tests/tests-reanalyze/deadcode/src/CrossFileOptionalArgProvider.res b/tests/analysis_tests/tests-reanalyze/deadcode/src/CrossFileOptionalArgProvider.res new file mode 100644 index 00000000000..7119d158f0e --- /dev/null +++ b/tests/analysis_tests/tests-reanalyze/deadcode/src/CrossFileOptionalArgProvider.res @@ -0,0 +1 @@ +let formatDate = (~fmt=?, s) => s diff --git a/tests/analysis_tests/tests-reanalyze/deadcode/src/CrossFileOptionalArgValueUse.res b/tests/analysis_tests/tests-reanalyze/deadcode/src/CrossFileOptionalArgValueUse.res new file mode 100644 index 00000000000..ad4d73f0d94 --- /dev/null +++ b/tests/analysis_tests/tests-reanalyze/deadcode/src/CrossFileOptionalArgValueUse.res @@ -0,0 +1,5 @@ +let liveReturnCaller = () => { + CrossFileOptionalArgProvider.formatDate +} + +let _ = liveReturnCaller() From 132331b7c2f5c42fb6b5d26ac678b73da2c43412 Mon Sep 17 00:00:00 2001 From: Christoph Knittel Date: Sat, 20 Jun 2026 20:36:00 +0700 Subject: [PATCH 08/10] Ignore optional arg wrappers as value escapes Signed-off-by: Christoph Knittel --- analysis/reanalyze/src/dead_value.ml | 52 ++++++++++- .../deadcode/expected/deadcode.txt | 92 +++++++++++-------- .../src/TopLevelOptionalArgValueUse.res | 6 ++ 3 files changed, 110 insertions(+), 40 deletions(-) diff --git a/analysis/reanalyze/src/dead_value.ml b/analysis/reanalyze/src/dead_value.ml index 49cff7ad7ec..88787c2dad7 100644 --- a/analysis/reanalyze/src/dead_value.ml +++ b/analysis/reanalyze/src/dead_value.ml @@ -125,7 +125,8 @@ let process_optional_args ~config ~cross_file ~exp_type ~(loc_from : Location.t) ~binding ~path) let rec collect_expr ~config ~decls ~refs ~file_deps ~cross_file ~callee_locs - ~(last_binding : Location.t) super self (e : Typedtree.expression) = + ~ignored_escape_locs ~(last_binding : Location.t) super self + (e : Typedtree.expression) = let loc_from = e.exp_loc in let binding = last_binding in let add_optional_arg_value_escape ~val_type pos_from pos_to = @@ -144,7 +145,43 @@ let rec collect_expr ~config ~decls ~refs ~file_deps ~cross_file ~callee_locs Some exp_loc | _ -> None in + let ignored_escape_loc_opt = + match e.exp_desc with + | Texp_let + ( Nonrecursive, + [ + { + vb_pat = {pat_desc = Tpat_var (id_arg, _)}; + vb_expr = + {exp_desc = Texp_ident (_, _, _); exp_loc = wrapper_ident_loc}; + }; + ], + { + exp_desc = + Texp_function + { + case = + { + c_lhs = {pat_desc = Tpat_var (eta_arg, _)}; + c_rhs = + { + exp_desc = + Texp_apply + {funct = {exp_desc = Texp_ident (id_arg2, _, _)}}; + }; + }; + }; + } ) + when Ident.name id_arg = "arg" + && Ident.name eta_arg = "eta" + && Path.name id_arg2 = "arg" -> + Some wrapper_ident_loc + | _ -> None + in Option.iter (fun loc -> callee_locs := loc :: !callee_locs) callee_loc_opt; + Option.iter + (fun loc -> ignored_escape_locs := loc :: !ignored_escape_locs) + ignored_escape_loc_opt; (match e.exp_desc with | Texp_ident (_path, _, {Types.val_loc = {loc_ghost = false; _} as loc_to; val_type}) @@ -162,7 +199,10 @@ let rec collect_expr ~config ~decls ~refs ~file_deps ~cross_file ~callee_locs else ( add_value_reference ~config ~refs ~file_deps ~binding ~add_file_reference:true ~loc_from ~loc_to; - if not (List.mem loc_from !callee_locs) then + if + (not (List.mem loc_from !callee_locs)) + && not (List.mem loc_from !ignored_escape_locs) + then let pos_from = if binding = Location.none then loc_from.loc_start else binding.loc_start @@ -255,7 +295,7 @@ let rec collect_expr ~config ~decls ~refs ~file_deps ~cross_file ~callee_locs (* Punned field in OCaml projects has ghost location in expression *) let e = {e with exp_loc = {exp_loc with loc_ghost = false}} in collect_expr ~config ~decls ~refs ~file_deps ~cross_file - ~callee_locs ~last_binding super self e + ~callee_locs ~ignored_escape_locs ~last_binding super self e |> ignore | _ -> ()) | _ -> ()) @@ -264,6 +304,9 @@ let rec collect_expr ~config ~decls ~refs ~file_deps ~cross_file ~callee_locs Option.iter (fun loc -> callee_locs := remove_first loc !callee_locs) callee_loc_opt; + Option.iter + (fun loc -> ignored_escape_locs := remove_first loc !ignored_escape_locs) + ignored_escape_loc_opt; result (* @@ -432,6 +475,7 @@ let rec process_signature_item ~config ~decls ~file ~do_types ~do_values let traverse_structure ~config ~decls ~refs ~file_deps ~cross_file ~file ~do_types ~do_externals (structure : Typedtree.structure) : unit = let callee_locs = ref [] in + let ignored_escape_locs = ref [] in let rec create_mapper (last_binding : Location.t) (module_path : Module_path.t) = let super = Tast_mapper.default in @@ -442,7 +486,7 @@ let traverse_structure ~config ~decls ~refs ~file_deps ~cross_file ~file (fun _self e -> e |> collect_expr ~config ~decls ~refs ~file_deps ~cross_file - ~callee_locs ~last_binding super mapper); + ~callee_locs ~ignored_escape_locs ~last_binding super mapper); pat = (fun _self p -> p diff --git a/tests/analysis_tests/tests-reanalyze/deadcode/expected/deadcode.txt b/tests/analysis_tests/tests-reanalyze/deadcode/expected/deadcode.txt index 3d7e7b3eaf0..38e70e54ea2 100644 --- a/tests/analysis_tests/tests-reanalyze/deadcode/expected/deadcode.txt +++ b/tests/analysis_tests/tests-reanalyze/deadcode/expected/deadcode.txt @@ -1514,31 +1514,39 @@ addValueDeclaration +formatDateEscapes TopLevelOptionalArgValueUse.res:5:4 path:+TopLevelOptionalArgValueUse addValueDeclaration +formatDateReturn TopLevelOptionalArgValueUse.res:6:4 path:+TopLevelOptionalArgValueUse addValueDeclaration +formatDateTuple TopLevelOptionalArgValueUse.res:7:4 path:+TopLevelOptionalArgValueUse - addValueDeclaration +takesFn TopLevelOptionalArgValueUse.res:9:4 path:+TopLevelOptionalArgValueUse - addValueDeclaration +deadEscape TopLevelOptionalArgValueUse.res:11:4 path:+TopLevelOptionalArgValueUse - addValueDeclaration +liveCaller TopLevelOptionalArgValueUse.res:13:4 path:+TopLevelOptionalArgValueUse - addValueDeclaration +liveEscapeCaller TopLevelOptionalArgValueUse.res:15:4 path:+TopLevelOptionalArgValueUse - addValueDeclaration +liveReturnCaller TopLevelOptionalArgValueUse.res:20:4 path:+TopLevelOptionalArgValueUse - addValueDeclaration +liveTupleCaller TopLevelOptionalArgValueUse.res:24:4 path:+TopLevelOptionalArgValueUse + addValueDeclaration +formatDateForwarded TopLevelOptionalArgValueUse.res:8:4 path:+TopLevelOptionalArgValueUse + addValueDeclaration +takesFn TopLevelOptionalArgValueUse.res:10:4 path:+TopLevelOptionalArgValueUse + addValueDeclaration +deadEscape TopLevelOptionalArgValueUse.res:12:4 path:+TopLevelOptionalArgValueUse + addValueDeclaration +liveCaller TopLevelOptionalArgValueUse.res:14:4 path:+TopLevelOptionalArgValueUse + addValueDeclaration +liveEscapeCaller TopLevelOptionalArgValueUse.res:16:4 path:+TopLevelOptionalArgValueUse + addValueDeclaration +liveReturnCaller TopLevelOptionalArgValueUse.res:21:4 path:+TopLevelOptionalArgValueUse + addValueDeclaration +liveTupleCaller TopLevelOptionalArgValueUse.res:25:4 path:+TopLevelOptionalArgValueUse + addValueDeclaration +liveForwardingCaller TopLevelOptionalArgValueUse.res:29:4 path:+TopLevelOptionalArgValueUse addValueReference TopLevelOptionalArgValueUse.res:4:4 --> TopLevelOptionalArgValueUse.res:4:26 addValueReference TopLevelOptionalArgValueUse.res:5:4 --> TopLevelOptionalArgValueUse.res:5:33 addValueReference TopLevelOptionalArgValueUse.res:6:4 --> TopLevelOptionalArgValueUse.res:6:32 addValueReference TopLevelOptionalArgValueUse.res:7:4 --> TopLevelOptionalArgValueUse.res:7:31 - addValueReference TopLevelOptionalArgValueUse.res:11:4 --> TopLevelOptionalArgValueUse.res:4:4 - addValueReference TopLevelOptionalArgValueUse.res:11:4 --> TopLevelOptionalArgValueUse.res:9:4 - DeadOptionalArgs.addReferences formatDate called with optional argNames: argNamesMaybe: TopLevelOptionalArgValueUse.res:13:23 - addValueReference TopLevelOptionalArgValueUse.res:13:4 --> TopLevelOptionalArgValueUse.res:4:4 - DeadOptionalArgs.addReferences formatDateEscapes called with optional argNames: argNamesMaybe: TopLevelOptionalArgValueUse.res:17:2 - addValueReference TopLevelOptionalArgValueUse.res:15:4 --> TopLevelOptionalArgValueUse.res:5:4 - addValueReference TopLevelOptionalArgValueUse.res:15:4 --> TopLevelOptionalArgValueUse.res:5:4 - addValueReference TopLevelOptionalArgValueUse.res:15:4 --> TopLevelOptionalArgValueUse.res:9:4 - addValueReference TopLevelOptionalArgValueUse.res:20:4 --> TopLevelOptionalArgValueUse.res:6:4 - addValueReference TopLevelOptionalArgValueUse.res:24:4 --> TopLevelOptionalArgValueUse.res:7:4 - addValueReference TopLevelOptionalArgValueUse.res:28:8 --> TopLevelOptionalArgValueUse.res:13:4 - addValueReference TopLevelOptionalArgValueUse.res:29:8 --> TopLevelOptionalArgValueUse.res:15:4 - DeadOptionalArgs.addReferences liveReturnCaller called with optional argNames: argNamesMaybe: TopLevelOptionalArgValueUse.res:30:8 - addValueReference TopLevelOptionalArgValueUse.res:30:8 --> TopLevelOptionalArgValueUse.res:20:4 - addValueReference TopLevelOptionalArgValueUse.res:31:8 --> TopLevelOptionalArgValueUse.res:24:4 + addValueReference TopLevelOptionalArgValueUse.res:8:4 --> TopLevelOptionalArgValueUse.res:8:35 + addValueReference TopLevelOptionalArgValueUse.res:12:4 --> TopLevelOptionalArgValueUse.res:4:4 + addValueReference TopLevelOptionalArgValueUse.res:12:4 --> TopLevelOptionalArgValueUse.res:10:4 + DeadOptionalArgs.addReferences formatDate called with optional argNames: argNamesMaybe: TopLevelOptionalArgValueUse.res:14:23 + addValueReference TopLevelOptionalArgValueUse.res:14:4 --> TopLevelOptionalArgValueUse.res:4:4 + DeadOptionalArgs.addReferences formatDateEscapes called with optional argNames: argNamesMaybe: TopLevelOptionalArgValueUse.res:18:2 + addValueReference TopLevelOptionalArgValueUse.res:16:4 --> TopLevelOptionalArgValueUse.res:5:4 + addValueReference TopLevelOptionalArgValueUse.res:16:4 --> TopLevelOptionalArgValueUse.res:5:4 + addValueReference TopLevelOptionalArgValueUse.res:16:4 --> TopLevelOptionalArgValueUse.res:10:4 + addValueReference TopLevelOptionalArgValueUse.res:21:4 --> TopLevelOptionalArgValueUse.res:6:4 + addValueReference TopLevelOptionalArgValueUse.res:25:4 --> TopLevelOptionalArgValueUse.res:7:4 + DeadOptionalArgs.addReferences formatDateForwarded called with optional argNames:fmt argNamesMaybe:fmt TopLevelOptionalArgValueUse.res:30:2 + addValueReference TopLevelOptionalArgValueUse.res:29:4 --> TopLevelOptionalArgValueUse.res:29:28 + addValueReference TopLevelOptionalArgValueUse.res:29:4 --> TopLevelOptionalArgValueUse.res:8:4 + addValueReference TopLevelOptionalArgValueUse.res:33:8 --> TopLevelOptionalArgValueUse.res:14:4 + addValueReference TopLevelOptionalArgValueUse.res:34:8 --> TopLevelOptionalArgValueUse.res:16:4 + DeadOptionalArgs.addReferences liveReturnCaller called with optional argNames: argNamesMaybe: TopLevelOptionalArgValueUse.res:35:8 + addValueReference TopLevelOptionalArgValueUse.res:35:8 --> TopLevelOptionalArgValueUse.res:21:4 + addValueReference TopLevelOptionalArgValueUse.res:36:8 --> TopLevelOptionalArgValueUse.res:25:4 + DeadOptionalArgs.addReferences liveForwardingCaller called with optional argNames: argNamesMaybe: TopLevelOptionalArgValueUse.res:37:8 + addValueReference TopLevelOptionalArgValueUse.res:37:8 --> TopLevelOptionalArgValueUse.res:29:4 Scanning TransitiveType1.cmt Source:TransitiveType1.res addValueDeclaration +convert TransitiveType1.res:2:4 path:+TransitiveType1 addValueDeclaration +convertAlias TransitiveType1.res:5:4 path:+TransitiveType1 @@ -2042,9 +2050,9 @@ Forward Liveness Analysis - decls: 721 - roots(external targets): 149 - decl-deps: decls_with_out=431 edges_to_decls=309 + decls: 727 + roots(external targets): 152 + decl-deps: decls_with_out=434 edges_to_decls=312 Root (external ref): Value +FirstClassModules.M.InnerModule2.+k Root (external ref): VariantCase DeadRT.moduleAccessPath.Root @@ -2057,6 +2065,7 @@ Forward Liveness Analysis Root (annotated): Value +Docstrings.+twoU Root (external ref): RecordLabel +TypeReexportCrossFileB.reexportedRecord.usedField Root (annotated): Value +NestedModules.Universe.Nested2.+nested2Function + Root (external ref): Value +TopLevelOptionalArgValueUse.+liveForwardingCaller Root (annotated): Value +Tuples.+marry Root (annotated): Value +Docstrings.+unitArgWithoutConversionU Root (annotated): Value +Types.+i64Const @@ -2103,6 +2112,7 @@ Forward Liveness Analysis Root (annotated): Value +ImportHooks.+foo Root (annotated): RecordLabel +ImportIndex.props.method Root (annotated): Value +Docstrings.+unnamed2U + Root (external ref): Value +TopLevelOptionalArgValueUse.+liveCaller Root (external ref): RecordLabel +RecordRest.config.name Root (external ref): Value +FirstClassModules.M.Z.+u Root (annotated): Value +Uncurried.+callback2U @@ -2136,7 +2146,6 @@ Forward Liveness Analysis Root (external ref): Value +FirstClassModules.M.InnerModule3.+k3 Root (external ref): Value +ContextOptionalArgs.ComponentNotUsingAction.+dispatchNotification Root (external ref): RecordLabel +Records.coord.x - Root (external ref): Value +TopLevelOptionalArgValueUse.+liveCaller Root (annotated): RecordLabel +DeadTypeTest.record.y Root (annotated): Value +TestImport.+defaultValue Root (external ref): Value +OptArg.+threeArgs @@ -2157,7 +2166,6 @@ Forward Liveness Analysis Root (external ref): Value +DeadTest.+make Root (annotated): Value +Records.+testMyRecBsAs Root (external ref): RecordLabel +DeadTest.inlineRecord.IR.c - Root (external ref): Value +TopLevelOptionalArgValueUse.+liveEscapeCaller Root (annotated): Value +Records.+origin Root (annotated): Value +Variants.+onlySunday Root (annotated): Value +Docstrings.+treeU @@ -2169,6 +2177,7 @@ Forward Liveness Analysis Root (annotated): Value +Tuples.+computeArea Root (annotated): Value +References.+get Root (annotated): Value +ModuleAliases.+testNested + Root (external ref): Value +TopLevelOptionalArgValueUse.+liveTupleCaller Root (external ref): Value +FirstClassModules.SomeFunctor.+ww Root (annotated): Value +Hooks.Inner.Inner2.+make Root (annotated): Value +ImportJsValue.+area @@ -2219,6 +2228,7 @@ Forward Liveness Analysis Root (annotated): Value +LetPrivate.local_1.+x Root (annotated): Value +TestImport.+make Root (external ref): Value +ContextOptionalArgs.NotificationProvider.Provider.+make + Root (external ref): Value +TopLevelOptionalArgValueUse.+liveEscapeCaller Root (annotated): Value +DeadTest.GloobLive.+globallyLive1 Root (annotated): Value +Docstrings.+grouped Root (annotated): Value +OcamlWarningSuppressToplevel.M.+suppressed4 @@ -2324,6 +2334,7 @@ Forward Liveness Analysis Root (annotated): Value +Shadow.M.+test Root (annotated): Value +ComponentAsProp.+make Root (annotated): Value +Records.+testMyRec2 + Root (external ref): Value +TopLevelOptionalArgValueUse.+liveReturnCaller Root (annotated): Value +VariantsWithPayload.+printManyPayloads Root (annotated): Value +TestFirstClassModules.+convertFirstClassModuleWithTypeEquations Root (annotated): Value +TransitiveType1.+convertAlias @@ -2335,12 +2346,10 @@ Forward Liveness Analysis Root (annotated): Value +Variants.+monday Root (annotated): Value +VariantsWithPayload.+printVariantWithPayloads Root (annotated): Value +Unboxed.+r2Test - Root (external ref): Value +TopLevelOptionalArgValueUse.+liveTupleCaller Root (external ref): RecordLabel +Records.coord.y Root (external ref): RecordLabel +DeadTest.record.xxx Root (annotated): Value +FirstClassModules.+firstClassModule Root (external ref): RecordLabel +Hooks.props.vehicle - Root (external ref): Value +TopLevelOptionalArgValueUse.+liveReturnCaller Root (annotated): Value +ImportJsValue.+useGetAbs Root (external ref): Value +JsxV4.C.+make Root (external ref): RecordLabel +Types.selfRecursive.self @@ -2386,10 +2395,11 @@ Forward Liveness Analysis Root (annotated): Value +UseImportJsValue.+useGetProp Root (annotated): Value +Hooks.+functionWithRenamedArgs - 336 roots found + 340 roots found Propagate: DeadRT.moduleAccessPath.Root -> +DeadRT.moduleAccessPath.Root Propagate: +TypeReexportCrossFileB.reexportedRecord.usedField -> +TypeReexportCrossFileA.originalRecord.usedField + Propagate: +TopLevelOptionalArgValueUse.+liveForwardingCaller -> +TopLevelOptionalArgValueUse.+formatDateForwarded Propagate: +DeadTypeTest.deadType.OnlyInImplementation -> DeadTypeTest.deadType.OnlyInImplementation Propagate: +OptionalArgsLiveDead.+liveCaller -> +OptionalArgsLiveDead.+formatDate Propagate: +Newton.+f -> +Newton.+- @@ -2399,15 +2409,14 @@ Forward Liveness Analysis Propagate: +ContextOptionalArgs.ComponentUsingAction.+make -> +ContextOptionalArgs.NotificationProvider.+useNotification Propagate: +DeadTest.+thisIsMarkedLive -> +DeadTest.+thisIsKeptAlive Propagate: +TypeReexport.UseOriginal.reexportedType.directlyUsed -> +TypeReexport.UseOriginal.originalType.directlyUsed + Propagate: +TopLevelOptionalArgValueUse.+liveCaller -> +TopLevelOptionalArgValueUse.+formatDate Propagate: +Hooks.+default -> +Hooks.+make Propagate: InnerModuleTypes.I.t.Foo -> +InnerModuleTypes.I.t.Foo Propagate: DeadTypeTest.deadType.OnlyInInterface -> +DeadTypeTest.deadType.OnlyInInterface - Propagate: +TopLevelOptionalArgValueUse.+liveCaller -> +TopLevelOptionalArgValueUse.+formatDate Propagate: +TypeReexport.UseReexported.reexportedType.usedField -> +TypeReexport.UseReexported.originalType.usedField Propagate: +CrossFileOptionalArgValueUse.+liveReturnCaller -> +CrossFileOptionalArgProvider.+formatDate - Propagate: +TopLevelOptionalArgValueUse.+liveEscapeCaller -> +TopLevelOptionalArgValueUse.+formatDateEscapes - Propagate: +TopLevelOptionalArgValueUse.+liveEscapeCaller -> +TopLevelOptionalArgValueUse.+takesFn Propagate: +References.+get -> +References.R.+get + Propagate: +TopLevelOptionalArgValueUse.+liveTupleCaller -> +TopLevelOptionalArgValueUse.+formatDateTuple Propagate: ErrorHandler.Make.+notify -> +ErrorHandler.Make.+notify Propagate: +References.+make -> +References.R.+make Propagate: +ScopedAnnotationsLiveVsDead.LiveScope.+root -> +ScopedAnnotationsLiveVsDead.+middleLive @@ -2419,6 +2428,8 @@ Forward Liveness Analysis Propagate: +OptArg.+wrapOneArg -> +OptArg.+oneArg Propagate: +TestImmutableArray.+testImmutableArrayGet -> ImmutableArray.Array.+get Propagate: +ContextOptionalArgs.NotificationProvider.Provider.+make -> +ContextOptionalArgs.NotificationProvider.+dispatchNotificationContext + Propagate: +TopLevelOptionalArgValueUse.+liveEscapeCaller -> +TopLevelOptionalArgValueUse.+formatDateEscapes + Propagate: +TopLevelOptionalArgValueUse.+liveEscapeCaller -> +TopLevelOptionalArgValueUse.+takesFn Propagate: +TypeReexport.VariantUseReexported.reexportedType.A -> +TypeReexport.VariantUseReexported.originalType.A Propagate: +Unison.+toString -> +Unison.+fits Propagate: +References.+set -> +References.R.+set @@ -2429,7 +2440,6 @@ Forward Liveness Analysis Propagate: +OptArg.+wrapfourArgs -> +OptArg.+fourArgs Propagate: +DeadRT.moduleAccessPath.Kaboom -> DeadRT.moduleAccessPath.Kaboom Propagate: DeadValueTest.+valueAlive -> +DeadValueTest.+valueAlive - Propagate: +TopLevelOptionalArgValueUse.+liveTupleCaller -> +TopLevelOptionalArgValueUse.+formatDateTuple Propagate: +TopLevelOptionalArgValueUse.+liveReturnCaller -> +TopLevelOptionalArgValueUse.+formatDateReturn Propagate: +ImportJsValue.+useGetAbs -> +ImportJsValue.AbsoluteValue.+getAbs Propagate: +TypeReexport.VariantUseOriginal.reexportedType.A -> +TypeReexport.VariantUseOriginal.originalType.A @@ -2450,7 +2460,7 @@ Forward Liveness Analysis Propagate: +DeadTest.MM.+x -> +DeadTest.MM.+y Propagate: +ImportJsValue.AbsoluteValue.+getAbs -> +ImportJsValue.AbsoluteValue.+getAbs - 61 declarations marked live via propagation + 62 declarations marked live via propagation Dead VariantCase +AutoAnnotate.variant.R Dead RecordLabel +AutoAnnotate.record.variant @@ -3844,6 +3854,9 @@ Forward Liveness Analysis Live (propagated) Value +TopLevelOptionalArgValueUse.+formatDateTuple deps: in=1 (live=1 dead=0) out=0 <- +TopLevelOptionalArgValueUse.+liveTupleCaller (live) + Live (propagated) Value +TopLevelOptionalArgValueUse.+formatDateForwarded + deps: in=1 (live=1 dead=0) out=0 + <- +TopLevelOptionalArgValueUse.+liveForwardingCaller (live) Live (propagated) Value +TopLevelOptionalArgValueUse.+takesFn deps: in=2 (live=1 dead=1) out=0 <- +TopLevelOptionalArgValueUse.+deadEscape (dead) @@ -3865,6 +3878,9 @@ Forward Liveness Analysis Live (external ref) Value +TopLevelOptionalArgValueUse.+liveTupleCaller deps: in=0 (live=0 dead=0) out=1 -> +TopLevelOptionalArgValueUse.+formatDateTuple + Live (external ref) Value +TopLevelOptionalArgValueUse.+liveForwardingCaller + deps: in=0 (live=0 dead=0) out=1 + -> +TopLevelOptionalArgValueUse.+formatDateForwarded Live (annotated) Value +TransitiveType1.+convert Live (annotated) Value +TransitiveType1.+convertAlias Dead Value +TransitiveType2.+convertT2 @@ -5226,7 +5242,7 @@ Forward Liveness Analysis toPayload.result is a record label never used to read a value Warning Dead Value - TopLevelOptionalArgValueUse.res:11:1-42 + TopLevelOptionalArgValueUse.res:12:1-42 deadEscape is never used Warning Dead Module @@ -5405,6 +5421,10 @@ Forward Liveness Analysis OptArg.res:24:1-63 optional argument d of function fourArgs is never used + Warning Unused Argument + TopLevelOptionalArgValueUse.res:29:1-85 + optional argument fmt of function liveForwardingCaller is never used + Warning Unused Argument TestOptArg.res:9:1-65 optional argument x of function notSuppressesOptArgs is never used @@ -5477,4 +5497,4 @@ Forward Liveness Analysis OptArg.res:26:1-70 optional argument c of function wrapfourArgs is always supplied (2 calls) - Analysis reported 320 issues (Incorrect Dead Annotation:1, Warning Dead Exception:2, Warning Dead Module:22, Warning Dead Type:93, Warning Dead Value:178, Warning Dead Value With Side Effects:5, Warning Redundant Optional Argument:6, Warning Unused Argument:13) + Analysis reported 322 issues (Incorrect Dead Annotation:1, Warning Dead Exception:2, Warning Dead Module:22, Warning Dead Type:94, Warning Dead Value:178, Warning Dead Value With Side Effects:5, Warning Redundant Optional Argument:6, Warning Unused Argument:14) diff --git a/tests/analysis_tests/tests-reanalyze/deadcode/src/TopLevelOptionalArgValueUse.res b/tests/analysis_tests/tests-reanalyze/deadcode/src/TopLevelOptionalArgValueUse.res index 225e43bcd53..ef080ed1b35 100644 --- a/tests/analysis_tests/tests-reanalyze/deadcode/src/TopLevelOptionalArgValueUse.res +++ b/tests/analysis_tests/tests-reanalyze/deadcode/src/TopLevelOptionalArgValueUse.res @@ -5,6 +5,7 @@ let formatDate = (~fmt=?, s) => s let formatDateEscapes = (~fmt=?, s) => s let formatDateReturn = (~fmt=?, s) => s let formatDateTuple = (~fmt=?, s) => s +let formatDateForwarded = (~fmt=?, s) => s let takesFn = _ => () @@ -25,7 +26,12 @@ let liveTupleCaller = () => { (formatDateTuple, "tuple") } +let liveForwardingCaller = (~fmt=?) => { + formatDateForwarded(~fmt?, "2024-01-01") +} + let _ = liveCaller() let _ = liveEscapeCaller() let _ = liveReturnCaller() let _ = liveTupleCaller() +let _ = liveForwardingCaller() From 4bee459892098b10d24dfe9e13cc8c64fb333ee6 Mon Sep 17 00:00:00 2001 From: Christoph Knittel Date: Fri, 10 Jul 2026 09:09:26 +0200 Subject: [PATCH 09/10] Let GPT 5.6 Sol take a stab at it --- CHANGELOG.md | 2 + analysis/reanalyze/src/dead_optional_args.ml | 37 ++-- analysis/reanalyze/src/dead_value.ml | 115 ++++-------- .../deadcode/expected/deadcode.txt | 167 ++++++++++++------ .../src/TopLevelOptionalArgValueUse.res | 17 ++ 5 files changed, 179 insertions(+), 159 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b1e7a3ac94a..640f5dc67d5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,8 @@ #### :bug: Bug fix +- Fix reanalyze optional-argument diagnostics for functions passed or returned as first-class values. https://github.com/rescript-lang/rescript/pull/8321 + #### :memo: Documentation #### :nail_care: Polish diff --git a/analysis/reanalyze/src/dead_optional_args.ml b/analysis/reanalyze/src/dead_optional_args.ml index 8e7012cefe5..f1ff37407f5 100644 --- a/analysis/reanalyze/src/dead_optional_args.ml +++ b/analysis/reanalyze/src/dead_optional_args.ml @@ -2,32 +2,6 @@ open Dead_common let active () = true -let add_function_reference ~config ~decls ~cross_file ~(loc_from : Location.t) - ~(loc_to : Location.t) = - if active () then - let pos_to = loc_to.loc_start in - let pos_from = loc_from.loc_start in - let has_optional_arg_state pos = - match Declarations.find_opt_builder decls pos with - | Some {decl_kind = Value {optional_args}} -> - not (Optional_args.is_empty optional_args) - | _ -> false - in - let should_add = - if - pos_to.pos_fname <> pos_from.pos_fname - && (file_is_implementation_of pos_to.pos_fname pos_from.pos_fname - || file_is_implementation_of pos_from.pos_fname pos_to.pos_fname) - then has_optional_arg_state pos_to - else has_optional_arg_state pos_from && has_optional_arg_state pos_to - in - if should_add then ( - if config.Dce_config.cli.debug then - Log_.item "OptionalArgs.addFunctionReference %s %s@." - (pos_from |> Pos.to_string) - (pos_to |> Pos.to_string); - Cross_file_items.add_function_reference cross_file ~pos_from ~pos_to) - let rec has_optional_args (texpr : Types.type_expr) = match texpr.desc with | _ when not (active ()) -> false @@ -37,6 +11,17 @@ let rec has_optional_args (texpr : Types.type_expr) = | Tsubst t -> has_optional_args t | _ -> false +let add_function_reference ~config ~cross_file ~(loc_from : Location.t) + ~(loc_to : Location.t) ~type_from ~type_to = + if has_optional_args type_from && has_optional_args type_to then ( + let pos_to = loc_to.loc_start in + let pos_from = loc_from.loc_start in + if config.Dce_config.cli.debug then + Log_.item "OptionalArgs.addFunctionReference %s %s@." + (pos_from |> Pos.to_string) + (pos_to |> Pos.to_string); + Cross_file_items.add_function_reference cross_file ~pos_from ~pos_to) + let rec from_type_expr (texpr : Types.type_expr) = match texpr.desc with | _ when not (active ()) -> [] diff --git a/analysis/reanalyze/src/dead_value.ml b/analysis/reanalyze/src/dead_value.ml index 88787c2dad7..69c92f09db4 100644 --- a/analysis/reanalyze/src/dead_value.ml +++ b/analysis/reanalyze/src/dead_value.ml @@ -2,6 +2,14 @@ open Dead_common +(** Identity avoids conflating generated expressions that share source locations. *) +module Expression_table = Hashtbl.Make (struct + type t = Typedtree.expression + + let equal = ( == ) + let hash (e : t) = Hashtbl.hash e.exp_loc +end) + let check_any_value_binding_with_no_side_effects ~config ~decls ~file ~(module_path : Module_path.t) ({vb_pat = {pat_desc}; vb_expr = expr; vb_loc = loc} : @@ -124,9 +132,8 @@ let process_optional_args ~config ~cross_file ~exp_type ~(loc_from : Location.t) |> Dead_optional_args.add_references ~config ~cross_file ~loc_from ~loc_to ~binding ~path) -let rec collect_expr ~config ~decls ~refs ~file_deps ~cross_file ~callee_locs - ~ignored_escape_locs ~(last_binding : Location.t) super self - (e : Typedtree.expression) = +let rec collect_expr ~config ~refs ~file_deps ~cross_file ~direct_callees + ~(last_binding : Location.t) super self (e : Typedtree.expression) = let loc_from = e.exp_loc in let binding = last_binding in let add_optional_arg_value_escape ~val_type pos_from pos_to = @@ -134,54 +141,6 @@ let rec collect_expr ~config ~decls ~refs ~file_deps ~cross_file ~callee_locs Cross_file_items.add_optional_arg_value_escape cross_file ~pos_from ~pos_to in - let rec remove_first target = function - | [] -> [] - | x :: xs when x = target -> xs - | x :: xs -> x :: remove_first target xs - in - let callee_loc_opt = - match e.exp_desc with - | Texp_apply {funct = {exp_desc = Texp_ident (_, _, _); exp_loc}; _} -> - Some exp_loc - | _ -> None - in - let ignored_escape_loc_opt = - match e.exp_desc with - | Texp_let - ( Nonrecursive, - [ - { - vb_pat = {pat_desc = Tpat_var (id_arg, _)}; - vb_expr = - {exp_desc = Texp_ident (_, _, _); exp_loc = wrapper_ident_loc}; - }; - ], - { - exp_desc = - Texp_function - { - case = - { - c_lhs = {pat_desc = Tpat_var (eta_arg, _)}; - c_rhs = - { - exp_desc = - Texp_apply - {funct = {exp_desc = Texp_ident (id_arg2, _, _)}}; - }; - }; - }; - } ) - when Ident.name id_arg = "arg" - && Ident.name eta_arg = "eta" - && Path.name id_arg2 = "arg" -> - Some wrapper_ident_loc - | _ -> None - in - Option.iter (fun loc -> callee_locs := loc :: !callee_locs) callee_loc_opt; - Option.iter - (fun loc -> ignored_escape_locs := loc :: !ignored_escape_locs) - ignored_escape_loc_opt; (match e.exp_desc with | Texp_ident (_path, _, {Types.val_loc = {loc_ghost = false; _} as loc_to; val_type}) @@ -199,10 +158,7 @@ let rec collect_expr ~config ~decls ~refs ~file_deps ~cross_file ~callee_locs else ( add_value_reference ~config ~refs ~file_deps ~binding ~add_file_reference:true ~loc_from ~loc_to; - if - (not (List.mem loc_from !callee_locs)) - && not (List.mem loc_from !ignored_escape_locs) - then + if not (Expression_table.mem direct_callees e) then let pos_from = if binding = Location.none then loc_from.loc_start else binding.loc_start @@ -216,9 +172,10 @@ let rec collect_expr ~config ~decls ~refs ~file_deps ~cross_file ~callee_locs Texp_ident (path, _, {Types.val_loc = {loc_ghost = false; _} as loc_to}); exp_type; - }; + } as direct_callee; args; } -> + Expression_table.replace direct_callees direct_callee (); args |> process_optional_args ~config ~cross_file ~exp_type ~loc_from:(loc_from : Location.t) @@ -235,7 +192,7 @@ let rec collect_expr ~config ~decls ~refs ~file_deps ~cross_file ~callee_locs Texp_ident (path, _, {Types.val_loc = {loc_ghost = false; _} as loc_to}); exp_type; - }; + } as direct_callee; }; ], { @@ -260,6 +217,7 @@ let rec collect_expr ~config ~decls ~refs ~file_deps ~cross_file ~callee_locs when Ident.name id_arg = "arg" && Ident.name eta_arg = "eta" && Path.name id_arg2 = "arg" -> + Expression_table.replace direct_callees direct_callee (); args |> process_optional_args ~config ~cross_file ~exp_type ~loc_from:(loc_from : Location.t) @@ -289,25 +247,16 @@ let rec collect_expr ~config ~decls ~refs ~file_deps ~cross_file ~callee_locs fields |> Array.iter (fun (_, record_label_definition, _) -> match record_label_definition with - | Typedtree.Overridden (_, e) -> ( - match e with - | {exp_loc; _} when exp_loc.loc_ghost -> - (* Punned field in OCaml projects has ghost location in expression *) - let e = {e with exp_loc = {exp_loc with loc_ghost = false}} in - collect_expr ~config ~decls ~refs ~file_deps ~cross_file - ~callee_locs ~ignored_escape_locs ~last_binding super self e - |> ignore - | _ -> ()) + | Typedtree.Overridden (_, ({exp_loc} as e)) when exp_loc.loc_ghost + -> + (* Punned field in OCaml projects has ghost location in expression *) + let e = {e with exp_loc = {exp_loc with loc_ghost = false}} in + collect_expr ~config ~refs ~file_deps ~cross_file ~direct_callees + ~last_binding super self e + |> ignore | _ -> ()) | _ -> ()); - let result = super.Tast_mapper.expr self e in - Option.iter - (fun loc -> callee_locs := remove_first loc !callee_locs) - callee_loc_opt; - Option.iter - (fun loc -> ignored_escape_locs := remove_first loc !ignored_escape_locs) - ignored_escape_loc_opt; - result + super.Tast_mapper.expr self e (* type k. is a locally abstract type @@ -474,8 +423,7 @@ let rec process_signature_item ~config ~decls ~file ~do_types ~do_values (* Traverse the AST *) let traverse_structure ~config ~decls ~refs ~file_deps ~cross_file ~file ~do_types ~do_externals (structure : Typedtree.structure) : unit = - let callee_locs = ref [] in - let ignored_escape_locs = ref [] in + let direct_callees = Expression_table.create 16 in let rec create_mapper (last_binding : Location.t) (module_path : Module_path.t) = let super = Tast_mapper.default in @@ -485,8 +433,8 @@ let traverse_structure ~config ~decls ~refs ~file_deps ~cross_file ~file expr = (fun _self e -> e - |> collect_expr ~config ~decls ~refs ~file_deps ~cross_file - ~callee_locs ~ignored_escape_locs ~last_binding super mapper); + |> collect_expr ~config ~refs ~file_deps ~cross_file ~direct_callees + ~last_binding super mapper); pat = (fun _self p -> p @@ -624,25 +572,27 @@ let traverse_structure ~config ~decls ~refs ~file_deps ~cross_file ~file mapper.structure mapper structure |> ignore (* Merge a location's references to another one's *) -let process_value_dependency ~config ~decls ~refs ~file_deps ~cross_file +let process_value_dependency ~config ~refs ~file_deps ~cross_file ( ({ val_loc = {loc_start = {pos_fname = fn_to} as pos_to; loc_ghost = ghost1} as loc_to; + val_type = type_to; } : Types.value_description), ({ val_loc = {loc_start = {pos_fname = fn_from} as pos_from; loc_ghost = ghost2} as loc_from; + val_type = type_from; } : Types.value_description) ) = if (not ghost1) && (not ghost2) && pos_to <> pos_from then ( let add_file_reference = file_is_implementation_of fn_to fn_from in add_value_reference ~config ~refs ~file_deps ~binding:Location.none ~add_file_reference ~loc_from ~loc_to; - Dead_optional_args.add_function_reference ~config ~decls ~cross_file - ~loc_from ~loc_to) + Dead_optional_args.add_function_reference ~config ~cross_file ~loc_from + ~loc_to ~type_from ~type_to) let process_structure ~config ~decls ~refs ~file_deps ~cross_file ~file ~cmt_value_dependencies ~do_types ~do_externals @@ -651,5 +601,4 @@ let process_structure ~config ~decls ~refs ~file_deps ~cross_file ~file ~do_externals structure; let value_dependencies = cmt_value_dependencies |> List.rev in value_dependencies - |> List.iter - (process_value_dependency ~config ~decls ~refs ~file_deps ~cross_file) + |> List.iter (process_value_dependency ~config ~refs ~file_deps ~cross_file) diff --git a/tests/analysis_tests/tests-reanalyze/deadcode/expected/deadcode.txt b/tests/analysis_tests/tests-reanalyze/deadcode/expected/deadcode.txt index 38e70e54ea2..9e56ebfcaa6 100644 --- a/tests/analysis_tests/tests-reanalyze/deadcode/expected/deadcode.txt +++ b/tests/analysis_tests/tests-reanalyze/deadcode/expected/deadcode.txt @@ -1515,38 +1515,65 @@ addValueDeclaration +formatDateReturn TopLevelOptionalArgValueUse.res:6:4 path:+TopLevelOptionalArgValueUse addValueDeclaration +formatDateTuple TopLevelOptionalArgValueUse.res:7:4 path:+TopLevelOptionalArgValueUse addValueDeclaration +formatDateForwarded TopLevelOptionalArgValueUse.res:8:4 path:+TopLevelOptionalArgValueUse - addValueDeclaration +takesFn TopLevelOptionalArgValueUse.res:10:4 path:+TopLevelOptionalArgValueUse - addValueDeclaration +deadEscape TopLevelOptionalArgValueUse.res:12:4 path:+TopLevelOptionalArgValueUse - addValueDeclaration +liveCaller TopLevelOptionalArgValueUse.res:14:4 path:+TopLevelOptionalArgValueUse - addValueDeclaration +liveEscapeCaller TopLevelOptionalArgValueUse.res:16:4 path:+TopLevelOptionalArgValueUse - addValueDeclaration +liveReturnCaller TopLevelOptionalArgValueUse.res:21:4 path:+TopLevelOptionalArgValueUse - addValueDeclaration +liveTupleCaller TopLevelOptionalArgValueUse.res:25:4 path:+TopLevelOptionalArgValueUse - addValueDeclaration +liveForwardingCaller TopLevelOptionalArgValueUse.res:29:4 path:+TopLevelOptionalArgValueUse + addValueDeclaration +formatDateTopLevelEscape TopLevelOptionalArgValueUse.res:9:4 path:+TopLevelOptionalArgValueUse + addValueDeclaration +formatDateAlias TopLevelOptionalArgValueUse.res:10:4 path:+TopLevelOptionalArgValueUse + addValueDeclaration +takesFn TopLevelOptionalArgValueUse.res:12:4 path:+TopLevelOptionalArgValueUse + addValueDeclaration +deadEscape TopLevelOptionalArgValueUse.res:16:4 path:+TopLevelOptionalArgValueUse + addValueDeclaration +liveCaller TopLevelOptionalArgValueUse.res:18:4 path:+TopLevelOptionalArgValueUse + addValueDeclaration +liveEscapeCaller TopLevelOptionalArgValueUse.res:20:4 path:+TopLevelOptionalArgValueUse + addValueDeclaration +liveReturnCaller TopLevelOptionalArgValueUse.res:25:4 path:+TopLevelOptionalArgValueUse + addValueDeclaration +liveTupleCaller TopLevelOptionalArgValueUse.res:29:4 path:+TopLevelOptionalArgValueUse + addValueDeclaration +liveForwardingCaller TopLevelOptionalArgValueUse.res:33:4 path:+TopLevelOptionalArgValueUse + addValueDeclaration +formatDateAlias2 TopLevelOptionalArgValueUse.res:37:4 path:+TopLevelOptionalArgValueUse + addValueDeclaration +liveAliasCaller TopLevelOptionalArgValueUse.res:38:4 path:+TopLevelOptionalArgValueUse + addValueDeclaration +mutuallyRecursiveCaller TopLevelOptionalArgValueUse.res:40:8 path:+TopLevelOptionalArgValueUse + addValueDeclaration +mutuallyRecursiveTarget TopLevelOptionalArgValueUse.res:41:4 path:+TopLevelOptionalArgValueUse + addValueDeclaration +returnsFunction TopLevelOptionalArgValueUse.res:43:4 path:+TopLevelOptionalArgValueUse + addValueDeclaration +returnedFunction TopLevelOptionalArgValueUse.res:44:4 path:+TopLevelOptionalArgValueUse + addValueDeclaration +liveReturnedFunctionCaller TopLevelOptionalArgValueUse.res:45:4 path:+TopLevelOptionalArgValueUse addValueReference TopLevelOptionalArgValueUse.res:4:4 --> TopLevelOptionalArgValueUse.res:4:26 addValueReference TopLevelOptionalArgValueUse.res:5:4 --> TopLevelOptionalArgValueUse.res:5:33 addValueReference TopLevelOptionalArgValueUse.res:6:4 --> TopLevelOptionalArgValueUse.res:6:32 addValueReference TopLevelOptionalArgValueUse.res:7:4 --> TopLevelOptionalArgValueUse.res:7:31 addValueReference TopLevelOptionalArgValueUse.res:8:4 --> TopLevelOptionalArgValueUse.res:8:35 - addValueReference TopLevelOptionalArgValueUse.res:12:4 --> TopLevelOptionalArgValueUse.res:4:4 - addValueReference TopLevelOptionalArgValueUse.res:12:4 --> TopLevelOptionalArgValueUse.res:10:4 - DeadOptionalArgs.addReferences formatDate called with optional argNames: argNamesMaybe: TopLevelOptionalArgValueUse.res:14:23 - addValueReference TopLevelOptionalArgValueUse.res:14:4 --> TopLevelOptionalArgValueUse.res:4:4 - DeadOptionalArgs.addReferences formatDateEscapes called with optional argNames: argNamesMaybe: TopLevelOptionalArgValueUse.res:18:2 - addValueReference TopLevelOptionalArgValueUse.res:16:4 --> TopLevelOptionalArgValueUse.res:5:4 - addValueReference TopLevelOptionalArgValueUse.res:16:4 --> TopLevelOptionalArgValueUse.res:5:4 - addValueReference TopLevelOptionalArgValueUse.res:16:4 --> TopLevelOptionalArgValueUse.res:10:4 - addValueReference TopLevelOptionalArgValueUse.res:21:4 --> TopLevelOptionalArgValueUse.res:6:4 - addValueReference TopLevelOptionalArgValueUse.res:25:4 --> TopLevelOptionalArgValueUse.res:7:4 - DeadOptionalArgs.addReferences formatDateForwarded called with optional argNames:fmt argNamesMaybe:fmt TopLevelOptionalArgValueUse.res:30:2 - addValueReference TopLevelOptionalArgValueUse.res:29:4 --> TopLevelOptionalArgValueUse.res:29:28 - addValueReference TopLevelOptionalArgValueUse.res:29:4 --> TopLevelOptionalArgValueUse.res:8:4 - addValueReference TopLevelOptionalArgValueUse.res:33:8 --> TopLevelOptionalArgValueUse.res:14:4 - addValueReference TopLevelOptionalArgValueUse.res:34:8 --> TopLevelOptionalArgValueUse.res:16:4 - DeadOptionalArgs.addReferences liveReturnCaller called with optional argNames: argNamesMaybe: TopLevelOptionalArgValueUse.res:35:8 - addValueReference TopLevelOptionalArgValueUse.res:35:8 --> TopLevelOptionalArgValueUse.res:21:4 - addValueReference TopLevelOptionalArgValueUse.res:36:8 --> TopLevelOptionalArgValueUse.res:25:4 - DeadOptionalArgs.addReferences liveForwardingCaller called with optional argNames: argNamesMaybe: TopLevelOptionalArgValueUse.res:37:8 - addValueReference TopLevelOptionalArgValueUse.res:37:8 --> TopLevelOptionalArgValueUse.res:29:4 + addValueReference TopLevelOptionalArgValueUse.res:9:4 --> TopLevelOptionalArgValueUse.res:9:40 + addValueReference TopLevelOptionalArgValueUse.res:10:4 --> TopLevelOptionalArgValueUse.res:10:31 + addValueReference TopLevelOptionalArgValueUse.res:14:8 --> TopLevelOptionalArgValueUse.res:9:4 + addValueReference TopLevelOptionalArgValueUse.res:14:0 --> TopLevelOptionalArgValueUse.res:12:4 + addValueReference TopLevelOptionalArgValueUse.res:16:4 --> TopLevelOptionalArgValueUse.res:4:4 + addValueReference TopLevelOptionalArgValueUse.res:16:4 --> TopLevelOptionalArgValueUse.res:12:4 + DeadOptionalArgs.addReferences formatDate called with optional argNames: argNamesMaybe: TopLevelOptionalArgValueUse.res:18:23 + addValueReference TopLevelOptionalArgValueUse.res:18:4 --> TopLevelOptionalArgValueUse.res:4:4 + DeadOptionalArgs.addReferences formatDateEscapes called with optional argNames: argNamesMaybe: TopLevelOptionalArgValueUse.res:22:2 + addValueReference TopLevelOptionalArgValueUse.res:20:4 --> TopLevelOptionalArgValueUse.res:5:4 + addValueReference TopLevelOptionalArgValueUse.res:20:4 --> TopLevelOptionalArgValueUse.res:5:4 + addValueReference TopLevelOptionalArgValueUse.res:20:4 --> TopLevelOptionalArgValueUse.res:12:4 + addValueReference TopLevelOptionalArgValueUse.res:25:4 --> TopLevelOptionalArgValueUse.res:6:4 + addValueReference TopLevelOptionalArgValueUse.res:29:4 --> TopLevelOptionalArgValueUse.res:7:4 + DeadOptionalArgs.addReferences formatDateForwarded called with optional argNames:fmt argNamesMaybe:fmt TopLevelOptionalArgValueUse.res:34:2 + addValueReference TopLevelOptionalArgValueUse.res:33:4 --> TopLevelOptionalArgValueUse.res:33:28 + addValueReference TopLevelOptionalArgValueUse.res:33:4 --> TopLevelOptionalArgValueUse.res:8:4 + addValueReference TopLevelOptionalArgValueUse.res:37:4 --> TopLevelOptionalArgValueUse.res:10:4 + DeadOptionalArgs.addReferences formatDateAlias2 called with optional argNames: argNamesMaybe: TopLevelOptionalArgValueUse.res:38:28 + addValueReference TopLevelOptionalArgValueUse.res:38:4 --> TopLevelOptionalArgValueUse.res:37:4 + DeadOptionalArgs.addReferences mutuallyRecursiveTarget called with optional argNames:fmt argNamesMaybe: TopLevelOptionalArgValueUse.res:40:40 + addValueReference TopLevelOptionalArgValueUse.res:40:8 --> TopLevelOptionalArgValueUse.res:41:4 + addValueReference TopLevelOptionalArgValueUse.res:41:4 --> TopLevelOptionalArgValueUse.res:41:39 + addValueReference TopLevelOptionalArgValueUse.res:43:4 --> TopLevelOptionalArgValueUse.res:43:22 + DeadOptionalArgs.addReferences returnsFunction called with optional argNames: argNamesMaybe: TopLevelOptionalArgValueUse.res:44:23 + addValueReference TopLevelOptionalArgValueUse.res:44:4 --> TopLevelOptionalArgValueUse.res:43:4 + DeadOptionalArgs.addReferences returnedFunction called with optional argNames: argNamesMaybe: TopLevelOptionalArgValueUse.res:45:39 + addValueReference TopLevelOptionalArgValueUse.res:45:4 --> TopLevelOptionalArgValueUse.res:44:4 + addValueReference TopLevelOptionalArgValueUse.res:47:8 --> TopLevelOptionalArgValueUse.res:18:4 + addValueReference TopLevelOptionalArgValueUse.res:48:8 --> TopLevelOptionalArgValueUse.res:20:4 + DeadOptionalArgs.addReferences liveReturnCaller called with optional argNames: argNamesMaybe: TopLevelOptionalArgValueUse.res:49:8 + addValueReference TopLevelOptionalArgValueUse.res:49:8 --> TopLevelOptionalArgValueUse.res:25:4 + addValueReference TopLevelOptionalArgValueUse.res:50:8 --> TopLevelOptionalArgValueUse.res:29:4 + DeadOptionalArgs.addReferences liveForwardingCaller called with optional argNames: argNamesMaybe: TopLevelOptionalArgValueUse.res:51:8 + addValueReference TopLevelOptionalArgValueUse.res:51:8 --> TopLevelOptionalArgValueUse.res:33:4 + addValueReference TopLevelOptionalArgValueUse.res:52:8 --> TopLevelOptionalArgValueUse.res:38:4 + addValueReference TopLevelOptionalArgValueUse.res:53:8 --> TopLevelOptionalArgValueUse.res:40:8 + addValueReference TopLevelOptionalArgValueUse.res:54:8 --> TopLevelOptionalArgValueUse.res:45:4 Scanning TransitiveType1.cmt Source:TransitiveType1.res addValueDeclaration +convert TransitiveType1.res:2:4 path:+TransitiveType1 addValueDeclaration +convertAlias TransitiveType1.res:5:4 path:+TransitiveType1 @@ -2050,9 +2077,9 @@ Forward Liveness Analysis - decls: 727 - roots(external targets): 152 - decl-deps: decls_with_out=434 edges_to_decls=312 + decls: 736 + roots(external targets): 157 + decl-deps: decls_with_out=443 edges_to_decls=317 Root (external ref): Value +FirstClassModules.M.InnerModule2.+k Root (external ref): VariantCase DeadRT.moduleAccessPath.Root @@ -2065,7 +2092,6 @@ Forward Liveness Analysis Root (annotated): Value +Docstrings.+twoU Root (external ref): RecordLabel +TypeReexportCrossFileB.reexportedRecord.usedField Root (annotated): Value +NestedModules.Universe.Nested2.+nested2Function - Root (external ref): Value +TopLevelOptionalArgValueUse.+liveForwardingCaller Root (annotated): Value +Tuples.+marry Root (annotated): Value +Docstrings.+unitArgWithoutConversionU Root (annotated): Value +Types.+i64Const @@ -2078,6 +2104,7 @@ Forward Liveness Analysis Root (annotated): Value +TypeParams3.+test Root (annotated): Value +Variants.+sunday Root (annotated): Value +NestedModules.Universe.Nested2.Nested3.+nested3Value + Root (external ref): Value +TopLevelOptionalArgValueUse.+liveReturnCaller Root (external ref): RecordLabel +Unison.t.doc Root (annotated): Value +Tuples.+computeAreaWithIdent Root (annotated): Value +LetPrivate.+y @@ -2112,7 +2139,6 @@ Forward Liveness Analysis Root (annotated): Value +ImportHooks.+foo Root (annotated): RecordLabel +ImportIndex.props.method Root (annotated): Value +Docstrings.+unnamed2U - Root (external ref): Value +TopLevelOptionalArgValueUse.+liveCaller Root (external ref): RecordLabel +RecordRest.config.name Root (external ref): Value +FirstClassModules.M.Z.+u Root (annotated): Value +Uncurried.+callback2U @@ -2126,6 +2152,7 @@ Forward Liveness Analysis Root (external ref): Value +OptArg.+foo Root (annotated): Value +Variants.+fortytwoOK Root (external ref): Value OptArg.+bar + Root (external ref): Value +TopLevelOptionalArgValueUse.+liveAliasCaller Root (annotated): Value +Records.+payloadValue Root (external ref): RecordLabel +DeadTest.props.s Root (annotated): Value +Hooks.+default @@ -2177,11 +2204,11 @@ Forward Liveness Analysis Root (annotated): Value +Tuples.+computeArea Root (annotated): Value +References.+get Root (annotated): Value +ModuleAliases.+testNested - Root (external ref): Value +TopLevelOptionalArgValueUse.+liveTupleCaller Root (external ref): Value +FirstClassModules.SomeFunctor.+ww Root (annotated): Value +Hooks.Inner.Inner2.+make Root (annotated): Value +ImportJsValue.+area Root (annotated): Value +Records.+testMyRec + Root (external ref): Value +TopLevelOptionalArgValueUse.+mutuallyRecursiveCaller Root (annotated): Value +DeadTest.GloobLive.+globallyLive3 Root (external ref): RecordLabel +Uncurried.auth.login Root (annotated): Value +ImportJsValue.+roundedNumber @@ -2206,6 +2233,7 @@ Forward Liveness Analysis Root (external ref): Value +OptArg.+twoArgs Root (annotated): Value +OcamlWarningSuppressToplevel.+suppressed1 Root (external ref): RecordLabel +Records.business2.address2 + Root (external ref): Value +TopLevelOptionalArgValueUse.+liveCaller Root (annotated): Value +Tuples.+testTuple Root (external ref): Value +ContextOptionalArgs.NotificationProvider.+dispatchNotification Root (annotated): Value +Records.+testMyObj2 @@ -2223,12 +2251,12 @@ Forward Liveness Analysis Root (annotated): Value +Uncurried.+callback Root (annotated): Value +VariantsWithPayload.+printVariantWithPayload Root (annotated): Value +TestImmutableArray.+testImmutableArrayGet + Root (external ref): Value +TopLevelOptionalArgValueUse.+formatDateTopLevelEscape Root (external ref): Value +TypeReexport.UseOriginal.+value Root (annotated): Value +Docstrings.+unnamed2 Root (annotated): Value +LetPrivate.local_1.+x Root (annotated): Value +TestImport.+make Root (external ref): Value +ContextOptionalArgs.NotificationProvider.Provider.+make - Root (external ref): Value +TopLevelOptionalArgValueUse.+liveEscapeCaller Root (annotated): Value +DeadTest.GloobLive.+globallyLive1 Root (annotated): Value +Docstrings.+grouped Root (annotated): Value +OcamlWarningSuppressToplevel.M.+suppressed4 @@ -2277,8 +2305,10 @@ Forward Liveness Analysis Root (annotated): Value +References.+create Root (annotated): Value +Types.+currentTime Root (annotated): Value +Records.+someBusiness2 + Root (external ref): Value +TopLevelOptionalArgValueUse.+liveEscapeCaller Root (external ref): Value +DeadTest.+ira Root (annotated): Value +FirstClassModules.+testConvert + Root (external ref): Value +TopLevelOptionalArgValueUse.+liveTupleCaller Root (external ref): RecordLabel +Records.coord.z Root (annotated): Value +Types.+someIntList Root (annotated): Value +Types.+jsString2T @@ -2324,6 +2354,7 @@ Forward Liveness Analysis Root (annotated): Value +Variants.+restResult2 Root (annotated): Value +Docstrings.+useParam Root (annotated): Value +Records.+getPayload + Root (external ref): Value +TopLevelOptionalArgValueUse.+liveReturnedFunctionCaller Root (annotated): Value +ScopedAnnotationsOverride.M.NestedInLive.+nestedLive Root (external ref): Value +FirstClassModules.M.+y Root (external ref): RecordLabel +Uncurried.authU.loginU @@ -2334,7 +2365,6 @@ Forward Liveness Analysis Root (annotated): Value +Shadow.M.+test Root (annotated): Value +ComponentAsProp.+make Root (annotated): Value +Records.+testMyRec2 - Root (external ref): Value +TopLevelOptionalArgValueUse.+liveReturnCaller Root (annotated): Value +VariantsWithPayload.+printManyPayloads Root (annotated): Value +TestFirstClassModules.+convertFirstClassModuleWithTypeEquations Root (annotated): Value +TransitiveType1.+convertAlias @@ -2342,6 +2372,7 @@ Forward Liveness Analysis Root (external ref): VariantCase +Unison.stack.Cons Root (external ref): Exception +DeadExn.Inside.Einside Root (annotated): Value +TestImport.+defaultValue2 + Root (external ref): Value +TopLevelOptionalArgValueUse.+liveForwardingCaller Root (external ref): Exception +DeadExn.Etoplevel Root (annotated): Value +Variants.+monday Root (annotated): Value +VariantsWithPayload.+printVariantWithPayloads @@ -2378,6 +2409,7 @@ Forward Liveness Analysis Root (annotated): Value +DeadTest.GloobLive.+globallyLive2 Root (external ref): RecordLabel +TypeReexport.OnlyReexportedDead.reexportedType.usedField Root (annotated): Value +Docstrings.+unitArgWithConversion + Root (external ref): Value +TopLevelOptionalArgValueUse.+takesFn Root (external ref): RecordLabel +Records.business.owner Root (external ref): VariantCase +DeadTest.WithInclude.t.A Root (external ref): VariantCase +DeadTypeTest.deadType.InBoth @@ -2395,13 +2427,13 @@ Forward Liveness Analysis Root (annotated): Value +UseImportJsValue.+useGetProp Root (annotated): Value +Hooks.+functionWithRenamedArgs - 340 roots found + 345 roots found Propagate: DeadRT.moduleAccessPath.Root -> +DeadRT.moduleAccessPath.Root Propagate: +TypeReexportCrossFileB.reexportedRecord.usedField -> +TypeReexportCrossFileA.originalRecord.usedField - Propagate: +TopLevelOptionalArgValueUse.+liveForwardingCaller -> +TopLevelOptionalArgValueUse.+formatDateForwarded Propagate: +DeadTypeTest.deadType.OnlyInImplementation -> DeadTypeTest.deadType.OnlyInImplementation Propagate: +OptionalArgsLiveDead.+liveCaller -> +OptionalArgsLiveDead.+formatDate + Propagate: +TopLevelOptionalArgValueUse.+liveReturnCaller -> +TopLevelOptionalArgValueUse.+formatDateReturn Propagate: +Newton.+f -> +Newton.+- Propagate: +Newton.+f -> +Newton.++ Propagate: +Newton.+f -> +Newton.+* @@ -2409,14 +2441,14 @@ Forward Liveness Analysis Propagate: +ContextOptionalArgs.ComponentUsingAction.+make -> +ContextOptionalArgs.NotificationProvider.+useNotification Propagate: +DeadTest.+thisIsMarkedLive -> +DeadTest.+thisIsKeptAlive Propagate: +TypeReexport.UseOriginal.reexportedType.directlyUsed -> +TypeReexport.UseOriginal.originalType.directlyUsed - Propagate: +TopLevelOptionalArgValueUse.+liveCaller -> +TopLevelOptionalArgValueUse.+formatDate + Propagate: +TopLevelOptionalArgValueUse.+liveAliasCaller -> +TopLevelOptionalArgValueUse.+formatDateAlias2 Propagate: +Hooks.+default -> +Hooks.+make Propagate: InnerModuleTypes.I.t.Foo -> +InnerModuleTypes.I.t.Foo Propagate: DeadTypeTest.deadType.OnlyInInterface -> +DeadTypeTest.deadType.OnlyInInterface Propagate: +TypeReexport.UseReexported.reexportedType.usedField -> +TypeReexport.UseReexported.originalType.usedField Propagate: +CrossFileOptionalArgValueUse.+liveReturnCaller -> +CrossFileOptionalArgProvider.+formatDate Propagate: +References.+get -> +References.R.+get - Propagate: +TopLevelOptionalArgValueUse.+liveTupleCaller -> +TopLevelOptionalArgValueUse.+formatDateTuple + Propagate: +TopLevelOptionalArgValueUse.+mutuallyRecursiveCaller -> +TopLevelOptionalArgValueUse.+mutuallyRecursiveTarget Propagate: ErrorHandler.Make.+notify -> +ErrorHandler.Make.+notify Propagate: +References.+make -> +References.R.+make Propagate: +ScopedAnnotationsLiveVsDead.LiveScope.+root -> +ScopedAnnotationsLiveVsDead.+middleLive @@ -2424,27 +2456,30 @@ Forward Liveness Analysis Propagate: +Newton.+result -> +Newton.+fPrimed Propagate: +Records.+findAllAddresses -> +Records.+getOpt Propagate: +TestOptArg.+bar -> +TestOptArg.+foo + Propagate: +TopLevelOptionalArgValueUse.+liveCaller -> +TopLevelOptionalArgValueUse.+formatDate Propagate: +DeadTest.VariantUsedOnlyInImplementation.+a -> +DeadTest.VariantUsedOnlyInImplementation.+a Propagate: +OptArg.+wrapOneArg -> +OptArg.+oneArg Propagate: +TestImmutableArray.+testImmutableArrayGet -> ImmutableArray.Array.+get Propagate: +ContextOptionalArgs.NotificationProvider.Provider.+make -> +ContextOptionalArgs.NotificationProvider.+dispatchNotificationContext - Propagate: +TopLevelOptionalArgValueUse.+liveEscapeCaller -> +TopLevelOptionalArgValueUse.+formatDateEscapes - Propagate: +TopLevelOptionalArgValueUse.+liveEscapeCaller -> +TopLevelOptionalArgValueUse.+takesFn Propagate: +TypeReexport.VariantUseReexported.reexportedType.A -> +TypeReexport.VariantUseReexported.originalType.A Propagate: +Unison.+toString -> +Unison.+fits Propagate: +References.+set -> +References.R.+set Propagate: +DeadTest.MM.+x -> +DeadTest.MM.+x Propagate: NestedModulesInSignature.Universe.+theAnswer -> +NestedModulesInSignature.Universe.+theAnswer + Propagate: +TopLevelOptionalArgValueUse.+liveEscapeCaller -> +TopLevelOptionalArgValueUse.+formatDateEscapes + Propagate: +TopLevelOptionalArgValueUse.+liveTupleCaller -> +TopLevelOptionalArgValueUse.+formatDateTuple Propagate: +DeadTypeTest.t.A -> DeadTypeTest.t.A Propagate: ImmutableArray.+fromArray -> +ImmutableArray.+fromArray Propagate: +OptArg.+wrapfourArgs -> +OptArg.+fourArgs Propagate: +DeadRT.moduleAccessPath.Kaboom -> DeadRT.moduleAccessPath.Kaboom + Propagate: +TopLevelOptionalArgValueUse.+liveReturnedFunctionCaller -> +TopLevelOptionalArgValueUse.+returnedFunction Propagate: DeadValueTest.+valueAlive -> +DeadValueTest.+valueAlive - Propagate: +TopLevelOptionalArgValueUse.+liveReturnCaller -> +TopLevelOptionalArgValueUse.+formatDateReturn + Propagate: +TopLevelOptionalArgValueUse.+liveForwardingCaller -> +TopLevelOptionalArgValueUse.+formatDateForwarded Propagate: +ImportJsValue.+useGetAbs -> +ImportJsValue.AbsoluteValue.+getAbs Propagate: +TypeReexport.VariantUseOriginal.reexportedType.A -> +TypeReexport.VariantUseOriginal.originalType.A Propagate: +TypeReexport.OnlyReexportedDead.reexportedType.usedField -> +TypeReexport.OnlyReexportedDead.originalType.usedField Propagate: +DeadTest.WithInclude.t.A -> +DeadTest.WithInclude.t.A + Propagate: +TopLevelOptionalArgValueUse.+formatDateAlias2 -> +TopLevelOptionalArgValueUse.+formatDateAlias Propagate: +References.R.+get -> +References.R.+get Propagate: +References.R.+make -> +References.R.+make Propagate: +ScopedAnnotationsLiveVsDead.+middleLive -> +ScopedAnnotationsLiveVsDead.+leafLive @@ -2458,9 +2493,10 @@ Forward Liveness Analysis Propagate: ImmutableArray.Array.+get -> +ImmutableArray.+get Propagate: +References.R.+set -> +References.R.+set Propagate: +DeadTest.MM.+x -> +DeadTest.MM.+y + Propagate: +TopLevelOptionalArgValueUse.+returnedFunction -> +TopLevelOptionalArgValueUse.+returnsFunction Propagate: +ImportJsValue.AbsoluteValue.+getAbs -> +ImportJsValue.AbsoluteValue.+getAbs - 62 declarations marked live via propagation + 66 declarations marked live via propagation Dead VariantCase +AutoAnnotate.variant.R Dead RecordLabel +AutoAnnotate.record.variant @@ -3857,7 +3893,11 @@ Forward Liveness Analysis Live (propagated) Value +TopLevelOptionalArgValueUse.+formatDateForwarded deps: in=1 (live=1 dead=0) out=0 <- +TopLevelOptionalArgValueUse.+liveForwardingCaller (live) - Live (propagated) Value +TopLevelOptionalArgValueUse.+takesFn + Live (external ref) Value +TopLevelOptionalArgValueUse.+formatDateTopLevelEscape + Live (propagated) Value +TopLevelOptionalArgValueUse.+formatDateAlias + deps: in=1 (live=1 dead=0) out=0 + <- +TopLevelOptionalArgValueUse.+formatDateAlias2 (live) + Live (external ref) Value +TopLevelOptionalArgValueUse.+takesFn deps: in=2 (live=1 dead=1) out=0 <- +TopLevelOptionalArgValueUse.+deadEscape (dead) <- +TopLevelOptionalArgValueUse.+liveEscapeCaller (live) @@ -3881,6 +3921,29 @@ Forward Liveness Analysis Live (external ref) Value +TopLevelOptionalArgValueUse.+liveForwardingCaller deps: in=0 (live=0 dead=0) out=1 -> +TopLevelOptionalArgValueUse.+formatDateForwarded + Live (propagated) Value +TopLevelOptionalArgValueUse.+formatDateAlias2 + deps: in=1 (live=1 dead=0) out=1 + <- +TopLevelOptionalArgValueUse.+liveAliasCaller (live) + -> +TopLevelOptionalArgValueUse.+formatDateAlias + Live (external ref) Value +TopLevelOptionalArgValueUse.+liveAliasCaller + deps: in=0 (live=0 dead=0) out=1 + -> +TopLevelOptionalArgValueUse.+formatDateAlias2 + Live (external ref) Value +TopLevelOptionalArgValueUse.+mutuallyRecursiveCaller + deps: in=0 (live=0 dead=0) out=1 + -> +TopLevelOptionalArgValueUse.+mutuallyRecursiveTarget + Live (propagated) Value +TopLevelOptionalArgValueUse.+mutuallyRecursiveTarget + deps: in=1 (live=1 dead=0) out=0 + <- +TopLevelOptionalArgValueUse.+mutuallyRecursiveCaller (live) + Live (propagated) Value +TopLevelOptionalArgValueUse.+returnsFunction + deps: in=1 (live=1 dead=0) out=0 + <- +TopLevelOptionalArgValueUse.+returnedFunction (live) + Live (propagated) Value +TopLevelOptionalArgValueUse.+returnedFunction + deps: in=1 (live=1 dead=0) out=1 + <- +TopLevelOptionalArgValueUse.+liveReturnedFunctionCaller (live) + -> +TopLevelOptionalArgValueUse.+returnsFunction + Live (external ref) Value +TopLevelOptionalArgValueUse.+liveReturnedFunctionCaller + deps: in=0 (live=0 dead=0) out=1 + -> +TopLevelOptionalArgValueUse.+returnedFunction Live (annotated) Value +TransitiveType1.+convert Live (annotated) Value +TransitiveType1.+convertAlias Dead Value +TransitiveType2.+convertT2 @@ -5242,7 +5305,7 @@ Forward Liveness Analysis toPayload.result is a record label never used to read a value Warning Dead Value - TopLevelOptionalArgValueUse.res:12:1-42 + TopLevelOptionalArgValueUse.res:16:1-42 deadEscape is never used Warning Dead Module @@ -5421,10 +5484,6 @@ Forward Liveness Analysis OptArg.res:24:1-63 optional argument d of function fourArgs is never used - Warning Unused Argument - TopLevelOptionalArgValueUse.res:29:1-85 - optional argument fmt of function liveForwardingCaller is never used - Warning Unused Argument TestOptArg.res:9:1-65 optional argument x of function notSuppressesOptArgs is never used @@ -5489,6 +5548,10 @@ Forward Liveness Analysis Unison.res:17:1-55 optional argument break_ of function group is always supplied (2 calls) + Warning Redundant Optional Argument + TopLevelOptionalArgValueUse.res:41:1-46 + optional argument fmt of function mutuallyRecursiveTarget is always supplied (1 calls) + Warning Unused Argument TopLevelOptionalArgValueUse.res:4:1-33 optional argument fmt of function formatDate is never used @@ -5496,5 +5559,9 @@ Forward Liveness Analysis Warning Redundant Optional Argument OptArg.res:26:1-70 optional argument c of function wrapfourArgs is always supplied (2 calls) + + Warning Unused Argument + TopLevelOptionalArgValueUse.res:33:1-85 + optional argument fmt of function liveForwardingCaller is never used - Analysis reported 322 issues (Incorrect Dead Annotation:1, Warning Dead Exception:2, Warning Dead Module:22, Warning Dead Type:94, Warning Dead Value:178, Warning Dead Value With Side Effects:5, Warning Redundant Optional Argument:6, Warning Unused Argument:14) + Analysis reported 323 issues (Incorrect Dead Annotation:1, Warning Dead Exception:2, Warning Dead Module:22, Warning Dead Type:94, Warning Dead Value:178, Warning Dead Value With Side Effects:5, Warning Redundant Optional Argument:7, Warning Unused Argument:14) diff --git a/tests/analysis_tests/tests-reanalyze/deadcode/src/TopLevelOptionalArgValueUse.res b/tests/analysis_tests/tests-reanalyze/deadcode/src/TopLevelOptionalArgValueUse.res index ef080ed1b35..32aba79370a 100644 --- a/tests/analysis_tests/tests-reanalyze/deadcode/src/TopLevelOptionalArgValueUse.res +++ b/tests/analysis_tests/tests-reanalyze/deadcode/src/TopLevelOptionalArgValueUse.res @@ -6,9 +6,13 @@ let formatDateEscapes = (~fmt=?, s) => s let formatDateReturn = (~fmt=?, s) => s let formatDateTuple = (~fmt=?, s) => s let formatDateForwarded = (~fmt=?, s) => s +let formatDateTopLevelEscape = (~fmt=?, s) => s +let formatDateAlias = (~fmt=?, s) => s let takesFn = _ => () +takesFn(formatDateTopLevelEscape) + let deadEscape = () => takesFn(formatDate) let liveCaller = () => formatDate("2024-01-01") @@ -30,8 +34,21 @@ let liveForwardingCaller = (~fmt=?) => { formatDateForwarded(~fmt?, "2024-01-01") } +let formatDateAlias2 = formatDateAlias +let liveAliasCaller = () => formatDateAlias2("2024-01-01") + +let rec mutuallyRecursiveCaller = () => mutuallyRecursiveTarget(~fmt="ISO", "2024-01-01") +and mutuallyRecursiveTarget = (~fmt=?, s) => s + +let returnsFunction = value => (~inner: option=?) => value +let returnedFunction = returnsFunction("value") +let liveReturnedFunctionCaller = () => returnedFunction() + let _ = liveCaller() let _ = liveEscapeCaller() let _ = liveReturnCaller() let _ = liveTupleCaller() let _ = liveForwardingCaller() +let _ = liveAliasCaller() +let _ = mutuallyRecursiveCaller() +let _ = liveReturnedFunctionCaller() From cf207f98d3f92e5738f8b7f7ee181cba9cbd3052 Mon Sep 17 00:00:00 2001 From: Christoph Knittel Date: Fri, 10 Jul 2026 09:29:23 +0200 Subject: [PATCH 10/10] Another fix... --- .../reanalyze/src/cross_file_items_store.ml | 19 ++++++++- .../deadcode/expected/deadcode.txt | 40 ++++++++++++++++--- .../src/InterfaceOptionalArgEscape.res | 5 +++ .../src/InterfaceOptionalArgEscape.resi | 1 + .../src/InterfaceOptionalArgEscapeUse.res | 3 ++ 5 files changed, 62 insertions(+), 6 deletions(-) create mode 100644 tests/analysis_tests/tests-reanalyze/deadcode/src/InterfaceOptionalArgEscape.res create mode 100644 tests/analysis_tests/tests-reanalyze/deadcode/src/InterfaceOptionalArgEscape.resi create mode 100644 tests/analysis_tests/tests-reanalyze/deadcode/src/InterfaceOptionalArgEscapeUse.res diff --git a/analysis/reanalyze/src/cross_file_items_store.ml b/analysis/reanalyze/src/cross_file_items_store.ml index 2fe61c74574..ec316d6b264 100644 --- a/analysis/reanalyze/src/cross_file_items_store.ml +++ b/analysis/reanalyze/src/cross_file_items_store.ml @@ -80,4 +80,21 @@ let compute_live_optional_arg_value_escapes (store : t) ~is_live : Pos_set.t = iter_optional_arg_value_escapes store (fun {Cross_file_items.pos_from; pos_to} -> if is_live pos_from then escapes := Pos_set.add pos_to !escapes); - !escapes + let function_refs = ref [] in + iter_function_refs store (fun {Cross_file_items.pos_from; pos_to} -> + if is_live pos_from then + function_refs := (pos_from, pos_to) :: !function_refs); + (* A function reference aliases both declaration positions. Close escapes over + the undirected links so aliases and interface/implementation pairs agree. *) + let rec propagate escapes = + let propagated = + List.fold_left + (fun escapes (pos_from, pos_to) -> + if Pos_set.mem pos_from escapes then Pos_set.add pos_to escapes + else if Pos_set.mem pos_to escapes then Pos_set.add pos_from escapes + else escapes) + escapes !function_refs + in + if Pos_set.equal propagated escapes then escapes else propagate propagated + in + propagate !escapes diff --git a/tests/analysis_tests/tests-reanalyze/deadcode/expected/deadcode.txt b/tests/analysis_tests/tests-reanalyze/deadcode/expected/deadcode.txt index 9e56ebfcaa6..8588578ef6f 100644 --- a/tests/analysis_tests/tests-reanalyze/deadcode/expected/deadcode.txt +++ b/tests/analysis_tests/tests-reanalyze/deadcode/expected/deadcode.txt @@ -1016,6 +1016,21 @@ addVariantCaseDeclaration Foo InnerModuleTypes.res:2:11 path:+InnerModuleTypes.I.t Scanning InnerModuleTypes.cmti Source:InnerModuleTypes.resi addVariantCaseDeclaration Foo InnerModuleTypes.resi:2:11 path:InnerModuleTypes.I.t + Scanning InterfaceOptionalArgEscape.cmt Source:InterfaceOptionalArgEscape.res + addValueDeclaration +escaped InterfaceOptionalArgEscape.res:1:4 path:+InterfaceOptionalArgEscape + addValueDeclaration +takesFn InterfaceOptionalArgEscape.res:3:4 path:+InterfaceOptionalArgEscape + addValueReference InterfaceOptionalArgEscape.res:1:4 --> InterfaceOptionalArgEscape.res:1:23 + addValueReference InterfaceOptionalArgEscape.res:5:8 --> InterfaceOptionalArgEscape.res:1:4 + addValueReference InterfaceOptionalArgEscape.res:5:0 --> InterfaceOptionalArgEscape.res:3:4 + addValueReference InterfaceOptionalArgEscape.resi:1:0 --> InterfaceOptionalArgEscape.res:1:4 + OptionalArgs.addFunctionReference InterfaceOptionalArgEscape.resi:1:0 InterfaceOptionalArgEscape.res:1:4 + Scanning InterfaceOptionalArgEscape.cmti Source:InterfaceOptionalArgEscape.resi + addValueDeclaration +escaped InterfaceOptionalArgEscape.resi:1:0 path:InterfaceOptionalArgEscape + Scanning InterfaceOptionalArgEscapeUse.cmt Source:InterfaceOptionalArgEscapeUse.res + addValueDeclaration +use InterfaceOptionalArgEscapeUse.res:1:4 path:+InterfaceOptionalArgEscapeUse + DeadOptionalArgs.addReferences InterfaceOptionalArgEscape.escaped called with optional argNames: argNamesMaybe: InterfaceOptionalArgEscapeUse.res:1:16 + addValueReference InterfaceOptionalArgEscapeUse.res:1:4 --> InterfaceOptionalArgEscape.resi:1:0 + addValueReference InterfaceOptionalArgEscapeUse.res:3:8 --> InterfaceOptionalArgEscapeUse.res:1:4 Scanning JSResource.cmt Source:JSResource.res Scanning JsxV4.cmt Source:JsxV4.res addValueDeclaration +make JsxV4.res:4:23 path:+JsxV4.C @@ -2077,9 +2092,9 @@ Forward Liveness Analysis - decls: 736 - roots(external targets): 157 - decl-deps: decls_with_out=443 edges_to_decls=317 + decls: 740 + roots(external targets): 160 + decl-deps: decls_with_out=446 edges_to_decls=319 Root (external ref): Value +FirstClassModules.M.InnerModule2.+k Root (external ref): VariantCase DeadRT.moduleAccessPath.Root @@ -2094,6 +2109,7 @@ Forward Liveness Analysis Root (annotated): Value +NestedModules.Universe.Nested2.+nested2Function Root (annotated): Value +Tuples.+marry Root (annotated): Value +Docstrings.+unitArgWithoutConversionU + Root (external ref): Value +InterfaceOptionalArgEscape.+escaped Root (annotated): Value +Types.+i64Const Root (external ref): VariantCase +DeadTypeTest.deadType.OnlyInImplementation Root (annotated): Value +TestImport.+valueStartingWithUpperCaseLetter @@ -2304,6 +2320,7 @@ Forward Liveness Analysis Root (annotated): Value +ContextOptionalArgs.+make Root (annotated): Value +References.+create Root (annotated): Value +Types.+currentTime + Root (external ref): Value +InterfaceOptionalArgEscapeUse.+use Root (annotated): Value +Records.+someBusiness2 Root (external ref): Value +TopLevelOptionalArgValueUse.+liveEscapeCaller Root (external ref): Value +DeadTest.+ira @@ -2391,6 +2408,7 @@ Forward Liveness Analysis Root (external ref): Value +FirstClassModules.M.+x Root (external ref): Value +TypeReexportCrossFileB.+recordValue Root (annotated): Value +Records.+computeArea4 + Root (external ref): Value +InterfaceOptionalArgEscape.+takesFn Root (annotated): Value +TestModuleAliases.+testInner1Expanded Root (external ref): Value +TypeReexport.OnlyReexportedDead.+value Root (annotated): Value +ImportIndex.+make @@ -2427,7 +2445,7 @@ Forward Liveness Analysis Root (annotated): Value +UseImportJsValue.+useGetProp Root (annotated): Value +Hooks.+functionWithRenamedArgs - 345 roots found + 348 roots found Propagate: DeadRT.moduleAccessPath.Root -> +DeadRT.moduleAccessPath.Root Propagate: +TypeReexportCrossFileB.reexportedRecord.usedField -> +TypeReexportCrossFileA.originalRecord.usedField @@ -2466,6 +2484,7 @@ Forward Liveness Analysis Propagate: +References.+set -> +References.R.+set Propagate: +DeadTest.MM.+x -> +DeadTest.MM.+x Propagate: NestedModulesInSignature.Universe.+theAnswer -> +NestedModulesInSignature.Universe.+theAnswer + Propagate: +InterfaceOptionalArgEscapeUse.+use -> InterfaceOptionalArgEscape.+escaped Propagate: +TopLevelOptionalArgValueUse.+liveEscapeCaller -> +TopLevelOptionalArgValueUse.+formatDateEscapes Propagate: +TopLevelOptionalArgValueUse.+liveTupleCaller -> +TopLevelOptionalArgValueUse.+formatDateTuple Propagate: +DeadTypeTest.t.A -> DeadTypeTest.t.A @@ -2496,7 +2515,7 @@ Forward Liveness Analysis Propagate: +TopLevelOptionalArgValueUse.+returnedFunction -> +TopLevelOptionalArgValueUse.+returnsFunction Propagate: +ImportJsValue.AbsoluteValue.+getAbs -> +ImportJsValue.AbsoluteValue.+getAbs - 66 declarations marked live via propagation + 67 declarations marked live via propagation Dead VariantCase +AutoAnnotate.variant.R Dead RecordLabel +AutoAnnotate.record.variant @@ -3459,6 +3478,17 @@ Forward Liveness Analysis <- +InnerModuleTypes.I.t.Foo (live) <- +TestInnedModuleTypes.+_ (dead) -> +InnerModuleTypes.I.t.Foo + Live (external ref) Value +InterfaceOptionalArgEscape.+escaped + deps: in=1 (live=1 dead=0) out=0 + <- InterfaceOptionalArgEscape.+escaped (live) + Live (external ref) Value +InterfaceOptionalArgEscape.+takesFn + Live (propagated) Value InterfaceOptionalArgEscape.+escaped + deps: in=1 (live=1 dead=0) out=1 + <- +InterfaceOptionalArgEscapeUse.+use (live) + -> +InterfaceOptionalArgEscape.+escaped + Live (external ref) Value +InterfaceOptionalArgEscapeUse.+use + deps: in=0 (live=0 dead=0) out=1 + -> InterfaceOptionalArgEscape.+escaped Live (external ref) Value +JsxV4.C.+make Live (annotated) Value +LetPrivate.local_1.+x deps: in=1 (live=1 dead=0) out=0 diff --git a/tests/analysis_tests/tests-reanalyze/deadcode/src/InterfaceOptionalArgEscape.res b/tests/analysis_tests/tests-reanalyze/deadcode/src/InterfaceOptionalArgEscape.res new file mode 100644 index 00000000000..15054699752 --- /dev/null +++ b/tests/analysis_tests/tests-reanalyze/deadcode/src/InterfaceOptionalArgEscape.res @@ -0,0 +1,5 @@ +let escaped = (~fmt=?, value) => value + +let takesFn = _ => () + +takesFn(escaped) diff --git a/tests/analysis_tests/tests-reanalyze/deadcode/src/InterfaceOptionalArgEscape.resi b/tests/analysis_tests/tests-reanalyze/deadcode/src/InterfaceOptionalArgEscape.resi new file mode 100644 index 00000000000..de0b5900d0c --- /dev/null +++ b/tests/analysis_tests/tests-reanalyze/deadcode/src/InterfaceOptionalArgEscape.resi @@ -0,0 +1 @@ +let escaped: (~fmt: string=?, string) => string diff --git a/tests/analysis_tests/tests-reanalyze/deadcode/src/InterfaceOptionalArgEscapeUse.res b/tests/analysis_tests/tests-reanalyze/deadcode/src/InterfaceOptionalArgEscapeUse.res new file mode 100644 index 00000000000..8c69690a4db --- /dev/null +++ b/tests/analysis_tests/tests-reanalyze/deadcode/src/InterfaceOptionalArgEscapeUse.res @@ -0,0 +1,3 @@ +let use = () => InterfaceOptionalArgEscape.escaped("value") + +let _ = use()