this repo has no description
at main 668 lines 20 kB view raw
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}.