ocaml / odoc

Documentation compiler for OCaml and Reason
Other
323 stars 92 forks source link

odoc sometimes force-inlines includes #1162

Open dlesbre opened 4 months ago

dlesbre commented 4 months ago

I've run into an issue where odoc force inlines my includes in the generated doc. Its quite brittle, so I'm not sure what exactly causes it, but here is a minimal example.

Lets say i have a library Lib with the following files:

Then for some reason Ok and Err1 / Err2 are rendered very differently. Ok is rendered with the correct signature and the closed include shows up correctly in its documentation, whereas the Errs are rendered as sig ... end and the include has been inlined in their documentation.

Is there any way around this? I would really like to use one of the Err style signature (in the full example, my modules aren't actually defined in the top library file lib.ml, they are in another file modules.ml, in which I would like to just open Sigs, not have to include it, and then include both Sigs and Modules in Lib).

dlesbre commented 4 months ago

Maybe related to #878 or #160

jonludlam commented 4 months ago

You mention "library interface files" Lib.ml and Lib.mli - are you building with dune and is your library by any chance named lib?

If so, try naming the library something different (ie, keep Lib.ml and Lib.mli but name the library foo, so they don't match)

dlesbre commented 4 months ago

Yes precisely, I'm using the same names for the library and file on purpose:

I understand why this double interface would cause problems though. I'm declaring a module whose type isn't exported directly, it is only exported through a separate include. Unifying these could prove challenging, but I believe it is a common enough pattern to be worth looking into.

jonludlam commented 4 months ago

OK, so that explains why you're seeing that behaviour. Odoc will inline includes if doesn't know of a non-hidden path to the module/type being included. In this case there's no link to TYPE that's available so it just inlines it. One way to fix this is to expose the Sigs module - ie, something like:

module Sigs = Sigs

(** This doesn't render correctly *)
module Err1 : Sigs.PARTIAL_ORDERED_TYPE

open Sigs

(** Neither does this *)
module Err2 : PARTIAL_ORDERED_TYPE

include module type of Sigs

(** This does though *)
module Ok : PARTIAL_ORDERED_TYPE

and that should fix all the expansions to be what you're expecting.

dlesbre commented 4 months ago

A solution mentioned by @jonludlam in https://github.com/codex-semantics-library/patricia-tree/pull/10 is to use @canonical tags to point odoc to the instance of the module type it should use:

For the toy example here, just add the tags to the module type in sigs.ml

(** some doc
    @canonical Lib.TYPE *)
module type TYPE = sig
  type t
  (** Doc of t*)
end

(** A supermodule of {!TYPE}, with partial ordering
    @canonical Lib.PARTIAL_ORDERED_TYPE *)
module type PARTIAL_ORDERED_TYPE = sig
  include TYPE (** @closed *)

  val partial_compare : t -> t -> int option
  (** order of t *)
end

And make sure those module types are available in Lib.ml/Lib.mli either by copying them one by one or through an include:

module Err1 : Sigs.PARTIAL_ORDERED_TYPE 
(** This now renders as expected, hurray! *)

open Sigs

module Err2 : PARTIAL_ORDERED_TYPE
(** Same for this one *)

include module type of Sigs
(** including the actual signatures is still required obviously, but @canonical tags allow changing their names *)