• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

ocaml / odoc / 3115

15 Apr 2026 03:40PM UTC coverage: 71.317% (-0.1%) from 71.459%
3115

Pull #1407

github

web-flow
Merge 107d5919c into 58a0431b1
Pull Request #1407: OxCaml: Support for unboxed named types

1 of 31 new or added lines in 10 files covered. (3.23%)

11 existing lines in 3 files now uncovered.

10413 of 14601 relevant lines covered (71.32%)

5766.06 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

82.98
/src/document/generator.ml
1
(*
2
 * Copyright (c) 2016 Thomas Refis <trefis@janestreet.com>
3
 *
4
 * Permission to use, copy, modify, and distribute this software for any
5
 * purpose with or without fee is hereby granted, provided that the above
6
 * copyright notice and this permission notice appear in all copies.
7
 *
8
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15
 *)
16

17
open Odoc_utils
18
open Odoc_model.Names
19
module Location = Odoc_model.Location_
20
module Paths = Odoc_model.Paths
21
open Types
22
module O = Codefmt
23
open O.Infix
24

25
let tag tag t = O.span ~attr:tag t
13,407✔
26

27
let label t =
28
  match t with
3,263✔
29
  | Odoc_model.Lang.TypeExpr.Label s -> tag "label" (O.txt s)
574✔
30
  | Optional s | RawOptional s -> tag "optlabel" (O.txt "?" ++ O.txt s)
1✔
31

32
let type_var tv = tag "type-var" (O.txt tv)
9,487✔
33

34
let enclose ~l ~r x = O.span (O.txt l ++ x ++ O.txt r)
14,348✔
35

36
let resolved p content =
37
  let link = { Link.target = Internal (Resolved p); content; tooltip = None } in
159,791✔
38
  O.elt [ inline @@ Link link ]
159,791✔
39

40
let path p content = resolved (Url.from_path p) content
2,955✔
41

42
let unresolved content =
43
  let link = { Link.target = Internal Unresolved; content; tooltip = None } in
582✔
44
  O.elt [ inline @@ Link link ]
582✔
45

46
let path_to_id path =
47
  let url = Url.Anchor.from_identifier (path :> Paths.Identifier.t) in
7,529✔
48
  Some url
7,529✔
49

50
let source_anchor source_loc =
51
  match source_loc with
7,891✔
52
  | Some id ->
104✔
53
      Some
54
        (Url.Anchor.from_identifier
104✔
55
           (id : Paths.Identifier.SourceLocation.t :> Paths.Identifier.t))
56
  | _ -> None
7,787✔
57

58
let attach_expansion ?(status = `Default) (eq, o, e) page text =
1,602✔
59
  match page with
3,445✔
60
  | None -> O.documentedSrc text
701✔
61
  | Some (page : Page.t) ->
2,744✔
62
      let url = page.url in
63
      let summary = O.render text in
64
      let expansion =
2,744✔
65
        O.documentedSrc (O.txt eq ++ O.keyword o)
2,744✔
66
        @ DocumentedSrc.[ Subpage { status; content = page } ]
67
        @ O.documentedSrc (O.keyword e)
2,744✔
68
      in
69
      DocumentedSrc.
70
        [ Alternative (Expansion { summary; url; status; expansion }) ]
71

72
let mk_heading ?(level = 1) ?label text =
366✔
73
  let title = [ inline @@ Text text ] in
366✔
74
  Item.Heading { label; level; title; source_anchor = None }
75

76
(** Returns the preamble as an item. Stop the preamble at the first heading. The
77
    rest is inserted into [items]. *)
78
let prepare_preamble comment items =
79
  let preamble, first_comment =
3,314✔
80
    List.split_at
81
      ~f:(function
82
        | { Odoc_model.Location_.value = `Heading _; _ } -> true | _ -> false)
32✔
83
      comment
84
  in
85
  (Comment.standalone preamble, Comment.standalone first_comment @ items)
3,314✔
86

87
let make_expansion_page ~source_anchor url comments items =
88
  let comment = List.concat comments in
3,314✔
89
  let preamble, items = prepare_preamble comment items in
3,314✔
90
  { Page.preamble; items; url; source_anchor }
3,314✔
91

92
include Generator_signatures
93

94
module Make (Syntax : SYNTAX) = struct
95
  module Link : sig
96
    val from_path : Paths.Path.t -> text
97

98
    val from_fragment : Paths.Fragment.leaf -> text
99
  end = struct
100
    open Paths
101

102
    let rec from_path : Path.t -> text =
103
     fun path ->
104
      match path with
181,953✔
105
      | `Identifier (id, _) ->
245✔
106
          unresolved [ inline @@ Text (Identifier.name id) ]
245✔
107
      | `Substituted m -> from_path (m :> Path.t)
×
108
      | `SubstitutedMT m -> from_path (m :> Path.t)
×
109
      | `SubstitutedT m -> from_path (m :> Path.t)
×
110
      | `SubstitutedCT m -> from_path (m :> Path.t)
×
NEW
111
      | `Unbox t -> from_path (t :> Path.t)
×
112
      | `Root root -> unresolved [ inline @@ Text (ModuleName.to_string root) ]
305✔
113
      | `Forward root -> unresolved [ inline @@ Text root ] (* FIXME *)
×
114
      | `Dot (prefix, suffix) ->
305✔
115
          let link = from_path (prefix :> Path.t) in
116
          link ++ O.txt ("." ^ ModuleName.to_string suffix)
305✔
117
      | `DotT (prefix, suffix) ->
297✔
118
          let link = from_path (prefix :> Path.t) in
119
          link ++ O.txt ("." ^ TypeName.to_string suffix)
297✔
120
      | `DotMT (prefix, suffix) ->
×
121
          let link = from_path (prefix :> Path.t) in
122
          link ++ O.txt ("." ^ ModuleTypeName.to_string suffix)
×
123
      | `DotV (prefix, suffix) ->
×
124
          let link = from_path (prefix :> Path.t) in
125
          link ++ O.txt ("." ^ ValueName.to_string suffix)
×
126
      | `Apply (p1, p2) ->
×
127
          let link1 = from_path (p1 :> Path.t) in
128
          let link2 = from_path (p2 :> Path.t) in
×
129
          link1 ++ O.txt "(" ++ link2 ++ O.txt ")"
×
130
      | `Resolved _ when Paths.Path.is_hidden path ->
180,801✔
131
          let txt = Url.render_path path in
32✔
132
          unresolved [ inline @@ Text txt ]
32✔
133
      | `Resolved rp -> (
180,769✔
134
          (* If the path is pointing to an opaque module or module type
135
             there won't be a page generated - so we stop before; at
136
             the parent page, and link instead to the anchor representing
137
             the declaration of the opaque module(_type) *)
138
          let stop_before =
139
            match rp with
140
            | `OpaqueModule _ | `OpaqueModuleType _ -> true
8✔
141
            | _ -> false
180,561✔
142
          in
143
          let txt = [ inline @@ Text (Url.render_path path) ] in
180,769✔
144
          match Paths.Path.Resolved.identifier rp with
145
          | Some id ->
156,330✔
146
              let href = Url.from_identifier ~stop_before id in
147
              resolved href txt
156,330✔
148
          | None -> O.elt txt)
24,439✔
149

150
    let dot prefix suffix = prefix ^ "." ^ suffix
49✔
151

152
    let rec render_fragment_any : Fragment.t -> string =
153
     fun fragment ->
154
      match fragment with
×
155
      | `Resolved rr -> render_resolved_fragment rr
×
156
      | `Dot (`Root, suffix) -> suffix
×
157
      | `Dot (prefix, suffix) ->
×
158
          dot (render_fragment_any (prefix :> Fragment.t)) suffix
×
159
      | `Root -> assert false
160

161
    and render_resolved_fragment : Fragment.Resolved.t -> string =
162
      let open Fragment.Resolved in
163
      fun fragment ->
164
        match fragment with
376✔
165
        | `Root _ -> assert false
166
        | `Subst (_, rr) -> render_resolved_fragment (rr :> t)
8✔
167
        | `Alias (_, rr) -> render_resolved_fragment (rr :> t)
24✔
168
        | `Module (`Root _, s) -> ModuleName.to_string s
121✔
169
        | `Module_type (`Root _, s) -> ModuleTypeName.to_string s
32✔
170
        | `Type (`Root _, s) -> TypeName.to_string s
142✔
171
        | `Class (`Root _, s) -> TypeName.to_string s
×
172
        | `ClassType (`Root _, s) -> TypeName.to_string s
×
173
        | `Module (rr, s) ->
8✔
174
            dot (render_resolved_fragment (rr :> t)) (ModuleName.to_string s)
8✔
175
        | `Module_type (rr, s) ->
16✔
176
            dot
177
              (render_resolved_fragment (rr :> t))
16✔
178
              (ModuleTypeName.to_string s)
16✔
179
        | `Type (rr, s) ->
25✔
180
            dot (render_resolved_fragment (rr :> t)) (TypeName.to_string s)
25✔
181
        | `Class (rr, s) ->
×
182
            dot (render_resolved_fragment (rr :> t)) (TypeName.to_string s)
×
183
        | `ClassType (rr, s) ->
×
184
            dot (render_resolved_fragment (rr :> t)) (TypeName.to_string s)
×
185
        | `OpaqueModule r -> render_resolved_fragment (r :> t)
×
186

187
    let resolved_fragment_to_ir : Fragment.Resolved.leaf -> text =
188
     fun fragment ->
189
      let open Fragment in
295✔
190
      let id = Resolved.identifier (fragment :> Resolved.t) in
191
      let txt = render_resolved_fragment (fragment :> Resolved.t) in
295✔
192
      match id with
295✔
193
      | Some id ->
295✔
194
          let href = Url.from_identifier ~stop_before:false id in
195
          resolved href [ inline @@ Text txt ]
295✔
196
      | None -> unresolved [ inline @@ Text txt ]
×
197

198
    let from_fragment : Fragment.leaf -> text = function
199
      | `Resolved r
295✔
200
        when not (Fragment.Resolved.is_hidden (r :> Fragment.Resolved.t)) ->
295✔
201
          resolved_fragment_to_ir r
295✔
202
      | f ->
×
203
          let txt = render_fragment_any (f :> Fragment.t) in
204
          unresolved [ inline @@ Text txt ]
×
205
  end
206

207
  module Impl = struct
208
    let impl ~infos src =
209
      let l =
28✔
210
        infos
211
        |> List.sort (fun (_, (l1, e1)) (_, (l2, e2)) ->
212
               if l1 = l2 then compare e2 e1
133✔
213
                 (* If two intervals open at the same time, we open
214
                    first the one that closes last *)
215
               else compare l1 l2)
3,166✔
216
      in
217
      let get_src a b =
28✔
218
        let in_bound x = min (max x 0) (String.length src) in
1,696✔
219
        let a = in_bound a and b = in_bound b in
1,696✔
220
        let a, b = (min a b, max a b) in
1,696✔
221
        String.with_range src ~first:a ~len:(b - a)
222
      in
223
      let plain_code = function
224
        | "" -> []
596✔
225
        | s -> [ Types.Source_page.Plain_code s ]
1,100✔
226
      in
227
      let min (a : int) b = if a < b then a else b in
161✔
228
      let rec extract from to_ list aux =
229
        match list with
1,696✔
230
        | (k, (loc_start, loc_end)) :: q when loc_start < to_ ->
1,640✔
231
            let loc_end = min loc_end to_ in
834✔
232
            (* In case of inconsistent [a  [b    a] b]
233
               we do                   [a  [b  b]a] *)
234
            let initial = plain_code (get_src from loc_start) in
834✔
235
            let next, q = extract loc_start loc_end q [] in
834✔
236
            extract loc_end to_ q
834✔
237
              ([ Types.Source_page.Tagged_code (k, List.rev next) ]
834✔
238
              @ initial @ aux)
239
        | q -> (plain_code (get_src from to_) @ aux, q)
862✔
240
      in
241
      let doc, _ = extract 0 (String.length src) l [] in
28✔
242
      List.rev doc
28✔
243
  end
244

245
  module Source_page : sig
246
    val source :
247
      Paths.Identifier.SourcePage.t ->
248
      Syntax_highlighter.infos ->
249
      Lang.Source_info.t ->
250
      string ->
251
      Source_page.t
252
  end = struct
253
    let path id = Url.Path.from_identifier id
28✔
254

255
    let to_link { Lang.Source_info.documentation; implementation } =
256
      let documentation =
34✔
257
        (* Since documentation link are not rendered, we comment the code to
258
           extract the href, and always output [None] *)
259
        ignore documentation;
260
        None
261
        (* let open Paths.Path.Resolved in *)
262
        (* match documentation with *)
263
        (* | Some (`Resolved p) when not (is_hidden (p :> t)) -> ( *)
264
        (*     let id = identifier (p :> t) in *)
265
        (*     match Url.from_identifier ~stop_before:false id with *)
266
        (*     | Ok link -> Some link *)
267
        (*     | _ -> None) *)
268
        (* | _ -> None *)
269
      in
270
      let implementation =
271
        match implementation with
272
        | Some (Odoc_model.Lang.Source_info.Resolved id) ->
15✔
273
            Some (Url.Anchor.from_identifier (id :> Paths.Identifier.t))
15✔
274
        | _ -> None
19✔
275
      in
276
      Some (Source_page.Link { implementation; documentation })
277

278
    let info_of_info : Lang.Source_info.annotation -> Source_page.info option =
279
      function
280
      | Definition id -> (
99✔
281
          match id.iv with
282
          | `SourceLocation (_, def) -> Some (Anchor (DefName.to_string def))
95✔
283
          | `SourceLocationInternal (_, local) ->
4✔
284
              Some (Anchor (LocalName.to_string local))
4✔
285
          | _ -> None)
×
286
      | Module v -> to_link v
11✔
287
      | ModuleType v -> to_link v
1✔
288
      | Type v -> to_link v
7✔
289
      | Value v -> to_link v
15✔
290

291
    let source id syntax_info infos source_code =
292
      let url = path id in
28✔
293
      let mapper (info, loc) =
28✔
294
        match info_of_info info with Some x -> Some (x, loc) | None -> None
×
295
      in
296
      let infos = Odoc_utils.List.filter_map mapper infos in
297
      let syntax_info =
28✔
298
        List.rev_map (fun (ty, loc) -> (Source_page.Syntax ty, loc)) syntax_info
701✔
299
        |> List.rev
28✔
300
      in
301
      let contents = Impl.impl ~infos:(infos @ syntax_info) source_code in
28✔
302
      { Source_page.url; contents }
28✔
303
  end
304

305
  module Type_expression : sig
306
    val type_expr : ?needs_parentheses:bool -> Lang.TypeExpr.t -> text
307

308
    val format_type_path :
309
      delim:[ `parens | `brackets ] -> Lang.TypeExpr.t list -> text -> text
310
  end = struct
311
    let rec te_variant (t : Odoc_model.Lang.TypeExpr.Polymorphic_variant.t) =
312
      let style_arguments ~constant arguments =
75,143✔
313
        (* Multiple arguments in a polymorphic variant constructor correspond
314
           to a conjunction of types, not a product: [`Lbl int&float].
315
           If constant is [true], the conjunction starts with an empty type,
316
           for instance [`Lbl &int].
317
        *)
318
        let wrapped_type_expr =
1,873✔
319
          (* type conjunction in Reason is printed as `Lbl (t1)&(t2)` *)
320
          if Syntax.Type.Variant.parenthesize_params then fun x ->
×
321
            enclose ~l:"(" ~r:")" (type_expr x)
×
322
          else fun x -> type_expr x
1,873✔
323
        in
324
        let arguments =
325
          O.list arguments ~sep:(O.txt " & ") ~f:wrapped_type_expr
1,873✔
326
        in
327
        if constant then O.txt "& " ++ arguments else arguments
×
328
      in
329
      let rec style_elements ~add_pipe = function
330
        | [] -> O.noop
75,143✔
331
        | first :: rest ->
108,795✔
332
            let first =
333
              match first with
334
              | Odoc_model.Lang.TypeExpr.Polymorphic_variant.Type te ->
40,832✔
335
                  let res = O.box_hv @@ type_expr te in
40,832✔
336
                  if add_pipe then O.sp ++ O.span (O.txt "| " ++ res) else res
2,565✔
337
              | Constructor { constant; name; arguments; _ } ->
67,963✔
338
                  let constr =
339
                    let name = "`" ^ name in
340
                    if add_pipe then O.span (O.txt ("| " ^ name))
31,095✔
341
                    else O.txt name
36,868✔
342
                  in
343
                  let res =
344
                    O.box_hv
345
                      (match arguments with
346
                      | [] -> constr
66,090✔
347
                      | _ ->
1,873✔
348
                          let arguments = style_arguments ~constant arguments in
349
                          O.span
1,873✔
350
                            (if Syntax.Type.Variant.parenthesize_params then
351
                               constr ++ arguments
×
352
                             else constr ++ O.txt " of" ++ O.sp ++ arguments))
1,873✔
353
                  in
354
                  if add_pipe then O.sp ++ res else res
31,095✔
355
            in
356
            first ++ style_elements ~add_pipe:true rest
108,795✔
357
      in
358
      let elements = style_elements ~add_pipe:false t.elements in
359
      O.box_hv_no_indent
75,143✔
360
      @@ O.span
75,143✔
361
           (match t.kind with
362
           | Fixed -> O.txt "[ " ++ elements ++ O.txt " ]"
7,068✔
363
           | Open -> O.txt "[> " ++ elements ++ O.txt " ]"
40,908✔
364
           | Closed [] -> O.txt "[< " ++ elements ++ O.txt " ]"
27,132✔
365
           | Closed lst ->
35✔
366
               let constrs = String.concat ~sep:" " lst in
367
               O.txt "[< " ++ elements ++ O.txt (" " ^ constrs ^ " ]"))
35✔
368

369
    and te_object (t : Odoc_model.Lang.TypeExpr.Object.t) =
370
      let fields =
80✔
371
        O.list
372
          ~sep:(O.sp ++ O.txt Syntax.Obj.field_separator)
80✔
373
          t.fields
374
          ~f:(function
375
            | Odoc_model.Lang.TypeExpr.Object.Method { name; type_ } ->
120✔
376
                O.box_hv_no_indent
377
                @@ O.txt (name ^ Syntax.Type.annotation_separator)
120✔
378
                   ++ O.cut ++ type_expr type_
120✔
379
            | Inherit type_ -> O.box_hv_no_indent @@ type_expr type_)
×
380
      in
381
      let open_tag =
80✔
382
        if t.open_ then O.txt Syntax.Obj.open_tag_extendable
32✔
383
        else O.txt Syntax.Obj.open_tag_closed
48✔
384
      in
385
      let close_tag =
386
        if t.open_ then O.txt Syntax.Obj.close_tag_extendable
32✔
387
        else O.txt Syntax.Obj.close_tag_closed
48✔
388
      in
389
      O.span (open_tag ++ fields ++ close_tag)
80✔
390

391
    and format_type_path ~delim (params : Odoc_model.Lang.TypeExpr.t list)
392
        (path : text) : text =
393
      O.box_hv
179,533✔
394
      @@
395
      match params with
396
      | [] -> path
94,880✔
397
      | [ param ] ->
71,291✔
398
          let param = type_expr ~needs_parentheses:true param in
399
          let args =
71,291✔
400
            if Syntax.Type.parenthesize_constructor then
401
              O.txt "(" ++ param ++ O.txt ")"
×
402
            else param
71,291✔
403
          in
404
          Syntax.Type.handle_constructor_params path args
71,291✔
405
      | params ->
13,362✔
406
          let params = O.list params ~sep:(O.txt "," ++ O.sp) ~f:type_expr in
13,362✔
407
          let params =
13,362✔
408
            match delim with
409
            | `parens -> enclose ~l:"(" params ~r:")"
13,362✔
410
            | `brackets -> enclose ~l:"[" params ~r:"]"
×
411
          in
412
          Syntax.Type.handle_constructor_params path (O.box_hv params)
13,362✔
413

414
    and tuple ?(needs_parentheses = false) ~boxed lst =
×
415
      let opt_label = function
677✔
416
        | None -> O.noop
1,544✔
417
        | Some lbl -> tag "label" (O.txt lbl) ++ O.txt ":" ++ O.cut
×
418
      in
419
      let res =
420
        O.box_hv_no_indent
421
          (O.list lst ~sep:Syntax.Type.Tuple.element_separator
677✔
422
             ~f:(fun (lbl, typ) ->
423
               opt_label lbl ++ type_expr ~needs_parentheses:true typ))
1,544✔
424
      in
425
      let lparen = if boxed then "(" else "#(" in
×
426
      if Syntax.Type.Tuple.always_parenthesize || needs_parentheses || not boxed
×
427
      then enclose ~l:lparen res ~r:")"
131✔
428
      else res
546✔
429

430
    and type_expr ?(needs_parentheses = false) (t : Odoc_model.Lang.TypeExpr.t)
191,635✔
431
        =
432
      let enclose_parens_if_needed res =
312,108✔
433
        if needs_parentheses then enclose ~l:"(" res ~r:")" else res
831✔
434
      in
435
      match t with
436
      | Var s -> type_var (Syntax.Type.var_prefix ^ s)
9,469✔
437
      | Any -> type_var Syntax.Type.any
18✔
438
      | Alias (te, alias) ->
133✔
439
          enclose_parens_if_needed
440
            (type_expr ~needs_parentheses:true te
133✔
441
            ++ O.txt " " ++ O.keyword "as" ++ O.txt " '" ++ O.txt alias)
133✔
442
      | Arrow (None, src, dst) ->
43,802✔
443
          let res =
444
            O.span
43,802✔
445
              ((O.box_hv @@ type_expr ~needs_parentheses:true src)
43,802✔
446
              ++ O.txt " " ++ Syntax.Type.arrow)
43,802✔
447
            ++ O.sp ++ type_expr dst
43,802✔
448
            (* ++ O.end_hv *)
449
          in
450
          enclose_parens_if_needed res
43,802✔
451
      | Arrow (Some (RawOptional _ as lbl), _src, dst) ->
1✔
452
          let res =
453
            O.span
1✔
454
              (O.box_hv
1✔
455
              @@ label lbl ++ O.txt ":"
1✔
456
                 ++ tag "error" (O.txt "???")
1✔
457
                 ++ O.txt " " ++ Syntax.Type.arrow)
1✔
458
            ++ O.sp ++ type_expr dst
1✔
459
          in
460
          enclose_parens_if_needed res
1✔
461
      | Arrow (Some lbl, src, dst) ->
3,262✔
462
          let res =
463
            O.span
3,262✔
464
              ((O.box_hv
3,262✔
465
               @@ label lbl ++ O.txt ":" ++ O.cut
3,262✔
466
                  ++ (O.box_hv @@ type_expr ~needs_parentheses:true src))
3,262✔
467
              ++ O.txt " " ++ Syntax.Type.arrow)
3,262✔
468
            ++ O.sp ++ type_expr dst
3,262✔
469
          in
470
          enclose_parens_if_needed res
3,262✔
471
      | Tuple lst -> tuple ~needs_parentheses ~boxed:true lst
677✔
472
      | Unboxed_tuple lst -> tuple ~needs_parentheses ~boxed:false lst
×
473
      | Constr (path, args) ->
179,467✔
474
          let link = Link.from_path (path :> Paths.Path.t) in
475
          format_type_path ~delim:`parens args link
179,467✔
476
      | Polymorphic_variant v -> te_variant v
75,143✔
477
      | Object o -> te_object o
80✔
478
      | Class (path, args) ->
8✔
479
          format_type_path ~delim:`brackets args
480
            (Link.from_path (path :> Paths.Path.t))
8✔
481
      | Poly (polyvars, t) ->
24✔
482
          enclose_parens_if_needed
483
          @@ O.txt ("'" ^ String.concat ~sep:" '" polyvars ^ ". ")
24✔
484
             ++ type_expr t
24✔
485
      | Quote t -> O.span (O.txt "<[ " ++ O.box_hv (type_expr t) ++ O.txt " ]>")
×
486
      | Splice t -> O.span (O.txt "$" ++ type_expr ~needs_parentheses:true t)
×
487
      | Package pkg ->
24✔
488
          enclose ~l:"(" ~r:")"
489
            (O.keyword "module" ++ O.txt " "
24✔
490
            ++ Link.from_path (pkg.path :> Paths.Path.t)
24✔
491
            ++
24✔
492
            match pkg.substitutions with
493
            | [] -> O.noop
16✔
494
            | fst :: lst ->
8✔
495
                O.sp
496
                ++ O.box_hv (O.keyword "with" ++ O.txt " " ++ package_subst fst)
8✔
497
                ++ O.list lst ~f:(fun s ->
8✔
498
                       O.cut
8✔
499
                       ++ (O.box_hv
8✔
500
                          @@ O.txt " " ++ O.keyword "and" ++ O.txt " "
8✔
501
                             ++ package_subst s)))
8✔
502

503
    and package_subst
504
        ((frag_typ, te) : Paths.Fragment.Type.t * Odoc_model.Lang.TypeExpr.t) :
505
        text =
506
      let typath = Link.from_fragment (frag_typ :> Paths.Fragment.leaf) in
16✔
507
      O.keyword "type" ++ O.txt " " ++ typath ++ O.txt " =" ++ O.sp
16✔
508
      ++ type_expr te
16✔
509
  end
510

511
  open Type_expression
512

513
  (* Also handles constructor declarations for exceptions and extensible
514
     variants, and exposes a few helpers used in formatting classes and signature
515
     constraints. *)
516
  module Type_declaration : sig
517
    val type_decl :
518
      ?is_substitution:bool ->
519
      Lang.Signature.recursive * Lang.TypeDecl.t ->
520
      Item.t
521

522
    val extension : Lang.Extension.t -> Item.t
523

524
    val record : Lang.TypeDecl.Field.t list -> DocumentedSrc.one list
525

526
    val unboxed_record :
527
      Lang.TypeDecl.UnboxedField.t list -> DocumentedSrc.one list
528

529
    val exn : Lang.Exception.t -> Item.t
530

531
    val format_params :
532
      ?delim:[ `parens | `brackets ] -> Lang.TypeDecl.param list -> text
533

534
    val format_manifest :
535
      ?is_substitution:bool ->
536
      ?compact_variants:bool ->
537
      Lang.TypeDecl.Equation.t ->
538
      text * bool
539

540
    val format_constraints : (Lang.TypeExpr.t * Lang.TypeExpr.t) list -> text
541
  end = struct
542
    let record fields =
543
      let field mutable_ id typ =
86✔
544
        let url = Url.from_identifier ~stop_before:true id in
155✔
545
        let name = Paths.Identifier.name id in
155✔
546
        let attrs = [ "def"; "record"; Url.Anchor.string_of_kind url.kind ] in
155✔
547
        let cell =
548
          (* O.td ~a:[ O.a_class ["def"; kind ] ]
549
           *   [O.a ~a:[O.a_href ("#" ^ anchor); O.a_class ["anchor"]] []
550
           *   ; *)
551
          O.code
552
            ((if mutable_ then O.keyword "mutable" ++ O.txt " " else O.noop)
24✔
553
            ++ O.txt name
155✔
554
            ++ O.txt Syntax.Type.annotation_separator
155✔
555
            ++ type_expr typ
155✔
556
            ++ O.txt Syntax.Type.Record.field_separator)
155✔
557
          (* ] *)
558
        in
559
        (url, attrs, cell)
155✔
560
      in
561
      let rows =
562
        fields
563
        |> List.map (fun fld ->
564
               let open Odoc_model.Lang.TypeDecl.Field in
155✔
565
               let url, attrs, code =
566
                 field fld.mutable_ (fld.id :> Paths.Identifier.t) fld.type_
567
               in
568
               let anchor = Some url in
155✔
569
               let doc = fld.doc.elements in
570
               let rhs = Comment.to_ir doc in
571
               let doc = if not (Comment.has_doc doc) then [] else rhs in
64✔
572
               let markers = Syntax.Comment.markers in
573
               DocumentedSrc.Documented { anchor; attrs; code; doc; markers })
574
      in
575
      let content =
86✔
576
        O.documentedSrc (O.txt "{") @ rows @ O.documentedSrc (O.txt "}")
86✔
577
      in
578
      content
579

580
    let unboxed_record fields =
581
      let field mutable_ id typ =
×
582
        let url = Url.from_identifier ~stop_before:true id in
×
583
        let name = Paths.Identifier.name id in
×
584
        let attrs = [ "def"; "record"; Url.Anchor.string_of_kind url.kind ] in
×
585
        let cell =
586
          (* O.td ~a:[ O.a_class ["def"; kind ] ]
587
           *   [O.a ~a:[O.a_href ("#" ^ anchor); O.a_class ["anchor"]] []
588
           *   ; *)
589
          O.code
590
            ((if mutable_ then O.keyword "mutable" ++ O.txt " " else O.noop)
×
591
            ++ O.txt name
×
592
            ++ O.txt Syntax.Type.annotation_separator
×
593
            ++ type_expr typ
×
594
            ++ O.txt Syntax.Type.Record.field_separator)
×
595
          (* ] *)
596
        in
597
        (url, attrs, cell)
×
598
      in
599
      let rows =
600
        fields
601
        |> List.map (fun fld ->
602
               let open Odoc_model.Lang.TypeDecl.UnboxedField in
×
603
               let url, attrs, code =
604
                 field fld.mutable_ (fld.id :> Paths.Identifier.t) fld.type_
605
               in
606
               let anchor = Some url in
×
607
               let doc = fld.doc.elements in
608
               let rhs = Comment.to_ir doc in
609
               let doc = if not (Comment.has_doc doc) then [] else rhs in
×
610
               let markers = Syntax.Comment.markers in
611
               DocumentedSrc.Documented { anchor; attrs; code; doc; markers })
612
      in
613
      let content =
×
614
        O.documentedSrc (O.txt "#{") @ rows @ O.documentedSrc (O.txt "}")
×
615
      in
616
      content
617

618
    let constructor :
619
        Paths.Identifier.t ->
620
        Odoc_model.Lang.TypeDecl.Constructor.argument ->
621
        Odoc_model.Lang.TypeExpr.t option ->
622
        DocumentedSrc.t =
623
     fun id args ret_type ->
624
      let name = Paths.Identifier.name id in
656✔
625
      let kind = Url.(kind id |> Anchor.string_of_kind) in
656✔
626
      let cstr = tag kind (O.txt name) in
656✔
627
      let is_gadt, ret_type =
656✔
628
        match ret_type with
629
        | None -> (false, O.noop)
520✔
630
        | Some te ->
136✔
631
            let constant = match args with Tuple [] -> true | _ -> false in
48✔
632
            let ret_type =
633
              O.txt " "
136✔
634
              ++ (if constant then O.txt ":" else Syntax.Type.GADT.arrow)
48✔
635
              ++ O.txt " " ++ type_expr te
136✔
636
            in
637
            (true, ret_type)
136✔
638
      in
639
      match args with
640
      | Tuple [] -> O.documentedSrc (cstr ++ ret_type)
289✔
641
      | Tuple lst ->
350✔
642
          let params =
643
            O.list lst ~sep:Syntax.Type.Tuple.element_separator
644
              ~f:(type_expr ~needs_parentheses:is_gadt)
645
          in
646
          O.documentedSrc
350✔
647
            (cstr
648
            ++ (if Syntax.Type.Variant.parenthesize_params then
350✔
649
                  O.txt "(" ++ params ++ O.txt ")"
×
650
                else
651
                  (if is_gadt then O.txt Syntax.Type.annotation_separator
88✔
652
                   else O.txt " " ++ O.keyword "of" ++ O.txt " ")
262✔
653
                  ++ params)
350✔
654
            ++ ret_type)
350✔
655
      | Record fields ->
17✔
656
          if is_gadt then
657
            O.documentedSrc (cstr ++ O.txt Syntax.Type.annotation_separator)
×
658
            @ record fields @ O.documentedSrc ret_type
×
659
          else
660
            O.documentedSrc (cstr ++ O.txt " " ++ O.keyword "of" ++ O.txt " ")
17✔
661
            @ record fields
17✔
662

663
    let variant cstrs : DocumentedSrc.t =
664
      let constructor id args res =
235✔
665
        let url = Url.from_identifier ~stop_before:true id in
436✔
666
        let attrs = [ "def"; "variant"; Url.Anchor.string_of_kind url.kind ] in
436✔
667
        let content =
668
          let doc = constructor id args res in
669
          O.documentedSrc (O.txt "| ") @ doc
436✔
670
        in
671
        (url, attrs, content)
672
      in
673
      match cstrs with
674
      | [] -> O.documentedSrc (O.txt "|")
×
675
      | _ :: _ ->
235✔
676
          let rows =
677
            cstrs
678
            |> List.map (fun cstr ->
679
                   let open Odoc_model.Lang.TypeDecl.Constructor in
436✔
680
                   let url, attrs, code =
681
                     constructor
682
                       (cstr.id :> Paths.Identifier.t)
683
                       cstr.args cstr.res
684
                   in
685
                   let anchor = Some url in
436✔
686
                   let doc = cstr.doc.elements in
687
                   let rhs = Comment.to_ir doc in
688
                   let doc = if not (Comment.has_doc doc) then [] else rhs in
73✔
689
                   let markers = Syntax.Comment.markers in
690
                   DocumentedSrc.Nested { anchor; attrs; code; doc; markers })
691
          in
692
          rows
235✔
693

694
    let extension_constructor (t : Odoc_model.Lang.Extension.Constructor.t) =
695
      let id = (t.id :> Paths.Identifier.t) in
154✔
696
      let url = Url.from_identifier ~stop_before:true id in
697
      let anchor = Some url in
154✔
698
      let attrs = [ "def"; "variant"; Url.Anchor.string_of_kind url.kind ] in
154✔
699
      let code = O.documentedSrc (O.txt "| ") @ constructor id t.args t.res in
154✔
700
      let doc = Comment.to_ir t.doc.elements in
701
      let markers = Syntax.Comment.markers in
154✔
702
      DocumentedSrc.Nested { anchor; attrs; code; doc; markers }
703

704
    let extension (t : Odoc_model.Lang.Extension.t) =
705
      let prefix =
126✔
706
        O.keyword "type" ++ O.txt " "
126✔
707
        ++ Link.from_path (t.type_path :> Paths.Path.t)
126✔
708
        ++ O.txt " +=" ++ O.sp
126✔
709
        ++
710
        if t.private_ then O.keyword Syntax.Type.private_keyword ++ O.sp
8✔
711
        else O.noop
118✔
712
      in
713
      let content =
126✔
714
        O.documentedSrc prefix
126✔
715
        @ List.map extension_constructor t.constructors
126✔
716
        @ O.documentedSrc
126✔
717
            (if Syntax.Type.type_def_semicolon then O.txt ";" else O.noop)
×
718
      in
719
      let attr = [ "type"; "extension" ] in
720
      let anchor = Some (Url.Anchor.extension_decl t) in
126✔
721
      let doc = Comment.to_ir t.doc.elements in
722
      let source_anchor =
126✔
723
        (* Take the anchor from the first constructor only for consistency with
724
           regular variants. *)
725
        match t.constructors with
726
        | hd :: _ -> source_anchor hd.source_loc
126✔
727
        | [] -> None
×
728
      in
729
      Item.Declaration { attr; anchor; doc; content; source_anchor }
730

731
    let exn (t : Odoc_model.Lang.Exception.t) =
732
      let cstr = constructor (t.id :> Paths.Identifier.t) t.args t.res in
66✔
733
      let content =
66✔
734
        O.documentedSrc (O.keyword "exception" ++ O.txt " ")
66✔
735
        @ cstr
736
        @ O.documentedSrc
66✔
737
            (if Syntax.Type.Exception.semicolon then O.txt ";" else O.noop)
×
738
      in
739
      let attr = [ "exception" ] in
740
      let anchor = path_to_id t.id in
741
      let doc = Comment.to_ir t.doc.elements in
66✔
742
      let source_anchor = source_anchor t.source_loc in
66✔
743
      Item.Declaration { attr; anchor; doc; content; source_anchor }
66✔
744

745
    let polymorphic_variant ~type_ident
746
        (t : Odoc_model.Lang.TypeExpr.Polymorphic_variant.t) =
747
      let row item =
74✔
748
        let kind_approx, cstr, doc =
156✔
749
          match item with
750
          | Odoc_model.Lang.TypeExpr.Polymorphic_variant.Type te ->
24✔
751
              ("unknown", O.documentedSrc (type_expr te), None)
24✔
752
          | Constructor { constant; name; arguments; doc; _ } -> (
132✔
753
              let cstr = "`" ^ name in
754
              ( "constructor",
755
                (match arguments with
756
                | [] -> O.documentedSrc (O.txt cstr)
58✔
757
                | _ ->
74✔
758
                    (* Multiple arguments in a polymorphic variant constructor correspond
759
                       to a conjunction of types, not a product: [`Lbl int&float].
760
                       If constant is [true], the conjunction starts with an empty type,
761
                       for instance [`Lbl &int].
762
                    *)
763
                    let wrapped_type_expr =
764
                      (* type conjunction in Reason is printed as `Lbl (t1)&(t2)` *)
765
                      if Syntax.Type.Variant.parenthesize_params then fun x ->
×
766
                        O.txt "(" ++ type_expr x ++ O.txt ")"
×
767
                      else fun x -> type_expr x
74✔
768
                    in
769
                    let params =
770
                      O.box_hv
771
                      @@ O.list arguments
74✔
772
                           ~sep:(O.txt " &" ++ O.sp)
74✔
773
                           ~f:wrapped_type_expr
774
                    in
775
                    let params =
74✔
776
                      if constant then O.txt "& " ++ params else params
×
777
                    in
778
                    O.documentedSrc
74✔
779
                      (O.txt cstr
74✔
780
                      ++
74✔
781
                      if Syntax.Type.Variant.parenthesize_params then params
×
782
                      else O.txt " " ++ O.keyword "of" ++ O.sp ++ params)),
74✔
783
                match doc with
784
                | { elements = []; _ } -> None
132✔
785
                | _ -> Some (Comment.to_ir doc.elements) ))
×
786
        in
787
        let markers = Syntax.Comment.markers in
788
        try
789
          let url = Url.Anchor.polymorphic_variant ~type_ident item in
790
          let attrs =
156✔
791
            [ "def"; "variant"; Url.Anchor.string_of_kind url.kind ]
156✔
792
          in
793
          let anchor = Some url in
794
          let code = O.documentedSrc (O.txt "| ") @ cstr in
156✔
795
          let doc = match doc with None -> [] | Some doc -> doc in
×
796
          DocumentedSrc.Nested { attrs; anchor; code; doc; markers }
797
        with Failure s ->
×
798
          Printf.eprintf "ERROR: %s\n%!" s;
799
          let code = O.documentedSrc (O.txt "| ") @ cstr in
×
800
          let attrs = [ "def"; kind_approx ] in
801
          let doc = [] in
802
          let anchor = None in
803
          DocumentedSrc.Nested { attrs; anchor; code; doc; markers }
804
      in
805
      let variants = List.map row t.elements in
806
      let intro, ending =
74✔
807
        match t.kind with
808
        | Fixed -> (O.documentedSrc (O.txt "[ "), O.documentedSrc (O.txt " ]"))
66✔
809
        | Open -> (O.documentedSrc (O.txt "[> "), O.documentedSrc (O.txt " ]"))
8✔
810
        | Closed [] ->
×
811
            (O.documentedSrc (O.txt "[< "), O.documentedSrc (O.txt " ]"))
×
812
        | Closed lst ->
×
813
            let constrs = String.concat ~sep:" " lst in
814
            ( O.documentedSrc (O.txt "[< "),
×
815
              O.documentedSrc (O.txt (" " ^ constrs ^ " ]")) )
×
816
      in
817
      intro @ variants @ ending
818

819
    let format_params :
820
        'row.
821
        ?delim:[ `parens | `brackets ] ->
822
        Odoc_model.Lang.TypeDecl.param list ->
823
        text =
824
     fun ?(delim = `parens) params ->
472✔
825
      let format_param { Odoc_model.Lang.TypeDecl.desc; variance; injectivity }
563✔
826
          =
827
        let desc =
624✔
828
          match desc with
829
          | Odoc_model.Lang.TypeDecl.Any -> [ "_" ]
37✔
830
          | Var s -> [ "'"; s ]
587✔
831
        in
832
        let var_desc =
833
          match variance with
834
          | None -> desc
608✔
835
          | Some Odoc_model.Lang.TypeDecl.Pos -> "+" :: desc
8✔
836
          | Some Odoc_model.Lang.TypeDecl.Neg -> "-" :: desc
8✔
837
          | Some Odoc_model.Lang.TypeDecl.Bivariant -> "+" :: "-" :: desc
×
838
        in
839
        let final = if injectivity then "!" :: var_desc else var_desc in
×
840
        String.concat ~sep:"" final
841
      in
842
      O.txt
843
        (match params with
844
        | [] -> ""
59✔
845
        | [ x ] -> format_param x |> Syntax.Type.handle_format_params
388✔
846
        | lst -> (
116✔
847
            let params = String.concat ~sep:", " (List.map format_param lst) in
116✔
848
            (match delim with `parens -> "(" | `brackets -> "[")
×
849
            ^ params
850
            ^ match delim with `parens -> ")" | `brackets -> "]"))
×
851

852
    let format_constraints constraints =
853
      O.list constraints ~f:(fun (t1, t2) ->
3,103✔
854
          O.sp
104✔
855
          ++ (O.box_hv
104✔
856
             @@ O.keyword "constraint" ++ O.sp
104✔
857
                ++ O.box_hv_no_indent (type_expr t1)
104✔
858
                ++ O.txt " =" ++ O.sp
104✔
859
                ++ O.box_hv_no_indent (type_expr t2)))
104✔
860

861
    let format_manifest :
862
        'inner_row 'outer_row.
863
        ?is_substitution:bool ->
864
        ?compact_variants:bool ->
865
        Odoc_model.Lang.TypeDecl.Equation.t ->
866
        text * bool =
867
     fun ?(is_substitution = false) ?(compact_variants = true) equation ->
104✔
868
      let _ = compact_variants in
3,021✔
869
      (* TODO *)
870
      let private_ = equation.private_ in
871
      match equation.manifest with
872
      | None -> (O.noop, private_)
1,753✔
873
      | Some t ->
1,268✔
874
          let manifest =
875
            O.txt (if is_substitution then " :=" else " =")
23✔
876
            ++ O.sp
1,268✔
877
            ++ (if private_ then
1,268✔
878
                  O.keyword Syntax.Type.private_keyword ++ O.txt " "
8✔
879
                else O.noop)
1,260✔
880
            ++ type_expr t
1,268✔
881
          in
882
          (manifest, false)
1,268✔
883

884
    let type_decl ?(is_substitution = false)
2,968✔
885
        ((recursive, t) : Lang.Signature.recursive * Lang.TypeDecl.t) =
886
      let keyword' =
2,991✔
887
        match recursive with
888
        | Ordinary | Rec -> O.keyword "type"
×
889
        | And -> O.keyword "and"
18✔
890
        | Nonrec -> O.keyword "type" ++ O.txt " " ++ O.keyword "nonrec"
1✔
891
      in
892
      let tyname = Paths.Identifier.name t.id in
893
      let tconstr =
2,991✔
894
        match t.equation.params with
895
        | [] -> O.txt tyname
2,545✔
896
        | l ->
446✔
897
            let params = format_params l in
898
            Syntax.Type.handle_constructor_params (O.txt tyname) params
446✔
899
      in
900
      let intro = keyword' ++ O.txt " " ++ tconstr in
2,991✔
901
      let constraints = format_constraints t.equation.constraints in
2,991✔
902
      let manifest, need_private, long_prefix =
2,991✔
903
        match t.equation.manifest with
904
        | Some (Odoc_model.Lang.TypeExpr.Polymorphic_variant variant) ->
74✔
905
            let code =
906
              polymorphic_variant
907
                ~type_ident:(t.id :> Paths.Identifier.t)
908
                variant
909
            in
910
            let manifest =
74✔
911
              O.documentedSrc
74✔
912
                (O.ignore intro
74✔
913
                ++ O.txt (if is_substitution then " :=" else " =")
×
914
                ++ O.sp
74✔
915
                ++
74✔
916
                if t.equation.private_ then
917
                  O.keyword Syntax.Type.private_keyword ++ O.txt " "
8✔
918
                else O.noop)
66✔
919
              @ code
920
            in
921
            (manifest, false, O.noop)
922
        | _ ->
2,917✔
923
            let manifest, need_private =
924
              format_manifest ~is_substitution t.equation
925
            in
926
            let text = O.ignore intro ++ manifest in
2,917✔
927
            (O.documentedSrc @@ text, need_private, text)
2,917✔
928
      in
929
      let representation =
930
        match t.representation with
931
        | None -> []
2,629✔
932
        | Some repr ->
362✔
933
            let content =
934
              match repr with
935
              | Extensible -> O.documentedSrc (O.txt "..")
61✔
936
              | Variant cstrs -> variant cstrs
235✔
937
              | Record fields -> record fields
66✔
938
              | Record_unboxed_product fields -> unboxed_record fields
×
939
            in
940
            if List.length content > 0 then
362✔
941
              O.documentedSrc
362✔
942
                (O.ignore long_prefix ++ O.txt " =" ++ O.sp
362✔
943
                ++
362✔
944
                if need_private then
945
                  O.keyword Syntax.Type.private_keyword ++ O.txt " "
8✔
946
                else O.noop)
354✔
947
              @ content
948
            else []
×
949
      in
950
      let content =
951
        O.documentedSrc intro @ manifest @ representation
2,991✔
952
        @ O.documentedSrc constraints
2,991✔
953
        @ O.documentedSrc
2,991✔
954
            (if Syntax.Type.type_def_semicolon then O.txt ";" else O.noop)
×
955
      in
956
      let attr = "type" :: (if is_substitution then [ "subst" ] else []) in
23✔
957
      let anchor = path_to_id t.id in
958
      let doc = Comment.to_ir t.doc.elements in
2,991✔
959
      let source_anchor = source_anchor t.source_loc in
2,991✔
960
      Item.Declaration { attr; anchor; doc; content; source_anchor }
2,991✔
961
  end
962

963
  open Type_declaration
964

965
  module Value : sig
966
    val value : Lang.Value.t -> Item.t
967
  end = struct
968
    let value (t : Odoc_model.Lang.Value.t) =
969
      let extra_attr, semicolon =
912✔
970
        match t.value with
971
        | Abstract -> ([], Syntax.Value.semicolon)
888✔
972
        | External _ -> ([ "external" ], Syntax.Type.External.semicolon)
24✔
973
      in
974
      let name = Paths.Identifier.name t.id in
975
      let content =
912✔
976
        O.documentedSrc
977
          (O.box_hv
912✔
978
          @@ O.keyword Syntax.Value.variable_keyword
912✔
979
             ++ O.txt " " ++ O.txt name
912✔
980
             ++ O.txt Syntax.Type.annotation_separator
912✔
981
             ++ O.cut ++ type_expr t.type_
912✔
982
             ++ if semicolon then O.txt ";" else O.noop)
×
983
      in
984
      let attr = [ "value" ] @ extra_attr in
912✔
985
      let anchor = path_to_id t.id in
986
      let doc = Comment.to_ir t.doc.elements in
912✔
987
      let source_anchor = source_anchor t.source_loc in
912✔
988
      Item.Declaration { attr; anchor; doc; content; source_anchor }
912✔
989
  end
990

991
  open Value
992

993
  (* This chunk of code is responsible for sectioning list of items
994
     according to headings by extracting headings as Items.
995

996
     TODO: This sectioning would be better done as a pass on the model directly.
997
  *)
998
  module Sectioning : sig
999
    open Odoc_model
1000

1001
    val comment_items : Comment.elements -> Item.t list
1002

1003
    val docs : Comment.elements -> Item.t list * Item.t list
1004
  end = struct
1005
    let take_until_heading_or_end (docs : Odoc_model.Comment.elements) =
1006
      let content, _, rest =
693✔
1007
        Doctree.Take.until docs ~classify:(fun b ->
1008
            match b.Location.value with
1,481✔
1009
            | `Heading _ -> Stop_and_keep
202✔
1010
            | #Odoc_model.Comment.attached_block_element as doc ->
1,279✔
1011
                let content = Comment.attached_block_element doc in
1012
                Accum content)
1,279✔
1013
      in
1014
      (content, rest)
693✔
1015

1016
    let comment_items (input0 : Odoc_model.Comment.elements) =
1017
      let rec loop input_comment acc =
963✔
1018
        match input_comment with
2,493✔
1019
        | [] -> List.rev acc
963✔
1020
        | element :: input_comment -> (
1,530✔
1021
            match element.Location.value with
1022
            | `Heading h ->
837✔
1023
                let item = Comment.heading h in
1024
                loop input_comment (item :: acc)
837✔
1025
            | _ ->
693✔
1026
                let content, input_comment =
1027
                  take_until_heading_or_end (element :: input_comment)
1028
                in
1029
                let item = Item.Text content in
693✔
1030
                loop input_comment (item :: acc))
1031
      in
1032
      loop input0 []
1033

1034
    (* For doc pages, we want the header to contain everything until
1035
       the first heading, then everything before the next heading which
1036
       is either lower, or a section.
1037
    *)
1038
    let docs input_comment =
1039
      let items = comment_items input_comment in
69✔
1040
      let until_first_heading, o, items =
69✔
1041
        Doctree.Take.until items ~classify:(function
1042
          | Item.Heading h as i -> Stop_and_accum ([ i ], Some h.level)
69✔
1043
          | i -> Accum [ i ])
×
1044
      in
1045
      match o with
69✔
1046
      | None -> (until_first_heading, items)
×
1047
      | Some level ->
69✔
1048
          let max_level = if level = 1 then 2 else level in
×
1049
          let before_second_heading, _, items =
1050
            Doctree.Take.until items ~classify:(function
1051
              | Item.Heading h when h.level >= max_level -> Stop_and_keep
13✔
1052
              | i -> Accum [ i ])
38✔
1053
          in
1054
          let header = until_first_heading @ before_second_heading in
69✔
1055
          (header, items)
1056
  end
1057

1058
  module Class : sig
1059
    val class_ : Lang.Class.t -> Item.t
1060

1061
    val class_type : Lang.ClassType.t -> Item.t
1062
  end = struct
1063
    let class_type_expr (cte : Odoc_model.Lang.ClassType.expr) =
1064
      match cte with
249✔
1065
      | Constr (path, args) ->
58✔
1066
          let link = Link.from_path (path :> Paths.Path.t) in
1067
          format_type_path ~delim:`brackets args link
58✔
1068
      | Signature _ ->
191✔
1069
          Syntax.Class.open_tag ++ O.txt " ... " ++ Syntax.Class.close_tag
191✔
1070

1071
    let method_ (t : Odoc_model.Lang.Method.t) =
1072
      let name = Paths.Identifier.name t.id in
90✔
1073
      let virtual_ =
90✔
1074
        if t.virtual_ then O.keyword "virtual" ++ O.txt " " else O.noop
8✔
1075
      in
1076
      let private_ =
1077
        if t.private_ then O.keyword "private" ++ O.txt " " else O.noop
8✔
1078
      in
1079
      let content =
1080
        O.documentedSrc
1081
          (O.keyword "method" ++ O.txt " " ++ private_ ++ virtual_ ++ O.txt name
90✔
1082
          ++ O.txt Syntax.Type.annotation_separator
90✔
1083
          ++ type_expr t.type_)
90✔
1084
      in
1085
      let attr = [ "method" ] in
90✔
1086
      let anchor = path_to_id t.id in
1087
      let doc = Comment.to_ir t.doc.elements in
90✔
1088
      Item.Declaration { attr; anchor; doc; content; source_anchor = None }
90✔
1089

1090
    let instance_variable (t : Odoc_model.Lang.InstanceVariable.t) =
1091
      let name = Paths.Identifier.name t.id in
17✔
1092
      let virtual_ =
17✔
1093
        if t.virtual_ then O.keyword "virtual" ++ O.txt " " else O.noop
8✔
1094
      in
1095
      let mutable_ =
1096
        if t.mutable_ then O.keyword "mutable" ++ O.txt " " else O.noop
8✔
1097
      in
1098
      let content =
1099
        O.documentedSrc
1100
          (O.keyword "val" ++ O.txt " " ++ mutable_ ++ virtual_ ++ O.txt name
17✔
1101
          ++ O.txt Syntax.Type.annotation_separator
17✔
1102
          ++ type_expr t.type_)
17✔
1103
      in
1104
      let attr = [ "value"; "instance-variable" ] in
17✔
1105
      let anchor = path_to_id t.id in
1106
      let doc = Comment.to_ir t.doc.elements in
17✔
1107
      Item.Declaration { attr; anchor; doc; content; source_anchor = None }
17✔
1108

1109
    let inherit_ (ih : Lang.ClassSignature.Inherit.t) =
1110
      let cte =
16✔
1111
        match ih.expr with
1112
        | Signature _ -> assert false (* Bold. *)
1113
        | cty -> cty
16✔
1114
      in
1115
      let content =
1116
        O.documentedSrc (O.keyword "inherit" ++ O.txt " " ++ class_type_expr cte)
16✔
1117
      in
1118
      let attr = [ "inherit" ] in
16✔
1119
      let anchor = None in
1120
      let doc = Comment.to_ir ih.doc.elements in
1121
      Item.Declaration { attr; anchor; doc; content; source_anchor = None }
16✔
1122

1123
    let constraint_ (cst : Lang.ClassSignature.Constraint.t) =
1124
      let content =
8✔
1125
        O.documentedSrc (format_constraints [ (cst.left, cst.right) ])
8✔
1126
      in
1127
      let attr = [] in
8✔
1128
      let anchor = None in
1129
      let doc = Comment.to_ir cst.doc.elements in
1130
      Item.Declaration { attr; anchor; doc; content; source_anchor = None }
8✔
1131

1132
    let class_signature (c : Lang.ClassSignature.t) =
1133
      let rec loop l acc_items =
233✔
1134
        match l with
388✔
1135
        | [] -> List.rev acc_items
233✔
1136
        | item :: rest -> (
155✔
1137
            let continue item = loop rest (item :: acc_items) in
131✔
1138
            match (item : Lang.ClassSignature.item) with
1139
            | Inherit cty -> continue @@ inherit_ cty
16✔
1140
            | Method m -> continue @@ method_ m
90✔
1141
            | InstanceVariable v -> continue @@ instance_variable v
17✔
1142
            | Constraint cst -> continue @@ constraint_ cst
8✔
1143
            | Comment `Stop ->
8✔
1144
                let rest =
1145
                  List.skip_until rest ~p:(function
1146
                    | Lang.ClassSignature.Comment `Stop -> true
8✔
1147
                    | _ -> false)
8✔
1148
                in
1149
                loop rest acc_items
8✔
1150
            | Comment (`Docs c) ->
16✔
1151
                let items = Sectioning.comment_items c.elements in
1152
                loop rest (List.rev_append items acc_items))
16✔
1153
      in
1154
      (* FIXME: use [t.self] *)
1155
      (c.doc.elements, loop c.items [])
233✔
1156

1157
    let rec class_decl (cd : Odoc_model.Lang.Class.decl) =
1158
      match cd with
182✔
1159
      | ClassType expr -> class_type_expr expr
166✔
1160
      (* TODO: factorize the following with [type_expr] *)
1161
      | Arrow (None, src, dst) ->
16✔
1162
          O.span
16✔
1163
            (type_expr ~needs_parentheses:true src
16✔
1164
            ++ O.txt " " ++ Syntax.Type.arrow)
16✔
1165
          ++ O.txt " " ++ class_decl dst
16✔
1166
      | Arrow (Some (RawOptional _ as lbl), _src, dst) ->
×
1167
          O.span
×
1168
            (O.box_hv
×
1169
            @@ label lbl ++ O.txt ":"
×
1170
               ++ tag "error" (O.txt "???")
×
1171
               ++ O.txt " " ++ Syntax.Type.arrow)
×
1172
          ++ O.sp ++ class_decl dst
×
1173
      | Arrow (Some lbl, src, dst) ->
×
1174
          O.span
×
1175
            (label lbl ++ O.txt ":"
×
1176
            ++ type_expr ~needs_parentheses:true src
×
1177
            ++ O.txt " " ++ Syntax.Type.arrow)
×
1178
          ++ O.txt " " ++ class_decl dst
×
1179

1180
    let class_ (t : Odoc_model.Lang.Class.t) =
1181
      let name = Paths.Identifier.name t.id in
166✔
1182
      let params =
166✔
1183
        match t.params with
1184
        | [] -> O.noop
142✔
1185
        | _ :: _ as params -> format_params ~delim:`brackets params ++ O.txt " "
24✔
1186
      in
1187
      let virtual_ =
1188
        if t.virtual_ then O.keyword "virtual" ++ O.txt " " else O.noop
24✔
1189
      in
1190

1191
      let source_anchor = source_anchor t.source_loc in
1192
      let cname, expansion, expansion_doc =
166✔
1193
        match t.expansion with
1194
        | None -> (O.documentedSrc @@ O.txt name, None, None)
×
1195
        | Some csig ->
166✔
1196
            let expansion_doc, items = class_signature csig in
1197
            let url = Url.Path.from_identifier t.id in
166✔
1198
            let page =
166✔
1199
              make_expansion_page ~source_anchor url
1200
                [ t.doc.elements; expansion_doc ]
1201
                items
1202
            in
1203
            ( O.documentedSrc @@ path url [ inline @@ Text name ],
166✔
1204
              Some page,
1205
              Some expansion_doc )
1206
      in
1207
      let summary =
1208
        O.txt Syntax.Type.annotation_separator ++ class_decl t.type_
166✔
1209
      in
1210
      let cd =
166✔
1211
        attach_expansion
1212
          (Syntax.Type.annotation_separator, "object", "end")
1213
          expansion summary
1214
      in
1215
      let content =
166✔
1216
        O.documentedSrc (O.keyword "class" ++ O.txt " " ++ virtual_ ++ params)
166✔
1217
        @ cname @ cd
1218
      in
1219
      let attr = [ "class" ] in
1220
      let anchor = path_to_id t.id in
1221
      let doc = Comment.synopsis ~decl_doc:t.doc.elements ~expansion_doc in
166✔
1222
      Item.Declaration { attr; anchor; doc; content; source_anchor }
1223

1224
    let class_type (t : Odoc_model.Lang.ClassType.t) =
1225
      let name = Paths.Identifier.name t.id in
67✔
1226
      let params = format_params ~delim:`brackets t.params in
67✔
1227
      let virtual_ =
67✔
1228
        if t.virtual_ then O.keyword "virtual" ++ O.txt " " else O.noop
8✔
1229
      in
1230
      let source_anchor = source_anchor t.source_loc in
1231
      let cname, expansion, expansion_doc =
67✔
1232
        match t.expansion with
1233
        | None -> (O.documentedSrc @@ O.txt name, None, None)
×
1234
        | Some csig ->
67✔
1235
            let url = Url.Path.from_identifier t.id in
1236
            let expansion_doc, items = class_signature csig in
67✔
1237
            let page =
67✔
1238
              make_expansion_page ~source_anchor url
1239
                [ t.doc.elements; expansion_doc ]
1240
                items
1241
            in
1242
            ( O.documentedSrc @@ path url [ inline @@ Text name ],
67✔
1243
              Some page,
1244
              Some expansion_doc )
1245
      in
1246
      let summary = O.txt " = " ++ class_type_expr t.expr in
67✔
1247
      let expr = attach_expansion (" = ", "object", "end") expansion summary in
67✔
1248
      let content =
67✔
1249
        O.documentedSrc
67✔
1250
          (O.keyword "class" ++ O.txt " " ++ O.keyword "type" ++ O.txt " "
67✔
1251
         ++ virtual_ ++ params ++ O.txt " ")
67✔
1252
        @ cname @ expr
1253
      in
1254
      let attr = [ "class-type" ] in
1255
      let anchor = path_to_id t.id in
1256
      let doc = Comment.synopsis ~decl_doc:t.doc.elements ~expansion_doc in
67✔
1257
      Item.Declaration { attr; anchor; doc; content; source_anchor }
1258
  end
1259

1260
  open Class
1261

1262
  module Module : sig
1263
    val signature : Lang.Signature.t -> Comment.Comment.elements * Item.t list
1264
    (** Returns [header_doc, content]. *)
1265
  end = struct
1266
    let internal_module m =
1267
      let open Lang.Module in
1,924✔
1268
      match m.id.iv with
1269
      | `Module (_, name) when ModuleName.is_hidden name -> true
81✔
1270
      | _ -> false
1,843✔
1271

1272
    let internal_type t =
1273
      let open Lang.TypeDecl in
2,969✔
1274
      match t.id.iv with
1275
      | `Type (_, name) when TypeName.is_hidden name -> true
1✔
1276
      | _ -> false
2,968✔
1277

1278
    let internal_value v =
1279
      let open Lang.Value in
1,018✔
1280
      match v.id.iv with
1281
      | `Value (_, name) when ValueName.is_hidden name -> true
106✔
1282
      | _ -> false
912✔
1283

1284
    let internal_module_type t =
1285
      let open Lang.ModuleType in
1,361✔
1286
      match t.id.iv with
1287
      | `ModuleType (_, name) when ModuleTypeName.is_hidden name -> true
×
1288
      | _ -> false
1,361✔
1289

1290
    let internal_module_substitution t =
1291
      let open Lang.ModuleSubstitution in
8✔
1292
      match t.id.iv with
1293
      | `Module (_, name) when ModuleName.is_hidden name -> true
×
1294
      | _ -> false
8✔
1295

1296
    let internal_module_type_substitution t =
1297
      let open Lang.ModuleTypeSubstitution in
8✔
1298
      match t.id.iv with
1299
      | `ModuleType (_, name) when ModuleTypeName.is_hidden name -> true
×
1300
      | _ -> false
8✔
1301

1302
    let rec signature (s : Lang.Signature.t) =
1303
      let rec loop l acc_items =
3,582✔
1304
        match l with
12,543✔
1305
        | [] -> List.rev acc_items
3,582✔
1306
        | item :: rest -> (
8,961✔
1307
            let continue (item : Item.t) = loop rest (item :: acc_items) in
7,838✔
1308
            match (item : Lang.Signature.item) with
1309
            | Module (_, m) when internal_module m -> loop rest acc_items
81✔
1310
            | Type (_, t) when internal_type t -> loop rest acc_items
1✔
1311
            | Value v when internal_value v -> loop rest acc_items
106✔
1312
            | ModuleType m when internal_module_type m -> loop rest acc_items
×
1313
            | ModuleSubstitution m when internal_module_substitution m ->
8✔
1314
                loop rest acc_items
×
1315
            | ModuleTypeSubstitution m when internal_module_type_substitution m
8✔
1316
              ->
1317
                loop rest acc_items
×
1318
            | ModuleTypeSubstitution m -> continue @@ module_type_substitution m
8✔
1319
            | Module (_, m) -> continue @@ module_ m
1,843✔
1320
            | ModuleType m -> continue @@ module_type m
1,361✔
1321
            | Class (_, c) -> continue @@ class_ c
166✔
1322
            | ClassType (_, c) -> continue @@ class_type c
67✔
1323
            | Include m -> continue @@ include_ m
290✔
1324
            | ModuleSubstitution m -> continue @@ module_substitution m
8✔
1325
            | TypeSubstitution t ->
23✔
1326
                continue @@ type_decl ~is_substitution:true (Ordinary, t)
23✔
1327
            | Type (r, t) -> continue @@ type_decl (r, t)
2,968✔
1328
            | TypExt e -> continue @@ extension e
126✔
1329
            | Exception e -> continue @@ exn e
66✔
1330
            | Value v -> continue @@ value v
912✔
1331
            | Open o ->
82✔
1332
                let items = Sectioning.comment_items o.doc.elements in
1333
                loop rest (List.rev_append items acc_items)
82✔
1334
            | Comment `Stop ->
57✔
1335
                let rest =
1336
                  List.skip_until rest ~p:(function
1337
                    | Lang.Signature.Comment `Stop -> true
49✔
1338
                    | _ -> false)
65✔
1339
                in
1340
                loop rest acc_items
57✔
1341
            | Comment (`Docs c) ->
796✔
1342
                let items = Sectioning.comment_items c.elements in
1343
                loop rest (List.rev_append items acc_items))
796✔
1344
      in
1345
      ((Lang.extract_signature_doc s).elements, loop s.items [])
3,582✔
1346

1347
    and functor_parameter :
1348
        Odoc_model.Lang.FunctorParameter.parameter -> DocumentedSrc.t =
1349
     fun arg ->
1350
      let open Odoc_model.Lang.FunctorParameter in
211✔
1351
      let name = Paths.Identifier.name arg.id in
1352
      let render_ty = arg.expr in
211✔
1353
      let modtyp =
1354
        mty_in_decl (arg.id :> Paths.Identifier.Signature.t) render_ty
1355
      in
1356
      let modname, mod_decl =
211✔
1357
        match expansion_of_module_type_expr arg.expr with
1358
        | None ->
×
1359
            let modname = O.txt (Paths.Identifier.name arg.id) in
×
1360
            (modname, O.documentedSrc modtyp)
×
1361
        | Some (expansion_doc, items) ->
211✔
1362
            let url = Url.Path.from_identifier arg.id in
1363
            let modname = path url [ inline @@ Text name ] in
211✔
1364
            let type_with_expansion =
211✔
1365
              let content =
1366
                make_expansion_page ~source_anchor:None url [ expansion_doc ]
1367
                  items
1368
              in
1369
              let summary = O.render modtyp in
211✔
1370
              let status = `Default in
211✔
1371
              let expansion =
1372
                O.documentedSrc
211✔
1373
                  (O.txt Syntax.Type.annotation_separator ++ O.keyword "sig")
211✔
1374
                @ DocumentedSrc.[ Subpage { content; status } ]
1375
                @ O.documentedSrc (O.keyword "end")
211✔
1376
              in
1377
              DocumentedSrc.
1378
                [
1379
                  Alternative
1380
                    (Expansion { status = `Default; summary; url; expansion });
1381
                ]
1382
            in
1383
            (modname, type_with_expansion)
1384
      in
1385
      O.documentedSrc (O.keyword "module" ++ O.txt " ")
211✔
1386
      @ O.documentedSrc modname @ mod_decl
211✔
1387

1388
    and module_substitution (t : Odoc_model.Lang.ModuleSubstitution.t) =
1389
      let name = Paths.Identifier.name t.id in
8✔
1390
      let path = Link.from_path (t.manifest :> Paths.Path.t) in
8✔
1391
      let content =
8✔
1392
        O.documentedSrc
1393
          (O.keyword "module" ++ O.txt " " ++ O.txt name ++ O.txt " :=" ++ O.sp
8✔
1394
         ++ path)
8✔
1395
      in
1396
      let attr = [ "module-substitution" ] in
8✔
1397
      let anchor = path_to_id t.id in
1398
      let doc = Comment.to_ir t.doc.elements in
8✔
1399
      Item.Declaration { attr; anchor; doc; content; source_anchor = None }
8✔
1400

1401
    and module_type_substitution (t : Odoc_model.Lang.ModuleTypeSubstitution.t)
1402
        =
1403
      let prefix =
8✔
1404
        O.keyword "module" ++ O.txt " " ++ O.keyword "type" ++ O.txt " "
8✔
1405
      in
1406
      let source_anchor = None in
8✔
1407
      let modname = Paths.Identifier.name t.id in
1408
      let modname, expansion_doc, mty =
8✔
1409
        module_type_manifest ~subst:true ~source_anchor modname t.id
1410
          t.doc.elements (Some t.manifest) prefix
1411
      in
1412
      let content =
8✔
1413
        O.documentedSrc (prefix ++ modname)
8✔
1414
        @ mty
1415
        @ O.documentedSrc
8✔
1416
            (if Syntax.Mod.close_tag_semicolon then O.txt ";" else O.noop)
×
1417
      in
1418
      let attr = [ "module-type" ] in
1419
      let anchor = path_to_id t.id in
1420
      let doc = Comment.synopsis ~decl_doc:t.doc.elements ~expansion_doc in
8✔
1421
      Item.Declaration { attr; anchor; doc; content; source_anchor }
1422

1423
    and simple_expansion :
1424
        Odoc_model.Lang.ModuleType.simple_expansion ->
1425
        Comment.Comment.elements * Item.t list =
1426
     fun t ->
1427
      let rec extract_functor_params
2,933✔
1428
          (f : Odoc_model.Lang.ModuleType.simple_expansion) =
1429
        match f with
3,152✔
1430
        | Signature sg -> (None, sg)
2,933✔
1431
        | Functor (p, expansion) ->
219✔
1432
            let add_to params =
1433
              match p with Unit -> params | Named p -> p :: params
8✔
1434
            in
1435
            let params, sg = extract_functor_params expansion in
1436
            let params = match params with None -> [] | Some p -> p in
36✔
1437
            (Some (add_to params), sg)
219✔
1438
      in
1439
      match extract_functor_params t with
1440
      | None, sg -> signature sg
2,750✔
1441
      | Some params, sg ->
183✔
1442
          let sg_doc, content = signature sg in
1443
          let params =
183✔
1444
            let decl_of_arg arg =
1445
              let content = functor_parameter arg in
211✔
1446
              let attr = [ "parameter" ] in
211✔
1447
              let anchor =
1448
                Some (Url.Anchor.from_identifier (arg.id :> Paths.Identifier.t))
211✔
1449
              in
1450
              let doc = [] in
1451
              [
1452
                Item.Declaration
1453
                  { content; anchor; attr; doc; source_anchor = None };
1454
              ]
1455
            in
1456
            List.concat_map decl_of_arg params
183✔
1457
          in
1458
          let prelude = mk_heading ~label:"parameters" "Parameters" :: params
183✔
1459
          and content = mk_heading ~label:"signature" "Signature" :: content in
183✔
1460
          (sg_doc, prelude @ content)
1461

1462
    and expansion_of_module_type_expr :
1463
        Odoc_model.Lang.ModuleType.expr ->
1464
        (Comment.Comment.elements * Item.t list) option =
1465
     fun t ->
1466
      let rec simple_expansion_of (t : Odoc_model.Lang.ModuleType.expr) =
3,218✔
1467
        match t with
3,437✔
1468
        | Path { p_expansion = None; _ }
379✔
1469
        | TypeOf { t_expansion = None; _ }
8✔
1470
        | With { w_expansion = None; _ }
×
1471
        | Strengthen { s_expansion = None; _ } ->
×
1472
            None
1473
        | Path { p_expansion = Some e; _ }
426✔
1474
        | TypeOf { t_expansion = Some e; _ }
56✔
1475
        | With { w_expansion = Some e; _ }
218✔
1476
        | Strengthen { s_expansion = Some e; _ } ->
×
1477
            Some e
1478
        | Signature sg -> Some (Signature sg)
2,131✔
1479
        | Functor (f_parameter, e) -> (
219✔
1480
            match simple_expansion_of e with
1481
            | Some e -> Some (Functor (f_parameter, e))
211✔
1482
            | None -> None)
8✔
1483
      in
1484
      match simple_expansion_of t with
1485
      | None -> None
387✔
1486
      | Some e -> Some (simple_expansion e)
2,831✔
1487

1488
    and module_ : Odoc_model.Lang.Module.t -> Item.t =
1489
     fun t ->
1490
      let modname = Paths.Identifier.name t.id in
1,843✔
1491
      let expansion =
1,843✔
1492
        match t.type_ with
1493
        | Alias (_, Some e) -> Some (simple_expansion e)
102✔
1494
        | Alias (_, None) -> None
186✔
1495
        | ModuleType e -> expansion_of_module_type_expr e
1,555✔
1496
      in
1497
      let source_anchor = source_anchor t.source_loc in
1498
      let modname, status, expansion, expansion_doc =
1,843✔
1499
        match expansion with
1500
        | None -> (O.txt modname, `Default, None, None)
322✔
1501
        | Some (expansion_doc, items) ->
1,521✔
1502
            let status =
1503
              match t.type_ with
1504
              | ModuleType (Signature _) -> `Inline
987✔
1505
              | _ -> `Default
534✔
1506
            in
1507
            let url = Url.Path.from_identifier t.id in
1508
            let link = path url [ inline @@ Text modname ] in
1,521✔
1509
            let page =
1,521✔
1510
              make_expansion_page ~source_anchor url
1511
                [ t.doc.elements; expansion_doc ]
1512
                items
1513
            in
1514
            (link, status, Some page, Some expansion_doc)
1,521✔
1515
      in
1516
      let intro = O.keyword "module" ++ O.txt " " ++ modname in
1,843✔
1517
      let summary = O.ignore intro ++ mdexpr_in_decl t.id t.type_ in
1,843✔
1518
      let modexpr =
1,843✔
1519
        attach_expansion ~status
1520
          (Syntax.Type.annotation_separator, "sig", "end")
1521
          expansion summary
1522
      in
1523
      let content =
1,843✔
1524
        O.documentedSrc intro @ modexpr
1,843✔
1525
        @ O.documentedSrc
1,843✔
1526
            (if Syntax.Mod.close_tag_semicolon then O.txt ";" else O.noop)
×
1527
      in
1528
      let attr = [ "module" ] in
1529
      let anchor = path_to_id t.id in
1530
      let doc = Comment.synopsis ~decl_doc:t.doc.elements ~expansion_doc in
1,843✔
1531
      Item.Declaration { attr; anchor; doc; content; source_anchor }
1532

1533
    and simple_expansion_in_decl (base : Paths.Identifier.Module.t) se =
1534
      let rec ty_of_se :
102✔
1535
          Lang.ModuleType.simple_expansion -> Lang.ModuleType.expr = function
1536
        | Signature sg -> Signature sg
102✔
1537
        | Functor (arg, sg) -> Functor (arg, ty_of_se sg)
×
1538
      in
1539
      mty_in_decl (base :> Paths.Identifier.Signature.t) (ty_of_se se)
102✔
1540

1541
    and mdexpr_in_decl (base : Paths.Identifier.Module.t) md =
1542
      let sig_dotdotdot =
1,843✔
1543
        O.txt Syntax.Type.annotation_separator
1,843✔
1544
        ++ O.cut ++ Syntax.Mod.open_tag ++ O.txt " ... " ++ Syntax.Mod.close_tag
1,843✔
1545
      in
1546
      match md with
1,843✔
1547
      | Alias (_, Some se) -> simple_expansion_in_decl base se
102✔
1548
      | Alias (p, _) when not Paths.Path.(is_hidden (p :> t)) ->
186✔
1549
          O.txt " =" ++ O.sp ++ mdexpr md
186✔
1550
      | Alias _ -> sig_dotdotdot
×
1551
      | ModuleType mt -> mty_in_decl (base :> Paths.Identifier.Signature.t) mt
1,555✔
1552

1553
    and mdexpr : Odoc_model.Lang.Module.decl -> text = function
1554
      | Alias (mod_path, _) -> Link.from_path (mod_path :> Paths.Path.t)
242✔
1555
      | ModuleType mt -> mty mt
×
1556

1557
    and module_type_manifest ~subst ~source_anchor modname id doc manifest
1558
        prefix =
1559
      let expansion =
1,369✔
1560
        match manifest with
1561
        | None -> None
128✔
1562
        | Some e -> expansion_of_module_type_expr e
1,241✔
1563
      in
1564
      let modname, expansion, expansion_doc =
1565
        match expansion with
1566
        | None -> (O.txt modname, None, None)
379✔
1567
        | Some (expansion_doc, items) ->
990✔
1568
            let url = Url.Path.from_identifier id in
1569
            let link = path url [ inline @@ Text modname ] in
990✔
1570
            let page =
990✔
1571
              make_expansion_page ~source_anchor url [ doc; expansion_doc ]
1572
                items
1573
            in
1574
            (link, Some page, Some expansion_doc)
990✔
1575
      in
1576
      let summary =
1577
        match manifest with
1578
        | None -> O.noop
128✔
1579
        | Some expr ->
1,241✔
1580
            O.ignore (prefix ++ modname)
1,241✔
1581
            ++ (if subst then O.txt " :=" ++ O.sp else O.txt " =" ++ O.sp)
8✔
1582
            ++ mty expr
1,241✔
1583
      in
1584
      ( modname,
1585
        expansion_doc,
1586
        attach_expansion (" = ", "sig", "end") expansion summary )
1,369✔
1587

1588
    and module_type (t : Odoc_model.Lang.ModuleType.t) =
1589
      let prefix =
1,361✔
1590
        O.keyword "module" ++ O.txt " " ++ O.keyword "type" ++ O.txt " "
1,361✔
1591
      in
1592
      let modname = Paths.Identifier.name t.id in
1,361✔
1593
      let source_anchor = source_anchor t.source_loc in
1,361✔
1594
      let modname, expansion_doc, mty =
1,361✔
1595
        module_type_manifest ~subst:false ~source_anchor modname t.id
1596
          t.doc.elements t.expr prefix
1597
      in
1598
      let content =
1,361✔
1599
        O.documentedSrc (prefix ++ modname)
1,361✔
1600
        @ mty
1601
        @ O.documentedSrc
1,361✔
1602
            (if Syntax.Mod.close_tag_semicolon then O.txt ";" else O.noop)
×
1603
      in
1604
      let attr = [ "module-type" ] in
1605
      let anchor = path_to_id t.id in
1606
      let doc = Comment.synopsis ~decl_doc:t.doc.elements ~expansion_doc in
1,361✔
1607
      Item.Declaration { attr; anchor; doc; content; source_anchor }
1608

1609
    and umty_hidden : Odoc_model.Lang.ModuleType.U.expr -> bool = function
1610
      | Path p -> Paths.Path.(is_hidden (p :> t))
403✔
1611
      | With (_, expr) -> umty_hidden expr
25✔
1612
      | TypeOf (ModPath m, _) | TypeOf (StructInclude m, _) ->
42✔
1613
          Paths.Path.(is_hidden (m :> t))
1614
      | Signature _ -> false
14✔
1615
      | Strengthen (expr, p, _) ->
×
1616
          umty_hidden expr || Paths.Path.(is_hidden (p :> t))
×
1617

1618
    and mty_hidden : Odoc_model.Lang.ModuleType.expr -> bool = function
1619
      | Path { p_path = mty_path; _ } -> Paths.Path.(is_hidden (mty_path :> t))
837✔
1620
      | With { w_expr; _ } -> umty_hidden w_expr
218✔
1621
      | TypeOf { t_desc = ModPath m; _ }
48✔
1622
      | TypeOf { t_desc = StructInclude m; _ } ->
16✔
1623
          Paths.Path.(is_hidden (m :> t))
1624
      | _ -> false
2,297✔
1625

1626
    and mty_with subs expr =
1627
      umty expr ++ O.sp ++ O.keyword "with" ++ O.txt " "
238✔
1628
      ++ O.list
238✔
1629
           ~sep:(O.cut ++ O.txt " " ++ O.keyword "and" ++ O.txt " ")
238✔
1630
           ~f:(fun x -> O.span (substitution x))
279✔
1631
           subs
1632

1633
    and mty_strengthen expr path =
1634
      umty expr ++ O.sp ++ O.keyword "with" ++ O.txt " "
×
1635
      ++ Link.from_path (path :> Paths.Path.t)
×
1636

1637
    and mty_typeof t_desc =
1638
      match t_desc with
154✔
1639
      | Odoc_model.Lang.ModuleType.ModPath m ->
90✔
1640
          O.keyword "module" ++ O.txt " " ++ O.keyword "type" ++ O.txt " "
90✔
1641
          ++ O.keyword "of" ++ O.txt " "
90✔
1642
          ++ Link.from_path (m :> Paths.Path.t)
90✔
1643
      | StructInclude m ->
64✔
1644
          O.keyword "module" ++ O.txt " " ++ O.keyword "type" ++ O.txt " "
64✔
1645
          ++ O.keyword "of" ++ O.txt " " ++ O.keyword "struct" ++ O.txt " "
64✔
1646
          ++ O.keyword "include" ++ O.txt " "
64✔
1647
          ++ Link.from_path (m :> Paths.Path.t)
64✔
1648
          ++ O.txt " " ++ O.keyword "end"
64✔
1649

1650
    and is_elidable_with_u : Odoc_model.Lang.ModuleType.U.expr -> bool =
1651
      function
1652
      | Path _ -> false
213✔
1653
      | Signature _ -> true
5✔
1654
      | With (_, expr) -> is_elidable_with_u expr
×
1655
      | TypeOf _ -> false
25✔
1656
      | Strengthen (expr, _, _) -> is_elidable_with_u expr
×
1657

1658
    and umty : Odoc_model.Lang.ModuleType.U.expr -> text =
1659
     fun m ->
1660
      match m with
527✔
1661
      | Path p -> Link.from_path (p :> Paths.Path.t)
403✔
1662
      | Signature _ ->
9✔
1663
          Syntax.Mod.open_tag ++ O.txt " ... " ++ Syntax.Mod.close_tag
9✔
1664
      | With (_, expr) when is_elidable_with_u expr ->
25✔
1665
          Syntax.Mod.open_tag ++ O.txt " ... " ++ Syntax.Mod.close_tag
5✔
1666
      | With (subs, expr) -> mty_with subs expr
20✔
1667
      | TypeOf (t_desc, _) -> mty_typeof t_desc
90✔
1668
      | Strengthen (expr, _, _) when is_elidable_with_u expr ->
×
1669
          Syntax.Mod.open_tag ++ O.txt " ... " ++ Syntax.Mod.close_tag
×
1670
      | Strengthen (expr, p, _) -> mty_strengthen expr (p :> Paths.Path.t)
×
1671

1672
    and mty : Odoc_model.Lang.ModuleType.expr -> text =
1673
     fun m ->
1674
      if mty_hidden m then
3,416✔
1675
        Syntax.Mod.open_tag ++ O.txt " ... " ++ Syntax.Mod.close_tag
×
1676
      else
1677
        match m with
3,416✔
1678
        | Path { p_path = mty_path; _ } ->
837✔
1679
            Link.from_path (mty_path :> Paths.Path.t)
1680
        | Functor (Unit, expr) ->
×
1681
            (if Syntax.Mod.functor_keyword then O.keyword "functor" else O.noop)
×
1682
            ++ O.span (O.txt " () " ++ Syntax.Type.arrow)
×
1683
            ++ O.sp ++ mty expr
×
1684
        | Functor (Named arg, expr) ->
48✔
1685
            let arg_expr = arg.expr in
1686
            let stop_before = expansion_of_module_type_expr arg_expr = None in
48✔
1687
            let name =
1688
              let open Odoc_model.Lang.FunctorParameter in
1689
              let name = Paths.Identifier.name arg.id in
1690
              let href =
48✔
1691
                Url.from_identifier ~stop_before (arg.id :> Paths.Identifier.t)
1692
              in
1693
              resolved href [ inline @@ Text name ]
48✔
1694
            in
1695
            (if Syntax.Mod.functor_keyword then O.keyword "functor" else O.noop)
×
1696
            ++ (O.box_hv @@ O.span
48✔
1697
               @@ O.txt " (" ++ name
48✔
1698
                  ++ O.txt Syntax.Type.annotation_separator
48✔
1699
                  ++ mty arg_expr ++ O.txt ")" ++ O.txt " " ++ Syntax.Type.arrow
48✔
1700
               )
1701
            ++ O.sp ++ mty expr
48✔
1702
        | With { w_expr; _ } when is_elidable_with_u w_expr ->
218✔
1703
            Syntax.Mod.open_tag ++ O.txt " ... " ++ Syntax.Mod.close_tag
×
1704
        | With { w_substitutions; w_expr; _ } ->
218✔
1705
            O.box_hv @@ mty_with w_substitutions w_expr
218✔
1706
        | TypeOf { t_desc; _ } -> mty_typeof t_desc
64✔
1707
        | Signature _ ->
2,249✔
1708
            Syntax.Mod.open_tag ++ O.txt " ... " ++ Syntax.Mod.close_tag
2,249✔
1709
        | Strengthen { s_expr; _ } when is_elidable_with_u s_expr ->
×
1710
            Syntax.Mod.open_tag ++ O.txt " ... " ++ Syntax.Mod.close_tag
×
1711
        | Strengthen { s_expr; s_path; _ } ->
×
1712
            O.box_hv @@ mty_strengthen s_expr (s_path :> Paths.Path.t)
×
1713

1714
    and mty_in_decl :
1715
        Paths.Identifier.Signature.t -> Odoc_model.Lang.ModuleType.expr -> text
1716
        =
1717
     fun base -> function
1718
      | (Path _ | Signature _ | With _ | TypeOf _ | Strengthen _) as m ->
×
1719
          O.txt Syntax.Type.annotation_separator ++ O.cut ++ mty m
1,868✔
1720
      | Functor _ as m when not Syntax.Mod.functor_contraction ->
171✔
1721
          O.txt Syntax.Type.annotation_separator ++ O.cut ++ mty m
×
1722
      | Functor (arg, expr) ->
171✔
1723
          let text_arg =
1724
            match arg with
1725
            | Unit -> O.txt "()"
8✔
1726
            | Named arg ->
163✔
1727
                let arg_expr = arg.expr in
1728
                let stop_before =
1729
                  expansion_of_module_type_expr arg_expr = None
163✔
1730
                in
1731
                let name =
1732
                  let open Odoc_model.Lang.FunctorParameter in
1733
                  let name = Paths.Identifier.name arg.id in
1734
                  let href =
163✔
1735
                    Url.from_identifier ~stop_before
1736
                      (arg.id :> Paths.Identifier.t)
1737
                  in
1738
                  resolved href [ inline @@ Text name ]
163✔
1739
                in
1740
                O.box_hv
163✔
1741
                @@ O.txt "(" ++ name
163✔
1742
                   ++ O.txt Syntax.Type.annotation_separator
163✔
1743
                   ++ O.cut ++ mty arg.expr ++ O.txt ")"
163✔
1744
          in
1745
          O.sp ++ text_arg ++ mty_in_decl base expr
171✔
1746

1747
    (* TODO : Centralize the list juggling for type parameters *)
1748
    and type_expr_in_subst td typath =
1749
      let typath = Link.from_fragment typath in
151✔
1750
      match td.Lang.TypeDecl.Equation.params with
151✔
1751
      | [] -> typath
125✔
1752
      | l -> Syntax.Type.handle_substitution_params typath (format_params l)
26✔
1753

1754
    and substitution : Odoc_model.Lang.ModuleType.substitution -> text =
1755
      function
1756
      | ModuleEq (frag_mod, md) ->
56✔
1757
          O.box_hv
1758
          @@ O.keyword "module" ++ O.txt " "
56✔
1759
             ++ Link.from_fragment (frag_mod :> Paths.Fragment.leaf)
56✔
1760
             ++ O.txt " =" ++ O.sp ++ mdexpr md
56✔
1761
      | ModuleTypeEq (frag_mty, md) ->
32✔
1762
          O.box_hv
1763
          @@ O.keyword "module" ++ O.txt " " ++ O.keyword "type" ++ O.txt " "
32✔
1764
             ++ Link.from_fragment (frag_mty :> Paths.Fragment.leaf)
32✔
1765
             ++ O.txt " =" ++ O.sp ++ mty md
32✔
1766
      | TypeEq (frag_typ, td) ->
104✔
1767
          O.box_hv
1768
          @@ O.keyword "type" ++ O.txt " "
104✔
1769
             ++ type_expr_in_subst td (frag_typ :> Paths.Fragment.leaf)
104✔
1770
             ++ fst (format_manifest td)
104✔
1771
             ++ format_constraints
104✔
1772
                  td.Odoc_model.Lang.TypeDecl.Equation.constraints
1773
      | ModuleSubst (frag_mod, mod_path) ->
24✔
1774
          O.box_hv
1775
          @@ O.keyword "module" ++ O.txt " "
24✔
1776
             ++ Link.from_fragment (frag_mod :> Paths.Fragment.leaf)
24✔
1777
             ++ O.txt " :=" ++ O.sp
24✔
1778
             ++ Link.from_path (mod_path :> Paths.Path.t)
24✔
1779
      | ModuleTypeSubst (frag_mty, md) ->
16✔
1780
          O.box_hv
1781
          @@ O.keyword "module" ++ O.txt " " ++ O.keyword "type" ++ O.txt " "
16✔
1782
             ++ Link.from_fragment (frag_mty :> Paths.Fragment.leaf)
16✔
1783
             ++ O.txt " :=" ++ O.sp ++ mty md
16✔
1784
      | TypeSubst (frag_typ, td) -> (
47✔
1785
          O.box_hv
1786
          @@ O.keyword "type" ++ O.txt " "
47✔
1787
             ++ type_expr_in_subst td (frag_typ :> Paths.Fragment.leaf)
47✔
1788
             ++ O.txt " :=" ++ O.sp
47✔
1789
             ++
47✔
1790
             match td.Lang.TypeDecl.Equation.manifest with
1791
             | None -> assert false (* cf loader/cmti *)
1792
             | Some te -> type_expr te)
47✔
1793

1794
    and include_ (t : Odoc_model.Lang.Include.t) =
1795
      let decl_hidden =
290✔
1796
        match t.decl with
1797
        | Alias p -> Paths.Path.(is_hidden (p :> t))
×
1798
        | ModuleType mty -> umty_hidden mty
290✔
1799
      in
1800
      let status = if decl_hidden then `Inline else t.status in
1✔
1801

1802
      let _, content = signature t.expansion.content in
1803
      let summary =
290✔
1804
        if decl_hidden then O.render (O.keyword "include" ++ O.txt " ...")
1✔
1805
        else
1806
          let include_decl =
289✔
1807
            match t.decl with
1808
            | Odoc_model.Lang.Include.Alias mod_path ->
×
1809
                Link.from_path (mod_path :> Paths.Path.t)
×
1810
            | ModuleType mt -> umty mt
289✔
1811
          in
1812
          O.render
289✔
1813
            (O.keyword "include" ++ O.txt " " ++ include_decl
289✔
1814
            ++ if Syntax.Mod.include_semicolon then O.keyword ";" else O.noop)
×
1815
      in
1816
      let content = { Include.content; status; summary } in
1817
      let attr = [ "include" ] in
1818
      let anchor = None in
1819
      let doc =
1820
        (* Documentation attached to includes behave differently than other
1821
           declarations, which show only the synopsis. We can't only show the
1822
           synopsis because no page is generated to render it and we'd loose
1823
           the full documentation.
1824
           The documentation from the expansion is not used. *)
1825
        Comment.to_ir t.doc.elements
1826
      in
1827
      Item.Include { attr; anchor; doc; content; source_anchor = None }
290✔
1828
  end
1829

1830
  open Module
1831

1832
  module Page : sig
1833
    val compilation_unit : Lang.Compilation_unit.t -> Document.t
1834

1835
    val page : Lang.Page.t -> Document.t
1836

1837
    val implementation :
1838
      Lang.Implementation.t ->
1839
      Syntax_highlighter.infos ->
1840
      string ->
1841
      Document.t list
1842
  end = struct
1843
    let pack : Lang.Compilation_unit.Packed.t -> Item.t list =
1844
     fun t ->
1845
      let f x =
×
1846
        let id = x.Lang.Compilation_unit.Packed.id in
×
1847
        let modname = Paths.Identifier.name id in
1848
        let md_def =
×
1849
          O.keyword "module" ++ O.txt " " ++ O.txt modname ++ O.txt " = "
×
1850
          ++ Link.from_path (x.path :> Paths.Path.t)
×
1851
        in
1852
        let content = O.documentedSrc md_def in
×
1853
        let anchor =
×
1854
          Some (Url.Anchor.from_identifier (id :> Paths.Identifier.t))
×
1855
        in
1856
        let attr = [ "modules" ] in
1857
        let doc = [] in
1858
        let decl = { Item.anchor; content; attr; doc; source_anchor = None } in
1859
        Item.Declaration decl
1860
      in
1861
      List.map f t
1862

1863
    let compilation_unit (t : Odoc_model.Lang.Compilation_unit.t) =
1864
      let url = Url.Path.from_identifier t.id in
359✔
1865
      let unit_doc, items =
359✔
1866
        match t.content with
1867
        | Module sign -> signature sign
359✔
1868
        | Pack packed -> ([], pack packed)
×
1869
      in
1870
      let source_anchor = source_anchor t.source_loc in
1871
      let page = make_expansion_page ~source_anchor url [ unit_doc ] items in
359✔
1872
      Document.Page page
359✔
1873

1874
    let page (t : Odoc_model.Lang.Page.t) =
1875
      (*let name =
1876
          match t.name.iv with `Page (_, name) | `LeafPage (_, name) -> name
1877
        in*)
1878
      (*let title = Odoc_model.Names.PageName.to_string name in*)
1879
      let url = Url.Path.from_identifier t.name in
69✔
1880
      let preamble, items = Sectioning.docs t.content.elements in
69✔
1881
      let source_anchor = None in
69✔
1882
      Document.Page { Page.preamble; items; url; source_anchor }
1883

1884
    let implementation (v : Odoc_model.Lang.Implementation.t) syntax_info
1885
        source_code =
1886
      match v.id with
28✔
1887
      | None -> []
×
1888
      | Some id ->
28✔
1889
          [
1890
            Document.Source_page
1891
              (Source_page.source id syntax_info v.source_info source_code);
28✔
1892
          ]
1893
  end
1894

1895
  include Page
1896

1897
  let type_expr = type_expr
1898

1899
  let record = record
1900

1901
  let unboxed_record = unboxed_record
1902
end
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc