···11{0 Writing Extensions}
2233+@admonition.note The odoc extension API is early in development and
44+the interface is still changing frequently. Expect breaking changes
55+between releases.
66+37[odoc] supports a plugin system for custom tags and code blocks. Extensions
48are OCaml libraries loaded at doc-generation time that transform custom
59markup into HTML, LaTeX, or other output formats.
···1216There are two kinds of extension:
13171418{ul
1515-{- {b Tag extensions} handle custom tags like [@@note], [@@rfc], [@@scrolly].
1919+{- {b Tag extensions} handle custom tags like [@note], [@rfc], [@scrolly].
1620 They receive the tag's content as a list of block elements and return
1721 document content, resources, and assets.}
1822{- {b Code block extensions} handle fenced code blocks with a custom
1919- language, e.g., [{@@dot ...}] or [{@@ocaml ...}].
2323+ language, e.g., [{@dot ...}] or [{@ocaml ...}].
2024 They receive the code text plus any options and return the same output
2125 types.}}
2226···8791(e.g., injecting a [<meta>] tag). Never put your main runtime in an
8892inline script.
89939494+{1 JavaScript Lifecycle}
9595+9696+The following diagram shows when extension JavaScript runs during both
9797+direct page loads and SPA navigations:
9898+9999+{@mermaid[
100100+sequenceDiagram
101101+ participant Browser
102102+ participant Head as head scripts
103103+ participant Shell as Docsite Shell
104104+ participant Ext as Extension JS
105105+ participant DOM
106106+107107+ rect rgb(40, 60, 80)
108108+ Note over Browser,DOM: Direct page load
109109+ Browser->>Head: Parse head
110110+ Head->>Ext: Load via script src="ext.js"
111111+ Ext->>Ext: readyState === 'loading'
112112+ Browser->>DOM: DOMContentLoaded
113113+ Ext->>DOM: initAll() — find and init widgets
114114+ Ext->>DOM: Listen for 'odoc-spa-loaded'
115115+ end
116116+117117+ rect rgb(60, 40, 60)
118118+ Note over Browser,DOM: SPA navigation (sidebar click)
119119+ Browser->>Shell: click event on sidebar link
120120+ Shell->>Shell: fetch(newPage.html)
121121+ Shell->>DOM: Swap .odoc-content innerHTML
122122+ Shell->>DOM: Load new head scripts
123123+ Shell->>DOM: Dispatch 'odoc-spa-loaded' event
124124+ Note over Head: ext.js already loaded — NOT re-executed
125125+ Note over DOM: DOMContentLoaded does NOT fire
126126+ DOM->>Ext: 'odoc-spa-loaded' handler fires
127127+ Ext->>DOM: initAll() — find new widgets, skip already-init'd
128128+ end
129129+]}
130130+131131+The key insight: on SPA navigation, neither the script nor
132132+[DOMContentLoaded] re-fires. The shell dispatches a custom
133133+[odoc-spa-loaded] event after swapping content and loading head
134134+scripts — this is the recommended way for extensions to detect
135135+new content after navigation.
136136+90137{1:spa SPA Navigation: The Critical Pitfall}
9113892139The odoc {b docsite shell} (and similar shells) implement single-page app
···155202156203 // Run on initial page load.
157204 if (document.readyState === 'loading') {
158158- document.addEventListener('DOMContentLoaded', function() {
159159- initAll();
160160- observe();
161161- });
205205+ document.addEventListener('DOMContentLoaded', function() { initAll(); });
162206 } else {
163207 initAll();
164164- observe();
165208 }
166209167167- // Watch for new content injected by SPA navigation.
168168- function observe() {
169169- new MutationObserver(function() { initAll(); })
170170- .observe(document.body, { childList: true, subtree: true });
171171- }
210210+ // Re-initialise after SPA navigation swaps in new content.
211211+ document.addEventListener('odoc-spa-loaded', function() { initAll(); });
172212})();
173213]}
174214···176216177217{ul
178218{- {b Guard against double-init.} Use a [data-*] attribute to mark
179179- initialised elements. The [MutationObserver] fires on every DOM
180180- mutation, so [initAll] may be called many times.}
219219+ initialised elements. [initAll] may be called multiple times.}
181220{- {b Check [document.readyState].} The script is in [<head>], so
182221 [document.body] doesn't exist yet on the initial load. Wait for
183183- [DOMContentLoaded] before attaching the [MutationObserver].}
222222+ [DOMContentLoaded] before first [initAll].}
223223+{- {b Listen for [odoc-spa-loaded].} The docsite shell dispatches this
224224+ custom event after swapping content and loading head scripts. This
225225+ is the recommended way for extensions to detect new content.}
184226{- {b Don't rely on [DOMContentLoaded] alone.} After SPA navigation the
185227 [Js_url] script has already loaded and [DOMContentLoaded] already fired.
186186- The [MutationObserver] is what detects the new content.}}
228228+ The [odoc-spa-loaded] event is what triggers re-initialisation.}}
187229188230{2 Case study: Scrollycode}
189231···225267]}
226268}
227269{- Replace the [DOMContentLoaded] gate with [readyState] check +
228228- [MutationObserver] (as shown in the pattern above).}
270270+ [odoc-spa-loaded] listener (as shown in the pattern above).}
229271{- Add a [data-sc-init] guard on each [.sc-container] to prevent
230272 double-initialisation.}}
231273···263305{- {b No body scripts.} All JavaScript is delivered via [Js_url] (support
264306 files) or small [Js_inline] bootstraps in [resources]. Nothing is
265307 embedded in the HTML body via [Raw_markup].}
266266-{- {b No [DOMContentLoaded] dependency.} Use [document.readyState] check +
267267- [MutationObserver] instead.}
308308+{- {b No [DOMContentLoaded] dependency.} Use [document.readyState] check
309309+ for initial load, plus [odoc-spa-loaded] listener for navigations.}
268310{- {b Double-init guard.} Every element you initialise is marked (e.g.,
269311 with a [data-*] attribute) and skipped on subsequent [initAll] calls.}
270312{- {b SPA navigation tested.} Both direct-load and sidebar-navigation
271313 paths work.}
272272-{- {b [MutationObserver] set up after [document.body] exists.} If your
273273- script is in [<head>], [document.body] is [null] on initial parse.}}
314314+{- {b Listens for [odoc-spa-loaded].} The docsite shell dispatches this
315315+ event after content swap — this is your extension's cue to scan for
316316+ new elements.}}
+3
src/extension_api/odoc_extension_api.ml
···11(** Odoc Extension API
2233+ {b Note:} This API is early in development and the interface is still
44+ changing frequently. Expect breaking changes between releases.
55+36 This module provides the interface for odoc tag extensions.
47 Extensions are dynamically loaded plugins that handle custom tags
58 like [@note], [@rfc], [@example], etc.
+14-15
test/generators/html/Markup.html
···2929//]]>
30303131 </script>
3232- <script>
3333-3434-//<![CDATA[
3535-(function(){if(window.__xOcamlLoaded)return;window.__xOcamlLoaded=true;var s=document.createElement('script');s.src='./_x-ocaml/x-ocaml.js';s.setAttribute('src-worker','./_x-ocaml/worker.js');s.setAttribute('backend','builtin');document.head.appendChild(s)})();
3636-//]]>
3737-3838- </script>
3932 </head>
4033 <body class="odoc">
4134 <nav class="odoc-nav"><a href="index.html">Up</a> –
···174167 <h2 id="preformatted-text">
175168 <a href="#preformatted-text" class="anchor"></a>Preformatted text
176169 </h2><p>This is a code block:</p>
177177- <x-ocaml mode="interactive"> let foo = ()
178178- (** There are some nested comments in here, but an unpaired comment
179179- terminator would terminate the whole doc surrounding comment. It's
180180- best to keep code blocks no wider than 72 characters. *)
181181-182182- let bar =
183183- ignore foo</x-ocaml>
184184- <p>There are also verbatim blocks:</p>
170170+ <div>
171171+ <pre class="language-ocaml">
172172+ <code>
173173+ let foo = ()
174174+ (** There are some nested comments in here, but an unpaired comment
175175+ terminator would terminate the whole doc surrounding comment.
176176+ It's
177177+ best to keep code blocks no wider than 72 characters. *)
178178+179179+ let bar =
180180+ ignore foo
181181+ </code>
182182+ </pre>
183183+ </div><p>There are also verbatim blocks:</p>
185184 <pre>The main difference is these don't get syntax highlighting.</pre>
186185 <h2 id="lists"><a href="#lists" class="anchor"></a>Lists</h2>
187186 <ul><li>This is a</li><li>shorthand bulleted list,</li>
+9-1
test/generators/latex/Markup.tex
···4444This is a reference to \hyperref[Markup--val-foo]{\ocamlinlinecode{\ocamlinlinecode{foo}}[p\pageref*{Markup--val-foo}]}. References can have replacement text: \hyperref[Markup--val-foo]{\ocamlinlinecode{the value foo}[p\pageref*{Markup--val-foo}]}. Except for the special lookup support, references are pretty much just like links. The replacement text can have nested styles: \hyperref[Markup--val-foo]{\ocamlinlinecode{\bold{bold}}[p\pageref*{Markup--val-foo}]}, \hyperref[Markup--val-foo]{\ocamlinlinecode{\emph{italic}}[p\pageref*{Markup--val-foo}]}, \hyperref[Markup--val-foo]{\ocamlinlinecode{\emph{emphasis}}[p\pageref*{Markup--val-foo}]}, \hyperref[Markup--val-foo]{\ocamlinlinecode{super\textsuperscript{script}}[p\pageref*{Markup--val-foo}]}, \hyperref[Markup--val-foo]{\ocamlinlinecode{sub\textsubscript{script}}[p\pageref*{Markup--val-foo}]}, and \hyperref[Markup--val-foo]{\ocamlinlinecode{\ocamlinlinecode{code}}[p\pageref*{Markup--val-foo}]}. It's also possible to surround a reference in a style: \bold{\hyperref[Markup--val-foo]{\ocamlinlinecode{\ocamlinlinecode{foo}}[p\pageref*{Markup--val-foo}]}}. References can't be nested inside references, and links and references can't be nested inside each other.
45454646\subsection{Preformatted text\label{Markup--preformatted-text}}%
4747-This is a code block:
4747+This is a code block:\medbreak
4848+\begin{ocamlcodeblock}
4949+ let foo = ()
5050+ (** There are some nested comments in here, but an unpaired comment
5151+ terminator would terminate the whole doc surrounding comment. It's
5252+ best to keep code blocks no wider than 72 characters. *)
48535454+ let bar =
5555+ ignore foo
5656+\end{ocamlcodeblock}\medbreak
4957There are also verbatim blocks:
50585159\begin{verbatim}The main difference is these don't get syntax highlighting.\end{verbatim}%
+10
test/generators/man/Markup.3o
···148148.fi
149149This is a code block:
150150.sp
151151+.EX
152152+ let foo = ()
153153+ (** There are some nested comments in here, but an unpaired comment
154154+ terminator would terminate the whole doc surrounding comment\. It's
155155+ best to keep code blocks no wider than 72 characters\. *)
156156+157157+ let bar =
158158+ ignore foo
159159+.EE
160160+.sp
151161There are also verbatim blocks:
152162.sp
153163.EX
+4-2
test/generators/markdown/Markup.md
···66666767This is a code block:
68686969-<x-ocaml mode="interactive"> let foo = ()
6969+```ocaml
7070+ let foo = ()
7071 (** There are some nested comments in here, but an unpaired comment
7172 terminator would terminate the whole doc surrounding comment. It's
7273 best to keep code blocks no wider than 72 characters. *)
73747475 let bar =
7575- ignore foo</x-ocaml>
7676+ ignore foo
7777+```
7678There are also verbatim blocks:
77797880```
+7-6
test/integration/code_block_handlers.t/run.t
···1313 $ odoc html-generate -o html page-test_code_blocks.odocl
1414 odoc: internal error, uncaught exception:
1515 Sys_error("html/test/test_code_blocks.html: Permission denied")
1616- Raised by primitive operation at Stdlib.open_out_gen in file "stdlib.ml", line 331, characters 29-55
1717- Called from Stdlib.open_out in file "stdlib.ml" (inlined), line 336, characters 2-74
1818- Called from Odoc_utils.Io_utils.with_open_out in file "odoc/src/utils/odoc_utils.ml" (inlined), line 55, characters 19-35
1919- Called from Odoc_utils.Io_utils.with_formatter_out in file "odoc/src/utils/odoc_utils.ml", line 62, characters 4-74
1616+ Raised by primitive operation at Stdlib.open_out_gen in file "stdlib.ml" (inlined), line 346, characters 29-55
1717+ Called from Stdlib.open_out in file "stdlib.ml" (inlined), line 351, characters 2-74
1818+ Called from Odoc_utils.Io_utils.with_open_out in file "odoc/src/utils/odoc_utils.ml", line 55, characters 19-35
2019 Called from Odoc_document__Renderer.traverse.aux in file "odoc/src/document/renderer.ml", line 18, characters 4-44
2121- Called from Stdlib__List.iter in file "list.ml", line 114, characters 12-15
2020+ Called from Stdlib__List.iter in file "list.ml" (inlined), line 117, characters 12-15
2121+ Called from Odoc_document__Renderer.traverse in file "odoc/src/document/renderer.ml", line 21, characters 2-17
2222 Called from Odoc_odoc__Rendering.generate_odoc.(fun) in file "odoc/src/odoc/rendering.ml", line 82, characters 2-68
2323- Called from Stdlib__List.fold_left in file "list.ml", line 125, characters 24-34
2323+ Called from Stdlib__List.fold_left in file "list.ml" (inlined), line 128, characters 24-34
2424+ Called from Dune__exe__Main.Make_renderer.Generate.generate in file "odoc/src/odoc/bin/main.ml", lines 919-921, characters 6-131
2425 Called from Cmdliner_term.app.(fun) in file "cmdliner_term.ml", line 24, characters 19-24
2526 Called from Cmdliner_term.app.(fun) in file "cmdliner_term.ml", line 22, characters 12-19
2627 Called from Cmdliner_eval.run_parser in file "cmdliner_eval.ml", line 35, characters 37-44
···28282929 For higher order functions, it will be suffixed **U** if it takes uncurried callback.
30303131- <x-ocaml mode="interactive"> val forEach : 'a t -> ('a -> unit) -> unit
3232- val forEachU : 'a t -> ('a -> unit [\@u]) -> unit</x-ocaml>
3131+ ```ocaml
3232+ val forEach : 'a t -> ('a -> unit) -> unit
3333+ val forEachU : 'a t -> ('a -> unit [\@u]) -> unit
3434+ ```
3335 In general, uncurried version will be faster, but it may be less familiar to people who have a background in functional programming.
34363537 **A special encoding for collection safety**
···38403941 The original OCaml stdlib solved the problem using *functor* which creates a big closure at runtime and makes dead code elimination much harder. We use a phantom type to solve the problem:
40424141- <x-ocaml mode="interactive"> module Comparable1 = Belt.Id.MakeComparable (struct
4343+ ```ocaml
4444+ module Comparable1 = Belt.Id.MakeComparable (struct
4245 type t = int * int
4346 let cmp (a0, a1) (b0, b1) =
4447 match Pervasives.compare a0 b0 with
4545- | 0 -> Pervasives.compare a1 b1
4646- | c -> c
4848+ | 0 -> Pervasives.compare a1 b1
4949+ | c -> c
4750 end)
48514952 let mySet1 = Belt.Set.make ~id:(module Comparable1)
···5255 type t = int * int
5356 let cmp (a0, a1) (b0, b1) =
5457 match Pervasives.compare a0 b0 with
5555- | 0 -> Pervasives.compare a1 b1
5656- | c -> c
5858+ | 0 -> Pervasives.compare a1 b1
5959+ | c -> c
5760 end)
58615959- let mySet2 = Belt.Set.make ~id:(module Comparable2)</x-ocaml>
6262+ let mySet2 = Belt.Set.make ~id:(module Comparable2)
6363+ ```
6064 Here, the compiler would infer `mySet1` and `mySet2` having different type, so e.g. a \`merge\` operation that tries to merge these two sets will correctly fail.
61656262- <x-ocaml mode="interactive"> val mySet1 : (int * int, Comparable1.identity) t
6363- val mySet2 : (int * int, Comparable2.identity) t</x-ocaml>
6666+ ```ocaml
6767+ val mySet1 : (int * int, Comparable1.identity) t
6868+ val mySet2 : (int * int, Comparable2.identity) t
6969+ ```
6470 `Comparable1.identity` and `Comparable2.identity` are not the same using our encoding scheme.
65716672 **Collection Hierarchy**
67736874 In general, we provide a generic collection module, but also create specialized modules for commonly used data type. Take *Belt.Set* for example, we provide:
69757070- <x-ocaml mode="interactive"> Belt.Set
7676+ ```ocaml
7777+ Belt.Set
7178 Belt.Set.Int
7272- Belt.Set.String</x-ocaml>
7979+ Belt.Set.String
8080+ ```
7381 The specialized modules *Belt.Set.Int*, *Belt.Set.String* are in general more efficient.
74827583 Currently, both *Belt\_Set* and *Belt.Set* are accessible to users for some technical reasons, we **strongly recommend** users stick to qualified import, *Belt.Set*, we may hide the internal, *i.e*, *Belt\_Set* in the future
···8383 {"id":[{"kind":"Root","name":"Main"},{"kind":"Type","name":"tdzdz"},{"kind":"Constructor","name":"B"}],"doc":"Bliiiiiiiiiii","kind":{"kind":"Constructor","args":{"kind":"Tuple","vals":["int list","int"]},"res":"tdzdz"},"display":{"url":"page/Main/index.html#type-tdzdz.B","html":"<code class=\"entry-kind\">cons</code><code class=\"entry-title\"><span class=\"prefix-name\">Main.tdzdz.</span><span class=\"entry-name\">B</span><code class=\"entry-rhs\"> : int list * int -> tdzdz</code></code><div class=\"entry-comment\"><div><p>Bliiiiiiiiiii</p></div></div>"}}
8484 {"id":[{"kind":"Root","name":"J"}],"doc":"a paragraph two","kind":{"kind":"Doc"},"display":{"url":"page/J/index.html","html":"<code class=\"entry-kind\">doc</code><code class=\"entry-title\"><span class=\"entry-name\">J</span></code><div class=\"entry-comment\"><div><p>a paragraph two</p></div></div>"}}
8585 {"id":[{"kind":"Root","name":"Main"}],"doc":"a paragraph two","kind":{"kind":"Doc"},"display":{"url":"page/Main/index.html","html":"<code class=\"entry-kind\">doc</code><code class=\"entry-title\"><span class=\"entry-name\">Main</span></code><div class=\"entry-comment\"><div><p>a paragraph two</p></div></div>"}}
8686- {"id":[{"kind":"Root","name":"Main"}],"doc":"a paragraph\nand another\nverbatim\nx + 1\nblibli","kind":{"kind":"Doc"},"display":{"url":"page/Main/index.html","html":"<code class=\"entry-kind\">doc</code><code class=\"entry-title\"><span class=\"entry-name\">Main</span></code><div class=\"entry-comment\"><div><p>a paragraph</p><p>and another</p><pre>verbatim</pre><p><code class=\"odoc-katex-math\">x + 1</code></p><x-ocaml mode=\"interactive\">blibli</x-ocaml></div></div>"}}
8686+ {"id":[{"kind":"Root","name":"Main"}],"doc":"a paragraph\nand another\nverbatim\nx + 1\nblibli","kind":{"kind":"Doc"},"display":{"url":"page/Main/index.html","html":"<code class=\"entry-kind\">doc</code><code class=\"entry-title\"><span class=\"entry-name\">Main</span></code><div class=\"entry-comment\"><div><p>a paragraph</p><p>and another</p><pre>verbatim</pre><p><code class=\"odoc-katex-math\">x + 1</code></p><div><pre class=\"language-ocaml\"><code>blibli</code></pre></div></div></div>"}}
8787 {"id":[{"kind":"Root","name":"Main"}],"doc":"this is a title\nand this is a paragraph","kind":{"kind":"Doc"},"display":{"url":"page/Main/index.html","html":"<code class=\"entry-kind\">doc</code><code class=\"entry-title\"><span class=\"entry-name\">Main</span></code><div class=\"entry-comment\"><div><p>this is a title</p><p>and this is a paragraph</p></div></div>"}}
8888- {"id":[{"kind":"Root","name":"Main"},{"kind":"Module","name":"I"}],"doc":"a paragraph\nand another\nverbatim\nx + 1\nblibli","kind":{"kind":"Doc"},"display":{"url":"page/Main/I/index.html","html":"<code class=\"entry-kind\">doc</code><code class=\"entry-title\"><span class=\"prefix-name\">Main.</span><span class=\"entry-name\">I</span></code><div class=\"entry-comment\"><div><p>a paragraph</p><p>and another</p><pre>verbatim</pre><p><code class=\"odoc-katex-math\">x + 1</code></p><x-ocaml mode=\"interactive\">blibli</x-ocaml></div></div>"}}
8888+ {"id":[{"kind":"Root","name":"Main"},{"kind":"Module","name":"I"}],"doc":"a paragraph\nand another\nverbatim\nx + 1\nblibli","kind":{"kind":"Doc"},"display":{"url":"page/Main/I/index.html","html":"<code class=\"entry-kind\">doc</code><code class=\"entry-title\"><span class=\"prefix-name\">Main.</span><span class=\"entry-name\">I</span></code><div class=\"entry-comment\"><div><p>a paragraph</p><p>and another</p><pre>verbatim</pre><p><code class=\"odoc-katex-math\">x + 1</code></p><div><pre class=\"language-ocaml\"><code>blibli</code></pre></div></div></div>"}}
8989 {"id":[{"kind":"Root","name":"J"}],"doc":"a paragraph one","kind":{"kind":"Module"},"display":{"url":"page/J/index.html","html":"<code class=\"entry-kind\">mod</code><code class=\"entry-title\"><span class=\"entry-name\">J</span></code><div class=\"entry-comment\"><div><p>a paragraph one</p></div></div>"}}
9090 {"id":[{"kind":"Root","name":"Main"}],"doc":"","kind":{"kind":"Module"},"display":{"url":"page/Main/index.html","html":"<code class=\"entry-kind\">mod</code><code class=\"entry-title\"><span class=\"entry-name\">Main</span></code><div class=\"entry-comment\"><div></div></div>"}}
9191 {"id":[{"kind":"Root","name":"Main"},{"kind":"Module","name":"I"}],"doc":"","kind":{"kind":"Module"},"display":{"url":"page/Main/index.html#module-I","html":"<code class=\"entry-kind\">mod</code><code class=\"entry-title\"><span class=\"prefix-name\">Main.</span><span class=\"entry-name\">I</span></code><div class=\"entry-comment\"><div></div></div>"}}
9292 {"id":[{"kind":"Root","name":"Main"},{"kind":"Module","name":"M"}],"doc":"","kind":{"kind":"Module"},"display":{"url":"page/Main/index.html#module-M","html":"<code class=\"entry-kind\">mod</code><code class=\"entry-title\"><span class=\"prefix-name\">Main.</span><span class=\"entry-name\">M</span></code><div class=\"entry-comment\"><div></div></div>"}}
9393 {"id":[{"kind":"Root","name":"Main"},{"kind":"Module","name":"X"}],"doc":"","kind":{"kind":"Module"},"display":{"url":"page/Main/index.html#module-X","html":"<code class=\"entry-kind\">mod</code><code class=\"entry-title\"><span class=\"prefix-name\">Main.</span><span class=\"entry-name\">X</span></code><div class=\"entry-comment\"><div></div></div>"}}
9494- {"id":[{"kind":"Page","name":"page"}],"doc":"A title\nA paragraph\nsome verbatim\nand code\na list of things bliblib","kind":{"kind":"Page"},"display":{"url":"page/index.html","html":"<code class=\"entry-kind\">page</code><code class=\"entry-title\"><span class=\"entry-name\">page</span></code><div class=\"entry-comment\"><div><p>A title</p><p>A paragraph</p><pre>some verbatim</pre><x-ocaml mode=\"interactive\">and code</x-ocaml><ul><li>a list <em>of</em> things</li><li>bliblib</li></ul></div></div>"}}
9494+ {"id":[{"kind":"Page","name":"page"}],"doc":"A title\nA paragraph\nsome verbatim\nand code\na list of things bliblib","kind":{"kind":"Page"},"display":{"url":"page/index.html","html":"<code class=\"entry-kind\">page</code><code class=\"entry-title\"><span class=\"entry-name\">page</span></code><div class=\"entry-comment\"><div><p>A title</p><p>A paragraph</p><pre>some verbatim</pre><div><pre class=\"language-ocaml\"><code>and code</code></pre></div><ul><li>a list <em>of</em> things</li><li>bliblib</li></ul></div></div>"}}
9595 {"id":[{"kind":"Root","name":"Main"},{"kind":"Type","name":"t"}],"doc":"A comment","kind":{"kind":"TypeDecl","private":false,"manifest":"int","constraints":[]},"display":{"url":"page/Main/index.html#type-t","html":"<code class=\"entry-kind\">type</code><code class=\"entry-title\"><span class=\"prefix-name\">Main.</span><span class=\"entry-name\">t</span><code class=\"entry-rhs\"> = int</code></code><div class=\"entry-comment\"><div><p>A comment</p></div></div>"}}
9696 {"id":[{"kind":"Root","name":"Main"},{"kind":"Type","name":"tdzdz"}],"doc":"A comment aaaaaaaaaa","kind":{"kind":"TypeDecl","private":false,"manifest":null,"constraints":[]},"display":{"url":"page/Main/index.html#type-tdzdz","html":"<code class=\"entry-kind\">type</code><code class=\"entry-title\"><span class=\"prefix-name\">Main.</span><span class=\"entry-name\">tdzdz</span><code class=\"entry-rhs\"> = A of int * int | B of int list * int</code></code><div class=\"entry-comment\"><div><p>A comment aaaaaaaaaa</p></div></div>"}}
9797 {"id":[{"kind":"Root","name":"Main"},{"kind":"Module","name":"M"},{"kind":"Type","name":"t"}],"doc":"dsdsd","kind":{"kind":"TypeDecl","private":false,"manifest":null,"constraints":[]},"display":{"url":"page/Main/M/index.html#type-t","html":"<code class=\"entry-kind\">type</code><code class=\"entry-title\"><span class=\"prefix-name\">Main.M.</span><span class=\"entry-name\">t</span></code><div class=\"entry-comment\"><div><p>dsdsd</p></div></div>"}}
···101101 {"id":[{"kind":"Root","name":"Main"},{"kind":"Value","name":"lorem4"}],"doc":"lorem 4","kind":{"kind":"Value","type":"int"},"display":{"url":"page/Main/index.html#val-lorem4","html":"<code class=\"entry-kind\">val</code><code class=\"entry-title\"><span class=\"prefix-name\">Main.</span><span class=\"entry-name\">lorem4</span><code class=\"entry-rhs\"> : int</code></code><div class=\"entry-comment\"><div><p>lorem 4</p></div></div>"}}
102102 {"id":[{"kind":"Root","name":"Main"},{"kind":"Value","name":"lorem"}],"doc":"lorem 1 and a link","kind":{"kind":"Value","type":"int"},"display":{"url":"page/Main/index.html#val-lorem","html":"<code class=\"entry-kind\">val</code><code class=\"entry-title\"><span class=\"prefix-name\">Main.</span><span class=\"entry-name\">lorem</span><code class=\"entry-rhs\"> : int</code></code><div class=\"entry-comment\"><div><p>lorem 1 and a <span>link</span></p></div></div>"}}
103103 {"id":[{"kind":"Root","name":"Main"},{"kind":"Value","name":"uu"}],"doc":"","kind":{"kind":"Value","type":"int"},"display":{"url":"page/Main/index.html#val-uu","html":"<code class=\"entry-kind\">val</code><code class=\"entry-title\"><span class=\"prefix-name\">Main.</span><span class=\"entry-name\">uu</span><code class=\"entry-rhs\"> : int</code></code><div class=\"entry-comment\"><div></div></div>"}}
104104- {"id":[{"kind":"Root","name":"Main"},{"kind":"Value","name":"v"}],"doc":"a reference , and some formatted content with code and\n code blocks","kind":{"kind":"Value","type":"int"},"display":{"url":"page/Main/index.html#val-v","html":"<code class=\"entry-kind\">val</code><code class=\"entry-title\"><span class=\"prefix-name\">Main.</span><span class=\"entry-name\">v</span><code class=\"entry-rhs\"> : int</code></code><div class=\"entry-comment\"><div><p>a reference <span><code>t</code></span>, and some <em>formatted</em> <b>content</b> with <code>code</code> and</p><x-ocaml mode=\"interactive\"> code blocks</x-ocaml></div></div>"}}
104104+ {"id":[{"kind":"Root","name":"Main"},{"kind":"Value","name":"v"}],"doc":"a reference , and some formatted content with code and\n code blocks","kind":{"kind":"Value","type":"int"},"display":{"url":"page/Main/index.html#val-v","html":"<code class=\"entry-kind\">val</code><code class=\"entry-title\"><span class=\"prefix-name\">Main.</span><span class=\"entry-name\">v</span><code class=\"entry-rhs\"> : int</code></code><div class=\"entry-comment\"><div><p>a reference <span><code>t</code></span>, and some <em>formatted</em> <b>content</b> with <code>code</code> and</p><div><pre class=\"language-ocaml\"><code> code blocks</code></pre></div></div></div>"}}
105105 {"id":[{"kind":"Root","name":"Main"},{"kind":"Value","name":"x"}],"doc":"","kind":{"kind":"Value","type":"int"},"display":{"url":"page/Main/index.html#val-x","html":"<code class=\"entry-kind\">val</code><code class=\"entry-title\"><span class=\"prefix-name\">Main.</span><span class=\"entry-name\">x</span><code class=\"entry-rhs\"> : int</code></code><div class=\"entry-comment\"><div></div></div>"}}
106106 {"id":[{"kind":"Root","name":"Main"},{"kind":"Value","name":"y"}],"doc":"","kind":{"kind":"Value","type":"int"},"display":{"url":"page/Main/index.html#val-y","html":"<code class=\"entry-kind\">val</code><code class=\"entry-title\"><span class=\"prefix-name\">Main.</span><span class=\"entry-name\">y</span><code class=\"entry-rhs\"> : int</code></code><div class=\"entry-comment\"><div></div></div>"}}
107107 {"id":[{"kind":"Root","name":"Main"},{"kind":"Module","name":"I"},{"kind":"Value","name":"x"}],"doc":"","kind":{"kind":"Value","type":"int"},"display":{"url":"page/Main/I/index.html#val-x","html":"<code class=\"entry-kind\">val</code><code class=\"entry-title\"><span class=\"prefix-name\">Main.I.</span><span class=\"entry-name\">x</span><code class=\"entry-rhs\"> : int</code></code><div class=\"entry-comment\"><div></div></div>"}}
+5-16
test/xref2/shadow3.t/run.t
···1717 (sig :
1818 module type {B}1/shadowed/(CCCC) = A.B
1919 include {B}1/shadowed/(CCCC)
2020- (sig : module {A}1/shadowed/(AAAA) = A.A end)
2121- include sig
2222- module A :
2323- sig
2424- include module type of struct include {A}1/shadowed/(AAAA) end
2525- (sig : type t end)
2626- type a
2727- endend (sig : module {A}2/shadowed/(CCCC) = A.A end)
2020+ (sig : module {A}1/shadowed/(AAAA) = A.{A}1/shadowed/(AAAA) end)
2121+ include sigend (sig : module {A}2/shadowed/(CCCC) = A.A end)
2822 end)
2923 include module type of struct include B end
3024 (sig :
3125 module type B = B.B
3232- include B (sig : module {A}1/shadowed/(BBBB) = B.A end)
3333- include sig
3434- module A :
3535- sig
3636- include module type of struct include {A}1/shadowed/(BBBB) end
3737- (sig : type t end)
3838- type b
3939- endend (sig : module {A}3/shadowed/(CCCC) = B.A end)
2626+ include B
2727+ (sig : module {A}1/shadowed/(BBBB) = B.{A}1/shadowed/(BBBB) end)
2828+ include sigend (sig : module {A}3/shadowed/(CCCC) = B.A end)
4029 end)
4130 module A :
4231 sig
···991010 $ cat a.mli
1111 (** Module A defines a signature with an inline include. *)
1212-1212+1313 module type S = sig
1414 type t
1515-1515+1616 include sig
1717 val x : t
1818 val y : t -> t
1919 end
2020 end
21212222+2323+2224 $ cat b.mli
2325 (** Module B includes A.S with a type substitution.
2424-2626+2527 The substitution flows through [fragmap] in tools.ml. When the inline
2628 include's decl has been stripped, [map_include_decl] needs the
2729 reconstructed decl to correctly wrap the substitution. Without
2830 reconstruction, the empty decl produces [With(subst, empty_sig)]
2931 which loses the vals from the include. *)
3030-3232+3133 module M : A.S with type t := int
3434+3535+32363337Compile and link:
3438···47514852 $ odoc html-generate b.odocl -o html --indent
4953 $ grep 'id="val-[xy]"' html/test/B/M/index.html
5050- <div class="spec value anchored" id="val-x">
5151- <div class="spec value anchored" id="val-y">
5454+ <div class="spec value anchored" id="val-x">
5555+ <div class="spec value anchored" id="val-y">