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

ocaml / odoc / 3178

18 May 2026 01:23PM UTC coverage: 71.092% (-0.05%) from 71.138%
3178

Pull #1422

github

web-flow
Merge 371944fc7 into da35f940e
Pull Request #1422: Zero alloc annotation for OxCaml

3 of 13 new or added lines in 1 file covered. (23.08%)

10 existing lines in 1 file now uncovered.

10432 of 14674 relevant lines covered (71.09%)

5884.32 hits per line

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

81.52
/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)
×
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 " " ++ package_path pkg)
24✔
490
      | Arrow_functor (lbl, m_arg, dst) ->
×
491
          let lbl =
492
            match lbl with None -> O.noop | Some lbl -> label lbl ++ O.txt ":"
×
493
          in
494
          let name =
495
            match m_arg.id.iv with
496
            | `Parameter (_, name) -> ModuleName.to_string name
×
497
          in
498
          let dst = type_expr dst in
499
          let pkg =
×
500
            enclose ~l:"(" ~r:")"
501
            @@ O.keyword "module" ++ O.txt " " ++ O.txt name ++ O.txt " : "
×
502
               ++ package_path m_arg.package
×
503
          in
504
          lbl ++ pkg ++ O.sp ++ Syntax.Type.arrow ++ O.sp ++ dst
×
505

506
    and package_path pkg =
507
      Link.from_path (pkg.path :> Paths.Path.t)
24✔
508
      ++
509
      match pkg.substitutions with
510
      | [] -> O.noop
16✔
511
      | fst :: lst ->
8✔
512
          O.sp
513
          ++ O.box_hv (O.keyword "with" ++ O.txt " " ++ package_subst fst)
8✔
514
          ++ O.list lst ~f:(fun s ->
8✔
515
                 O.cut
8✔
516
                 ++ (O.box_hv
8✔
517
                    @@ O.txt " " ++ O.keyword "and" ++ O.txt " "
8✔
518
                       ++ package_subst s))
8✔
519

520
    and package_subst
521
        ((frag_typ, te) : Paths.Fragment.Type.t * Odoc_model.Lang.TypeExpr.t) :
522
        text =
523
      let typath = Link.from_fragment (frag_typ :> Paths.Fragment.leaf) in
16✔
524
      O.keyword "type" ++ O.txt " " ++ typath ++ O.txt " =" ++ O.sp
16✔
525
      ++ type_expr te
16✔
526
  end
527

528
  open Type_expression
529

530
  (* Also handles constructor declarations for exceptions and extensible
531
     variants, and exposes a few helpers used in formatting classes and signature
532
     constraints. *)
533
  module Type_declaration : sig
534
    val type_decl :
535
      ?is_substitution:bool ->
536
      Lang.Signature.recursive * Lang.TypeDecl.t ->
537
      Item.t
538

539
    val extension : Lang.Extension.t -> Item.t
540

541
    val record : Lang.TypeDecl.Field.t list -> DocumentedSrc.one list
542

543
    val unboxed_record :
544
      Lang.TypeDecl.UnboxedField.t list -> DocumentedSrc.one list
545

546
    val exn : Lang.Exception.t -> Item.t
547

548
    val format_params :
549
      ?delim:[ `parens | `brackets ] -> Lang.TypeDecl.param list -> text
550

551
    val format_manifest :
552
      ?is_substitution:bool ->
553
      ?compact_variants:bool ->
554
      Lang.TypeDecl.Equation.t ->
555
      text * bool
556

557
    val format_constraints : (Lang.TypeExpr.t * Lang.TypeExpr.t) list -> text
558
  end = struct
559
    let record fields =
560
      let field mutable_ id typ =
86✔
561
        let url = Url.from_identifier ~stop_before:true id in
155✔
562
        let name = Paths.Identifier.name id in
155✔
563
        let attrs = [ "def"; "record"; Url.Anchor.string_of_kind url.kind ] in
155✔
564
        let cell =
565
          (* O.td ~a:[ O.a_class ["def"; kind ] ]
566
           *   [O.a ~a:[O.a_href ("#" ^ anchor); O.a_class ["anchor"]] []
567
           *   ; *)
568
          O.code
569
            ((if mutable_ then O.keyword "mutable" ++ O.txt " " else O.noop)
24✔
570
            ++ O.txt name
155✔
571
            ++ O.txt Syntax.Type.annotation_separator
155✔
572
            ++ type_expr typ
155✔
573
            ++ O.txt Syntax.Type.Record.field_separator)
155✔
574
          (* ] *)
575
        in
576
        (url, attrs, cell)
155✔
577
      in
578
      let rows =
579
        fields
580
        |> List.map (fun fld ->
581
               let open Odoc_model.Lang.TypeDecl.Field in
155✔
582
               let url, attrs, code =
583
                 field fld.mutable_ (fld.id :> Paths.Identifier.t) fld.type_
584
               in
585
               let anchor = Some url in
155✔
586
               let doc = fld.doc.elements in
587
               let rhs = Comment.to_ir doc in
588
               let doc = if not (Comment.has_doc doc) then [] else rhs in
64✔
589
               let markers = Syntax.Comment.markers in
590
               DocumentedSrc.Documented { anchor; attrs; code; doc; markers })
591
      in
592
      let content =
86✔
593
        O.documentedSrc (O.txt "{") @ rows @ O.documentedSrc (O.txt "}")
86✔
594
      in
595
      content
596

597
    let unboxed_record fields =
598
      let field mutable_ id typ =
×
599
        let url = Url.from_identifier ~stop_before:true id in
×
600
        let name = Paths.Identifier.name id in
×
601
        let attrs = [ "def"; "record"; Url.Anchor.string_of_kind url.kind ] in
×
602
        let cell =
603
          (* O.td ~a:[ O.a_class ["def"; kind ] ]
604
           *   [O.a ~a:[O.a_href ("#" ^ anchor); O.a_class ["anchor"]] []
605
           *   ; *)
606
          O.code
607
            ((if mutable_ then O.keyword "mutable" ++ O.txt " " else O.noop)
×
608
            ++ O.txt name
×
609
            ++ O.txt Syntax.Type.annotation_separator
×
610
            ++ type_expr typ
×
611
            ++ O.txt Syntax.Type.Record.field_separator)
×
612
          (* ] *)
613
        in
614
        (url, attrs, cell)
×
615
      in
616
      let rows =
617
        fields
618
        |> List.map (fun fld ->
619
               let open Odoc_model.Lang.TypeDecl.UnboxedField in
×
620
               let url, attrs, code =
621
                 field fld.mutable_ (fld.id :> Paths.Identifier.t) fld.type_
622
               in
623
               let anchor = Some url in
×
624
               let doc = fld.doc.elements in
625
               let rhs = Comment.to_ir doc in
626
               let doc = if not (Comment.has_doc doc) then [] else rhs in
×
627
               let markers = Syntax.Comment.markers in
628
               DocumentedSrc.Documented { anchor; attrs; code; doc; markers })
629
      in
630
      let content =
×
631
        O.documentedSrc (O.txt "#{") @ rows @ O.documentedSrc (O.txt "}")
×
632
      in
633
      content
634

635
    let constructor :
636
        Paths.Identifier.t ->
637
        Odoc_model.Lang.TypeDecl.Constructor.argument ->
638
        Odoc_model.Lang.TypeExpr.t option ->
639
        DocumentedSrc.t =
640
     fun id args ret_type ->
641
      let name = Paths.Identifier.name id in
656✔
642
      let kind = Url.(kind id |> Anchor.string_of_kind) in
656✔
643
      let cstr = tag kind (O.txt name) in
656✔
644
      let is_gadt, ret_type =
656✔
645
        match ret_type with
646
        | None -> (false, O.noop)
520✔
647
        | Some te ->
136✔
648
            let constant = match args with Tuple [] -> true | _ -> false in
48✔
649
            let ret_type =
650
              O.txt " "
136✔
651
              ++ (if constant then O.txt ":" else Syntax.Type.GADT.arrow)
48✔
652
              ++ O.txt " " ++ type_expr te
136✔
653
            in
654
            (true, ret_type)
136✔
655
      in
656
      match args with
657
      | Tuple [] -> O.documentedSrc (cstr ++ ret_type)
289✔
658
      | Tuple lst ->
350✔
659
          let params =
660
            O.list lst ~sep:Syntax.Type.Tuple.element_separator
661
              ~f:(type_expr ~needs_parentheses:is_gadt)
662
          in
663
          O.documentedSrc
350✔
664
            (cstr
665
            ++ (if Syntax.Type.Variant.parenthesize_params then
350✔
666
                  O.txt "(" ++ params ++ O.txt ")"
×
667
                else
668
                  (if is_gadt then O.txt Syntax.Type.annotation_separator
88✔
669
                   else O.txt " " ++ O.keyword "of" ++ O.txt " ")
262✔
670
                  ++ params)
350✔
671
            ++ ret_type)
350✔
672
      | Record fields ->
17✔
673
          if is_gadt then
674
            O.documentedSrc (cstr ++ O.txt Syntax.Type.annotation_separator)
×
675
            @ record fields @ O.documentedSrc ret_type
×
676
          else
677
            O.documentedSrc (cstr ++ O.txt " " ++ O.keyword "of" ++ O.txt " ")
17✔
678
            @ record fields
17✔
679

680
    let variant cstrs : DocumentedSrc.t =
681
      let constructor id args res =
235✔
682
        let url = Url.from_identifier ~stop_before:true id in
436✔
683
        let attrs = [ "def"; "variant"; Url.Anchor.string_of_kind url.kind ] in
436✔
684
        let content =
685
          let doc = constructor id args res in
686
          O.documentedSrc (O.txt "| ") @ doc
436✔
687
        in
688
        (url, attrs, content)
689
      in
690
      match cstrs with
691
      | [] -> O.documentedSrc (O.txt "|")
×
692
      | _ :: _ ->
235✔
693
          let rows =
694
            cstrs
695
            |> List.map (fun cstr ->
696
                   let open Odoc_model.Lang.TypeDecl.Constructor in
436✔
697
                   let url, attrs, code =
698
                     constructor
699
                       (cstr.id :> Paths.Identifier.t)
700
                       cstr.args cstr.res
701
                   in
702
                   let anchor = Some url in
436✔
703
                   let doc = cstr.doc.elements in
704
                   let rhs = Comment.to_ir doc in
705
                   let doc = if not (Comment.has_doc doc) then [] else rhs in
73✔
706
                   let markers = Syntax.Comment.markers in
707
                   DocumentedSrc.Nested { anchor; attrs; code; doc; markers })
708
          in
709
          rows
235✔
710

711
    let extension_constructor (t : Odoc_model.Lang.Extension.Constructor.t) =
712
      let id = (t.id :> Paths.Identifier.t) in
154✔
713
      let url = Url.from_identifier ~stop_before:true id in
714
      let anchor = Some url in
154✔
715
      let attrs = [ "def"; "variant"; Url.Anchor.string_of_kind url.kind ] in
154✔
716
      let code = O.documentedSrc (O.txt "| ") @ constructor id t.args t.res in
154✔
717
      let doc = Comment.to_ir t.doc.elements in
718
      let markers = Syntax.Comment.markers in
154✔
719
      DocumentedSrc.Nested { anchor; attrs; code; doc; markers }
720

721
    let extension (t : Odoc_model.Lang.Extension.t) =
722
      let prefix =
126✔
723
        O.keyword "type" ++ O.txt " "
126✔
724
        ++ Link.from_path (t.type_path :> Paths.Path.t)
126✔
725
        ++ O.txt " +=" ++ O.sp
126✔
726
        ++
727
        if t.private_ then O.keyword Syntax.Type.private_keyword ++ O.sp
8✔
728
        else O.noop
118✔
729
      in
730
      let content =
126✔
731
        O.documentedSrc prefix
126✔
732
        @ List.map extension_constructor t.constructors
126✔
733
        @ O.documentedSrc
126✔
734
            (if Syntax.Type.type_def_semicolon then O.txt ";" else O.noop)
×
735
      in
736
      let attr = [ "type"; "extension" ] in
737
      let anchor = Some (Url.Anchor.extension_decl t) in
126✔
738
      let doc = Comment.to_ir t.doc.elements in
739
      let source_anchor =
126✔
740
        (* Take the anchor from the first constructor only for consistency with
741
           regular variants. *)
742
        match t.constructors with
743
        | hd :: _ -> source_anchor hd.source_loc
126✔
744
        | [] -> None
×
745
      in
746
      Item.Declaration { attr; anchor; doc; content; source_anchor }
747

748
    let exn (t : Odoc_model.Lang.Exception.t) =
749
      let cstr = constructor (t.id :> Paths.Identifier.t) t.args t.res in
66✔
750
      let content =
66✔
751
        O.documentedSrc (O.keyword "exception" ++ O.txt " ")
66✔
752
        @ cstr
753
        @ O.documentedSrc
66✔
754
            (if Syntax.Type.Exception.semicolon then O.txt ";" else O.noop)
×
755
      in
756
      let attr = [ "exception" ] in
757
      let anchor = path_to_id t.id in
758
      let doc = Comment.to_ir t.doc.elements in
66✔
759
      let source_anchor = source_anchor t.source_loc in
66✔
760
      Item.Declaration { attr; anchor; doc; content; source_anchor }
66✔
761

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

836
    let format_params :
837
        'row.
838
        ?delim:[ `parens | `brackets ] ->
839
        Odoc_model.Lang.TypeDecl.param list ->
840
        text =
841
     fun ?(delim = `parens) params ->
472✔
842
      let format_param { Odoc_model.Lang.TypeDecl.desc; variance; injectivity }
563✔
843
          =
844
        let desc =
624✔
845
          match desc with
846
          | Odoc_model.Lang.TypeDecl.Any -> [ "_" ]
37✔
847
          | Var s -> [ "'"; s ]
587✔
848
        in
849
        let var_desc =
850
          match variance with
851
          | None -> desc
608✔
852
          | Some Odoc_model.Lang.TypeDecl.Pos -> "+" :: desc
8✔
853
          | Some Odoc_model.Lang.TypeDecl.Neg -> "-" :: desc
8✔
854
          | Some Odoc_model.Lang.TypeDecl.Bivariant -> "+" :: "-" :: desc
×
855
        in
856
        let final = if injectivity then "!" :: var_desc else var_desc in
×
857
        String.concat ~sep:"" final
858
      in
859
      O.txt
860
        (match params with
861
        | [] -> ""
59✔
862
        | [ x ] -> format_param x |> Syntax.Type.handle_format_params
388✔
863
        | lst -> (
116✔
864
            let params = String.concat ~sep:", " (List.map format_param lst) in
116✔
865
            (match delim with `parens -> "(" | `brackets -> "[")
×
866
            ^ params
867
            ^ match delim with `parens -> ")" | `brackets -> "]"))
×
868

869
    let format_constraints constraints =
870
      O.list constraints ~f:(fun (t1, t2) ->
3,103✔
871
          O.sp
104✔
872
          ++ (O.box_hv
104✔
873
             @@ O.keyword "constraint" ++ O.sp
104✔
874
                ++ O.box_hv_no_indent (type_expr t1)
104✔
875
                ++ O.txt " =" ++ O.sp
104✔
876
                ++ O.box_hv_no_indent (type_expr t2)))
104✔
877

878
    let format_manifest :
879
        'inner_row 'outer_row.
880
        ?is_substitution:bool ->
881
        ?compact_variants:bool ->
882
        Odoc_model.Lang.TypeDecl.Equation.t ->
883
        text * bool =
884
     fun ?(is_substitution = false) ?(compact_variants = true) equation ->
104✔
885
      let _ = compact_variants in
3,021✔
886
      (* TODO *)
887
      let private_ = equation.private_ in
888
      match equation.manifest with
889
      | None -> (O.noop, private_)
1,753✔
890
      | Some t ->
1,268✔
891
          let manifest =
892
            O.txt (if is_substitution then " :=" else " =")
23✔
893
            ++ O.sp
1,268✔
894
            ++ (if private_ then
1,268✔
895
                  O.keyword Syntax.Type.private_keyword ++ O.txt " "
8✔
896
                else O.noop)
1,260✔
897
            ++ type_expr t
1,268✔
898
          in
899
          (manifest, false)
1,268✔
900

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

980
  open Type_declaration
981

982
  module Value : sig
983
    val value : Lang.Value.t -> Item.t
984
  end = struct
985
    let value (t : Odoc_model.Lang.Value.t) =
986
      let extra_attr, semicolon =
912✔
987
        match t.value with
988
        | Abstract -> ([], Syntax.Value.semicolon)
888✔
989
        | External _ -> ([ "external" ], Syntax.Type.External.semicolon)
24✔
990
      in
991
      let name = Paths.Identifier.name t.id in
992
      let zero_alloc =
912✔
993
        match
994
          List.find
NEW
UNCOV
995
            (function Odoc_model.Lang.Value.Zero_alloc _ -> true)
×
996
            t.ext_attr
997
        with
998
        | exception Not_found -> O.noop
912✔
NEW
999
        | Zero_alloc { opt; strict; arity; custom_error_msg } ->
×
1000
            let ext_arg =
1001
              match (opt, strict) with
NEW
UNCOV
1002
              | true, false -> " opt"
×
NEW
UNCOV
1003
              | false, true -> " strict"
×
NEW
UNCOV
1004
              | true, true -> " strict opt"
×
NEW
UNCOV
1005
              | false, false -> ""
×
1006
            in
NEW
UNCOV
1007
            let ext_arg = ext_arg ^ Printf.sprintf " arity %d" arity in
×
1008
            let ext_arg =
1009
              match custom_error_msg with
NEW
UNCOV
1010
              | None -> ext_arg
×
NEW
UNCOV
1011
              | Some s -> ext_arg ^ Printf.sprintf "custom_error_message %S" s
×
1012
            in
1013
            let ext_attr = Printf.sprintf "[@@zero_alloc%s]" ext_arg in
NEW
UNCOV
1014
            O.cut ++ O.txt " " ++ O.txt ext_attr
×
1015
      in
1016
      let content =
1017
        O.documentedSrc
1018
          (O.box_hv
912✔
1019
          @@ O.keyword Syntax.Value.variable_keyword
912✔
1020
             ++ O.txt " " ++ O.txt name
912✔
1021
             ++ O.txt Syntax.Type.annotation_separator
912✔
1022
             ++ O.cut ++ type_expr t.type_ ++ zero_alloc
912✔
UNCOV
1023
             ++ if semicolon then O.txt ";" else O.noop)
×
1024
      in
1025
      let attr = [ "value" ] @ extra_attr in
912✔
1026
      let anchor = path_to_id t.id in
1027
      let doc = Comment.to_ir t.doc.elements in
912✔
1028
      let source_anchor = source_anchor t.source_loc in
912✔
1029
      Item.Declaration { attr; anchor; doc; content; source_anchor }
912✔
1030
  end
1031

1032
  open Value
1033

1034
  (* This chunk of code is responsible for sectioning list of items
1035
     according to headings by extracting headings as Items.
1036

1037
     TODO: This sectioning would be better done as a pass on the model directly.
1038
  *)
1039
  module Sectioning : sig
1040
    open Odoc_model
1041

1042
    val comment_items : Comment.elements -> Item.t list
1043

1044
    val docs : Comment.elements -> Item.t list * Item.t list
1045
  end = struct
1046
    let take_until_heading_or_end (docs : Odoc_model.Comment.elements) =
1047
      let content, _, rest =
693✔
1048
        Doctree.Take.until docs ~classify:(fun b ->
1049
            match b.Location.value with
1,481✔
1050
            | `Heading _ -> Stop_and_keep
202✔
1051
            | #Odoc_model.Comment.attached_block_element as doc ->
1,279✔
1052
                let content = Comment.attached_block_element doc in
1053
                Accum content)
1,279✔
1054
      in
1055
      (content, rest)
693✔
1056

1057
    let comment_items (input0 : Odoc_model.Comment.elements) =
1058
      let rec loop input_comment acc =
963✔
1059
        match input_comment with
2,493✔
1060
        | [] -> List.rev acc
963✔
1061
        | element :: input_comment -> (
1,530✔
1062
            match element.Location.value with
1063
            | `Heading h ->
837✔
1064
                let item = Comment.heading h in
1065
                loop input_comment (item :: acc)
837✔
1066
            | _ ->
693✔
1067
                let content, input_comment =
1068
                  take_until_heading_or_end (element :: input_comment)
1069
                in
1070
                let item = Item.Text content in
693✔
1071
                loop input_comment (item :: acc))
1072
      in
1073
      loop input0 []
1074

1075
    (* For doc pages, we want the header to contain everything until
1076
       the first heading, then everything before the next heading which
1077
       is either lower, or a section.
1078
    *)
1079
    let docs input_comment =
1080
      let items = comment_items input_comment in
69✔
1081
      let until_first_heading, o, items =
69✔
1082
        Doctree.Take.until items ~classify:(function
1083
          | Item.Heading h as i -> Stop_and_accum ([ i ], Some h.level)
69✔
1084
          | i -> Accum [ i ])
×
1085
      in
1086
      match o with
69✔
1087
      | None -> (until_first_heading, items)
×
1088
      | Some level ->
69✔
1089
          let max_level = if level = 1 then 2 else level in
×
1090
          let before_second_heading, _, items =
1091
            Doctree.Take.until items ~classify:(function
1092
              | Item.Heading h when h.level >= max_level -> Stop_and_keep
13✔
1093
              | i -> Accum [ i ])
38✔
1094
          in
1095
          let header = until_first_heading @ before_second_heading in
69✔
1096
          (header, items)
1097
  end
1098

1099
  module Class : sig
1100
    val class_ : Lang.Class.t -> Item.t
1101

1102
    val class_type : Lang.ClassType.t -> Item.t
1103
  end = struct
1104
    let class_type_expr (cte : Odoc_model.Lang.ClassType.expr) =
1105
      match cte with
249✔
1106
      | Constr (path, args) ->
58✔
1107
          let link = Link.from_path (path :> Paths.Path.t) in
1108
          format_type_path ~delim:`brackets args link
58✔
1109
      | Signature _ ->
191✔
1110
          Syntax.Class.open_tag ++ O.txt " ... " ++ Syntax.Class.close_tag
191✔
1111

1112
    let method_ (t : Odoc_model.Lang.Method.t) =
1113
      let name = Paths.Identifier.name t.id in
90✔
1114
      let virtual_ =
90✔
1115
        if t.virtual_ then O.keyword "virtual" ++ O.txt " " else O.noop
8✔
1116
      in
1117
      let private_ =
1118
        if t.private_ then O.keyword "private" ++ O.txt " " else O.noop
8✔
1119
      in
1120
      let content =
1121
        O.documentedSrc
1122
          (O.keyword "method" ++ O.txt " " ++ private_ ++ virtual_ ++ O.txt name
90✔
1123
          ++ O.txt Syntax.Type.annotation_separator
90✔
1124
          ++ type_expr t.type_)
90✔
1125
      in
1126
      let attr = [ "method" ] in
90✔
1127
      let anchor = path_to_id t.id in
1128
      let doc = Comment.to_ir t.doc.elements in
90✔
1129
      Item.Declaration { attr; anchor; doc; content; source_anchor = None }
90✔
1130

1131
    let instance_variable (t : Odoc_model.Lang.InstanceVariable.t) =
1132
      let name = Paths.Identifier.name t.id in
17✔
1133
      let virtual_ =
17✔
1134
        if t.virtual_ then O.keyword "virtual" ++ O.txt " " else O.noop
8✔
1135
      in
1136
      let mutable_ =
1137
        if t.mutable_ then O.keyword "mutable" ++ O.txt " " else O.noop
8✔
1138
      in
1139
      let content =
1140
        O.documentedSrc
1141
          (O.keyword "val" ++ O.txt " " ++ mutable_ ++ virtual_ ++ O.txt name
17✔
1142
          ++ O.txt Syntax.Type.annotation_separator
17✔
1143
          ++ type_expr t.type_)
17✔
1144
      in
1145
      let attr = [ "value"; "instance-variable" ] in
17✔
1146
      let anchor = path_to_id t.id in
1147
      let doc = Comment.to_ir t.doc.elements in
17✔
1148
      Item.Declaration { attr; anchor; doc; content; source_anchor = None }
17✔
1149

1150
    let inherit_ (ih : Lang.ClassSignature.Inherit.t) =
1151
      let cte =
16✔
1152
        match ih.expr with
1153
        | Signature _ -> assert false (* Bold. *)
1154
        | cty -> cty
16✔
1155
      in
1156
      let content =
1157
        O.documentedSrc (O.keyword "inherit" ++ O.txt " " ++ class_type_expr cte)
16✔
1158
      in
1159
      let attr = [ "inherit" ] in
16✔
1160
      let anchor = None in
1161
      let doc = Comment.to_ir ih.doc.elements in
1162
      Item.Declaration { attr; anchor; doc; content; source_anchor = None }
16✔
1163

1164
    let constraint_ (cst : Lang.ClassSignature.Constraint.t) =
1165
      let content =
8✔
1166
        O.documentedSrc (format_constraints [ (cst.left, cst.right) ])
8✔
1167
      in
1168
      let attr = [] in
8✔
1169
      let anchor = None in
1170
      let doc = Comment.to_ir cst.doc.elements in
1171
      Item.Declaration { attr; anchor; doc; content; source_anchor = None }
8✔
1172

1173
    let class_signature (c : Lang.ClassSignature.t) =
1174
      let rec loop l acc_items =
233✔
1175
        match l with
388✔
1176
        | [] -> List.rev acc_items
233✔
1177
        | item :: rest -> (
155✔
1178
            let continue item = loop rest (item :: acc_items) in
131✔
1179
            match (item : Lang.ClassSignature.item) with
1180
            | Inherit cty -> continue @@ inherit_ cty
16✔
1181
            | Method m -> continue @@ method_ m
90✔
1182
            | InstanceVariable v -> continue @@ instance_variable v
17✔
1183
            | Constraint cst -> continue @@ constraint_ cst
8✔
1184
            | Comment `Stop ->
8✔
1185
                let rest =
1186
                  List.skip_until rest ~p:(function
1187
                    | Lang.ClassSignature.Comment `Stop -> true
8✔
1188
                    | _ -> false)
8✔
1189
                in
1190
                loop rest acc_items
8✔
1191
            | Comment (`Docs c) ->
16✔
1192
                let items = Sectioning.comment_items c.elements in
1193
                loop rest (List.rev_append items acc_items))
16✔
1194
      in
1195
      (* FIXME: use [t.self] *)
1196
      (c.doc.elements, loop c.items [])
233✔
1197

1198
    let rec class_decl (cd : Odoc_model.Lang.Class.decl) =
1199
      match cd with
182✔
1200
      | ClassType expr -> class_type_expr expr
166✔
1201
      (* TODO: factorize the following with [type_expr] *)
1202
      | Arrow (None, src, dst) ->
16✔
1203
          O.span
16✔
1204
            (type_expr ~needs_parentheses:true src
16✔
1205
            ++ O.txt " " ++ Syntax.Type.arrow)
16✔
1206
          ++ O.txt " " ++ class_decl dst
16✔
1207
      | Arrow (Some (RawOptional _ as lbl), _src, dst) ->
×
1208
          O.span
×
1209
            (O.box_hv
×
1210
            @@ label lbl ++ O.txt ":"
×
1211
               ++ tag "error" (O.txt "???")
×
1212
               ++ O.txt " " ++ Syntax.Type.arrow)
×
1213
          ++ O.sp ++ class_decl dst
×
1214
      | Arrow (Some lbl, src, dst) ->
×
1215
          O.span
×
1216
            (label lbl ++ O.txt ":"
×
1217
            ++ type_expr ~needs_parentheses:true src
×
1218
            ++ O.txt " " ++ Syntax.Type.arrow)
×
1219
          ++ O.txt " " ++ class_decl dst
×
1220

1221
    let class_ (t : Odoc_model.Lang.Class.t) =
1222
      let name = Paths.Identifier.name t.id in
166✔
1223
      let params =
166✔
1224
        match t.params with
1225
        | [] -> O.noop
142✔
1226
        | _ :: _ as params -> format_params ~delim:`brackets params ++ O.txt " "
24✔
1227
      in
1228
      let virtual_ =
1229
        if t.virtual_ then O.keyword "virtual" ++ O.txt " " else O.noop
24✔
1230
      in
1231

1232
      let source_anchor = source_anchor t.source_loc in
1233
      let cname, expansion, expansion_doc =
166✔
1234
        match t.expansion with
1235
        | None -> (O.documentedSrc @@ O.txt name, None, None)
×
1236
        | Some csig ->
166✔
1237
            let expansion_doc, items = class_signature csig in
1238
            let url = Url.Path.from_identifier t.id in
166✔
1239
            let page =
166✔
1240
              make_expansion_page ~source_anchor url
1241
                [ t.doc.elements; expansion_doc ]
1242
                items
1243
            in
1244
            ( O.documentedSrc @@ path url [ inline @@ Text name ],
166✔
1245
              Some page,
1246
              Some expansion_doc )
1247
      in
1248
      let summary =
1249
        O.txt Syntax.Type.annotation_separator ++ class_decl t.type_
166✔
1250
      in
1251
      let cd =
166✔
1252
        attach_expansion
1253
          (Syntax.Type.annotation_separator, "object", "end")
1254
          expansion summary
1255
      in
1256
      let content =
166✔
1257
        O.documentedSrc (O.keyword "class" ++ O.txt " " ++ virtual_ ++ params)
166✔
1258
        @ cname @ cd
1259
      in
1260
      let attr = [ "class" ] in
1261
      let anchor = path_to_id t.id in
1262
      let doc = Comment.synopsis ~decl_doc:t.doc.elements ~expansion_doc in
166✔
1263
      Item.Declaration { attr; anchor; doc; content; source_anchor }
1264

1265
    let class_type (t : Odoc_model.Lang.ClassType.t) =
1266
      let name = Paths.Identifier.name t.id in
67✔
1267
      let params = format_params ~delim:`brackets t.params in
67✔
1268
      let virtual_ =
67✔
1269
        if t.virtual_ then O.keyword "virtual" ++ O.txt " " else O.noop
8✔
1270
      in
1271
      let source_anchor = source_anchor t.source_loc in
1272
      let cname, expansion, expansion_doc =
67✔
1273
        match t.expansion with
1274
        | None -> (O.documentedSrc @@ O.txt name, None, None)
×
1275
        | Some csig ->
67✔
1276
            let url = Url.Path.from_identifier t.id in
1277
            let expansion_doc, items = class_signature csig in
67✔
1278
            let page =
67✔
1279
              make_expansion_page ~source_anchor url
1280
                [ t.doc.elements; expansion_doc ]
1281
                items
1282
            in
1283
            ( O.documentedSrc @@ path url [ inline @@ Text name ],
67✔
1284
              Some page,
1285
              Some expansion_doc )
1286
      in
1287
      let summary = O.txt " = " ++ class_type_expr t.expr in
67✔
1288
      let expr = attach_expansion (" = ", "object", "end") expansion summary in
67✔
1289
      let content =
67✔
1290
        O.documentedSrc
67✔
1291
          (O.keyword "class" ++ O.txt " " ++ O.keyword "type" ++ O.txt " "
67✔
1292
         ++ virtual_ ++ params ++ O.txt " ")
67✔
1293
        @ cname @ expr
1294
      in
1295
      let attr = [ "class-type" ] in
1296
      let anchor = path_to_id t.id in
1297
      let doc = Comment.synopsis ~decl_doc:t.doc.elements ~expansion_doc in
67✔
1298
      Item.Declaration { attr; anchor; doc; content; source_anchor }
1299
  end
1300

1301
  open Class
1302

1303
  module Module : sig
1304
    val signature : Lang.Signature.t -> Comment.Comment.elements * Item.t list
1305
    (** Returns [header_doc, content]. *)
1306
  end = struct
1307
    let internal_module m =
1308
      let open Lang.Module in
1,924✔
1309
      match m.id.iv with
1310
      | `Module (_, name) when ModuleName.is_hidden name -> true
81✔
1311
      | _ -> false
1,843✔
1312

1313
    let internal_type t =
1314
      let open Lang.TypeDecl in
2,969✔
1315
      match t.id.iv with
1316
      | `Type (_, name) when TypeName.is_hidden name -> true
1✔
1317
      | _ -> false
2,968✔
1318

1319
    let internal_value v =
1320
      let open Lang.Value in
1,018✔
1321
      match v.id.iv with
1322
      | `Value (_, name) when ValueName.is_hidden name -> true
106✔
1323
      | _ -> false
912✔
1324

1325
    let internal_module_type t =
1326
      let open Lang.ModuleType in
1,361✔
1327
      match t.id.iv with
1328
      | `ModuleType (_, name) when ModuleTypeName.is_hidden name -> true
×
1329
      | _ -> false
1,361✔
1330

1331
    let internal_module_substitution t =
1332
      let open Lang.ModuleSubstitution in
8✔
1333
      match t.id.iv with
1334
      | `Module (_, name) when ModuleName.is_hidden name -> true
×
1335
      | _ -> false
8✔
1336

1337
    let internal_module_type_substitution t =
1338
      let open Lang.ModuleTypeSubstitution in
8✔
1339
      match t.id.iv with
1340
      | `ModuleType (_, name) when ModuleTypeName.is_hidden name -> true
×
1341
      | _ -> false
8✔
1342

1343
    let rec signature (s : Lang.Signature.t) =
1344
      let rec loop l acc_items =
3,582✔
1345
        match l with
12,543✔
1346
        | [] -> List.rev acc_items
3,582✔
1347
        | item :: rest -> (
8,961✔
1348
            let continue (item : Item.t) = loop rest (item :: acc_items) in
7,838✔
1349
            match (item : Lang.Signature.item) with
1350
            | Module (_, m) when internal_module m -> loop rest acc_items
81✔
1351
            | Type (_, t) when internal_type t -> loop rest acc_items
1✔
1352
            | Value v when internal_value v -> loop rest acc_items
106✔
1353
            | ModuleType m when internal_module_type m -> loop rest acc_items
×
1354
            | ModuleSubstitution m when internal_module_substitution m ->
8✔
1355
                loop rest acc_items
×
1356
            | ModuleTypeSubstitution m when internal_module_type_substitution m
8✔
1357
              ->
1358
                loop rest acc_items
×
1359
            | ModuleTypeSubstitution m -> continue @@ module_type_substitution m
8✔
1360
            | Module (_, m) -> continue @@ module_ m
1,843✔
1361
            | ModuleType m -> continue @@ module_type m
1,361✔
1362
            | Class (_, c) -> continue @@ class_ c
166✔
1363
            | ClassType (_, c) -> continue @@ class_type c
67✔
1364
            | Include m -> continue @@ include_ m
290✔
1365
            | ModuleSubstitution m -> continue @@ module_substitution m
8✔
1366
            | TypeSubstitution t ->
23✔
1367
                continue @@ type_decl ~is_substitution:true (Ordinary, t)
23✔
1368
            | Type (r, t) -> continue @@ type_decl (r, t)
2,968✔
1369
            | TypExt e -> continue @@ extension e
126✔
1370
            | Exception e -> continue @@ exn e
66✔
1371
            | Value v -> continue @@ value v
912✔
1372
            | Open o ->
82✔
1373
                let items = Sectioning.comment_items o.doc.elements in
1374
                loop rest (List.rev_append items acc_items)
82✔
1375
            | Comment `Stop ->
57✔
1376
                let rest =
1377
                  List.skip_until rest ~p:(function
1378
                    | Lang.Signature.Comment `Stop -> true
49✔
1379
                    | _ -> false)
65✔
1380
                in
1381
                loop rest acc_items
57✔
1382
            | Comment (`Docs c) ->
796✔
1383
                let items = Sectioning.comment_items c.elements in
1384
                loop rest (List.rev_append items acc_items))
796✔
1385
      in
1386
      ((Lang.extract_signature_doc s).elements, loop s.items [])
3,582✔
1387

1388
    and functor_parameter :
1389
        Odoc_model.Lang.FunctorParameter.parameter -> DocumentedSrc.t =
1390
     fun arg ->
1391
      let open Odoc_model.Lang.FunctorParameter in
211✔
1392
      let name = Paths.Identifier.name arg.id in
1393
      let render_ty = arg.expr in
211✔
1394
      let modtyp =
1395
        mty_in_decl (arg.id :> Paths.Identifier.Signature.t) render_ty
1396
      in
1397
      let modname, mod_decl =
211✔
1398
        match expansion_of_module_type_expr arg.expr with
1399
        | None ->
×
1400
            let modname = O.txt (Paths.Identifier.name arg.id) in
×
1401
            (modname, O.documentedSrc modtyp)
×
1402
        | Some (expansion_doc, items) ->
211✔
1403
            let url = Url.Path.from_identifier arg.id in
1404
            let modname = path url [ inline @@ Text name ] in
211✔
1405
            let type_with_expansion =
211✔
1406
              let content =
1407
                make_expansion_page ~source_anchor:None url [ expansion_doc ]
1408
                  items
1409
              in
1410
              let summary = O.render modtyp in
211✔
1411
              let status = `Default in
211✔
1412
              let expansion =
1413
                O.documentedSrc
211✔
1414
                  (O.txt Syntax.Type.annotation_separator ++ O.keyword "sig")
211✔
1415
                @ DocumentedSrc.[ Subpage { content; status } ]
1416
                @ O.documentedSrc (O.keyword "end")
211✔
1417
              in
1418
              DocumentedSrc.
1419
                [
1420
                  Alternative
1421
                    (Expansion { status = `Default; summary; url; expansion });
1422
                ]
1423
            in
1424
            (modname, type_with_expansion)
1425
      in
1426
      O.documentedSrc (O.keyword "module" ++ O.txt " ")
211✔
1427
      @ O.documentedSrc modname @ mod_decl
211✔
1428

1429
    and module_substitution (t : Odoc_model.Lang.ModuleSubstitution.t) =
1430
      let name = Paths.Identifier.name t.id in
8✔
1431
      let path = Link.from_path (t.manifest :> Paths.Path.t) in
8✔
1432
      let content =
8✔
1433
        O.documentedSrc
1434
          (O.keyword "module" ++ O.txt " " ++ O.txt name ++ O.txt " :=" ++ O.sp
8✔
1435
         ++ path)
8✔
1436
      in
1437
      let attr = [ "module-substitution" ] in
8✔
1438
      let anchor = path_to_id t.id in
1439
      let doc = Comment.to_ir t.doc.elements in
8✔
1440
      Item.Declaration { attr; anchor; doc; content; source_anchor = None }
8✔
1441

1442
    and module_type_substitution (t : Odoc_model.Lang.ModuleTypeSubstitution.t)
1443
        =
1444
      let prefix =
8✔
1445
        O.keyword "module" ++ O.txt " " ++ O.keyword "type" ++ O.txt " "
8✔
1446
      in
1447
      let source_anchor = None in
8✔
1448
      let modname = Paths.Identifier.name t.id in
1449
      let modname, expansion_doc, mty =
8✔
1450
        module_type_manifest ~subst:true ~source_anchor modname t.id
1451
          t.doc.elements (Some t.manifest) prefix
1452
      in
1453
      let content =
8✔
1454
        O.documentedSrc (prefix ++ modname)
8✔
1455
        @ mty
1456
        @ O.documentedSrc
8✔
1457
            (if Syntax.Mod.close_tag_semicolon then O.txt ";" else O.noop)
×
1458
      in
1459
      let attr = [ "module-type" ] in
1460
      let anchor = path_to_id t.id in
1461
      let doc = Comment.synopsis ~decl_doc:t.doc.elements ~expansion_doc in
8✔
1462
      Item.Declaration { attr; anchor; doc; content; source_anchor }
1463

1464
    and simple_expansion :
1465
        Odoc_model.Lang.ModuleType.simple_expansion ->
1466
        Comment.Comment.elements * Item.t list =
1467
     fun t ->
1468
      let rec extract_functor_params
2,933✔
1469
          (f : Odoc_model.Lang.ModuleType.simple_expansion) =
1470
        match f with
3,152✔
1471
        | Signature sg -> (None, sg)
2,933✔
1472
        | Functor (p, expansion) ->
219✔
1473
            let add_to params =
1474
              match p with Unit -> params | Named p -> p :: params
8✔
1475
            in
1476
            let params, sg = extract_functor_params expansion in
1477
            let params = match params with None -> [] | Some p -> p in
36✔
1478
            (Some (add_to params), sg)
219✔
1479
      in
1480
      match extract_functor_params t with
1481
      | None, sg -> signature sg
2,750✔
1482
      | Some params, sg ->
183✔
1483
          let sg_doc, content = signature sg in
1484
          let params =
183✔
1485
            let decl_of_arg arg =
1486
              let content = functor_parameter arg in
211✔
1487
              let attr = [ "parameter" ] in
211✔
1488
              let anchor =
1489
                Some (Url.Anchor.from_identifier (arg.id :> Paths.Identifier.t))
211✔
1490
              in
1491
              let doc = [] in
1492
              [
1493
                Item.Declaration
1494
                  { content; anchor; attr; doc; source_anchor = None };
1495
              ]
1496
            in
1497
            List.concat_map decl_of_arg params
183✔
1498
          in
1499
          let prelude = mk_heading ~label:"parameters" "Parameters" :: params
183✔
1500
          and content = mk_heading ~label:"signature" "Signature" :: content in
183✔
1501
          (sg_doc, prelude @ content)
1502

1503
    and expansion_of_module_type_expr :
1504
        Odoc_model.Lang.ModuleType.expr ->
1505
        (Comment.Comment.elements * Item.t list) option =
1506
     fun t ->
1507
      let rec simple_expansion_of (t : Odoc_model.Lang.ModuleType.expr) =
3,218✔
1508
        match t with
3,437✔
1509
        | Path { p_expansion = None; _ }
379✔
1510
        | TypeOf { t_expansion = None; _ }
8✔
1511
        | With { w_expansion = None; _ }
×
1512
        | Strengthen { s_expansion = None; _ } ->
×
1513
            None
1514
        | Path { p_expansion = Some e; _ }
426✔
1515
        | TypeOf { t_expansion = Some e; _ }
56✔
1516
        | With { w_expansion = Some e; _ }
218✔
1517
        | Strengthen { s_expansion = Some e; _ } ->
×
1518
            Some e
1519
        | Signature sg -> Some (Signature sg)
2,131✔
1520
        | Functor (f_parameter, e) -> (
219✔
1521
            match simple_expansion_of e with
1522
            | Some e -> Some (Functor (f_parameter, e))
211✔
1523
            | None -> None)
8✔
1524
      in
1525
      match simple_expansion_of t with
1526
      | None -> None
387✔
1527
      | Some e -> Some (simple_expansion e)
2,831✔
1528

1529
    and module_ : Odoc_model.Lang.Module.t -> Item.t =
1530
     fun t ->
1531
      let modname = Paths.Identifier.name t.id in
1,843✔
1532
      let expansion =
1,843✔
1533
        match t.type_ with
1534
        | Alias (_, Some e) -> Some (simple_expansion e)
102✔
1535
        | Alias (_, None) -> None
186✔
1536
        | ModuleType e -> expansion_of_module_type_expr e
1,555✔
1537
      in
1538
      let source_anchor = source_anchor t.source_loc in
1539
      let modname, status, expansion, expansion_doc =
1,843✔
1540
        match expansion with
1541
        | None -> (O.txt modname, `Default, None, None)
322✔
1542
        | Some (expansion_doc, items) ->
1,521✔
1543
            let status =
1544
              match t.type_ with
1545
              | ModuleType (Signature _) -> `Inline
987✔
1546
              | _ -> `Default
534✔
1547
            in
1548
            let url = Url.Path.from_identifier t.id in
1549
            let link = path url [ inline @@ Text modname ] in
1,521✔
1550
            let page =
1,521✔
1551
              make_expansion_page ~source_anchor url
1552
                [ t.doc.elements; expansion_doc ]
1553
                items
1554
            in
1555
            (link, status, Some page, Some expansion_doc)
1,521✔
1556
      in
1557
      let intro = O.keyword "module" ++ O.txt " " ++ modname in
1,843✔
1558
      let summary = O.ignore intro ++ mdexpr_in_decl t.id t.type_ in
1,843✔
1559
      let modexpr =
1,843✔
1560
        attach_expansion ~status
1561
          (Syntax.Type.annotation_separator, "sig", "end")
1562
          expansion summary
1563
      in
1564
      let content =
1,843✔
1565
        O.documentedSrc intro @ modexpr
1,843✔
1566
        @ O.documentedSrc
1,843✔
1567
            (if Syntax.Mod.close_tag_semicolon then O.txt ";" else O.noop)
×
1568
      in
1569
      let attr = [ "module" ] in
1570
      let anchor = path_to_id t.id in
1571
      let doc = Comment.synopsis ~decl_doc:t.doc.elements ~expansion_doc in
1,843✔
1572
      Item.Declaration { attr; anchor; doc; content; source_anchor }
1573

1574
    and simple_expansion_in_decl (base : Paths.Identifier.Module.t) se =
1575
      let rec ty_of_se :
102✔
1576
          Lang.ModuleType.simple_expansion -> Lang.ModuleType.expr = function
1577
        | Signature sg -> Signature sg
102✔
1578
        | Functor (arg, sg) -> Functor (arg, ty_of_se sg)
×
1579
      in
1580
      mty_in_decl (base :> Paths.Identifier.Signature.t) (ty_of_se se)
102✔
1581

1582
    and mdexpr_in_decl (base : Paths.Identifier.Module.t) md =
1583
      let sig_dotdotdot =
1,843✔
1584
        O.txt Syntax.Type.annotation_separator
1,843✔
1585
        ++ O.cut ++ Syntax.Mod.open_tag ++ O.txt " ... " ++ Syntax.Mod.close_tag
1,843✔
1586
      in
1587
      match md with
1,843✔
1588
      | Alias (_, Some se) -> simple_expansion_in_decl base se
102✔
1589
      | Alias (p, _) when not Paths.Path.(is_hidden (p :> t)) ->
186✔
1590
          O.txt " =" ++ O.sp ++ mdexpr md
186✔
1591
      | Alias _ -> sig_dotdotdot
×
1592
      | ModuleType mt -> mty_in_decl (base :> Paths.Identifier.Signature.t) mt
1,555✔
1593

1594
    and mdexpr : Odoc_model.Lang.Module.decl -> text = function
1595
      | Alias (mod_path, _) -> Link.from_path (mod_path :> Paths.Path.t)
242✔
1596
      | ModuleType mt -> mty mt
×
1597

1598
    and module_type_manifest ~subst ~source_anchor modname id doc manifest
1599
        prefix =
1600
      let expansion =
1,369✔
1601
        match manifest with
1602
        | None -> None
128✔
1603
        | Some e -> expansion_of_module_type_expr e
1,241✔
1604
      in
1605
      let modname, expansion, expansion_doc =
1606
        match expansion with
1607
        | None -> (O.txt modname, None, None)
379✔
1608
        | Some (expansion_doc, items) ->
990✔
1609
            let url = Url.Path.from_identifier id in
1610
            let link = path url [ inline @@ Text modname ] in
990✔
1611
            let page =
990✔
1612
              make_expansion_page ~source_anchor url [ doc; expansion_doc ]
1613
                items
1614
            in
1615
            (link, Some page, Some expansion_doc)
990✔
1616
      in
1617
      let summary =
1618
        match manifest with
1619
        | None -> O.noop
128✔
1620
        | Some expr ->
1,241✔
1621
            O.ignore (prefix ++ modname)
1,241✔
1622
            ++ (if subst then O.txt " :=" ++ O.sp else O.txt " =" ++ O.sp)
8✔
1623
            ++ mty expr
1,241✔
1624
      in
1625
      ( modname,
1626
        expansion_doc,
1627
        attach_expansion (" = ", "sig", "end") expansion summary )
1,369✔
1628

1629
    and module_type (t : Odoc_model.Lang.ModuleType.t) =
1630
      let prefix =
1,361✔
1631
        O.keyword "module" ++ O.txt " " ++ O.keyword "type" ++ O.txt " "
1,361✔
1632
      in
1633
      let modname = Paths.Identifier.name t.id in
1,361✔
1634
      let source_anchor = source_anchor t.source_loc in
1,361✔
1635
      let modname, expansion_doc, mty =
1,361✔
1636
        module_type_manifest ~subst:false ~source_anchor modname t.id
1637
          t.doc.elements t.expr prefix
1638
      in
1639
      let content =
1,361✔
1640
        O.documentedSrc (prefix ++ modname)
1,361✔
1641
        @ mty
1642
        @ O.documentedSrc
1,361✔
1643
            (if Syntax.Mod.close_tag_semicolon then O.txt ";" else O.noop)
×
1644
      in
1645
      let attr = [ "module-type" ] in
1646
      let anchor = path_to_id t.id in
1647
      let doc = Comment.synopsis ~decl_doc:t.doc.elements ~expansion_doc in
1,361✔
1648
      Item.Declaration { attr; anchor; doc; content; source_anchor }
1649

1650
    and umty_hidden : Odoc_model.Lang.ModuleType.U.expr -> bool = function
1651
      | Path p -> Paths.Path.(is_hidden (p :> t))
403✔
1652
      | With (_, expr) -> umty_hidden expr
25✔
1653
      | TypeOf (ModPath m, _) | TypeOf (StructInclude m, _) ->
42✔
1654
          Paths.Path.(is_hidden (m :> t))
1655
      | Signature _ -> false
14✔
1656
      | Strengthen (expr, p, _) ->
×
1657
          umty_hidden expr || Paths.Path.(is_hidden (p :> t))
×
1658

1659
    and mty_hidden : Odoc_model.Lang.ModuleType.expr -> bool = function
1660
      | Path { p_path = mty_path; _ } -> Paths.Path.(is_hidden (mty_path :> t))
837✔
1661
      | With { w_expr; _ } -> umty_hidden w_expr
218✔
1662
      | TypeOf { t_desc = ModPath m; _ }
48✔
1663
      | TypeOf { t_desc = StructInclude m; _ } ->
16✔
1664
          Paths.Path.(is_hidden (m :> t))
1665
      | _ -> false
2,297✔
1666

1667
    and mty_with subs expr =
1668
      umty expr ++ O.sp ++ O.keyword "with" ++ O.txt " "
238✔
1669
      ++ O.list
238✔
1670
           ~sep:(O.cut ++ O.txt " " ++ O.keyword "and" ++ O.txt " ")
238✔
1671
           ~f:(fun x -> O.span (substitution x))
279✔
1672
           subs
1673

1674
    and mty_strengthen expr path =
1675
      umty expr ++ O.sp ++ O.keyword "with" ++ O.txt " "
×
1676
      ++ Link.from_path (path :> Paths.Path.t)
×
1677

1678
    and mty_typeof t_desc =
1679
      match t_desc with
154✔
1680
      | Odoc_model.Lang.ModuleType.ModPath m ->
90✔
1681
          O.keyword "module" ++ O.txt " " ++ O.keyword "type" ++ O.txt " "
90✔
1682
          ++ O.keyword "of" ++ O.txt " "
90✔
1683
          ++ Link.from_path (m :> Paths.Path.t)
90✔
1684
      | StructInclude m ->
64✔
1685
          O.keyword "module" ++ O.txt " " ++ O.keyword "type" ++ O.txt " "
64✔
1686
          ++ O.keyword "of" ++ O.txt " " ++ O.keyword "struct" ++ O.txt " "
64✔
1687
          ++ O.keyword "include" ++ O.txt " "
64✔
1688
          ++ Link.from_path (m :> Paths.Path.t)
64✔
1689
          ++ O.txt " " ++ O.keyword "end"
64✔
1690

1691
    and is_elidable_with_u : Odoc_model.Lang.ModuleType.U.expr -> bool =
1692
      function
1693
      | Path _ -> false
213✔
1694
      | Signature _ -> true
5✔
1695
      | With (_, expr) -> is_elidable_with_u expr
×
1696
      | TypeOf _ -> false
25✔
1697
      | Strengthen (expr, _, _) -> is_elidable_with_u expr
×
1698

1699
    and umty : Odoc_model.Lang.ModuleType.U.expr -> text =
1700
     fun m ->
1701
      match m with
527✔
1702
      | Path p -> Link.from_path (p :> Paths.Path.t)
403✔
1703
      | Signature _ ->
9✔
1704
          Syntax.Mod.open_tag ++ O.txt " ... " ++ Syntax.Mod.close_tag
9✔
1705
      | With (_, expr) when is_elidable_with_u expr ->
25✔
1706
          Syntax.Mod.open_tag ++ O.txt " ... " ++ Syntax.Mod.close_tag
5✔
1707
      | With (subs, expr) -> mty_with subs expr
20✔
1708
      | TypeOf (t_desc, _) -> mty_typeof t_desc
90✔
1709
      | Strengthen (expr, _, _) when is_elidable_with_u expr ->
×
1710
          Syntax.Mod.open_tag ++ O.txt " ... " ++ Syntax.Mod.close_tag
×
1711
      | Strengthen (expr, p, _) -> mty_strengthen expr (p :> Paths.Path.t)
×
1712

1713
    and mty : Odoc_model.Lang.ModuleType.expr -> text =
1714
     fun m ->
1715
      if mty_hidden m then
3,416✔
1716
        Syntax.Mod.open_tag ++ O.txt " ... " ++ Syntax.Mod.close_tag
×
1717
      else
1718
        match m with
3,416✔
1719
        | Path { p_path = mty_path; _ } ->
837✔
1720
            Link.from_path (mty_path :> Paths.Path.t)
1721
        | Functor (Unit, expr) ->
×
1722
            (if Syntax.Mod.functor_keyword then O.keyword "functor" else O.noop)
×
1723
            ++ O.span (O.txt " () " ++ Syntax.Type.arrow)
×
1724
            ++ O.sp ++ mty expr
×
1725
        | Functor (Named arg, expr) ->
48✔
1726
            let arg_expr = arg.expr in
1727
            let stop_before = expansion_of_module_type_expr arg_expr = None in
48✔
1728
            let name =
1729
              let open Odoc_model.Lang.FunctorParameter in
1730
              let name = Paths.Identifier.name arg.id in
1731
              let href =
48✔
1732
                Url.from_identifier ~stop_before (arg.id :> Paths.Identifier.t)
1733
              in
1734
              resolved href [ inline @@ Text name ]
48✔
1735
            in
1736
            (if Syntax.Mod.functor_keyword then O.keyword "functor" else O.noop)
×
1737
            ++ (O.box_hv @@ O.span
48✔
1738
               @@ O.txt " (" ++ name
48✔
1739
                  ++ O.txt Syntax.Type.annotation_separator
48✔
1740
                  ++ mty arg_expr ++ O.txt ")" ++ O.txt " " ++ Syntax.Type.arrow
48✔
1741
               )
1742
            ++ O.sp ++ mty expr
48✔
1743
        | With { w_expr; _ } when is_elidable_with_u w_expr ->
218✔
1744
            Syntax.Mod.open_tag ++ O.txt " ... " ++ Syntax.Mod.close_tag
×
1745
        | With { w_substitutions; w_expr; _ } ->
218✔
1746
            O.box_hv @@ mty_with w_substitutions w_expr
218✔
1747
        | TypeOf { t_desc; _ } -> mty_typeof t_desc
64✔
1748
        | Signature _ ->
2,249✔
1749
            Syntax.Mod.open_tag ++ O.txt " ... " ++ Syntax.Mod.close_tag
2,249✔
1750
        | Strengthen { s_expr; _ } when is_elidable_with_u s_expr ->
×
1751
            Syntax.Mod.open_tag ++ O.txt " ... " ++ Syntax.Mod.close_tag
×
1752
        | Strengthen { s_expr; s_path; _ } ->
×
1753
            O.box_hv @@ mty_strengthen s_expr (s_path :> Paths.Path.t)
×
1754

1755
    and mty_in_decl :
1756
        Paths.Identifier.Signature.t -> Odoc_model.Lang.ModuleType.expr -> text
1757
        =
1758
     fun base -> function
1759
      | (Path _ | Signature _ | With _ | TypeOf _ | Strengthen _) as m ->
×
1760
          O.txt Syntax.Type.annotation_separator ++ O.cut ++ mty m
1,868✔
1761
      | Functor _ as m when not Syntax.Mod.functor_contraction ->
171✔
1762
          O.txt Syntax.Type.annotation_separator ++ O.cut ++ mty m
×
1763
      | Functor (arg, expr) ->
171✔
1764
          let text_arg =
1765
            match arg with
1766
            | Unit -> O.txt "()"
8✔
1767
            | Named arg ->
163✔
1768
                let arg_expr = arg.expr in
1769
                let stop_before =
1770
                  expansion_of_module_type_expr arg_expr = None
163✔
1771
                in
1772
                let name =
1773
                  let open Odoc_model.Lang.FunctorParameter in
1774
                  let name = Paths.Identifier.name arg.id in
1775
                  let href =
163✔
1776
                    Url.from_identifier ~stop_before
1777
                      (arg.id :> Paths.Identifier.t)
1778
                  in
1779
                  resolved href [ inline @@ Text name ]
163✔
1780
                in
1781
                O.box_hv
163✔
1782
                @@ O.txt "(" ++ name
163✔
1783
                   ++ O.txt Syntax.Type.annotation_separator
163✔
1784
                   ++ O.cut ++ mty arg.expr ++ O.txt ")"
163✔
1785
          in
1786
          O.sp ++ text_arg ++ mty_in_decl base expr
171✔
1787

1788
    (* TODO : Centralize the list juggling for type parameters *)
1789
    and type_expr_in_subst td typath =
1790
      let typath = Link.from_fragment typath in
151✔
1791
      match td.Lang.TypeDecl.Equation.params with
151✔
1792
      | [] -> typath
125✔
1793
      | l -> Syntax.Type.handle_substitution_params typath (format_params l)
26✔
1794

1795
    and substitution : Odoc_model.Lang.ModuleType.substitution -> text =
1796
      function
1797
      | ModuleEq (frag_mod, md) ->
56✔
1798
          O.box_hv
1799
          @@ O.keyword "module" ++ O.txt " "
56✔
1800
             ++ Link.from_fragment (frag_mod :> Paths.Fragment.leaf)
56✔
1801
             ++ O.txt " =" ++ O.sp ++ mdexpr md
56✔
1802
      | ModuleTypeEq (frag_mty, md) ->
32✔
1803
          O.box_hv
1804
          @@ O.keyword "module" ++ O.txt " " ++ O.keyword "type" ++ O.txt " "
32✔
1805
             ++ Link.from_fragment (frag_mty :> Paths.Fragment.leaf)
32✔
1806
             ++ O.txt " =" ++ O.sp ++ mty md
32✔
1807
      | TypeEq (frag_typ, td) ->
104✔
1808
          O.box_hv
1809
          @@ O.keyword "type" ++ O.txt " "
104✔
1810
             ++ type_expr_in_subst td (frag_typ :> Paths.Fragment.leaf)
104✔
1811
             ++ fst (format_manifest td)
104✔
1812
             ++ format_constraints
104✔
1813
                  td.Odoc_model.Lang.TypeDecl.Equation.constraints
1814
      | ModuleSubst (frag_mod, mod_path) ->
24✔
1815
          O.box_hv
1816
          @@ O.keyword "module" ++ O.txt " "
24✔
1817
             ++ Link.from_fragment (frag_mod :> Paths.Fragment.leaf)
24✔
1818
             ++ O.txt " :=" ++ O.sp
24✔
1819
             ++ Link.from_path (mod_path :> Paths.Path.t)
24✔
1820
      | ModuleTypeSubst (frag_mty, md) ->
16✔
1821
          O.box_hv
1822
          @@ O.keyword "module" ++ O.txt " " ++ O.keyword "type" ++ O.txt " "
16✔
1823
             ++ Link.from_fragment (frag_mty :> Paths.Fragment.leaf)
16✔
1824
             ++ O.txt " :=" ++ O.sp ++ mty md
16✔
1825
      | TypeSubst (frag_typ, td) -> (
47✔
1826
          O.box_hv
1827
          @@ O.keyword "type" ++ O.txt " "
47✔
1828
             ++ type_expr_in_subst td (frag_typ :> Paths.Fragment.leaf)
47✔
1829
             ++ O.txt " :=" ++ O.sp
47✔
1830
             ++
47✔
1831
             match td.Lang.TypeDecl.Equation.manifest with
1832
             | None -> assert false (* cf loader/cmti *)
1833
             | Some te -> type_expr te)
47✔
1834

1835
    and include_ (t : Odoc_model.Lang.Include.t) =
1836
      let decl_hidden =
290✔
1837
        match t.decl with
1838
        | Alias p -> Paths.Path.(is_hidden (p :> t))
×
1839
        | ModuleType mty -> umty_hidden mty
290✔
1840
      in
1841
      let status = if decl_hidden then `Inline else t.status in
1✔
1842

1843
      let _, content = signature t.expansion.content in
1844
      let summary =
290✔
1845
        if decl_hidden then O.render (O.keyword "include" ++ O.txt " ...")
1✔
1846
        else
1847
          let include_decl =
289✔
1848
            match t.decl with
1849
            | Odoc_model.Lang.Include.Alias mod_path ->
×
1850
                Link.from_path (mod_path :> Paths.Path.t)
×
1851
            | ModuleType mt -> umty mt
289✔
1852
          in
1853
          O.render
289✔
1854
            (O.keyword "include" ++ O.txt " " ++ include_decl
289✔
1855
            ++ if Syntax.Mod.include_semicolon then O.keyword ";" else O.noop)
×
1856
      in
1857
      let content = { Include.content; status; summary } in
1858
      let attr = [ "include" ] in
1859
      let anchor = None in
1860
      let doc =
1861
        (* Documentation attached to includes behave differently than other
1862
           declarations, which show only the synopsis. We can't only show the
1863
           synopsis because no page is generated to render it and we'd loose
1864
           the full documentation.
1865
           The documentation from the expansion is not used. *)
1866
        Comment.to_ir t.doc.elements
1867
      in
1868
      Item.Include { attr; anchor; doc; content; source_anchor = None }
290✔
1869
  end
1870

1871
  open Module
1872

1873
  module Page : sig
1874
    val compilation_unit : Lang.Compilation_unit.t -> Document.t
1875

1876
    val page : Lang.Page.t -> Document.t
1877

1878
    val implementation :
1879
      Lang.Implementation.t ->
1880
      Syntax_highlighter.infos ->
1881
      string ->
1882
      Document.t list
1883
  end = struct
1884
    let pack : Lang.Compilation_unit.Packed.t -> Item.t list =
1885
     fun t ->
1886
      let f x =
×
1887
        let id = x.Lang.Compilation_unit.Packed.id in
×
1888
        let modname = Paths.Identifier.name id in
1889
        let md_def =
×
1890
          O.keyword "module" ++ O.txt " " ++ O.txt modname ++ O.txt " = "
×
1891
          ++ Link.from_path (x.path :> Paths.Path.t)
×
1892
        in
1893
        let content = O.documentedSrc md_def in
×
1894
        let anchor =
×
1895
          Some (Url.Anchor.from_identifier (id :> Paths.Identifier.t))
×
1896
        in
1897
        let attr = [ "modules" ] in
1898
        let doc = [] in
1899
        let decl = { Item.anchor; content; attr; doc; source_anchor = None } in
1900
        Item.Declaration decl
1901
      in
1902
      List.map f t
1903

1904
    let compilation_unit (t : Odoc_model.Lang.Compilation_unit.t) =
1905
      let url = Url.Path.from_identifier t.id in
359✔
1906
      let unit_doc, items =
359✔
1907
        match t.content with
1908
        | Module sign -> signature sign
359✔
1909
        | Pack packed -> ([], pack packed)
×
1910
      in
1911
      let source_anchor = source_anchor t.source_loc in
1912
      let page = make_expansion_page ~source_anchor url [ unit_doc ] items in
359✔
1913
      Document.Page page
359✔
1914

1915
    let page (t : Odoc_model.Lang.Page.t) =
1916
      (*let name =
1917
          match t.name.iv with `Page (_, name) | `LeafPage (_, name) -> name
1918
        in*)
1919
      (*let title = Odoc_model.Names.PageName.to_string name in*)
1920
      let url = Url.Path.from_identifier t.name in
69✔
1921
      let preamble, items = Sectioning.docs t.content.elements in
69✔
1922
      let source_anchor = None in
69✔
1923
      Document.Page { Page.preamble; items; url; source_anchor }
1924

1925
    let implementation (v : Odoc_model.Lang.Implementation.t) syntax_info
1926
        source_code =
1927
      match v.id with
28✔
1928
      | None -> []
×
1929
      | Some id ->
28✔
1930
          [
1931
            Document.Source_page
1932
              (Source_page.source id syntax_info v.source_info source_code);
28✔
1933
          ]
1934
  end
1935

1936
  include Page
1937

1938
  let type_expr = type_expr
1939

1940
  let record = record
1941

1942
  let unboxed_record = unboxed_record
1943
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