Skip to content
Draft
Show file tree
Hide file tree
Changes from 10 commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
8d28353
Add failing fixture for functors constrained by a named module type
JonoPrest Sep 3, 2026
3d8c667
Fix DCE false positives for functors constrained by a named module type
JonoPrest Sep 3, 2026
fa6d29a
Address review: register all cmts up front, forward optional-arg calls
JonoPrest Sep 3, 2026
3c193b6
Resolve identifier occurrences through shapes for precise attribution
JonoPrest Sep 3, 2026
8493b34
Move shared-signature case to version-gated regression test
JonoPrest Sep 3, 2026
e2dfc05
Address review: shape-resolve functor arguments, key occurrences prec…
JonoPrest Sep 3, 2026
5bb8c69
Address review: scope unit cache, fix mixed-reference suppression, cr…
JonoPrest Sep 3, 2026
22edc55
Address review: constrained arguments, inline functors, unit selectio…
JonoPrest Sep 3, 2026
de59edb
Address review: cache key digest, conservative crediting, constrained…
JonoPrest Sep 3, 2026
446daf5
Address review: partial applications and let-module functors
JonoPrest Sep 3, 2026
3176e1e
Fix let-module functor keys on OCaml 5.3 and 5.4
JonoPrest Sep 3, 2026
8e42af7
Address review: parameter aliases and recursive functors
JonoPrest Sep 3, 2026
e52dbbc
Address review: fall back conservatively on ppx key collisions
JonoPrest Sep 3, 2026
5b80fba
Address review: indistinguishable units and occurrences resolve nothing
JonoPrest Sep 3, 2026
3fab645
Address review: module type aliases, forwarded parameters, include of…
JonoPrest Sep 3, 2026
3d82a82
Address review: collect nested module type ranges
JonoPrest Sep 3, 2026
33f89bd
Address review: functor aliases, local module type roots, identity ma…
JonoPrest Sep 3, 2026
72dbfa8
Address review: nested module types, Sig_modtype, alias cycles; fix c…
JonoPrest Sep 3, 2026
3e7135f
Address review: forwarding cycles, cross-file aliases, functor-body s…
JonoPrest Sep 3, 2026
a776e3a
Address review: partial applications held locally or bound in other u…
JonoPrest Sep 3, 2026
16fb42b
Address review: with-constrained module types, alias cycle detection
JonoPrest Sep 3, 2026
ba08473
Address review: structural path resolution, recursive aliases
JonoPrest Sep 3, 2026
30361d3
Address review: with module type constraints, recursive parameter ali…
JonoPrest Sep 3, 2026
a0d2415
Address review: module type of, included members in structural lookup
JonoPrest Sep 3, 2026
0d401be
Address review: resolve included members with the defining unit's res…
JonoPrest Sep 3, 2026
85d6f5e
Address review: forward aliases in recursive groups
JonoPrest Sep 3, 2026
826c582
Address review: keep the resolver of an included member
JonoPrest Sep 3, 2026
ba5090d
Address review: module types rooted at functor applications
JonoPrest Sep 3, 2026
5acb538
Address review: module types rooted at functor parameters and applica…
JonoPrest Sep 3, 2026
97eac09
Address review: aliased result members, parameter alias cycles, full …
JonoPrest Sep 3, 2026
409984e
Address review: recurse through submodules in the shape fallback
JonoPrest Sep 3, 2026
ab3bcb2
Address review: forward parameter aliases in recursive groups
JonoPrest Sep 3, 2026
30f8eb1
Register let-module parameter aliases before their body is visited (O…
JonoPrest Sep 3, 2026
a7b33a3
Address review: colliding functor keys credit all applications; exact…
JonoPrest Sep 3, 2026
5ac1335
Normalize path segments in the source-file comparison
JonoPrest Sep 3, 2026
110d0be
Address review: copies of a compiled unit are one unit; both path sep…
JonoPrest Sep 3, 2026
6d7d18d
Address review: fall back to a constrained argument's concrete type
JonoPrest Sep 3, 2026
9e798e5
Address review: module type ranges in applications and parameter types
JonoPrest Sep 3, 2026
f1842d2
Address review: constraint-wrapped right-hand sides when deriving fun…
JonoPrest Sep 3, 2026
1b1c3e1
Address review: unit applications in chains, shapes of applied arguments
JonoPrest Sep 4, 2026
319ce46
Address review: higher-order functors, first-class modules, unmatched…
JonoPrest Sep 4, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions Changes.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
# Unreleased

- Fix false positives for values in functor bodies (and modules) constrained by a module type defined in another file, e.g. `module Make (K) : Gen.S with type key = K.t`. On OCaml 5.3+ declaration dependencies pointing at other compilation units are now resolved, and references to `val` items of a named module type are forwarded to the implementations satisfying it.

# 2.26.0

- Support OCaml 5.3, 5.4, and 5.5.
Expand Down
14 changes: 14 additions & 0 deletions examples/deadcode/src/FunctorSigGen.ml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
(* Named module types used as functor result signatures from other files. *)
module type S = sig
val find_opt : int -> int option

val unused_in_sig : int -> int

val with_opt : ?x:int -> unit -> int
end

module type T = sig
val lookup : int -> int option

val unused_in_inline_sig : int -> int
end
9 changes: 9 additions & 0 deletions examples/deadcode/src/FunctorSigInline.ml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
module Make (K : sig
type t
end) : FunctorSigGen.T = struct
let lookup k = Some k

let unused_in_inline_sig k = k

let truly_dead_inline k = k
end
12 changes: 12 additions & 0 deletions examples/deadcode/src/FunctorSigMli.ml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
module Make (K : sig
type t
end) =
struct
let find_opt k = Some k

let unused_in_sig k = k

let truly_dead k = k

let with_opt ?(x = 0) () = x
end
3 changes: 3 additions & 0 deletions examples/deadcode/src/FunctorSigMli.mli
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module Make (K : sig
type t
end) : FunctorSigGen.S
13 changes: 13 additions & 0 deletions examples/deadcode/src/FunctorSigUse.ml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
module H = FunctorSigMli.Make (struct
type t = int
end)

module I = FunctorSigInline.Make (struct
type t = int
end)

let run () =
ignore (H.with_opt ~x:1 ());
match (H.find_opt 1, I.lookup 2) with
| Some _, Some _ -> print_endline "x"
| _ -> ()
1 change: 1 addition & 0 deletions examples/deadcode/src/Main.ml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ let () =
ignore (Types.make_live 1);
ignore TransitiveTypes.live_value;
FirstClassModules.run ();
FunctorSigUse.run ();
OptionalArgs.live_optional ~used:1 ();
ignore (Externals.live_external "abc");
Annotations.live_value ();
Expand Down
30 changes: 29 additions & 1 deletion examples/deadcode/src/deadcode.txt
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@
File "OptionalArgs.ml", line 1, characters 0-86
optional argument used of function +live_optional is always supplied (1 calls)

Warning Redundant Optional Argument
File "FunctorSigMli.ml", line 11, characters 2-30
optional argument x of function Make.+with_opt is always supplied (1 calls)

Warning Dead Value
File "Annotations.ml", line 5, characters 0-33
+stale_dead_annotation is never used
Expand Down Expand Up @@ -41,6 +45,30 @@
<-- line 10
let run () = () [@@dead "DeadRunner.+run"]

Warning Dead Value
File "FunctorSigInline.ml", line 6, characters 2-32
Make.+unused_in_inline_sig is never used
<-- line 6
let unused_in_inline_sig k = k [@@dead "Make.+unused_in_inline_sig"]

Warning Dead Value
File "FunctorSigInline.ml", line 8, characters 2-29
Make.+truly_dead_inline is never used
<-- line 8
let truly_dead_inline k = k [@@dead "Make.+truly_dead_inline"]

Warning Dead Value
File "FunctorSigMli.ml", line 7, characters 2-25
Make.+unused_in_sig is never used
<-- line 7
let unused_in_sig k = k [@@dead "Make.+unused_in_sig"]

Warning Dead Value
File "FunctorSigMli.ml", line 9, characters 2-22
Make.+truly_dead is never used
<-- line 9
let truly_dead k = k [@@dead "Make.+truly_dead"]

Warning Dead Value
File "Includes.ml", line 4, characters 2-29
+included_unused is never used
Expand Down Expand Up @@ -123,4 +151,4 @@
<-- line 4
| LiveA [@dead "live_variant.LiveA"]

Analysis reported 22 issues (Warning Dead Module:2, Warning Dead Type:5, Warning Dead Value:13, Warning Redundant Optional Argument:1, Warning Unused Argument:1)
Analysis reported 27 issues (Warning Dead Module:2, Warning Dead Type:5, Warning Dead Value:17, Warning Redundant Optional Argument:2, Warning Unused Argument:1)
4 changes: 4 additions & 0 deletions examples/deadcode/src/dune
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@
Exceptions
Externals
FirstClassModules
FunctorSigGen
FunctorSigInline
FunctorSigMli
FunctorSigUse
Includes
Main
ModuleAliases
Expand Down
8 changes: 7 additions & 1 deletion examples/regression/src/Main.ml
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,10 @@ let () =
Functor_argument.use_set ();
Functor_argument.use_anonymous_set ();
Local_side_effects.start ();
Ocaml_compiler_compat.run ()
Ocaml_compiler_compat.run ();
Shared_signature_use.run ();
Shared_signature_arg.run ();
Shared_signature_arg.run_more ();
Shared_signature_arg.run_even_more ();
Shared_signature_arg.run_sig ();
Shared_signature_arg.run_partial ()
10 changes: 10 additions & 0 deletions examples/regression/src/Shared_signature.ml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
(* A named module type shared by two functors. Only [Used] is instantiated, so
[Unused.Make.f] must be reported dead: a call through [Used]'s instance must
not keep [Unused]'s implementation alive. *)
module type S = sig
val f : unit -> int
end

module type O = sig
val g : ?x:int -> unit -> int
end
133 changes: 133 additions & 0 deletions examples/regression/src/Shared_signature_arg.ml
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
(* Two modules implement the shared named signature; only [Chosen] is passed
to a functor. [Ignored.f] must be reported dead. *)
module Chosen : Shared_signature.S = struct
let f () = 3
end

module Ignored : Shared_signature.S = struct
let f () = 4
end

module Apply (M : Shared_signature.S) = struct
let run () = M.f ()
end

module Applied = Apply (Chosen)

(* Same, but the module is used directly from the same file. *)
module Local_used : Shared_signature.S = struct
let f () = 5
end

module Local_unused : Shared_signature.S = struct
let f () = 6
end

let run () = ignore (Applied.run () + Local_used.f ())

(* A binding calling both through the parameter and a resolved module: the
parameter call must still not be forwarded to [Ignored]. *)
module Apply_mixed (M : Shared_signature.S) = struct
let run () = M.f () + Chosen.f ()
end

module Applied_mixed = Apply_mixed (Chosen)

(* Optional argument supplied only through a functor parameter call: must be
credited to [Opt_chosen] only, so [Opt_direct]'s [x] stays never used. *)
module Opt_chosen : Shared_signature.O = struct
let g ?(x = 0) () = x
end

module Opt_direct : Shared_signature.O = struct
let g ?(x = 0) () = x
end

module Apply_opt (M : Shared_signature.O) = struct
let run () = M.g ~x:1 ()
end

module Applied_opt = Apply_opt (Opt_chosen)

let run_more () =
ignore (Applied_mixed.run () + Applied_opt.run () + Opt_direct.g ())

(* Argument wrapped in a constraint: [(Opt_constrained : O)]. *)
module Opt_constrained : Shared_signature.O = struct
let g ?(x = 0) () = x
end

module Applied_constrained = Apply_opt ((Opt_constrained : Shared_signature.O))

(* Inline functor applied directly. *)
module Opt_inline : Shared_signature.O = struct
let g ?(x = 0) () = x
end

module Applied_inline =
(functor (M : Shared_signature.O) -> struct
let run () = M.g ~x:1 ()
end)
(Opt_inline)

(* Constrained argument for liveness: [Ignored2] must stay dead. *)
module Chosen2 : Shared_signature.S = struct
let f () = 7
end

module Ignored2 : Shared_signature.S = struct
let f () = 8
end

module Applied_constrained_value = Apply ((Chosen2 : Shared_signature.S))

let run_even_more () =
ignore
(Applied_constrained.run () + Applied_inline.run ()
+ Applied_constrained_value.run ())

(* Named functor with a whole-functor signature: the binding expression is a
constraint around the functor. *)
module Opt_sigfun : Shared_signature.O = struct
let g ?(x = 0) () = x
end

module Apply_sig : functor (M : Shared_signature.O) -> sig
val run : unit -> int
end =
functor
(M : Shared_signature.O)
->
struct
let run () = M.g ~x:1 ()
end

module Applied_sig = Apply_sig (Opt_sigfun)

let run_sig () = ignore (Applied_sig.run ())

(* Curried functor, partially applied and named before the second argument. *)
module Opt_partial : Shared_signature.O = struct
let g ?(x = 0) () = x
end

module Apply2 (A : Shared_signature.S) (B : Shared_signature.O) = struct
let run () = A.f () + B.g ~x:1 ()
end

module Apply2_partial = Apply2 (Chosen)
module Applied_partial = Apply2_partial (Opt_partial)

(* Functor bound with [let module]. *)
module Opt_letmodule : Shared_signature.O = struct
let g ?(x = 0) () = x
end

let run_local () =
let module Apply_local (M : Shared_signature.O) = struct
let run () = M.g ~x:1 ()
end in
let module Applied_local = Apply_local (Opt_letmodule) in
Applied_local.run ()

let run_partial () = ignore (Applied_partial.run () + run_local ())
5 changes: 5 additions & 0 deletions examples/regression/src/Shared_signature_unused.ml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
module Make (K : sig
type t
end) : Shared_signature.S = struct
let f () = 2
end
5 changes: 5 additions & 0 deletions examples/regression/src/Shared_signature_use.ml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
module H = Shared_signature_used.Make (struct
type t = int
end)

let run () = ignore (H.f ())
5 changes: 5 additions & 0 deletions examples/regression/src/Shared_signature_used.ml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
module Make (K : sig
type t
end) : Shared_signature.S = struct
let f () = 1
end
7 changes: 6 additions & 1 deletion examples/regression/src/dune
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,10 @@
Live_ancestors
Local_side_effects
Main
Ocaml_compiler_compat)
Ocaml_compiler_compat
Shared_signature
Shared_signature_arg
Shared_signature_unused
Shared_signature_use
Shared_signature_used)
(flags :standard -w -27-32-34-37-39 -bin-annot))
Loading
Loading