this repo has no description
1{0 Language Features}
2
3[odoc] works by taking module interfaces, processing them to make them more
4useful, and turning them into documentation. This processing is largely {{!hiding}hiding}, handling of {{!canonical}
5canonical references}, {{!expansion}expansion}, and {{!resolution}resolution}.
6This document explains the features of these processes in detail.
7
8{1:hiding Hiding}
9
10Some items are not intended to be used directly but are present as
11implementation detail, e.g., for testing, implementing Dune's namespacing, or other reasons.
12
13There are two mechanisms for explicitly hiding elements from the final
14output. The first is to use {{:https://ocaml.org/manual/ocamldoc.html#ss:ocamldoc-stop}documentation stop comments},
15which can be used to hide any items from the output. The second
16mechanism is only used for modules and is a naming convention. If any module has a double underscore in its name, it’s considered to be hidden.
17
18{1:canonical Canonical Items}
19
20Occasionally it’s useful to declare an item in one place and expose
21it in the public interface in another. In order to prevent unwanted occurrences
22of the actual definition site, [odoc] has a feature whereby the 'canonical'
23location of a module, module type, or type can be specified.
24
25The biggest user of module aliases is Dune's {{:https://dune.readthedocs.io/en/stable/reference/dune/library.html#library}wrapped libraries}.
26This feature allows Dune to produce a library whose interface is exposed entirely
27though a single top-level module. It does this by mangling the names of the
28real implementation modules and generating the single top-level module that
29simply contains aliases to the these implementation modules. With [odoc]'s
30canonical modules feature, all references to the implementation modules are
31rewritten to point at the top-level module aliases instead. Please
32see the section on Dune's
33{{!page-dune.library_wrapping}library wrapping} for more detail.
34
35In a similar fashion, it's sometimes useful to have this feature on module
36types and types. For example, [odoc] itself uses this for its 'paths types'.
37Since they are all mutually recursive, they all have to be declared at the
38same time in the module {{:https://github.com/ocaml/odoc/blob/91f6310967e64f3fa88445f3daf2fea2acc0bb49/src/model/paths_types.ml#L201-L219}[Paths_types]}, but [odoc] exposes them in separate modules.
39By annotating the definition point with [@canonical] tags pointing to the
40{{:https://github.com/ocaml/odoc/blob/master/src/model/paths.mli#L419}aliases}, we ensure that all their
41references point at the {{!Odoc_model.Paths.Path.Resolved.Module}separate} {{!Odoc_model.Paths.Path.Resolved.ModuleType}modules} as intended.
42
43{1:expansion Expansion}
44
45There are many instances of items in signatures where what's written is
46the best thing in terms of semantics, but it’s not necessarily useful in terms
47of documentation. For example:
48
49{[
50module StringSet : Stdlib.Set.S with type t = string
51]}
52
53[odoc] will {e expand} these items, augmenting the declaration with the
54signature along with all the documentation comments that can be found, e.g., those from [Stdlib.Set.S] in the above example. While the compiler also does
55a very similar procedure and determines the signature of the module,
56[odoc] tries quite hard to preserve as much as possible from the original items’ context.
57
58The declaration can be seen rendered {{!Odoc_examples.Expansion.Simple}here},
59and the full expansion can be found by clicking on the name of the module
60([StringSet] in this case). The direct link is {{!Odoc_examples.Expansion.Simple.StringSet}here}.
61
62These expansions have to be done carefully. A number of cases follow
63in which [odoc] has to treat specially.
64
65{2:expansion_aliases Aliases}
66
67In general, [odoc] doesn’t expand module aliases unless they are an
68alias to a hidden module. If this is the case, the right-hand side of
69the declaration is dropped and replaced with [sig ... end], and
70the expansion is created.
71
72For example, given the following source,
73
74{[
75
76module Hidden__module : sig
77 type t
78 val f : t -> t
79end
80
81module Alias = Hidden__module
82
83]}
84
85The [Hidden__module] module won’t be present in the output, and
86the [Alias] module will be rendered as if it were a simple
87signature. This can be seen in the example rendering {{!Odoc_examples.Expansion.Aliases}here}.
88
89As well as expanding aliases to hidden modules, modules are also expanded if the module
90alias is "self canonical." That is,
91if module [A] is an alias to module [B], that declares [A] to be the canonical
92path to the module (i.e., it has the tag [@canonical A] in an associated
93comment).
94
95{3 Module Type Aliases}
96
97Module types don’t have aliases in the same way that modules do, but it
98is possible and common to declare an effective alias to another by
99simply creating a new module type that’s equal to a previous one. For example:
100
101{[
102module type A = sig
103 type t
104end
105
106module type B = A
107]}
108
109For these simple [module type] declarations, where the right-hand side is just a
110path, [odoc] treats them as module aliases and doesn’t produce an expansion.
111This example is rendered {{!Odoc_examples.Expansion.ModuleTypeAliases}here}.
112
113When strengthening, OCaml turns modules into aliases of the original
114module, but nothing is done to module types. In contrast, [odoc] replaces
115module types with 'aliases' of the originals, too. These are not expanded, hence this is important for reducing the size of the output.
116
117The following examples use [module type of struct include ... end] to
118obtain the strengthened signature of [A] (see the {{!module_type_of}[Module Type Of]}
119section for more details on this).
120
121{[
122module A : sig
123 module type A = sig type t end
124 module X : A
125end
126module B : module type of struct include A end
127]}
128
129OCaml evaluates the following signature for [B]:
130
131{[
132module B : sig module type A = sig type t end module X = A.X end
133]}
134
135whereas [odoc] internally evaluates this as:
136
137{[
138module B : sig module type A = A.A end module X = A.X end
139]}
140
141This example is rendered {{!Odoc_examples.Expansion.ModuleTypeAliases2.B}here}
142
143
144{2 Functors}
145
146When [odoc] encounters a functor, it is also expanded. The parameters
147are expanded in the body of the functor expansion, above the signature
148representing the functor’s result.
149
150For example, given the following,
151
152{[
153module type Argument = sig
154
155 (** This type [a] is declared in the Argument module type *)
156 type a
157
158end
159
160module type Result = sig
161
162 (** This type [r] is declared in the Result module type *)
163 type r
164
165end
166
167module Functor : functor (X : Argument) (Y : Argument) -> Result
168]}
169
170an expansion will be created for [Functor], containing
171expansions for [X] and [Y] within it and followed by the [Result]’s signature.
172The above functor can be seen rendered {{!Odoc_examples.Expansion.Functors.Functor}here}.
173
174{2 Includes}
175
176If part of your module signature comes from an include of another
177module or module type, [odoc] keeps track of this and can render
178the included items in a clearly delimited and collapsible way.
179For example, given the following:
180
181{[
182module type ToBeIncluded = sig
183 type t
184
185 val f : t -> t
186 (** The description of [f] *)
187end
188
189module A : sig
190 include ToBeIncluded
191
192 val g : t -> t
193end
194]}
195
196The {{!Odoc_examples.Expansion.Include.A}expansion of module [A]} will contain a
197clearly demarcated section showing the included items.
198
199If this behaviour is not desired, the include may be inlined with the
200tag [@inline] as follows:
201
202{[
203module B : sig
204 include ToBeIncluded
205 (** @inline *)
206
207 val g : t -> t
208end
209]}
210
211The {{!Odoc_examples.Expansion.Include.B}expansion of module [B]} does not
212contain an indication that the elements [t] and [f] came from an [include]
213directive.
214
215{2 Shadowing}
216
217OCaml ordinarily does not allow two items of the same type with
218the same name. For example, the following is illegal:
219
220{[
221type t = int
222type t = string
223]}
224
225However, if the item comes in via an include, then OCaml allows it.
226For example:
227
228{[
229module type A = sig
230 type t = int
231 val f : t
232end
233
234module type B = sig
235 include A
236 type t = string
237 val g : t
238end
239]}
240
241Since [odoc] is required to do its own expansions, it must take
242account of this behaviour. The previous example is rendered
243{{!Odoc_examples.Expansion.Shadowing}here}.
244
245{2 Deep Equations}
246
247The module type system allows for adding equations to abstract types
248(as seen above in the [StringSet] declaration). These equations
249may be 'deep' in the sense that they operate on a nested module
250rather than the outer one. For example:
251
252{[
253module type SIG = sig
254 type t
255end
256
257module type MODTYPE = sig
258 module X : SIG
259 module Y : SIG
260end
261
262type foo
263
264module M : MODTYPE with type X.t = foo
265]}
266
267Here we've got a module type [SIG] that contains an abstract type [t] and a module type [MODTYPE] that contains two modules, [X] and [Y],
268that have signature [SIG]. Lastly, we declare a module [M] that
269has signature [MODTYPE] with an additional type equality [X.t = foo].
270When the compiler evaluates
271the signature of module [M] here, the definition of [X]
272within it is simply replaced with a signature:
273
274{[
275module M : sig
276 module X : sig type t = foo end
277 module Y : SIG
278end
279]}
280
281We lose both the fact that it came from [MODTYPE] and also
282that within it, [X] originally had signature [SIG]. [odoc] tries
283to be more careful. Instead, it keeps both the [MODTYPE] on [M] with the
284type equality [X.t = foo]
285and the [SIG] on [X] with the type equality [t = foo]. The expansion of
286of module [M] in this example can be seen {{!Odoc_examples.Expansion.DeepEquality.M}here}.
287
288Note that if [X] was a simple signature before the type equality was added, that does not get preserved. In the following example,
289
290{[
291module type MODTYPE = sig
292 module X : sig type t end
293 module Y : sig type t end
294end
295
296type foo
297
298module M : MODTYPE with type X.t = foo
299]}
300
301The {{!Odoc_examples.Expansion.DeepEquality2.M}expansion of M} does not contain any [with type] equations.
302
303{2 Substitution}
304
305Similar to the addition of equations in the previous section, OCaml allows
306for types and modules to be {e destructively} substituted, so the
307type or module is entirely removed from the resulting signature.
308
309As with the addition of equations above, these substitutions may be on
310deeply nested modules. Care must be taken to ensure that there are
311no references to the removed module or type left. For example:
312
313{[
314module type S = sig
315 module M: sig type t end
316
317 type t = M.t
318end
319
320module type T = S with type M.t := int
321]}
322
323The expansion of [T] internally is different from what is rendered.
324Internally, it becomes:
325
326{[
327module M: sig type t end with type t := int
328
329type t = M.t
330]}
331
332From this expansion, it is still clear how to resolve the right-hand side
333of [type t = M.t]. The next phase of [odoc]'s transformation turns the
334right-hand side of [M.t] into [int].
335
336In the output documentation, the declaration of [module M] is rendered
337simply as
338
339{[
340module M : sig ... end
341]}
342
343with the type substitution dropped. This is because the simple signature's type substitition
344isn't useful for the reader. The link [t] would
345have no destination. This example is rendered {{!Odoc_examples.Expansion.TypeSubstitution}here}.
346
347
348{2:module_type_of [module type of]}
349
350The OCaml construct [module type of] allows the type of a module to be
351recovered. As usual, when OCaml performs this operation, it only retains
352the simplified signature, stripped of comments, includes, and more
353complex [module type] expressions. As with the previous sections, [odoc]
354tries a little harder to keep track of these things and also of the
355fact that the signature came from a [module type of] expression.
356
357For example, consider the following:
358
359{[
360module A : sig
361
362 (** This comment for [type t] is written in module [A] *)
363 type t
364
365end
366
367module M : module type of A
368]}
369
370The [type t] in module [M] has the comment from the original module.
371There is also logic in [odoc] to manage the similar construct
372[module type of struct include ... end], which is used where the types
373and modules are required to be strengthened. That is, the types in
374the signature are equal to those in the original module, and any
375modules in the new signature are aliases of those in the original.
376For example,
377
378{[
379module M' : module type of struct include A end
380]}
381
382In {{!Odoc_examples.Expansion.ModuleTypeOf.M}M’}, type [t] is equal to
383[A.t], whereas in {{!Odoc_examples.Expansion.ModuleTypeOf.M}M} there is
384no equation.
385
386{2 Complications of [module type of]}
387
388Doing the expansion like this comes with some complications, particularly
389when the result is further modified. For example, consider this example:
390
391{[
392module type S = sig
393 module X : sig
394 type t
395 end
396
397 module type Y = module type of X
398 module type Z = module type of struct include X end
399end
400]}
401
402When OCaml operates on this, it calculates the signature of [S] immediately,
403resulting in the module type:
404
405{[
406module type S =
407 sig
408 module X : sig type t end
409 module type Y = sig type t end
410 module type Z = sig type t = X.t end
411 end
412]}
413
414On the other hand, [odoc] preserves the fact that [Y] and [Z] are calculated from [X]. If the
415module [X] is subsequently replaced using a destructive substitution on [S], the
416results would be different. For example:
417
418{[
419module X1 : sig
420 type t
421 type u
422end
423
424module type T = S with module X := X1
425]}
426
427The signature of [T], as calculated by OCaml, will be
428
429{[
430sig
431 module type Y = sig type t end
432 module type Z = sig type t = X1.t end
433end
434]}
435
436However, it's clear that if the [module type of] operations were evaluated {e after}
437the substitution, both [Y] and [Z] would contain [type u].
438
439There is logic in [odoc] to handle this correctly, but since there is currently no
440syntax for representing transparent ascription, the consequence is that we lose
441the fact that [Y] and [Z] originally came from [module type of] expressions.
442
443This example is rendered {{!Odoc_examples.Expansion.ModuleTypeOfComplications}here}.
444In the {{!Odoc_examples.Expansion.ModuleTypeOfComplications.T}expansion of T}, it
445can be seen that [Y] and [Z] are simple signatures only containing [type t].
446
447
448{1:resolution Resolution}
449
450There are several different but related constructs for referring to elements in
451[odoc]. The following example demonstrates each:
452
453{[
454(** The module {!module-M} and type {!module-M.module-X.type-t} *)
455module M : A.B.C with type X.t = int
456]}
457
458Here, [M] is an {e identifier} that uniquely identifies the module [M]. [A.B.C] is a
459{e path} used to locate a particular identifier, [X.t] is a {e fragment} that refers
460to an element within a module type, and [module-M] and [module-M.module-X.type-t] are
461{e references} that are similar to paths in that they are used to locate particular
462identifiers; however, unlike paths, they are not checked by the compiler and are entirely
463resolved by [odoc].
464
465In most of the output formats, [odoc] supports paths. References and fragments will be
466turned into links that take the reader to the referred identifier. These
467links need to consider some of the expansions’ features,
468as outlined above. In order to decide where the links should point to and how to
469turn them into text, a process called 'Resolution' is required.
470
471{2:resolution_aliases Aliases}
472
473Since aliases are not usually expanded, a path or reference to an item contained in
474an aliased module must link directly to the item inside the aliased module. For
475example:
476
477{[
478module A : sig
479 type t
480end
481
482module B = A
483
484type t = B.t
485]}
486
487The right-hand side of [type t] should render as [B.t], but it should link to
488the definition of [t] in [module A]. This example is demonstrated {{!Odoc_examples.Resolution.Alias}here}.
489
490Aliases of hidden modules {e are} expanded, so the following example demonstrates
491this alteration:
492
493{[
494(**/**)
495
496module A : sig
497 type t
498end
499
500(**/**)
501
502module B = A
503
504type t = B.t
505]}
506
507Here we've hidden [A] via the {{:https://ocaml.org/manual/ocamldoc.html#ss:ocamldoc-stop}documentation stop comment}
508mechanism. This example is demonstrated {{!Odoc_examples.Resolution.HiddenAlias}here}.
509
510{2 Canonical Paths}
511
512When encountering a module, module type, or a type that has been marked with a
513[@canonical] tag, [odoc] first has to check that the specified canonical path
514actually resolves. If this is the case, in a similar way to the alias above, the
515path's target will be rewritten to point to the canonical path.
516However, in contrast to the alias behaviour, the path's {e text} will
517also be rewritten, so it will be as if the canonical path had been written instead
518of whatever path was actually there.
519
520For example:
521
522{[
523module A : sig
524 (** @canonical Odoc_examples.Resolution.Canonical.B *)
525
526 type t
527end
528
529module B = A
530
531type t = A.t
532]}
533
534Note that in this example the [@canonical] tag has been given the path
535[Odoc_examples.Resolution.Canonical.B]. This {e must} be the fully qualified
536path to the canonical item.
537
538The right-hand side of [type t] will be rewritten such
539that it will be as if [B.t] had been written instead.
540
541Note that canonical tags are only used when resolving {e paths}, not
542fragments (which are relative anyway) nor references. Since they are
543written by the author, they’re assumed to point to the correct destination.
544
545{2 Fragment Resolution}
546
547Fragments are relative paths that appear in [module type] expressions when
548adding equations or substituting types or modules. For example:
549
550{[
551module type A = sig
552 module B : sig
553 type t
554 val f : t -> t
555 end
556end
557
558module C : A with type B.t = int
559module D : module type of C.B with type t := int
560]}
561
562In this expression, the fragment [B.t] should link to the definition of [type t]
563inside module [B] inside module [type A]. The fragment [t] in the definition of
564module [D] should link to the definition of [type t] inside module [B] inside
565module [C]. Note that it can't link to [type t] in [D] since that type has been
566destroyed!
567
568This example is rendered {{!Odoc_examples.Resolution.Fragments}here}.
569
570{2 Hidden Elements}
571
572If there are paths that refer to hidden elements, these are removed from the
573interface unless there is an equal non-hidden type that can replace it. For
574example, in the following type definitions,
575
576{[
577
578(**/**)
579
580type t = int
581type u
582
583(**/**)
584
585type v = T of t
586type w = U of u
587
588]}
589
590[type v] will have a right-hand side of [T of int], as the hidden [type t] is
591equal to [int]. Conversely, there is no non-hidden type equivalent to [u], so the
592right-hand side of [type w] is omitted from the output.
593
594{2:reference_resolution Reference Resolution}
595
596References are handwritten in comments and not evaluated in any way by the
597compiler.
598
599{[
600module type A = sig
601
602 type t
603 (** type [t] in module type [A] *)
604
605end
606
607module A : sig
608
609 type t
610 (** type [t] in module [A] *)
611
612 module B : sig type t end
613 module type B = sig type t end
614
615end
616
617(** We can refer unambiguously to {!module-type-A.t} in module type [A]
618 or {!module-A.t} in module [A], and also where there are name clashes
619 within the path: {!A.module-B.t} or {!A.module-type-B.t} *)
620]}
621
622This demonstrates that it’s possible to refer to elements even when there’s ambiguity,
623if just the names were used. If [odoc] detects any ambiguity, it will emit a warning.
624
625
626{2 Module Type Challenges}
627
628In some cases, resolution can be more challenging than others. Consider this example:
629
630{[
631module type A = sig
632 module M : sig module type S end
633 module N : M.S
634end
635
636module B : sig module type S = sig type t end end
637
638module C : A with module M = B with type N.t = int
639
640type t = C.N.t
641]}
642
643In the expansion of module type [A], module [N] has no expansion because
644module type [S] is abstract. Therefore, in the definition of module [C],
645the fragment [N.t] cannot link to module [N] in module type [A],
646but instead it must link to module type [S] in module [B].
647
648This example is rendered {{!Odoc_examples.Resolution.Complicated_1}here}.
649
650Now for a very complicated example:
651
652{[
653module type Type = sig module type T end
654
655module App : functor (T : Type) (F : Type -> Type) (M : F(T).T) -> F(T).T
656
657module Bar : sig module type T = sig type bar end end
658
659module Foo :
660 functor (T : Type) -> sig module type T = sig module Foo : T.T end end
661
662module FooBarInt : sig module Foo : sig type bar = int end end
663
664type t = App(Bar)(Foo)(FooBarInt).Foo.bar
665]}
666
667This one is left as an exercise to the reader! It can be seen rendered
668by [odoc] {{!Odoc_examples.Resolution.Complicated_2}here}.