From 5c5462bd3fc88db3aa5ac846a2e7f42306afad12 Mon Sep 17 00:00:00 2001 From: Jon Ludlam Date: Fri, 5 Jun 2026 20:12:18 +0100 Subject: [PATCH 01/11] xref2: add an MDX demonstration of Component.Fmt Embed an example of the Fmt module. We'll use this example over the next few commits to demonstrate how they change the formatting. Co-Authored-By: Claude Opus 4.8 (1M context) --- src/xref2/component.mli | 47 +++++++++++++++++++++++++++++ src/xref2/component_fmt_prelude.txt | 16 ++++++++++ src/xref2/dune | 18 +++++++++++ 3 files changed, 81 insertions(+) create mode 100644 src/xref2/component_fmt_prelude.txt diff --git a/src/xref2/component.mli b/src/xref2/component.mli index 349ec900e0..0bbc8f8c09 100644 --- a/src/xref2/component.mli +++ b/src/xref2/component.mli @@ -545,6 +545,53 @@ end (** Formatting functions for components *) module Fmt : sig + (** Pretty-printer for the {!Component} representation, used by debug output + and tests. + + The following example uses [show] to compile a snippet of [.mli] source + into a {!Signature.t} and renders it with {!Fmt.signature} (see the + [component_fmt_prelude] MDX prelude): + + {@ocaml[ + # show {| + module type S = sig type t end + module M : S + module Internal__thing : S + module F (X : S) : sig end + module type T = S with type t = int + module type Tsub = S with type t := int + type fn = (int -> int) -> int + type curried = int -> int -> int + type 'a boxed = 'a list + type ('a, 'b) either = Left of 'a | Right of 'b + type ('a, 'b) reassoc = ('a, 'b) either + type e = A | B of int + + (** @canonical Root.Pub *) + module Hidden__impl : S + + module Pub = Hidden__impl + type via_canonical = Hidden__impl.t + |};; + module type S = sig type t end + module M : S (sig : type t end) (canonical=None) + module Internal__thing : S (sig : type t end) (canonical=None) + module F : (X/25 : S) -> sig end (canonical=None) + module type T = S with t(params ) = int (sig : type t = int end) + module type Tsub = S with t(params ) = int + (sig : (removed=type () t = (int)) end) + type fn = ((int) -> int) -> int + type curried = (int) -> (int) -> int + type boxed = [a] list + type either = Left of a | Right of b + type reassoc = [a * b] either + type e = A of | B of int + module Hidden__impl : S (sig : type t end) (canonical=Some(Root.Pub)) + module Pub = Hidden__impl (canonical=None) + type via_canonical = Hidden__impl.t + - : unit = () + ]} *) + type config = { short_paths : bool; show_canonical : bool; diff --git a/src/xref2/component_fmt_prelude.txt b/src/xref2/component_fmt_prelude.txt new file mode 100644 index 0000000000..215659729d --- /dev/null +++ b/src/xref2/component_fmt_prelude.txt @@ -0,0 +1,16 @@ +(* MDX prelude for the Component.Fmt examples in component.mli. + Loaded with [ocaml-mdx-test --prelude]; uses #require so odoc's libraries + are loaded dynamically (avoiding a static double-link of odoc_parser with + mdx's vendored copy). *) + +#require "odoc.xref2";; +#require "odoc.xref_test";; + +open Odoc_xref2;; +open Odoc_xref_test;; + +let show ?(config = { Component.Fmt.default with short_paths = true }) src = + let sg = Common.signature_of_mli_string src in + let sg = Common.compile_signature sg in + let csg = Component.Of_Lang.(signature (empty ()) sg) in + Format.printf "@[%a@]@." (Component.Fmt.signature config) csg;; diff --git a/src/xref2/dune b/src/xref2/dune index f20fa7f5ed..ce9a5cfcbe 100644 --- a/src/xref2/dune +++ b/src/xref2/dune @@ -32,3 +32,21 @@ (with-stdout-to shape_tools.mli (run %{bin:cppo} -V OCAML:%{ocaml_version} %{dep:shape_tools.cppo.mli}))) + +; MDX demonstration of Component.Fmt, embedded in component.mli's Fmt +; submodule. Run via ocaml-mdx-test (not the dune (mdx) stanza) so odoc's +; libraries load dynamically through #require in the prelude, avoiding the +; static double-link of odoc_parser against mdx's vendored copy. + +(rule + (alias runmdx) + (deps + (:x component.mli) + component_fmt_prelude.txt + (package odoc)) + (enabled_if + (> %{ocaml_version} 4.08)) + (action + (progn + (run ocaml-mdx-test --prelude=component_fmt_prelude.txt %{x}) + (diff? %{x} %{x}.corrected)))) From 5b24f1e0f1cff9c054e4b14a5ac0a12f9f4ef41c Mon Sep 17 00:00:00 2001 From: Jon Ludlam Date: Fri, 5 Jun 2026 20:13:26 +0100 Subject: [PATCH 02/11] xref2: Component.Fmt: prefer canonical paths under short_paths Render the canonical `Canonical`/`CanonicalType`/`CanonicalModuleType` constructors with wrap2r (which picks the shorter canonical path when short_paths is set) instead of wrap2. No change to the demonstration, which has no canonical paths. Co-Authored-By: Claude Opus 4.8 (1M context) --- src/xref2/component.ml | 12 ++++++------ src/xref2/component.mli | 4 ++-- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/xref2/component.ml b/src/xref2/component.ml index 2197e9fae0..1dda9c8fba 100644 --- a/src/xref2/component.ml +++ b/src/xref2/component.ml @@ -1244,7 +1244,7 @@ module Fmt = struct p2 | `Hidden p1 -> wrap c "hidden" resolved_module_path ppf p1 | `Canonical (p1, p2) -> - wrap2 c "canonical" resolved_module_path model_path ppf p1 (p2 :> path) + wrap2r c "canonical" resolved_module_path model_path ppf p1 (p2 :> path) | `OpaqueModule m -> wrap c "opaquemodule" resolved_module_path ppf m and module_path : config -> Format.formatter -> Cpath.module_ -> unit = @@ -1275,7 +1275,7 @@ module Fmt = struct Format.fprintf ppf "%a.%s" (resolved_parent_path c) p (ModuleTypeName.to_string m) | `CanonicalModuleType (m1, m2) -> - wrap2 c "canonicalt" resolved_module_type_path model_path ppf m1 + wrap2r c "canonicalt" resolved_module_type_path model_path ppf m1 (m2 :> path) | `OpaqueModuleType m -> wrap c "opaquemoduletype" resolved_module_type_path ppf m @@ -1311,7 +1311,7 @@ module Fmt = struct | `Substituted x -> wrap c "substituted" resolved_type_path ppf x | `Unbox x -> wrap c "unbox" resolved_type_path ppf x | `CanonicalType (t1, t2) -> - wrap2 c "canonicaltype" resolved_type_path model_path ppf t1 + wrap2r c "canonicaltype" resolved_type_path model_path ppf t1 (t2 :> path) | `Class (p, t) -> Format.fprintf ppf "%a.%s" (resolved_parent_path c) p @@ -1477,11 +1477,11 @@ module Fmt = struct (t1 :> t) (t2 :> t) | `CanonicalModuleType (t1, t2) -> - wrap2 c "canonicalmoduletype" model_resolved_path model_path ppf + wrap2r c "canonicalmoduletype" model_resolved_path model_path ppf (t1 :> t) (t2 :> path) | `CanonicalType (t1, t2) -> - wrap2 c "canonicaltype" model_resolved_path model_path ppf + wrap2r c "canonicaltype" model_resolved_path model_path ppf (t1 :> t) (t2 :> path) | `Apply (funct, arg) -> @@ -1490,7 +1490,7 @@ module Fmt = struct (model_resolved_path c) (arg :> t) | `Canonical (p1, p2) -> - wrap2 c "canonical" model_resolved_path model_path ppf + wrap2r c "canonical" model_resolved_path model_path ppf (p1 :> t) (p2 :> path) | `Hidden p -> wrap c "hidden" model_resolved_path ppf (p :> t) diff --git a/src/xref2/component.mli b/src/xref2/component.mli index 0bbc8f8c09..22d9500892 100644 --- a/src/xref2/component.mli +++ b/src/xref2/component.mli @@ -587,8 +587,8 @@ module Fmt : sig type reassoc = [a * b] either type e = A of | B of int module Hidden__impl : S (sig : type t end) (canonical=Some(Root.Pub)) - module Pub = Hidden__impl (canonical=None) - type via_canonical = Hidden__impl.t + module Pub = Root.Pub (canonical=None) + type via_canonical = Root.Pub.t - : unit = () ]} *) From f6c2be747d208b343620acde7dd26cabb59b6c06 Mon Sep 17 00:00:00 2001 From: Jon Ludlam Date: Fri, 5 Jun 2026 20:15:05 +0100 Subject: [PATCH 03/11] xref2: Component.Fmt: add identifier_name_only flag Add a config flag that prints Identifiers using only the leaf name, as the HTML renderer does. Co-Authored-By: Claude Opus 4.8 (1M context) --- src/xref2/component.ml | 11 +++++++++++ src/xref2/component.mli | 5 +++++ test/odoc_print/odoc_print.ml | 1 + 3 files changed, 17 insertions(+) diff --git a/src/xref2/component.ml b/src/xref2/component.ml index 1dda9c8fba..43585b81e1 100644 --- a/src/xref2/component.ml +++ b/src/xref2/component.ml @@ -590,6 +590,12 @@ module Fmt = struct show_removed : bool; show_expansions : bool; show_include_expansions : bool; + identifier_name_only : bool; + (** Print Identifier-wrapped paths using only the leaf name (as the HTML + renderer does). When canonical resolution has lifted a path like + [Mylib.A] into a single Identifier, this prints just [A]. Has no + effect on Module-step paths where the structure inherently requires + walking the chain. *) } let default = @@ -599,6 +605,7 @@ module Fmt = struct show_removed = true; show_expansions = true; show_include_expansions = true; + identifier_name_only = false; } type id = Odoc_model.Paths.Identifier.t @@ -1416,6 +1423,8 @@ module Fmt = struct match p with | `Resolved rp -> wrap c "resolved" model_resolved_path ppf rp + | `Identifier (id, _) when c.identifier_name_only -> + Format.fprintf ppf "%s" (Odoc_model.Paths.Identifier.name (id :> id)) | `Identifier (id, b) -> wrap2 c "identifier" model_identifier bool ppf (id :> id) b | `Root s -> wrap c "root" str ppf (ModuleName.to_string s) @@ -1443,6 +1452,8 @@ module Fmt = struct let open Odoc_model.Paths.Path.Resolved in match p with | `CoreType x -> Format.fprintf ppf "%s" (TypeName.to_string x) + | `Identifier id when c.identifier_name_only -> + Format.fprintf ppf "%s" (Odoc_model.Paths.Identifier.name (id :> id)) | `Identifier id -> Format.fprintf ppf "%a" (model_identifier c) (id :> id) | `Module (parent, name) -> Format.fprintf ppf "%a.%s" (model_resolved_path c) diff --git a/src/xref2/component.mli b/src/xref2/component.mli index 22d9500892..932a2f8164 100644 --- a/src/xref2/component.mli +++ b/src/xref2/component.mli @@ -598,6 +598,11 @@ module Fmt : sig show_removed : bool; show_expansions : bool; show_include_expansions : bool; + identifier_name_only : bool; + (** Print Identifier-wrapped paths using only the leaf name (as the HTML + renderer does). When canonical resolution has lifted a path like + [Mylib.A] into a single Identifier, this prints just [A]. Has no + effect on Module-step paths. *) } val default : config diff --git a/test/odoc_print/odoc_print.ml b/test/odoc_print/odoc_print.ml index b5c664dd62..b5e0ec2e88 100644 --- a/test/odoc_print/odoc_print.ml +++ b/test/odoc_print/odoc_print.ml @@ -222,6 +222,7 @@ let run inp short long_paths show_canonical show_expansions show_expansions; show_include_expansions; show_removed; + identifier_name_only = false; } in Odoc_file.load inp >>= fun unit -> From fb2dd53cfea8527a7b0e5726a3567f26bcb66a88 Mon Sep 17 00:00:00 2001 From: Jon Ludlam Date: Fri, 5 Jun 2026 20:15:36 +0100 Subject: [PATCH 04/11] xref2: Component.Fmt: mark expansions with => instead of (sig : ...) Render module expansions as `=> sig ... end` / `=> functor ...` rather than the slightly odd `(sig : ... end)` / `(functor: ...)`. Co-Authored-By: Claude Opus 4.8 (1M context) --- src/xref2/component.ml | 4 ++-- src/xref2/component.mli | 10 ++++----- test/xref2/expansion.t/run.t | 24 +++++++++++----------- test/xref2/js_stack_overflow.t/run.t | 10 ++++----- test/xref2/module_type_of.t/run.t | 10 ++++----- test/xref2/module_type_of_extra.t/run.t | 22 ++++++++++---------- test/xref2/shadow3.t/run.t | 27 ++++++++++++------------- test/xref2/shadow5.t/run.t | 16 +++++++-------- 8 files changed, 61 insertions(+), 62 deletions(-) diff --git a/src/xref2/component.ml b/src/xref2/component.ml index 43585b81e1..5735d6f2af 100644 --- a/src/xref2/component.ml +++ b/src/xref2/component.ml @@ -957,9 +957,9 @@ module Fmt = struct if c.show_expansions || (is_include && c.show_include_expansions) then match m with | ModuleType.Signature sg -> - Format.fprintf ppf "@[(sig :@ %a@;<1 -1>end@])" (signature c) sg + Format.fprintf ppf "@[=> sig@ %a@;<1 -1>end@]" (signature c) sg | Functor (arg, sg) -> - Format.fprintf ppf "(functor: (%a) -> %a)" (functor_parameter c) arg + Format.fprintf ppf "=> functor (%a) -> %a" (functor_parameter c) arg (simple_expansion c is_include) sg else () diff --git a/src/xref2/component.mli b/src/xref2/component.mli index 932a2f8164..c9c00f70ed 100644 --- a/src/xref2/component.mli +++ b/src/xref2/component.mli @@ -574,19 +574,19 @@ module Fmt : sig type via_canonical = Hidden__impl.t |};; module type S = sig type t end - module M : S (sig : type t end) (canonical=None) - module Internal__thing : S (sig : type t end) (canonical=None) + module M : S => sig type t end (canonical=None) + module Internal__thing : S => sig type t end (canonical=None) module F : (X/25 : S) -> sig end (canonical=None) - module type T = S with t(params ) = int (sig : type t = int end) + module type T = S with t(params ) = int => sig type t = int end module type Tsub = S with t(params ) = int - (sig : (removed=type () t = (int)) end) + => sig (removed=type () t = (int)) end type fn = ((int) -> int) -> int type curried = (int) -> (int) -> int type boxed = [a] list type either = Left of a | Right of b type reassoc = [a * b] either type e = A of | B of int - module Hidden__impl : S (sig : type t end) (canonical=Some(Root.Pub)) + module Hidden__impl : S => sig type t end (canonical=Some(Root.Pub)) module Pub = Root.Pub (canonical=None) type via_canonical = Root.Pub.t - : unit = () diff --git a/test/xref2/expansion.t/run.t b/test/xref2/expansion.t/run.t index 458bb7ce54..225219dc85 100644 --- a/test/xref2/expansion.t/run.t +++ b/test/xref2/expansion.t/run.t @@ -21,10 +21,10 @@ In addition, the expansion for the alias Test after the link phase should be the sig module type X = sig module M : sig type t end end module X : X with M.t(params ) = int - (sig : module M : sig type t = int end end) + => sig module M : sig type t = int end end module Y : sig type t end - module Z : module type of Y (sig : type t end) - module A : X (sig : module M : sig type t end end) + module Z : module type of Y => sig type t end + module A : X => sig module M : sig type t end end module B = H end module Test = S @@ -35,19 +35,19 @@ In addition, the expansion for the alias Test after the link phase should be the sig module type X = sig module M : sig type t end end module X : X with M.t(params ) = int - (sig : module M : sig type t = int end end) + => sig module M : sig type t = int end end module Y : sig type t end - module Z : module type of Y (sig : type t end) - module A : X (sig : module M : sig type t end end) + module Z : module type of Y => sig type t end + module A : X => sig module M : sig type t end end module B = H end module Test = S - (sig : + => sig module type X = sig module M : sig type t end end module X : X with M.t(params ) = int - (sig : module M : sig type t = int end end) + => sig module M : sig type t = int end end module Y : sig type t end - module Z : module type of Y (sig : type t end) - module A : X (sig : module M : sig type t end end) - module B = H (sig : type t end) - end) + module Z : module type of Y => sig type t end + module A : X => sig module M : sig type t end end + module B = H => sig type t end + end diff --git a/test/xref2/js_stack_overflow.t/run.t b/test/xref2/js_stack_overflow.t/run.t index ce7cb6f59b..604b6d7d8a 100644 --- a/test/xref2/js_stack_overflow.t/run.t +++ b/test/xref2/js_stack_overflow.t/run.t @@ -13,14 +13,14 @@ simply finishing! $ odoc_print --short a.odocl --show-expansions --show-include-expansions open [ ] include Import.S - (sig : + => sig include Import.S0 - (sig : + => sig module {Thing}1/shadowed/(IIII) : sig module Config : sig end end - end) + end module {Thing}1/shadowed/(AAAA) : - sig module Config = {Thing}1/shadowed/(IIII).Config (sig : end) end - end) + sig module Config = {Thing}1/shadowed/(IIII).Config => sig end end + end module Thing : sig end diff --git a/test/xref2/module_type_of.t/run.t b/test/xref2/module_type_of.t/run.t index a28f19c39d..4a39736ef5 100644 --- a/test/xref2/module_type_of.t/run.t +++ b/test/xref2/module_type_of.t/run.t @@ -43,17 +43,17 @@ contains only 2 modules (the module `X` should have been removed) $ odoc_print m.odocl -r T --short --show-expansions module type M.T = M.S with X := M.X1 - (sig : - module type Y = module type of M.X1 (sig : type t end) + => sig + module type Y = module type of M.X1 => sig type t end module type Z = module type of struct include M.X1 end - (sig : type t = M.X1.t end) - end) + => sig type t = M.X1.t end + end Check that the expansion of 'T.Y' contains only 1 type $ odoc_print m.odocl -r T.Y --short --show-expansions module type M.T.Y = module type of M.X1 - (sig : type t end) + => sig type t end Verify that T.Y.t has not been strengthened diff --git a/test/xref2/module_type_of_extra.t/run.t b/test/xref2/module_type_of_extra.t/run.t index ef773b7be6..52cdfb5e0a 100644 --- a/test/xref2/module_type_of_extra.t/run.t +++ b/test/xref2/module_type_of_extra.t/run.t @@ -50,27 +50,27 @@ The expansions below should match OCaml's calculated signatures above. $ odoc_print b.odocl -r X1 --short --show-expansions module type B.X1 = module type of B.X - (sig : type t end) + => sig type t end $ odoc_print b.odocl -r X2 --short --show-expansions module type B.X2 = module type of A - (sig : module X : sig type t end end) + => sig module X : sig type t end end $ odoc_print b.odocl -r X3 --short --show-expansions module type B.X3 = module type of A.X - (sig : type t end) + => sig type t end $ odoc_print b.odocl -r X4 --short --show-expansions module type B.X4 = module type of B.Y - (functor: (A/0 : - sig type t end) -> (sig : type t end)) + => functor (A/0 : + sig type t end) -> => sig type t end $ odoc_print b.odocl -r X5 --short --show-expansions module type B.X5 = B.Foo with X := B.SubX - (sig : + => sig module Y : (A/3 : sig type t end) -> sig module Z = B.SubX end - module type Z = module type of Y (functor: (A/6 : - sig type t end) -> (sig : module Z = B.SubX end)) + module type Z = module type of Y => functor (A/6 : + sig type t end) -> => sig module Z = B.SubX end module X' : module type of struct include B.SubX end - (sig : type t = B.SubX.t end) - end) + => sig type t = B.SubX.t end + end $ odoc_print b.odocl -r X6 --short --show-expansions module type B.X6 = module type of A.X - (sig : type t end) + => sig type t end diff --git a/test/xref2/shadow3.t/run.t b/test/xref2/shadow3.t/run.t index 9213785732..e5b1998d1d 100644 --- a/test/xref2/shadow3.t/run.t +++ b/test/xref2/shadow3.t/run.t @@ -14,36 +14,35 @@ Module `C` then includes them both, causing further shadowing. $ odoc_print --short --show-include-expansions c.odoc include module type of struct include A end - (sig : + => sig module type {B}1/shadowed/(CCCC) = A.B - include {B}1/shadowed/(CCCC) - (sig : module {A}1/shadowed/(AAAA) = A.A end) + include {B}1/shadowed/(CCCC) => sig module {A}1/shadowed/(AAAA) = A.A end include sig module A : sig include module type of struct include {A}1/shadowed/(AAAA) end - (sig : type t end) + => sig type t end type a - endend (sig : module {A}2/shadowed/(CCCC) = A.A end) - end) + endend => sig module {A}2/shadowed/(CCCC) = A.A end + end include module type of struct include B end - (sig : + => sig module type B = B.B - include B (sig : module {A}1/shadowed/(BBBB) = B.A end) + include B => sig module {A}1/shadowed/(BBBB) = B.A end include sig module A : sig include module type of struct include {A}1/shadowed/(BBBB) end - (sig : type t end) + => sig type t end type b - endend (sig : module {A}3/shadowed/(CCCC) = B.A end) - end) + endend => sig module {A}3/shadowed/(CCCC) = B.A end + end module A : sig include module type of struct include {A}3/shadowed/(CCCC) end - (sig : + => sig include module type of struct include B.{A}1/shadowed/(BBBB) end - (sig : type t = B.A.t end) + => sig type t = B.A.t end type b = B.A.b - end) + end end diff --git a/test/xref2/shadow5.t/run.t b/test/xref2/shadow5.t/run.t index cad60761e8..05bfd6762f 100644 --- a/test/xref2/shadow5.t/run.t +++ b/test/xref2/shadow5.t/run.t @@ -33,19 +33,19 @@ type `t`, but in the subsequent include, the type `t` within the signature _isn' type t = int val y : t include sigtype t = t - val z : tend with [t(params ) = t] (sig : val z : t end) + val z : tend with [t(params ) = t] => sig val z : t end end module type Z = sig include Y - (sig : + => sig type {t}1/shadowed/(AAAA) = int val y : int include sig type t = {t}1/shadowed/(AAAA) val z : tend with [t(params ) = {t}1/shadowed/(AAAA)] - (sig : val z : int end) - end) + => sig val z : int end + end type t = int end $ odoc html-generate a.odocl -o html @@ -81,15 +81,15 @@ For comparison, another test case that didn't have the bug: sig type t = int val y : t - include X with [t(params ) = t] (sig : val z : t end) + include X with [t(params ) = t] => sig val z : t end end module type Z = sig include Y - (sig : + => sig type {t}1/shadowed/(BBBB) = int val y : int - include X with [t(params ) = int] (sig : val z : int end) - end) + include X with [t(params ) = int] => sig val z : int end + end type t = int end From 50b5695f14f0fa41e2ccfcde07812bed1372e3c2 Mon Sep 17 00:00:00 2001 From: Jon Ludlam Date: Fri, 5 Jun 2026 20:16:04 +0100 Subject: [PATCH 05/11] xref2: Component.Fmt: skip (canonical=None) annotations Only print the canonical annotation when a module actually has one, instead of emitting `(canonical=None)` on every module declaration. Co-Authored-By: Claude Opus 4.8 (1M context) --- src/xref2/component.ml | 7 ++++--- src/xref2/component.mli | 10 +++++----- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/xref2/component.ml b/src/xref2/component.ml index 5735d6f2af..c45277c7b3 100644 --- a/src/xref2/component.ml +++ b/src/xref2/component.ml @@ -946,9 +946,10 @@ module Fmt = struct and module_ c ppf m = let fmt_canonical ppf popt = - if c.show_canonical then - Format.fprintf ppf "@ (canonical=%a)" (option c model_path) popt - else () + match popt with + | Some p when c.show_canonical -> + Format.fprintf ppf "@ (canonical=%a)" (model_path c) p + | _ -> () in Format.fprintf ppf "%a%a" (module_decl c) m.type_ fmt_canonical (m.canonical :> path option) diff --git a/src/xref2/component.mli b/src/xref2/component.mli index c9c00f70ed..de70e2e316 100644 --- a/src/xref2/component.mli +++ b/src/xref2/component.mli @@ -574,9 +574,9 @@ module Fmt : sig type via_canonical = Hidden__impl.t |};; module type S = sig type t end - module M : S => sig type t end (canonical=None) - module Internal__thing : S => sig type t end (canonical=None) - module F : (X/25 : S) -> sig end (canonical=None) + module M : S => sig type t end + module Internal__thing : S => sig type t end + module F : (X/25 : S) -> sig end module type T = S with t(params ) = int => sig type t = int end module type Tsub = S with t(params ) = int => sig (removed=type () t = (int)) end @@ -586,8 +586,8 @@ module Fmt : sig type either = Left of a | Right of b type reassoc = [a * b] either type e = A of | B of int - module Hidden__impl : S => sig type t end (canonical=Some(Root.Pub)) - module Pub = Root.Pub (canonical=None) + module Hidden__impl : S => sig type t end (canonical=Root.Pub) + module Pub = Root.Pub type via_canonical = Root.Pub.t - : unit = () ]} *) From 37fae4219090311e9e5ce6b49a41e78929828604 Mon Sep 17 00:00:00 2001 From: Jon Ludlam Date: Fri, 5 Jun 2026 20:17:02 +0100 Subject: [PATCH 06/11] xref2: Component.Fmt: drop spurious parens on type expressions Print arrow types without wrapping every left operand in parens (only when the operand is itself an arrow), and render type-constructor applications as `a t` / `(a, b) t` instead of `[a] t` / `[a, b] t`. Co-Authored-By: Claude Opus 4.8 (1M context) --- src/xref2/component.ml | 24 ++++++++++++++++++++---- src/xref2/component.mli | 8 ++++---- 2 files changed, 24 insertions(+), 8 deletions(-) diff --git a/src/xref2/component.ml b/src/xref2/component.ml index c45277c7b3..0c7de28e41 100644 --- a/src/xref2/component.ml +++ b/src/xref2/component.ml @@ -1201,20 +1201,36 @@ module Fmt = struct and type_expr c ppf e = let open TypeExpr in + let needs_outer_parens = function + (* Things that are looser than the constructor-application slot. *) + | Arrow _ | Tuple _ | Unboxed_tuple _ -> true + | _ -> false + in + let pp_atom ppf t = + if needs_outer_parens t then Format.fprintf ppf "(%a)" (type_expr c) t + else type_expr c ppf t + in match e with | Var x -> Format.fprintf ppf "%s" x | Any -> Format.fprintf ppf "_" | Alias (x, y) -> Format.fprintf ppf "(alias %a %s)" (type_expr c) x y - | Arrow (l, t1, t2) -> - Format.fprintf ppf "%a(%a) -> %a" type_expr_label l (type_expr c) t1 - (type_expr c) t2 + | Arrow (l, t1, t2) -> ( + (* -> is right-associative: only wrap LHS when it is itself an arrow. *) + match t1 with + | Arrow _ -> + Format.fprintf ppf "%a(%a) -> %a" type_expr_label l (type_expr c) t1 + (type_expr c) t2 + | _ -> + Format.fprintf ppf "%a%a -> %a" type_expr_label l (type_expr c) t1 + (type_expr c) t2) | Tuple ts -> Format.fprintf ppf "(%a)" (type_labeled_tuple c) ts | Unboxed_tuple ts -> Format.fprintf ppf "#(%a)" (type_labeled_tuple c) ts | Constr (p, args) -> ( match args with | [] -> Format.fprintf ppf "%a" (type_path c) p + | [ a ] -> Format.fprintf ppf "%a %a" pp_atom a (type_path c) p | _ -> - Format.fprintf ppf "[%a] %a" (type_expr_list c) args (type_path c) p + Format.fprintf ppf "(%a) %a" (type_expr_list c) args (type_path c) p ) | Polymorphic_variant poly -> Format.fprintf ppf "(poly_var %a)" diff --git a/src/xref2/component.mli b/src/xref2/component.mli index de70e2e316..f170b841db 100644 --- a/src/xref2/component.mli +++ b/src/xref2/component.mli @@ -580,11 +580,11 @@ module Fmt : sig module type T = S with t(params ) = int => sig type t = int end module type Tsub = S with t(params ) = int => sig (removed=type () t = (int)) end - type fn = ((int) -> int) -> int - type curried = (int) -> (int) -> int - type boxed = [a] list + type fn = (int -> int) -> int + type curried = int -> int -> int + type boxed = a list type either = Left of a | Right of b - type reassoc = [a * b] either + type reassoc = (a * b) either type e = A of | B of int module Hidden__impl : S => sig type t end (canonical=Root.Pub) module Pub = Root.Pub From 65e69af8ba96ddcfb3beb3ce5eb62ee06d1fa7fc Mon Sep 17 00:00:00 2001 From: Jon Ludlam Date: Fri, 5 Jun 2026 20:18:02 +0100 Subject: [PATCH 07/11] xref2: Component.Fmt: cleaner with-constraint output Render `with type` constraints as `t = ...` / `t := ...` instead of the noisy `t(params ) = ...` Co-Authored-By: Claude Opus 4.8 (1M context) --- src/xref2/component.ml | 17 ++++++++++------- src/xref2/component.mli | 8 ++++---- test/xref2/expansion.t/run.t | 9 +++------ test/xref2/shadow5.t/run.t | 12 ++++-------- 4 files changed, 21 insertions(+), 25 deletions(-) diff --git a/src/xref2/component.ml b/src/xref2/component.ml index 0c7de28e41..e9601c2198 100644 --- a/src/xref2/component.ml +++ b/src/xref2/component.ml @@ -1101,19 +1101,20 @@ module Fmt = struct let pp_sep ppf () = Format.fprintf ppf ", " in Format.fprintf ppf "(%a)" (Format.pp_print_list ~pp_sep type_param) ts - and type_equation_manifest c ppf t = + and type_equation_manifest ?(sep = "=") c ppf t = match t.TypeDecl.Equation.manifest with | None -> () - | Some m -> Format.fprintf ppf " = %a" (type_expr c) m + | Some m -> Format.fprintf ppf " %s %a" sep (type_expr c) m and type_equation_params _c ppf t = match t.TypeDecl.Equation.params with | [] -> () - | ps -> Format.fprintf ppf "%a" type_params ps + | ps -> Format.fprintf ppf " %a" type_params ps - and type_equation c ppf t = - Format.fprintf ppf "(params %a)%a" (type_equation_params c) t - (type_equation_manifest c) t + and type_equation ?sep c ppf t = + Format.fprintf ppf "%a%a" (type_equation_params c) t + (type_equation_manifest ?sep c) + t and exception_ _c _ppf _e = () @@ -1137,7 +1138,9 @@ module Fmt = struct | TypeEq (frag, decl) -> Format.fprintf ppf "%a%a" (type_fragment c) frag (type_equation c) decl | TypeSubst (frag, decl) -> - Format.fprintf ppf "%a%a" (type_fragment c) frag (type_equation c) decl + Format.fprintf ppf "%a%a" (type_fragment c) frag + (type_equation ~sep:":=" c) + decl and substitution_list c ppf l = match l with diff --git a/src/xref2/component.mli b/src/xref2/component.mli index f170b841db..bf40db34f9 100644 --- a/src/xref2/component.mli +++ b/src/xref2/component.mli @@ -577,9 +577,8 @@ module Fmt : sig module M : S => sig type t end module Internal__thing : S => sig type t end module F : (X/25 : S) -> sig end - module type T = S with t(params ) = int => sig type t = int end - module type Tsub = S with t(params ) = int - => sig (removed=type () t = (int)) end + module type T = S with t = int => sig type t = int end + module type Tsub = S with t := int => sig (removed=type () t = (int)) end type fn = (int -> int) -> int type curried = int -> int -> int type boxed = a list @@ -650,7 +649,8 @@ module Fmt : sig val type_decl : config -> Format.formatter -> TypeDecl.t -> unit - val type_equation : config -> Format.formatter -> TypeDecl.Equation.t -> unit + val type_equation : + ?sep:string -> config -> Format.formatter -> TypeDecl.Equation.t -> unit val exception_ : config -> Format.formatter -> Exception.t -> unit diff --git a/test/xref2/expansion.t/run.t b/test/xref2/expansion.t/run.t index 225219dc85..6fb8317a09 100644 --- a/test/xref2/expansion.t/run.t +++ b/test/xref2/expansion.t/run.t @@ -20,8 +20,7 @@ In addition, the expansion for the alias Test after the link phase should be the module S : sig module type X = sig module M : sig type t end end - module X : X with M.t(params ) = int - => sig module M : sig type t = int end end + module X : X with M.t = int => sig module M : sig type t = int end end module Y : sig type t end module Z : module type of Y => sig type t end module A : X => sig module M : sig type t end end @@ -34,8 +33,7 @@ In addition, the expansion for the alias Test after the link phase should be the module S : sig module type X = sig module M : sig type t end end - module X : X with M.t(params ) = int - => sig module M : sig type t = int end end + module X : X with M.t = int => sig module M : sig type t = int end end module Y : sig type t end module Z : module type of Y => sig type t end module A : X => sig module M : sig type t end end @@ -44,8 +42,7 @@ In addition, the expansion for the alias Test after the link phase should be the module Test = S => sig module type X = sig module M : sig type t end end - module X : X with M.t(params ) = int - => sig module M : sig type t = int end end + module X : X with M.t = int => sig module M : sig type t = int end end module Y : sig type t end module Z : module type of Y => sig type t end module A : X => sig module M : sig type t end end diff --git a/test/xref2/shadow5.t/run.t b/test/xref2/shadow5.t/run.t index 05bfd6762f..d0c33296de 100644 --- a/test/xref2/shadow5.t/run.t +++ b/test/xref2/shadow5.t/run.t @@ -33,7 +33,7 @@ type `t`, but in the subsequent include, the type `t` within the signature _isn' type t = int val y : t include sigtype t = t - val z : tend with [t(params ) = t] => sig val z : t end + val z : tend with [t := t] => sig val z : t end end module type Z = sig @@ -43,7 +43,7 @@ type `t`, but in the subsequent include, the type `t` within the signature _isn' val y : int include sig type t = {t}1/shadowed/(AAAA) - val z : tend with [t(params ) = {t}1/shadowed/(AAAA)] + val z : tend with [t := {t}1/shadowed/(AAAA)] => sig val z : int end end type t = int @@ -78,18 +78,14 @@ For comparison, another test case that didn't have the bug: $ odoc_print b.odocl --short --show-include-expansions module type X = sig type t val z : t end module type Y = - sig - type t = int - val y : t - include X with [t(params ) = t] => sig val z : t end - end + sig type t = int val y : t include X with [t := t] => sig val z : t end end module type Z = sig include Y => sig type {t}1/shadowed/(BBBB) = int val y : int - include X with [t(params ) = int] => sig val z : int end + include X with [t := int] => sig val z : int end end type t = int end From 8f65405e0661d4792c16cd61ed002756bf73bf51 Mon Sep 17 00:00:00 2001 From: Jon Ludlam Date: Fri, 5 Jun 2026 20:19:51 +0100 Subject: [PATCH 08/11] xref2: Component.Fmt: cleaner functor params, no-arg variants, empty sigs Render empty signatures as `sig end` (not `sig end` with a stray gap), print functor-parameter idents short when short_paths is set, and drop the trailing `of ` on argument-less variant constructors (`A` not `A of `). Co-Authored-By: Claude Opus 4.8 (1M context) --- src/xref2/component.ml | 14 +++++++++++++- src/xref2/component.mli | 4 ++-- test/xref2/js_stack_overflow.t/run.t | 6 +++--- test/xref2/module_type_of_extra.t/run.t | 6 +++--- 4 files changed, 21 insertions(+), 9 deletions(-) diff --git a/src/xref2/component.ml b/src/xref2/component.ml index e9601c2198..de83b274bf 100644 --- a/src/xref2/component.ml +++ b/src/xref2/component.ml @@ -957,6 +957,8 @@ module Fmt = struct and simple_expansion c is_include ppf (m : ModuleType.simple_expansion) = if c.show_expansions || (is_include && c.show_include_expansions) then match m with + | ModuleType.Signature sg when is_empty_sg sg -> + Format.fprintf ppf "=> sig end" | ModuleType.Signature sg -> Format.fprintf ppf "@[=> sig@ %a@;<1 -1>end@]" (signature c) sg | Functor (arg, sg) -> @@ -980,10 +982,13 @@ module Fmt = struct Format.fprintf ppf "module type of struct include %a end" (module_path c) p + and is_empty_sg (sg : Signature.t) = sg.items = [] && sg.removed = [] + and u_module_type_expr c ppf mt = let open ModuleType.U in match mt with | Path p -> module_type_path c ppf p + | Signature sg when is_empty_sg sg -> Format.fprintf ppf "sig end" | Signature sg -> Format.fprintf ppf "sig@,@[%a@]end" (signature c) sg | With (subs, e) -> Format.fprintf ppf "%a with [%a]" (u_module_type_expr c) e @@ -997,6 +1002,7 @@ module Fmt = struct let open ModuleType in match mt with | Path { p_path; _ } -> module_type_path c ppf p_path + | Signature sg when is_empty_sg sg -> Format.fprintf ppf "sig end" | Signature sg -> Format.fprintf ppf "@,@[sig@ %a@;<1 -2>end@]" (signature c) sg | With { w_substitutions = subs; w_expr; _ } -> @@ -1031,7 +1037,8 @@ module Fmt = struct | Named x -> Format.fprintf ppf "%a" (functor_parameter_parameter c) x and functor_parameter_parameter c ppf x = - Format.fprintf ppf "%a : %a" Ident.fmt x.FunctorParameter.id + let ident_fmt = if c.short_paths then Ident.short_fmt else Ident.fmt in + Format.fprintf ppf "%a : %a" ident_fmt x.FunctorParameter.id (module_type_expr c) x.FunctorParameter.expr and type_decl c ppf t = @@ -1053,11 +1060,16 @@ module Fmt = struct and type_decl_constructor c ppf t = let open TypeDecl.Constructor in + let no_args = + match t.args with Tuple [] -> true | Tuple _ | Record _ -> false + in match t.res with + | Some res when no_args -> fpf ppf "%s : %a" t.name (type_expr c) res | Some res -> fpf ppf "%s : %a -> %a" t.name (type_decl_constructor_arg c) t.args (type_expr c) res + | None when no_args -> fpf ppf "%s" t.name | None -> fpf ppf "%s of %a" t.name (type_decl_constructor_arg c) t.args and type_decl_constructor_arg c ppf = diff --git a/src/xref2/component.mli b/src/xref2/component.mli index bf40db34f9..8e4854151f 100644 --- a/src/xref2/component.mli +++ b/src/xref2/component.mli @@ -576,7 +576,7 @@ module Fmt : sig module type S = sig type t end module M : S => sig type t end module Internal__thing : S => sig type t end - module F : (X/25 : S) -> sig end + module F : (X : S) -> sig end module type T = S with t = int => sig type t = int end module type Tsub = S with t := int => sig (removed=type () t = (int)) end type fn = (int -> int) -> int @@ -584,7 +584,7 @@ module Fmt : sig type boxed = a list type either = Left of a | Right of b type reassoc = (a * b) either - type e = A of | B of int + type e = A | B of int module Hidden__impl : S => sig type t end (canonical=Root.Pub) module Pub = Root.Pub type via_canonical = Root.Pub.t diff --git a/test/xref2/js_stack_overflow.t/run.t b/test/xref2/js_stack_overflow.t/run.t index 604b6d7d8a..5536f1f64a 100644 --- a/test/xref2/js_stack_overflow.t/run.t +++ b/test/xref2/js_stack_overflow.t/run.t @@ -16,11 +16,11 @@ simply finishing! => sig include Import.S0 => sig - module {Thing}1/shadowed/(IIII) : sig module Config : sig end end + module {Thing}1/shadowed/(IIII) : sig module Config : sig end end end module {Thing}1/shadowed/(AAAA) : - sig module Config = {Thing}1/shadowed/(IIII).Config => sig end end + sig module Config = {Thing}1/shadowed/(IIII).Config => sig end end end - module Thing : sig end + module Thing : sig end diff --git a/test/xref2/module_type_of_extra.t/run.t b/test/xref2/module_type_of_extra.t/run.t index 52cdfb5e0a..0936ae2354 100644 --- a/test/xref2/module_type_of_extra.t/run.t +++ b/test/xref2/module_type_of_extra.t/run.t @@ -59,13 +59,13 @@ The expansions below should match OCaml's calculated signatures above. => sig type t end $ odoc_print b.odocl -r X4 --short --show-expansions module type B.X4 = module type of B.Y - => functor (A/0 : + => functor (A : sig type t end) -> => sig type t end $ odoc_print b.odocl -r X5 --short --show-expansions module type B.X5 = B.Foo with X := B.SubX => sig - module Y : (A/3 : sig type t end) -> sig module Z = B.SubX end - module type Z = module type of Y => functor (A/6 : + module Y : (A : sig type t end) -> sig module Z = B.SubX end + module type Z = module type of Y => functor (A : sig type t end) -> => sig module Z = B.SubX end module X' : module type of struct include B.SubX end => sig type t = B.SubX.t end From 400284b8c59f3869898afef18e2147000e864ddf Mon Sep 17 00:00:00 2001 From: Jon Ludlam Date: Fri, 5 Jun 2026 20:20:26 +0100 Subject: [PATCH 09/11] xref2: Component.Fmt: bracket hidden module declarations Wrap the name of a hidden module declaration in [brackets] so hidden modules are distinguishable in the rendered signature. Co-Authored-By: Claude Opus 4.8 (1M context) --- src/xref2/component.ml | 9 +++++++-- src/xref2/component.mli | 4 ++-- test/xref2/expansion.t/run.t | 8 ++++---- test/xref2/js_stack_overflow.t/run.t | 4 ++-- test/xref2/shadow3.t/run.t | 9 +++++---- 5 files changed, 20 insertions(+), 14 deletions(-) diff --git a/src/xref2/component.ml b/src/xref2/component.ml index de83b274bf..91b39d0092 100644 --- a/src/xref2/component.ml +++ b/src/xref2/component.ml @@ -779,8 +779,13 @@ module Fmt = struct let ident_fmt = if c.short_paths then Ident.short_fmt else Ident.fmt in let sig_item ppf = function | Module (id, _, m) -> - Format.fprintf ppf "@[module %a %a@]" ident_fmt id (module_ c) - (Delayed.get m) + let m = Delayed.get m in + let hidden_name = ModuleName.is_hidden (Ident.Name.typed_module id) in + let lb, rb = + if m.hidden || hidden_name then ("[", "]") else ("", "") + in + Format.fprintf ppf "@[module %s%a%s %a@]" lb ident_fmt id rb + (module_ c) m | ModuleSubstitution (id, m) -> Format.fprintf ppf "@[module %a := %a@]" ident_fmt id (module_path c) m.ModuleSubstitution.manifest diff --git a/src/xref2/component.mli b/src/xref2/component.mli index 8e4854151f..0a92158ad1 100644 --- a/src/xref2/component.mli +++ b/src/xref2/component.mli @@ -575,7 +575,7 @@ module Fmt : sig |};; module type S = sig type t end module M : S => sig type t end - module Internal__thing : S => sig type t end + module [Internal__thing] : S => sig type t end module F : (X : S) -> sig end module type T = S with t = int => sig type t = int end module type Tsub = S with t := int => sig (removed=type () t = (int)) end @@ -585,7 +585,7 @@ module Fmt : sig type either = Left of a | Right of b type reassoc = (a * b) either type e = A | B of int - module Hidden__impl : S => sig type t end (canonical=Root.Pub) + module [Hidden__impl] : S => sig type t end (canonical=Root.Pub) module Pub = Root.Pub type via_canonical = Root.Pub.t - : unit = () diff --git a/test/xref2/expansion.t/run.t b/test/xref2/expansion.t/run.t index 6fb8317a09..6680d21331 100644 --- a/test/xref2/expansion.t/run.t +++ b/test/xref2/expansion.t/run.t @@ -16,8 +16,8 @@ In addition, the expansion for the alias Test after the link phase should be the module Test = S $ odoc compile test.cmti $ odoc_print --short --show-expansions test.odoc - module H : sig type t end - module S : + module [H] : sig type t end + module [S] : sig module type X = sig module M : sig type t end end module X : X with M.t = int => sig module M : sig type t = int end end @@ -29,8 +29,8 @@ In addition, the expansion for the alias Test after the link phase should be the module Test = S $ odoc link test.odoc $ odoc_print --short --show-expansions test.odocl - module H : sig type t end - module S : + module [H] : sig type t end + module [S] : sig module type X = sig module M : sig type t end end module X : X with M.t = int => sig module M : sig type t = int end end diff --git a/test/xref2/js_stack_overflow.t/run.t b/test/xref2/js_stack_overflow.t/run.t index 5536f1f64a..58b49cf625 100644 --- a/test/xref2/js_stack_overflow.t/run.t +++ b/test/xref2/js_stack_overflow.t/run.t @@ -16,9 +16,9 @@ simply finishing! => sig include Import.S0 => sig - module {Thing}1/shadowed/(IIII) : sig module Config : sig end end + module [{Thing}1/shadowed/(IIII)] : sig module Config : sig end end end - module {Thing}1/shadowed/(AAAA) : + module [{Thing}1/shadowed/(AAAA)] : sig module Config = {Thing}1/shadowed/(IIII).Config => sig end end end module Thing : sig end diff --git a/test/xref2/shadow3.t/run.t b/test/xref2/shadow3.t/run.t index e5b1998d1d..960ff77b45 100644 --- a/test/xref2/shadow3.t/run.t +++ b/test/xref2/shadow3.t/run.t @@ -16,26 +16,27 @@ Module `C` then includes them both, causing further shadowing. include module type of struct include A end => sig module type {B}1/shadowed/(CCCC) = A.B - include {B}1/shadowed/(CCCC) => sig module {A}1/shadowed/(AAAA) = A.A end + include {B}1/shadowed/(CCCC) + => sig module [{A}1/shadowed/(AAAA)] = A.A end include sig module A : sig include module type of struct include {A}1/shadowed/(AAAA) end => sig type t end type a - endend => sig module {A}2/shadowed/(CCCC) = A.A end + endend => sig module [{A}2/shadowed/(CCCC)] = A.A end end include module type of struct include B end => sig module type B = B.B - include B => sig module {A}1/shadowed/(BBBB) = B.A end + include B => sig module [{A}1/shadowed/(BBBB)] = B.A end include sig module A : sig include module type of struct include {A}1/shadowed/(BBBB) end => sig type t end type b - endend => sig module {A}3/shadowed/(CCCC) = B.A end + endend => sig module [{A}3/shadowed/(CCCC)] = B.A end end module A : sig From ea6b85d1d540f773764076006007f6685eefbd55 Mon Sep 17 00:00:00 2001 From: Jon Ludlam Date: Fri, 5 Jun 2026 20:20:57 +0100 Subject: [PATCH 10/11] xref2: Component.Fmt: space sig keyword from contents in u_module_type_expr Lay out unexpanded signatures as `sig end` with hv boxing, matching the other signature printers, instead of cramming the keyword against the first item. Co-Authored-By: Claude Opus 4.8 (1M context) --- src/xref2/component.ml | 3 ++- test/xref2/shadow3.t/run.t | 26 ++++++++++++++------------ test/xref2/shadow5.t/run.t | 7 ++----- 3 files changed, 18 insertions(+), 18 deletions(-) diff --git a/src/xref2/component.ml b/src/xref2/component.ml index 91b39d0092..98e1168109 100644 --- a/src/xref2/component.ml +++ b/src/xref2/component.ml @@ -994,7 +994,8 @@ module Fmt = struct match mt with | Path p -> module_type_path c ppf p | Signature sg when is_empty_sg sg -> Format.fprintf ppf "sig end" - | Signature sg -> Format.fprintf ppf "sig@,@[%a@]end" (signature c) sg + | Signature sg -> + Format.fprintf ppf "@[sig@ %a@;<1 -2>end@]" (signature c) sg | With (subs, e) -> Format.fprintf ppf "%a with [%a]" (u_module_type_expr c) e (substitution_list c) subs diff --git a/test/xref2/shadow3.t/run.t b/test/xref2/shadow3.t/run.t index 960ff77b45..44e45e5a99 100644 --- a/test/xref2/shadow3.t/run.t +++ b/test/xref2/shadow3.t/run.t @@ -19,24 +19,26 @@ Module `C` then includes them both, causing further shadowing. include {B}1/shadowed/(CCCC) => sig module [{A}1/shadowed/(AAAA)] = A.A end include sig - module A : - sig - include module type of struct include {A}1/shadowed/(AAAA) end - => sig type t end - type a - endend => sig module [{A}2/shadowed/(CCCC)] = A.A end + module A : + sig + include module type of struct include {A}1/shadowed/(AAAA) end + => sig type t end + type a + end + end => sig module [{A}2/shadowed/(CCCC)] = A.A end end include module type of struct include B end => sig module type B = B.B include B => sig module [{A}1/shadowed/(BBBB)] = B.A end include sig - module A : - sig - include module type of struct include {A}1/shadowed/(BBBB) end - => sig type t end - type b - endend => sig module [{A}3/shadowed/(CCCC)] = B.A end + module A : + sig + include module type of struct include {A}1/shadowed/(BBBB) end + => sig type t end + type b + end + end => sig module [{A}3/shadowed/(CCCC)] = B.A end end module A : sig diff --git a/test/xref2/shadow5.t/run.t b/test/xref2/shadow5.t/run.t index d0c33296de..9f4e49edc6 100644 --- a/test/xref2/shadow5.t/run.t +++ b/test/xref2/shadow5.t/run.t @@ -32,8 +32,7 @@ type `t`, but in the subsequent include, the type `t` within the signature _isn' sig type t = int val y : t - include sigtype t = t - val z : tend with [t := t] => sig val z : t end + include sig type t = t val z : t end with [t := t] => sig val z : t end end module type Z = sig @@ -41,9 +40,7 @@ type `t`, but in the subsequent include, the type `t` within the signature _isn' => sig type {t}1/shadowed/(AAAA) = int val y : int - include sig - type t = {t}1/shadowed/(AAAA) - val z : tend with [t := {t}1/shadowed/(AAAA)] + include sig type t = {t}1/shadowed/(AAAA) val z : t end with [t := {t}1/shadowed/(AAAA)] => sig val z : int end end type t = int From f15ceb3db3df421d019df5c47ad4e3c242e44100 Mon Sep 17 00:00:00 2001 From: Jon Ludlam Date: Mon, 8 Jun 2026 11:13:03 +0100 Subject: [PATCH 11/11] xref2: Component.Fmt: turn the demo into config-option documentation Rework the Fmt docstring from a single signature dump into worked documentation. The prelude now exposes [sig_of] (source string -> Component.Signature.t) and a [readable] config alias ([default] with short_paths on) instead of the previous all-in-one [show]. Co-Authored-By: Claude Opus 4.8 (1M context) --- src/xref2/component.mli | 122 ++++++++++++++++++++-------- src/xref2/component_fmt_prelude.txt | 11 ++- 2 files changed, 94 insertions(+), 39 deletions(-) diff --git a/src/xref2/component.mli b/src/xref2/component.mli index 0a92158ad1..828168dff9 100644 --- a/src/xref2/component.mli +++ b/src/xref2/component.mli @@ -545,63 +545,113 @@ end (** Formatting functions for components *) module Fmt : sig - (** Pretty-printer for the {!Component} representation, used by debug output - and tests. + (** Pretty-printer for the {!Component} representation. {!signature} renders a + {!Signature.t}; what it prints is controlled by a {!config}. - The following example uses [show] to compile a snippet of [.mli] source - into a {!Signature.t} and renders it with {!Fmt.signature} (see the - [component_fmt_prelude] MDX prelude): + The examples below compile a fragment of [.mli] source into the + {!Signature.t} it denotes with [sig_of] (a helper from the + [component_fmt_prelude] MDX prelude) and render it with {!signature}. + [readable] is {!default} with {!short_paths} enabled. {@ocaml[ - # show {| + # let example = {| module type S = sig type t end module M : S - module Internal__thing : S module F (X : S) : sig end - module type T = S with type t = int + include S module type Tsub = S with type t := int - type fn = (int -> int) -> int - type curried = int -> int -> int - type 'a boxed = 'a list - type ('a, 'b) either = Left of 'a | Right of 'b - type ('a, 'b) reassoc = ('a, 'b) either - type e = A | B of int - - (** @canonical Root.Pub *) - module Hidden__impl : S - - module Pub = Hidden__impl - type via_canonical = Hidden__impl.t |};; + val example : string = + "\n module type S = sig type t end\n module M : S\n module F (X : S) : sig end\n include S\n module type Tsub = S with type t := int\n " + ]} + + With [readable] the rendering stays close to source syntax: a module's + expansion is shown after [=>], and a destructively-removed item is flagged + with [(removed=...)]: + + {@ocaml[ + # Format.printf "@[%a@]@." (Component.Fmt.signature readable) (sig_of example);; module type S = sig type t end module M : S => sig type t end - module [Internal__thing] : S => sig type t end module F : (X : S) -> sig end - module type T = S with t = int => sig type t = int end + include S => sig type t end module type Tsub = S with t := int => sig (removed=type () t = (int)) end - type fn = (int -> int) -> int - type curried = int -> int -> int - type boxed = a list - type either = Left of a | Right of b - type reassoc = (a * b) either - type e = A | B of int - module [Hidden__impl] : S => sig type t end (canonical=Root.Pub) - module Pub = Root.Pub - type via_canonical = Root.Pub.t - : unit = () - ]} *) + ]} + + Each {!config} field switches one of these off. [short_paths = false] is + the raw internal view — unique [name/NN] idents and the + [r(...)]/[resolved(...)] wrappers used while resolving paths: + + {@ocaml[ + # Format.printf "@[%a@]@." + (Component.Fmt.signature { readable with short_paths = false }) + (sig_of example);; + module type S/6 = sig type t/7 end + module M/4 : r(S/6) => sig type t/8 end + module F/3 : (X/9 : r(S/6)) -> sig end + include r(S/6) => sig type t/2 end + module type Tsub/5 = r(S/6) with resolved(root(S/6).t) := resolved(int) + => sig (removed=type () t = (resolved(int))) end + - : unit = () + ]} + + [show_expansions = false] hides the [=> sig ... end] expansions. Includes + are governed separately by [show_include_expansions], so the [include] + keeps its expansion while [M] loses it: + + {@ocaml[ + # Format.printf "@[%a@]@." + (Component.Fmt.signature { readable with show_expansions = false }) + (sig_of example);; + module type S = sig type t end + module M : S + module F : (X : S) -> sig end + include S => sig type t end + module type Tsub = S with t := int + - : unit = () + ]} + + [show_removed = false] hides items deleted by destructive substitution + (here the [t] removed by [Tsub], leaving an empty expansion): + + {@ocaml[ + # Format.printf "@[%a@]@." + (Component.Fmt.signature { readable with show_removed = false }) + (sig_of example);; + module type S = sig type t end + module M : S => sig type t end + module F : (X : S) -> sig end + include S => sig type t end + module type Tsub = S with t := int => sig end + - : unit = () + ]} + + The remaining two fields need inputs this self-contained example does not + produce: [show_canonical] toggles the [(canonical=...)] note printed for a + module carrying a [@canonical] tag, and [identifier_name_only] shortens + paths that resolution has lifted to a single cross-unit + {!Odoc_model.Paths.Identifier.t} — which only arises across compilation + units, not the in-unit signatures [sig_of] builds. *) type config = { short_paths : bool; + (** Render short, source-like names instead of the raw [name/NN] idents + and [r(...)]/[resolved(...)] path wrappers. *) show_canonical : bool; + (** Print the [(canonical=...)] note for a module with a [@canonical] + target. *) show_removed : bool; + (** Print items removed by destructive substitution, flagged + [(removed=...)]. *) show_expansions : bool; + (** Print module and module-type expansions after [=>]. *) show_include_expansions : bool; + (** Print the expansion of [include]d signatures, even when + [show_expansions] is off. *) identifier_name_only : bool; - (** Print Identifier-wrapped paths using only the leaf name (as the HTML - renderer does). When canonical resolution has lifted a path like - [Mylib.A] into a single Identifier, this prints just [A]. Has no - effect on Module-step paths. *) + (** Print Identifiers using only the leaf name (as the HTML renderer + does). *) } val default : config diff --git a/src/xref2/component_fmt_prelude.txt b/src/xref2/component_fmt_prelude.txt index 215659729d..7438c17d84 100644 --- a/src/xref2/component_fmt_prelude.txt +++ b/src/xref2/component_fmt_prelude.txt @@ -9,8 +9,13 @@ open Odoc_xref2;; open Odoc_xref_test;; -let show ?(config = { Component.Fmt.default with short_paths = true }) src = +(* [readable] is [Component.Fmt.default] with [short_paths] turned on, the form + wanted for most debugging output. *) +let readable = { Component.Fmt.default with short_paths = true } + +(* Compile a fragment of [.mli] source into the {!Component.Signature.t} it + denotes, ready to render with [Component.Fmt.signature]. *) +let sig_of src = let sg = Common.signature_of_mli_string src in let sg = Common.compile_signature sg in - let csg = Component.Of_Lang.(signature (empty ()) sg) in - Format.printf "@[%a@]@." (Component.Fmt.signature config) csg;; + Component.Of_Lang.(signature (empty ()) sg);;