OCaml Documentation (odoc) Skill
When to Use
Use this skill when fixing odoc documentation warnings, typically from dune build @doc.
Prerequisites: This skill covers odoc v3 syntax which is not yet in released versions of dune or odoc. You need:
- dune pinned to https://github.com/jonludlam/dune/tree/odoc-v3-rules-3.21
- odoc pinned to https://github.com/jonludlam/odoc/tree/staging
Reference Syntax
Use path-based disambiguation {!Path.To.kind-Name} rather than {!kind:Path.To.Name}:
(* Correct *)
{!Jsont.exception-Error}
{!Proto.Incoming.t.constructor-Message}
{!module-Foo.module-type-Bar.exception-Baz}
(* Incorrect *)
{!exception:Jsont.Error}
{!constructor:Proto.Incoming.t.Message}
This allows disambiguation at any position in the path.
Reference Kinds
module-for modulestype-for typesval-for valuesexception-for exceptionsconstructor-for variant constructorsfield-for record fieldsmodule-type-for module types
Cross-Package References
When odoc cannot resolve a reference to another package, add a documentation dependency in dune-project:
(package
(name mypackage)
...
(documentation (depends other-package)))
Do NOT convert doc references {!Foo} to code markup [Foo] - this loses the hyperlink.
Cross-Library References (Same Package)
When referencing modules from another library in the same package, use the full path through re-exported modules.
Example: If claude.mli has module Proto = Proto, reference proto modules as {!Proto.Incoming} not {!Incoming}.
Missing Module Exports
If odoc reports "Couldn't find X" where X is the last path component:
- Check if the module is re-exported in the parent module's
.mli - Add
module X = Xto the parent's.mliif missing
Ambiguous References
When odoc warns about ambiguity (e.g., both an exception and module named Error):
{!Jsont.exception-Error} (* for the exception *)
{!Jsont.module-Error} (* for the module *)
@raise Tags
For @raise documentation tags, use the exception path with disambiguation:
@raise Jsont.exception-Error
@raise Tomlt.Toml.Error.exception-Error
Escaping @ Symbols
The @ character is interpreted as a tag marker in odoc. When you need a literal @ in documentation text (e.g., describing @-mentions), escape it with a backslash:
(* Correct - escaped @ *)
(** User was \@-mentioned *)
(** Mentioned via \@all/\@everyone *)
(* Incorrect - will produce "Stray '@'" or "Unknown tag" warnings *)
(** User was @-mentioned *)
(** Mentioned via @all *)
Hidden Fields Warning
When odoc warns about "Hidden fields in type 'Foo.Bar.t': field_name", it means a record field uses a type that odoc can't resolve in the documentation.
Diagnosis:
- Find the field definition in the
.mlifile - Identify what type the field uses (e.g.,
uri : Uri.t) - Check if that type's module is re-exported in the wrapper
.mli
Fix Option 1: Re-export the module in the wrapper .mli:
(** RFC 3986 URI parsing *)
module Uri = Uri
Fix Option 2: If you only want to expose the type (not the whole module), use @canonical:
Add a type alias in the wrapper
.mli:type uri = Uri.tAdd
@canonicalto the original type's documentation:(* In uri.mli *) type t (** A URI. @canonical Requests.uri *)
This tells odoc to link Uri.t to Requests.uri in the generated documentation.
Interpreting Error Messages
| Error Pattern | Meaning | Fix |
|---|---|---|
unresolvedroot(X) |
X not found as root module | Check library dependencies, add documentation depends |
Couldn't find "Y" after valid path |
Y doesn't exist at that location | Verify module structure, check exports |
Reference to 'X' is ambiguous |
Multiple items named X | Add kind qualifier (e.g., exception-X) |
Hidden fields in type ... : field |
Field's type not resolvable | Re-export the type's module in wrapper .mli |
Debugging
- Run
dune cleanbeforedune build @docto ensure fresh builds - Check the library's
.mlifile to see what modules are exported - For cross-library refs, trace the module path through re-exports