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

ocaml / odoc / 3188

21 May 2026 08:16AM UTC coverage: 71.012% (-0.1%) from 71.138%
3188

Pull #1420

github

web-flow
Merge 1f44d77e6 into a3b579e48
Pull Request #1420: OxCaml: Support for modalities

36 of 74 new or added lines in 7 files covered. (48.65%)

90 existing lines in 3 files now uncovered.

10453 of 14720 relevant lines covered (71.01%)

5868.7 hits per line

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

80.4
/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

100
    val render_fragment_any : Paths.Fragment.t -> string
101
  end = struct
102
    open Paths
103

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

152
    let dot prefix suffix = prefix ^ "." ^ suffix
49✔
153

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

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

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

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

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

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

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

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

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

307
  module Modalities : sig
308
    val format : Odoc_model.Lang.Modalities.t -> text
309
  end = struct
310
    let format modalities =
311
      match modalities with
1,492✔
312
      | [] -> O.noop
1,492✔
NEW
313
      | mods ->
×
NEW
314
          O.txt " " ++ O.txt "@@" ++ O.txt " "
×
NEW
315
          ++ O.txt (String.concat ~sep:" " mods)
×
316
  end
317

318
  module Type_expression : sig
319
    val type_expr : ?needs_parentheses:bool -> Lang.TypeExpr.t -> text
320

321
    val format_type_path :
322
      delim:[ `parens | `brackets ] -> Lang.TypeExpr.t list -> text -> text
323

324
    val kind_annotation :
325
      ?needs_parentheses:bool -> Odoc_model.Lang.Kind.t -> text
326

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

387
    and te_object (t : Odoc_model.Lang.TypeExpr.Object.t) =
388
      let fields =
80✔
389
        O.list
390
          ~sep:(O.sp ++ O.txt Syntax.Obj.field_separator)
80✔
391
          t.fields
392
          ~f:(function
393
            | Odoc_model.Lang.TypeExpr.Object.Method { name; type_ } ->
120✔
394
                O.box_hv_no_indent
395
                @@ O.txt (name ^ Syntax.Type.annotation_separator)
120✔
396
                   ++ O.cut ++ type_expr type_
120✔
UNCOV
397
            | Inherit type_ -> O.box_hv_no_indent @@ type_expr type_)
×
398
      in
399
      let open_tag =
80✔
400
        if t.open_ then O.txt Syntax.Obj.open_tag_extendable
32✔
401
        else O.txt Syntax.Obj.open_tag_closed
48✔
402
      in
403
      let close_tag =
404
        if t.open_ then O.txt Syntax.Obj.close_tag_extendable
32✔
405
        else O.txt Syntax.Obj.close_tag_closed
48✔
406
      in
407
      O.span (open_tag ++ fields ++ close_tag)
80✔
408

409
    and format_type_path ~delim (params : Odoc_model.Lang.TypeExpr.t list)
410
        (path : text) : text =
411
      O.box_hv
179,533✔
412
      @@
413
      match params with
414
      | [] -> path
94,880✔
415
      | [ param ] ->
71,291✔
416
          let param = type_expr ~needs_parentheses:true param in
417
          let args =
71,291✔
418
            if Syntax.Type.parenthesize_constructor then
UNCOV
419
              O.txt "(" ++ param ++ O.txt ")"
×
420
            else param
71,291✔
421
          in
422
          Syntax.Type.handle_constructor_params path args
71,291✔
423
      | params ->
13,362✔
424
          let params = O.list params ~sep:(O.txt "," ++ O.sp) ~f:type_expr in
13,362✔
425
          let params =
13,362✔
426
            match delim with
427
            | `parens -> enclose ~l:"(" params ~r:")"
13,362✔
UNCOV
428
            | `brackets -> enclose ~l:"[" params ~r:"]"
×
429
          in
430
          Syntax.Type.handle_constructor_params path (O.box_hv params)
13,362✔
431

UNCOV
432
    and tuple ?(needs_parentheses = false) ~boxed lst =
×
433
      let opt_label = function
677✔
434
        | None -> O.noop
1,544✔
UNCOV
435
        | Some lbl -> tag "label" (O.txt lbl) ++ O.txt ":" ++ O.cut
×
436
      in
437
      let res =
438
        O.box_hv_no_indent
439
          (O.list lst ~sep:Syntax.Type.Tuple.element_separator
677✔
440
             ~f:(fun (lbl, typ) ->
441
               opt_label lbl ++ type_expr ~needs_parentheses:true typ))
1,544✔
442
      in
443
      let lparen = if boxed then "(" else "#(" in
×
UNCOV
444
      if Syntax.Type.Tuple.always_parenthesize || needs_parentheses || not boxed
×
445
      then enclose ~l:lparen res ~r:")"
131✔
446
      else res
546✔
447

NEW
448
    and kind_annotation ?(needs_parentheses = false)
×
449
        (k : Odoc_model.Lang.Kind.t) =
NEW
450
      let enclose_parens_if_needed res =
×
NEW
451
        if needs_parentheses then enclose ~l:"(" res ~r:")" else res
×
452
      in
453
      match k with
NEW
454
      | Default -> O.noop
×
NEW
455
      | Abbreviation frag ->
×
NEW
456
          O.txt (Link.render_fragment_any (frag :> Paths.Fragment.t))
×
NEW
457
      | Mod (base, modes) ->
×
458
          let res =
NEW
459
            kind_annotation ~needs_parentheses:true base
×
NEW
460
            ++ O.txt " " ++ O.keyword "mod"
×
NEW
461
            ++ O.txt (" " ^ String.concat ~sep:" " modes)
×
462
          in
NEW
463
          enclose_parens_if_needed res
×
NEW
464
      | With (base, ty, modalities) ->
×
465
          let res =
NEW
466
            kind_annotation ~needs_parentheses:true base
×
NEW
467
            ++ O.txt " " ++ O.keyword "with" ++ O.txt " " ++ type_expr ty
×
NEW
468
            ++ Modalities.format modalities
×
469
          in
NEW
470
          enclose_parens_if_needed res
×
NEW
471
      | Kind_of ty ->
×
NEW
472
          let res = O.keyword "kind_of_" ++ O.txt " " ++ type_expr ty in
×
NEW
473
          enclose_parens_if_needed res
×
NEW
474
      | Product ks ->
×
475
          let res =
NEW
476
            O.list ks ~sep:(O.txt " & ") ~f:(fun k ->
×
NEW
477
                kind_annotation ~needs_parentheses:true k)
×
478
          in
NEW
479
          enclose_parens_if_needed res
×
480

481
    and with_kind_annotation kind base =
482
      match kind with
648✔
483
      | Odoc_model.Lang.Kind.Default -> base
648✔
NEW
484
      | k -> O.txt "(" ++ base ++ O.txt " : " ++ kind_annotation k ++ O.txt ")"
×
485

486
    and type_expr ?(needs_parentheses = false) (t : Odoc_model.Lang.TypeExpr.t)
191,635✔
487
        =
488
      let enclose_parens_if_needed res =
312,108✔
489
        if needs_parentheses then enclose ~l:"(" res ~r:")" else res
831✔
490
      in
491
      match t with
492
      | Var s -> type_var (Syntax.Type.var_prefix ^ s)
9,469✔
493
      | Any -> type_var Syntax.Type.any
18✔
494
      | Alias (te, alias) ->
133✔
495
          enclose_parens_if_needed
496
            (type_expr ~needs_parentheses:true te
133✔
497
            ++ O.txt " " ++ O.keyword "as" ++ O.txt " '" ++ O.txt alias)
133✔
498
      | Arrow (None, src, dst) ->
43,802✔
499
          let res =
500
            O.span
43,802✔
501
              ((O.box_hv @@ type_expr ~needs_parentheses:true src)
43,802✔
502
              ++ O.txt " " ++ Syntax.Type.arrow)
43,802✔
503
            ++ O.sp ++ type_expr dst
43,802✔
504
            (* ++ O.end_hv *)
505
          in
506
          enclose_parens_if_needed res
43,802✔
507
      | Arrow (Some (RawOptional _ as lbl), _src, dst) ->
1✔
508
          let res =
509
            O.span
1✔
510
              (O.box_hv
1✔
511
              @@ label lbl ++ O.txt ":"
1✔
512
                 ++ tag "error" (O.txt "???")
1✔
513
                 ++ O.txt " " ++ Syntax.Type.arrow)
1✔
514
            ++ O.sp ++ type_expr dst
1✔
515
          in
516
          enclose_parens_if_needed res
1✔
517
      | Arrow (Some lbl, src, dst) ->
3,262✔
518
          let res =
519
            O.span
3,262✔
520
              ((O.box_hv
3,262✔
521
               @@ label lbl ++ O.txt ":" ++ O.cut
3,262✔
522
                  ++ (O.box_hv @@ type_expr ~needs_parentheses:true src))
3,262✔
523
              ++ O.txt " " ++ Syntax.Type.arrow)
3,262✔
524
            ++ O.sp ++ type_expr dst
3,262✔
525
          in
526
          enclose_parens_if_needed res
3,262✔
527
      | Tuple lst -> tuple ~needs_parentheses ~boxed:true lst
677✔
UNCOV
528
      | Unboxed_tuple lst -> tuple ~needs_parentheses ~boxed:false lst
×
529
      | Constr (path, args) ->
179,467✔
530
          let link = Link.from_path (path :> Paths.Path.t) in
531
          format_type_path ~delim:`parens args link
179,467✔
532
      | Polymorphic_variant v -> te_variant v
75,143✔
533
      | Object o -> te_object o
80✔
534
      | Class (path, args) ->
8✔
535
          format_type_path ~delim:`brackets args
536
            (Link.from_path (path :> Paths.Path.t))
8✔
537
      | Poly (polyvars, t) ->
24✔
538
          let format_poly_var (name, kind) =
539
            with_kind_annotation kind (O.txt ("'" ^ name))
24✔
540
          in
541
          let vars = O.list polyvars ~sep:(O.txt " ") ~f:format_poly_var in
24✔
542
          enclose_parens_if_needed @@ (vars ++ O.txt ". " ++ type_expr t)
24✔
543
      | Quote t -> O.span (O.txt "<[ " ++ O.box_hv (type_expr t) ++ O.txt " ]>")
×
UNCOV
544
      | Splice t -> O.span (O.txt "$" ++ type_expr ~needs_parentheses:true t)
×
545
      | Package pkg ->
24✔
546
          enclose ~l:"(" ~r:")"
547
            (O.keyword "module" ++ O.txt " " ++ package_path pkg)
24✔
UNCOV
548
      | Arrow_functor (lbl, m_arg, dst) ->
×
549
          let lbl =
UNCOV
550
            match lbl with None -> O.noop | Some lbl -> label lbl ++ O.txt ":"
×
551
          in
552
          let name =
553
            match m_arg.id.iv with
UNCOV
554
            | `Parameter (_, name) -> ModuleName.to_string name
×
555
          in
556
          let dst = type_expr dst in
UNCOV
557
          let pkg =
×
558
            enclose ~l:"(" ~r:")"
559
            @@ O.keyword "module" ++ O.txt " " ++ O.txt name ++ O.txt " : "
×
UNCOV
560
               ++ package_path m_arg.package
×
561
          in
UNCOV
562
          lbl ++ pkg ++ O.sp ++ Syntax.Type.arrow ++ O.sp ++ dst
×
563

564
    and package_path pkg =
565
      Link.from_path (pkg.path :> Paths.Path.t)
24✔
566
      ++
567
      match pkg.substitutions with
568
      | [] -> O.noop
16✔
569
      | fst :: lst ->
8✔
570
          O.sp
571
          ++ O.box_hv (O.keyword "with" ++ O.txt " " ++ package_subst fst)
8✔
572
          ++ O.list lst ~f:(fun s ->
8✔
573
                 O.cut
8✔
574
                 ++ (O.box_hv
8✔
575
                    @@ O.txt " " ++ O.keyword "and" ++ O.txt " "
8✔
576
                       ++ package_subst s))
8✔
577

578
    and package_subst
579
        ((frag_typ, te) : Paths.Fragment.Type.t * Odoc_model.Lang.TypeExpr.t) :
580
        text =
581
      let typath = Link.from_fragment (frag_typ :> Paths.Fragment.leaf) in
16✔
582
      O.keyword "type" ++ O.txt " " ++ typath ++ O.txt " =" ++ O.sp
16✔
583
      ++ type_expr te
16✔
584
  end
585

586
  open Type_expression
587

588
  (* Also handles constructor declarations for exceptions and extensible
589
     variants, and exposes a few helpers used in formatting classes and signature
590
     constraints. *)
591
  module Type_declaration : sig
592
    val type_decl :
593
      ?is_substitution:bool ->
594
      Lang.Signature.recursive * Lang.TypeDecl.t ->
595
      Item.t
596

597
    val extension : Lang.Extension.t -> Item.t
598

599
    val record : Lang.TypeDecl.Field.t list -> DocumentedSrc.one list
600

601
    val unboxed_record :
602
      Lang.TypeDecl.UnboxedField.t list -> DocumentedSrc.one list
603

604
    val exn : Lang.Exception.t -> Item.t
605

606
    val format_params :
607
      ?delim:[ `parens | `brackets ] -> Lang.TypeDecl.param list -> text
608

609
    val format_manifest :
610
      ?is_substitution:bool ->
611
      ?compact_variants:bool ->
612
      Lang.TypeDecl.Equation.t ->
613
      text * bool
614

615
    val format_constraints : (Lang.TypeExpr.t * Lang.TypeExpr.t) list -> text
616
  end = struct
617
    let record fields =
618
      let field mutable_ id typ modalities =
86✔
619
        let url = Url.from_identifier ~stop_before:true id in
155✔
620
        let name = Paths.Identifier.name id in
155✔
621
        let attrs = [ "def"; "record"; Url.Anchor.string_of_kind url.kind ] in
155✔
622
        let cell =
623
          O.code
624
            ((if mutable_ then O.keyword "mutable" ++ O.txt " " else O.noop)
24✔
625
            ++ O.txt name
155✔
626
            ++ O.txt Syntax.Type.annotation_separator
155✔
627
            ++ type_expr typ
155✔
628
            ++ Modalities.format modalities
155✔
629
            ++ O.txt Syntax.Type.Record.field_separator)
155✔
630
        in
631
        (url, attrs, cell)
155✔
632
      in
633
      let rows =
634
        fields
635
        |> List.map (fun fld ->
636
               let open Odoc_model.Lang.TypeDecl.Field in
155✔
637
               let url, attrs, code =
638
                 field fld.mutable_
639
                   (fld.id :> Paths.Identifier.t)
640
                   fld.type_ fld.modalities
641
               in
642
               let anchor = Some url in
155✔
643
               let doc = fld.doc.elements in
644
               let rhs = Comment.to_ir doc in
645
               let doc = if not (Comment.has_doc doc) then [] else rhs in
64✔
646
               let markers = Syntax.Comment.markers in
647
               DocumentedSrc.Documented { anchor; attrs; code; doc; markers })
648
      in
649
      let content =
86✔
650
        O.documentedSrc (O.txt "{") @ rows @ O.documentedSrc (O.txt "}")
86✔
651
      in
652
      content
653

654
    let unboxed_record fields =
655
      let field mutable_ id typ =
×
656
        let url = Url.from_identifier ~stop_before:true id in
×
657
        let name = Paths.Identifier.name id in
×
UNCOV
658
        let attrs = [ "def"; "record"; Url.Anchor.string_of_kind url.kind ] in
×
659
        let cell =
660
          (* O.td ~a:[ O.a_class ["def"; kind ] ]
661
           *   [O.a ~a:[O.a_href ("#" ^ anchor); O.a_class ["anchor"]] []
662
           *   ; *)
663
          O.code
664
            ((if mutable_ then O.keyword "mutable" ++ O.txt " " else O.noop)
×
665
            ++ O.txt name
×
666
            ++ O.txt Syntax.Type.annotation_separator
×
667
            ++ type_expr typ
×
UNCOV
668
            ++ O.txt Syntax.Type.Record.field_separator)
×
669
          (* ] *)
670
        in
UNCOV
671
        (url, attrs, cell)
×
672
      in
673
      let rows =
674
        fields
675
        |> List.map (fun fld ->
UNCOV
676
               let open Odoc_model.Lang.TypeDecl.UnboxedField in
×
677
               let url, attrs, code =
678
                 field fld.mutable_ (fld.id :> Paths.Identifier.t) fld.type_
679
               in
UNCOV
680
               let anchor = Some url in
×
681
               let doc = fld.doc.elements in
682
               let rhs = Comment.to_ir doc in
UNCOV
683
               let doc = if not (Comment.has_doc doc) then [] else rhs in
×
684
               let markers = Syntax.Comment.markers in
685
               DocumentedSrc.Documented { anchor; attrs; code; doc; markers })
686
      in
687
      let content =
×
UNCOV
688
        O.documentedSrc (O.txt "#{") @ rows @ O.documentedSrc (O.txt "}")
×
689
      in
690
      content
691

692
    let constructor :
693
        Paths.Identifier.t ->
694
        Odoc_model.Lang.TypeDecl.Constructor.argument ->
695
        Odoc_model.Lang.TypeExpr.t option ->
696
        DocumentedSrc.t =
697
     fun id args ret_type ->
698
      let name = Paths.Identifier.name id in
656✔
699
      let kind = Url.(kind id |> Anchor.string_of_kind) in
656✔
700
      let cstr = tag kind (O.txt name) in
656✔
701
      let is_gadt, ret_type =
656✔
702
        match ret_type with
703
        | None -> (false, O.noop)
520✔
704
        | Some te ->
136✔
705
            let constant = match args with Tuple [] -> true | _ -> false in
48✔
706
            let ret_type =
707
              O.txt " "
136✔
708
              ++ (if constant then O.txt ":" else Syntax.Type.GADT.arrow)
48✔
709
              ++ O.txt " " ++ type_expr te
136✔
710
            in
711
            (true, ret_type)
136✔
712
      in
713
      match args with
714
      | Tuple [] -> O.documentedSrc (cstr ++ ret_type)
289✔
715
      | Tuple lst ->
350✔
716
          let params =
717
            O.list lst ~sep:Syntax.Type.Tuple.element_separator
718
              ~f:(fun (te, mods) ->
719
                type_expr ~needs_parentheses:is_gadt te
425✔
720
                ++ Modalities.format mods)
425✔
721
          in
722
          O.documentedSrc
350✔
723
            (cstr
724
            ++ (if Syntax.Type.Variant.parenthesize_params then
350✔
UNCOV
725
                  O.txt "(" ++ params ++ O.txt ")"
×
726
                else
727
                  (if is_gadt then O.txt Syntax.Type.annotation_separator
88✔
728
                   else O.txt " " ++ O.keyword "of" ++ O.txt " ")
262✔
729
                  ++ params)
350✔
730
            ++ ret_type)
350✔
731
      | Record fields ->
17✔
732
          if is_gadt then
733
            O.documentedSrc (cstr ++ O.txt Syntax.Type.annotation_separator)
×
UNCOV
734
            @ record fields @ O.documentedSrc ret_type
×
735
          else
736
            O.documentedSrc (cstr ++ O.txt " " ++ O.keyword "of" ++ O.txt " ")
17✔
737
            @ record fields
17✔
738

739
    let variant cstrs : DocumentedSrc.t =
740
      let constructor id args res =
235✔
741
        let url = Url.from_identifier ~stop_before:true id in
436✔
742
        let attrs = [ "def"; "variant"; Url.Anchor.string_of_kind url.kind ] in
436✔
743
        let content =
744
          let doc = constructor id args res in
745
          O.documentedSrc (O.txt "| ") @ doc
436✔
746
        in
747
        (url, attrs, content)
748
      in
749
      match cstrs with
UNCOV
750
      | [] -> O.documentedSrc (O.txt "|")
×
751
      | _ :: _ ->
235✔
752
          let rows =
753
            cstrs
754
            |> List.map (fun cstr ->
755
                   let open Odoc_model.Lang.TypeDecl.Constructor in
436✔
756
                   let url, attrs, code =
757
                     constructor
758
                       (cstr.id :> Paths.Identifier.t)
759
                       cstr.args cstr.res
760
                   in
761
                   let anchor = Some url in
436✔
762
                   let doc = cstr.doc.elements in
763
                   let rhs = Comment.to_ir doc in
764
                   let doc = if not (Comment.has_doc doc) then [] else rhs in
73✔
765
                   let markers = Syntax.Comment.markers in
766
                   DocumentedSrc.Nested { anchor; attrs; code; doc; markers })
767
          in
768
          rows
235✔
769

770
    let extension_constructor (t : Odoc_model.Lang.Extension.Constructor.t) =
771
      let id = (t.id :> Paths.Identifier.t) in
154✔
772
      let url = Url.from_identifier ~stop_before:true id in
773
      let anchor = Some url in
154✔
774
      let attrs = [ "def"; "variant"; Url.Anchor.string_of_kind url.kind ] in
154✔
775
      let code = O.documentedSrc (O.txt "| ") @ constructor id t.args t.res in
154✔
776
      let doc = Comment.to_ir t.doc.elements in
777
      let markers = Syntax.Comment.markers in
154✔
778
      DocumentedSrc.Nested { anchor; attrs; code; doc; markers }
779

780
    let extension (t : Odoc_model.Lang.Extension.t) =
781
      let prefix =
126✔
782
        O.keyword "type" ++ O.txt " "
126✔
783
        ++ Link.from_path (t.type_path :> Paths.Path.t)
126✔
784
        ++ O.txt " +=" ++ O.sp
126✔
785
        ++
786
        if t.private_ then O.keyword Syntax.Type.private_keyword ++ O.sp
8✔
787
        else O.noop
118✔
788
      in
789
      let content =
126✔
790
        O.documentedSrc prefix
126✔
791
        @ List.map extension_constructor t.constructors
126✔
792
        @ O.documentedSrc
126✔
UNCOV
793
            (if Syntax.Type.type_def_semicolon then O.txt ";" else O.noop)
×
794
      in
795
      let attr = [ "type"; "extension" ] in
796
      let anchor = Some (Url.Anchor.extension_decl t) in
126✔
797
      let doc = Comment.to_ir t.doc.elements in
798
      let source_anchor =
126✔
799
        (* Take the anchor from the first constructor only for consistency with
800
           regular variants. *)
801
        match t.constructors with
802
        | hd :: _ -> source_anchor hd.source_loc
126✔
UNCOV
803
        | [] -> None
×
804
      in
805
      Item.Declaration { attr; anchor; doc; content; source_anchor }
806

807
    let exn (t : Odoc_model.Lang.Exception.t) =
808
      let cstr = constructor (t.id :> Paths.Identifier.t) t.args t.res in
66✔
809
      let content =
66✔
810
        O.documentedSrc (O.keyword "exception" ++ O.txt " ")
66✔
811
        @ cstr
812
        @ O.documentedSrc
66✔
UNCOV
813
            (if Syntax.Type.Exception.semicolon then O.txt ";" else O.noop)
×
814
      in
815
      let attr = [ "exception" ] in
816
      let anchor = path_to_id t.id in
817
      let doc = Comment.to_ir t.doc.elements in
66✔
818
      let source_anchor = source_anchor t.source_loc in
66✔
819
      Item.Declaration { attr; anchor; doc; content; source_anchor }
66✔
820

821
    let polymorphic_variant ~type_ident
822
        (t : Odoc_model.Lang.TypeExpr.Polymorphic_variant.t) =
823
      let row item =
74✔
824
        let kind_approx, cstr, doc =
156✔
825
          match item with
826
          | Odoc_model.Lang.TypeExpr.Polymorphic_variant.Type te ->
24✔
827
              ("unknown", O.documentedSrc (type_expr te), None)
24✔
828
          | Constructor { constant; name; arguments; doc; _ } -> (
132✔
829
              let cstr = "`" ^ name in
830
              ( "constructor",
831
                (match arguments with
832
                | [] -> O.documentedSrc (O.txt cstr)
58✔
833
                | _ ->
74✔
834
                    (* Multiple arguments in a polymorphic variant constructor correspond
835
                       to a conjunction of types, not a product: [`Lbl int&float].
836
                       If constant is [true], the conjunction starts with an empty type,
837
                       for instance [`Lbl &int].
838
                    *)
839
                    let wrapped_type_expr =
840
                      (* type conjunction in Reason is printed as `Lbl (t1)&(t2)` *)
841
                      if Syntax.Type.Variant.parenthesize_params then fun x ->
×
UNCOV
842
                        O.txt "(" ++ type_expr x ++ O.txt ")"
×
843
                      else fun x -> type_expr x
74✔
844
                    in
845
                    let params =
846
                      O.box_hv
847
                      @@ O.list arguments
74✔
848
                           ~sep:(O.txt " &" ++ O.sp)
74✔
849
                           ~f:wrapped_type_expr
850
                    in
851
                    let params =
74✔
UNCOV
852
                      if constant then O.txt "& " ++ params else params
×
853
                    in
854
                    O.documentedSrc
74✔
855
                      (O.txt cstr
74✔
856
                      ++
74✔
UNCOV
857
                      if Syntax.Type.Variant.parenthesize_params then params
×
858
                      else O.txt " " ++ O.keyword "of" ++ O.sp ++ params)),
74✔
859
                match doc with
860
                | { elements = []; _ } -> None
132✔
UNCOV
861
                | _ -> Some (Comment.to_ir doc.elements) ))
×
862
        in
863
        let markers = Syntax.Comment.markers in
864
        try
865
          let url = Url.Anchor.polymorphic_variant ~type_ident item in
866
          let attrs =
156✔
867
            [ "def"; "variant"; Url.Anchor.string_of_kind url.kind ]
156✔
868
          in
869
          let anchor = Some url in
870
          let code = O.documentedSrc (O.txt "| ") @ cstr in
156✔
UNCOV
871
          let doc = match doc with None -> [] | Some doc -> doc in
×
872
          DocumentedSrc.Nested { attrs; anchor; code; doc; markers }
UNCOV
873
        with Failure s ->
×
874
          Printf.eprintf "ERROR: %s\n%!" s;
UNCOV
875
          let code = O.documentedSrc (O.txt "| ") @ cstr in
×
876
          let attrs = [ "def"; kind_approx ] in
877
          let doc = [] in
878
          let anchor = None in
879
          DocumentedSrc.Nested { attrs; anchor; code; doc; markers }
880
      in
881
      let variants = List.map row t.elements in
882
      let intro, ending =
74✔
883
        match t.kind with
884
        | Fixed -> (O.documentedSrc (O.txt "[ "), O.documentedSrc (O.txt " ]"))
66✔
885
        | Open -> (O.documentedSrc (O.txt "[> "), O.documentedSrc (O.txt " ]"))
8✔
886
        | Closed [] ->
×
887
            (O.documentedSrc (O.txt "[< "), O.documentedSrc (O.txt " ]"))
×
UNCOV
888
        | Closed lst ->
×
889
            let constrs = String.concat ~sep:" " lst in
890
            ( O.documentedSrc (O.txt "[< "),
×
UNCOV
891
              O.documentedSrc (O.txt (" " ^ constrs ^ " ]")) )
×
892
      in
893
      intro @ variants @ ending
894

895
    let format_params :
896
        'row.
897
        ?delim:[ `parens | `brackets ] ->
898
        Odoc_model.Lang.TypeDecl.param list ->
899
        text =
900
     fun ?(delim = `parens) params ->
472✔
901
      let format_param_str
563✔
902
          { Odoc_model.Lang.TypeDecl.desc; variance; injectivity; kind = _ } =
903
        let desc =
624✔
904
          match desc with
905
          | Odoc_model.Lang.TypeDecl.Any -> [ "_" ]
37✔
906
          | Var s -> [ "'"; s ]
587✔
907
        in
908
        let var_desc =
909
          match variance with
910
          | None -> desc
608✔
911
          | Some Odoc_model.Lang.TypeDecl.Pos -> "+" :: desc
8✔
912
          | Some Odoc_model.Lang.TypeDecl.Neg -> "-" :: desc
8✔
UNCOV
913
          | Some Odoc_model.Lang.TypeDecl.Bivariant -> "+" :: "-" :: desc
×
914
        in
UNCOV
915
        let final = if injectivity then "!" :: var_desc else var_desc in
×
916
        String.concat ~sep:"" final
917
      in
918
      let format_param p =
919
        Type_expression.with_kind_annotation p.Odoc_model.Lang.TypeDecl.kind
236✔
920
          (O.txt (format_param_str p))
236✔
921
      in
922
      match params with
923
      | [] -> O.noop
59✔
924
      | [ x ] ->
388✔
925
          let base = format_param_str x |> Syntax.Type.handle_format_params in
388✔
926
          Type_expression.with_kind_annotation x.kind (O.txt base)
388✔
927
      | lst ->
116✔
928
          let left, right =
NEW
929
            match delim with `parens -> ("(", ")") | `brackets -> ("[", "]")
×
930
          in
931
          O.txt left
116✔
932
          ++ O.list lst ~sep:(O.txt ", ") ~f:format_param
116✔
933
          ++ O.txt right
116✔
934

935
    let format_constraints constraints =
936
      O.list constraints ~f:(fun (t1, t2) ->
3,103✔
937
          O.sp
104✔
938
          ++ (O.box_hv
104✔
939
             @@ O.keyword "constraint" ++ O.sp
104✔
940
                ++ O.box_hv_no_indent (type_expr t1)
104✔
941
                ++ O.txt " =" ++ O.sp
104✔
942
                ++ O.box_hv_no_indent (type_expr t2)))
104✔
943

944
    let format_manifest :
945
        'inner_row 'outer_row.
946
        ?is_substitution:bool ->
947
        ?compact_variants:bool ->
948
        Odoc_model.Lang.TypeDecl.Equation.t ->
949
        text * bool =
950
     fun ?(is_substitution = false) ?(compact_variants = true) equation ->
104✔
951
      let _ = compact_variants in
3,021✔
952
      (* TODO *)
953
      let private_ = equation.private_ in
954
      match equation.manifest with
955
      | None -> (O.noop, private_)
1,753✔
956
      | Some t ->
1,268✔
957
          let manifest =
958
            O.txt (if is_substitution then " :=" else " =")
23✔
959
            ++ O.sp
1,268✔
960
            ++ (if private_ then
1,268✔
961
                  O.keyword Syntax.Type.private_keyword ++ O.txt " "
8✔
962
                else O.noop)
1,260✔
963
            ++ type_expr t
1,268✔
964
          in
965
          (manifest, false)
1,268✔
966

967
    let type_decl ?(is_substitution = false)
2,968✔
968
        ((recursive, t) : Lang.Signature.recursive * Lang.TypeDecl.t) =
969
      let keyword' =
2,991✔
970
        match recursive with
UNCOV
971
        | Ordinary | Rec -> O.keyword "type"
×
972
        | And -> O.keyword "and"
18✔
973
        | Nonrec -> O.keyword "type" ++ O.txt " " ++ O.keyword "nonrec"
1✔
974
      in
975
      let tyname = Paths.Identifier.name t.id in
976
      let tconstr =
2,991✔
977
        match t.equation.params with
978
        | [] -> O.txt tyname
2,545✔
979
        | l ->
446✔
980
            let params = format_params l in
981
            Syntax.Type.handle_constructor_params (O.txt tyname) params
446✔
982
      in
983
      let kind_annot =
984
        match t.equation.kind with
985
        | Default -> O.noop
2,991✔
NEW
986
        | k -> O.txt " : " ++ Type_expression.kind_annotation k
×
987
      in
988
      let intro = keyword' ++ O.txt " " ++ tconstr ++ kind_annot in
2,991✔
989
      let constraints = format_constraints t.equation.constraints in
2,991✔
990
      let manifest, need_private, long_prefix =
2,991✔
991
        match t.equation.manifest with
992
        | Some (Odoc_model.Lang.TypeExpr.Polymorphic_variant variant) ->
74✔
993
            let code =
994
              polymorphic_variant
995
                ~type_ident:(t.id :> Paths.Identifier.t)
996
                variant
997
            in
998
            let manifest =
74✔
999
              O.documentedSrc
74✔
1000
                (O.ignore intro
74✔
UNCOV
1001
                ++ O.txt (if is_substitution then " :=" else " =")
×
1002
                ++ O.sp
74✔
1003
                ++
74✔
1004
                if t.equation.private_ then
1005
                  O.keyword Syntax.Type.private_keyword ++ O.txt " "
8✔
1006
                else O.noop)
66✔
1007
              @ code
1008
            in
1009
            (manifest, false, O.noop)
1010
        | _ ->
2,917✔
1011
            let manifest, need_private =
1012
              format_manifest ~is_substitution t.equation
1013
            in
1014
            let text = O.ignore intro ++ manifest in
2,917✔
1015
            (O.documentedSrc @@ text, need_private, text)
2,917✔
1016
      in
1017
      let representation =
1018
        match t.representation with
1019
        | None -> []
2,629✔
1020
        | Some repr ->
362✔
1021
            let content =
1022
              match repr with
1023
              | Extensible -> O.documentedSrc (O.txt "..")
61✔
1024
              | Variant cstrs -> variant cstrs
235✔
1025
              | Record fields -> record fields
66✔
UNCOV
1026
              | Record_unboxed_product fields -> unboxed_record fields
×
1027
            in
1028
            if List.length content > 0 then
362✔
1029
              O.documentedSrc
362✔
1030
                (O.ignore long_prefix ++ O.txt " =" ++ O.sp
362✔
1031
                ++
362✔
1032
                if need_private then
1033
                  O.keyword Syntax.Type.private_keyword ++ O.txt " "
8✔
1034
                else O.noop)
354✔
1035
              @ content
UNCOV
1036
            else []
×
1037
      in
1038
      let content =
1039
        O.documentedSrc intro @ manifest @ representation
2,991✔
1040
        @ O.documentedSrc constraints
2,991✔
1041
        @ O.documentedSrc
2,991✔
UNCOV
1042
            (if Syntax.Type.type_def_semicolon then O.txt ";" else O.noop)
×
1043
      in
1044
      let attr = "type" :: (if is_substitution then [ "subst" ] else []) in
23✔
1045
      let anchor = path_to_id t.id in
1046
      let doc = Comment.to_ir t.doc.elements in
2,991✔
1047
      let source_anchor = source_anchor t.source_loc in
2,991✔
1048
      Item.Declaration { attr; anchor; doc; content; source_anchor }
2,991✔
1049
  end
1050

1051
  open Type_declaration
1052

1053
  module Value : sig
1054
    val value : Lang.Value.t -> Item.t
1055
  end = struct
1056
    let value (t : Odoc_model.Lang.Value.t) =
1057
      let extra_attr, semicolon =
912✔
1058
        match t.value with
1059
        | Abstract -> ([], Syntax.Value.semicolon)
888✔
1060
        | External _ -> ([ "external" ], Syntax.Type.External.semicolon)
24✔
1061
      in
1062
      let name = Paths.Identifier.name t.id in
1063
      let content =
912✔
1064
        O.documentedSrc
1065
          (O.box_hv
912✔
1066
          @@ O.keyword Syntax.Value.variable_keyword
912✔
1067
             ++ O.txt " " ++ O.txt name
912✔
1068
             ++ O.txt Syntax.Type.annotation_separator
912✔
1069
             ++ O.cut ++ type_expr t.type_
912✔
1070
             ++ Modalities.format t.modalities
912✔
UNCOV
1071
             ++ if semicolon then O.txt ";" else O.noop)
×
1072
      in
1073
      let attr = [ "value" ] @ extra_attr in
912✔
1074
      let anchor = path_to_id t.id in
1075
      let doc = Comment.to_ir t.doc.elements in
912✔
1076
      let source_anchor = source_anchor t.source_loc in
912✔
1077
      Item.Declaration { attr; anchor; doc; content; source_anchor }
912✔
1078
  end
1079

1080
  open Value
1081

1082
  (* This chunk of code is responsible for sectioning list of items
1083
     according to headings by extracting headings as Items.
1084

1085
     TODO: This sectioning would be better done as a pass on the model directly.
1086
  *)
1087
  module Sectioning : sig
1088
    open Odoc_model
1089

1090
    val comment_items : Comment.elements -> Item.t list
1091

1092
    val docs : Comment.elements -> Item.t list * Item.t list
1093
  end = struct
1094
    let take_until_heading_or_end (docs : Odoc_model.Comment.elements) =
1095
      let content, _, rest =
693✔
1096
        Doctree.Take.until docs ~classify:(fun b ->
1097
            match b.Location.value with
1,481✔
1098
            | `Heading _ -> Stop_and_keep
202✔
1099
            | #Odoc_model.Comment.attached_block_element as doc ->
1,279✔
1100
                let content = Comment.attached_block_element doc in
1101
                Accum content)
1,279✔
1102
      in
1103
      (content, rest)
693✔
1104

1105
    let comment_items (input0 : Odoc_model.Comment.elements) =
1106
      let rec loop input_comment acc =
963✔
1107
        match input_comment with
2,493✔
1108
        | [] -> List.rev acc
963✔
1109
        | element :: input_comment -> (
1,530✔
1110
            match element.Location.value with
1111
            | `Heading h ->
837✔
1112
                let item = Comment.heading h in
1113
                loop input_comment (item :: acc)
837✔
1114
            | _ ->
693✔
1115
                let content, input_comment =
1116
                  take_until_heading_or_end (element :: input_comment)
1117
                in
1118
                let item = Item.Text content in
693✔
1119
                loop input_comment (item :: acc))
1120
      in
1121
      loop input0 []
1122

1123
    (* For doc pages, we want the header to contain everything until
1124
       the first heading, then everything before the next heading which
1125
       is either lower, or a section.
1126
    *)
1127
    let docs input_comment =
1128
      let items = comment_items input_comment in
69✔
1129
      let until_first_heading, o, items =
69✔
1130
        Doctree.Take.until items ~classify:(function
1131
          | Item.Heading h as i -> Stop_and_accum ([ i ], Some h.level)
69✔
UNCOV
1132
          | i -> Accum [ i ])
×
1133
      in
1134
      match o with
69✔
UNCOV
1135
      | None -> (until_first_heading, items)
×
1136
      | Some level ->
69✔
UNCOV
1137
          let max_level = if level = 1 then 2 else level in
×
1138
          let before_second_heading, _, items =
1139
            Doctree.Take.until items ~classify:(function
1140
              | Item.Heading h when h.level >= max_level -> Stop_and_keep
13✔
1141
              | i -> Accum [ i ])
38✔
1142
          in
1143
          let header = until_first_heading @ before_second_heading in
69✔
1144
          (header, items)
1145
  end
1146

1147
  module Class : sig
1148
    val class_ : Lang.Class.t -> Item.t
1149

1150
    val class_type : Lang.ClassType.t -> Item.t
1151
  end = struct
1152
    let class_type_expr (cte : Odoc_model.Lang.ClassType.expr) =
1153
      match cte with
249✔
1154
      | Constr (path, args) ->
58✔
1155
          let link = Link.from_path (path :> Paths.Path.t) in
1156
          format_type_path ~delim:`brackets args link
58✔
1157
      | Signature _ ->
191✔
1158
          Syntax.Class.open_tag ++ O.txt " ... " ++ Syntax.Class.close_tag
191✔
1159

1160
    let method_ (t : Odoc_model.Lang.Method.t) =
1161
      let name = Paths.Identifier.name t.id in
90✔
1162
      let virtual_ =
90✔
1163
        if t.virtual_ then O.keyword "virtual" ++ O.txt " " else O.noop
8✔
1164
      in
1165
      let private_ =
1166
        if t.private_ then O.keyword "private" ++ O.txt " " else O.noop
8✔
1167
      in
1168
      let content =
1169
        O.documentedSrc
1170
          (O.keyword "method" ++ O.txt " " ++ private_ ++ virtual_ ++ O.txt name
90✔
1171
          ++ O.txt Syntax.Type.annotation_separator
90✔
1172
          ++ type_expr t.type_)
90✔
1173
      in
1174
      let attr = [ "method" ] in
90✔
1175
      let anchor = path_to_id t.id in
1176
      let doc = Comment.to_ir t.doc.elements in
90✔
1177
      Item.Declaration { attr; anchor; doc; content; source_anchor = None }
90✔
1178

1179
    let instance_variable (t : Odoc_model.Lang.InstanceVariable.t) =
1180
      let name = Paths.Identifier.name t.id in
17✔
1181
      let virtual_ =
17✔
1182
        if t.virtual_ then O.keyword "virtual" ++ O.txt " " else O.noop
8✔
1183
      in
1184
      let mutable_ =
1185
        if t.mutable_ then O.keyword "mutable" ++ O.txt " " else O.noop
8✔
1186
      in
1187
      let content =
1188
        O.documentedSrc
1189
          (O.keyword "val" ++ O.txt " " ++ mutable_ ++ virtual_ ++ O.txt name
17✔
1190
          ++ O.txt Syntax.Type.annotation_separator
17✔
1191
          ++ type_expr t.type_)
17✔
1192
      in
1193
      let attr = [ "value"; "instance-variable" ] in
17✔
1194
      let anchor = path_to_id t.id in
1195
      let doc = Comment.to_ir t.doc.elements in
17✔
1196
      Item.Declaration { attr; anchor; doc; content; source_anchor = None }
17✔
1197

1198
    let inherit_ (ih : Lang.ClassSignature.Inherit.t) =
1199
      let cte =
16✔
1200
        match ih.expr with
1201
        | Signature _ -> assert false (* Bold. *)
1202
        | cty -> cty
16✔
1203
      in
1204
      let content =
1205
        O.documentedSrc (O.keyword "inherit" ++ O.txt " " ++ class_type_expr cte)
16✔
1206
      in
1207
      let attr = [ "inherit" ] in
16✔
1208
      let anchor = None in
1209
      let doc = Comment.to_ir ih.doc.elements in
1210
      Item.Declaration { attr; anchor; doc; content; source_anchor = None }
16✔
1211

1212
    let constraint_ (cst : Lang.ClassSignature.Constraint.t) =
1213
      let content =
8✔
1214
        O.documentedSrc (format_constraints [ (cst.left, cst.right) ])
8✔
1215
      in
1216
      let attr = [] in
8✔
1217
      let anchor = None in
1218
      let doc = Comment.to_ir cst.doc.elements in
1219
      Item.Declaration { attr; anchor; doc; content; source_anchor = None }
8✔
1220

1221
    let class_signature (c : Lang.ClassSignature.t) =
1222
      let rec loop l acc_items =
233✔
1223
        match l with
388✔
1224
        | [] -> List.rev acc_items
233✔
1225
        | item :: rest -> (
155✔
1226
            let continue item = loop rest (item :: acc_items) in
131✔
1227
            match (item : Lang.ClassSignature.item) with
1228
            | Inherit cty -> continue @@ inherit_ cty
16✔
1229
            | Method m -> continue @@ method_ m
90✔
1230
            | InstanceVariable v -> continue @@ instance_variable v
17✔
1231
            | Constraint cst -> continue @@ constraint_ cst
8✔
1232
            | Comment `Stop ->
8✔
1233
                let rest =
1234
                  List.skip_until rest ~p:(function
1235
                    | Lang.ClassSignature.Comment `Stop -> true
8✔
1236
                    | _ -> false)
8✔
1237
                in
1238
                loop rest acc_items
8✔
1239
            | Comment (`Docs c) ->
16✔
1240
                let items = Sectioning.comment_items c.elements in
1241
                loop rest (List.rev_append items acc_items))
16✔
1242
      in
1243
      (* FIXME: use [t.self] *)
1244
      (c.doc.elements, loop c.items [])
233✔
1245

1246
    let rec class_decl (cd : Odoc_model.Lang.Class.decl) =
1247
      match cd with
182✔
1248
      | ClassType expr -> class_type_expr expr
166✔
1249
      (* TODO: factorize the following with [type_expr] *)
1250
      | Arrow (None, src, dst) ->
16✔
1251
          O.span
16✔
1252
            (type_expr ~needs_parentheses:true src
16✔
1253
            ++ O.txt " " ++ Syntax.Type.arrow)
16✔
1254
          ++ O.txt " " ++ class_decl dst
16✔
1255
      | Arrow (Some (RawOptional _ as lbl), _src, dst) ->
×
1256
          O.span
×
1257
            (O.box_hv
×
1258
            @@ label lbl ++ O.txt ":"
×
1259
               ++ tag "error" (O.txt "???")
×
1260
               ++ O.txt " " ++ Syntax.Type.arrow)
×
1261
          ++ O.sp ++ class_decl dst
×
1262
      | Arrow (Some lbl, src, dst) ->
×
1263
          O.span
×
1264
            (label lbl ++ O.txt ":"
×
1265
            ++ type_expr ~needs_parentheses:true src
×
1266
            ++ O.txt " " ++ Syntax.Type.arrow)
×
UNCOV
1267
          ++ O.txt " " ++ class_decl dst
×
1268

1269
    let class_ (t : Odoc_model.Lang.Class.t) =
1270
      let name = Paths.Identifier.name t.id in
166✔
1271
      let params =
166✔
1272
        match t.params with
1273
        | [] -> O.noop
142✔
1274
        | _ :: _ as params -> format_params ~delim:`brackets params ++ O.txt " "
24✔
1275
      in
1276
      let virtual_ =
1277
        if t.virtual_ then O.keyword "virtual" ++ O.txt " " else O.noop
24✔
1278
      in
1279

1280
      let source_anchor = source_anchor t.source_loc in
1281
      let cname, expansion, expansion_doc =
166✔
1282
        match t.expansion with
UNCOV
1283
        | None -> (O.documentedSrc @@ O.txt name, None, None)
×
1284
        | Some csig ->
166✔
1285
            let expansion_doc, items = class_signature csig in
1286
            let url = Url.Path.from_identifier t.id in
166✔
1287
            let page =
166✔
1288
              make_expansion_page ~source_anchor url
1289
                [ t.doc.elements; expansion_doc ]
1290
                items
1291
            in
1292
            ( O.documentedSrc @@ path url [ inline @@ Text name ],
166✔
1293
              Some page,
1294
              Some expansion_doc )
1295
      in
1296
      let summary =
1297
        O.txt Syntax.Type.annotation_separator ++ class_decl t.type_
166✔
1298
      in
1299
      let cd =
166✔
1300
        attach_expansion
1301
          (Syntax.Type.annotation_separator, "object", "end")
1302
          expansion summary
1303
      in
1304
      let content =
166✔
1305
        O.documentedSrc (O.keyword "class" ++ O.txt " " ++ virtual_ ++ params)
166✔
1306
        @ cname @ cd
1307
      in
1308
      let attr = [ "class" ] in
1309
      let anchor = path_to_id t.id in
1310
      let doc = Comment.synopsis ~decl_doc:t.doc.elements ~expansion_doc in
166✔
1311
      Item.Declaration { attr; anchor; doc; content; source_anchor }
1312

1313
    let class_type (t : Odoc_model.Lang.ClassType.t) =
1314
      let name = Paths.Identifier.name t.id in
67✔
1315
      let params = format_params ~delim:`brackets t.params in
67✔
1316
      let virtual_ =
67✔
1317
        if t.virtual_ then O.keyword "virtual" ++ O.txt " " else O.noop
8✔
1318
      in
1319
      let source_anchor = source_anchor t.source_loc in
1320
      let cname, expansion, expansion_doc =
67✔
1321
        match t.expansion with
UNCOV
1322
        | None -> (O.documentedSrc @@ O.txt name, None, None)
×
1323
        | Some csig ->
67✔
1324
            let url = Url.Path.from_identifier t.id in
1325
            let expansion_doc, items = class_signature csig in
67✔
1326
            let page =
67✔
1327
              make_expansion_page ~source_anchor url
1328
                [ t.doc.elements; expansion_doc ]
1329
                items
1330
            in
1331
            ( O.documentedSrc @@ path url [ inline @@ Text name ],
67✔
1332
              Some page,
1333
              Some expansion_doc )
1334
      in
1335
      let summary = O.txt " = " ++ class_type_expr t.expr in
67✔
1336
      let expr = attach_expansion (" = ", "object", "end") expansion summary in
67✔
1337
      let content =
67✔
1338
        O.documentedSrc
67✔
1339
          (O.keyword "class" ++ O.txt " " ++ O.keyword "type" ++ O.txt " "
67✔
1340
         ++ virtual_ ++ params ++ O.txt " ")
67✔
1341
        @ cname @ expr
1342
      in
1343
      let attr = [ "class-type" ] in
1344
      let anchor = path_to_id t.id in
1345
      let doc = Comment.synopsis ~decl_doc:t.doc.elements ~expansion_doc in
67✔
1346
      Item.Declaration { attr; anchor; doc; content; source_anchor }
1347
  end
1348

1349
  open Class
1350

1351
  module Module : sig
1352
    val signature : Lang.Signature.t -> Comment.Comment.elements * Item.t list
1353
    (** Returns [header_doc, content]. *)
1354
  end = struct
1355
    let internal_module m =
1356
      let open Lang.Module in
1,924✔
1357
      match m.id.iv with
1358
      | `Module (_, name) when ModuleName.is_hidden name -> true
81✔
1359
      | _ -> false
1,843✔
1360

1361
    let internal_type t =
1362
      let open Lang.TypeDecl in
2,969✔
1363
      match t.id.iv with
1364
      | `Type (_, name) when TypeName.is_hidden name -> true
1✔
1365
      | _ -> false
2,968✔
1366

1367
    let internal_value v =
1368
      let open Lang.Value in
1,018✔
1369
      match v.id.iv with
1370
      | `Value (_, name) when ValueName.is_hidden name -> true
106✔
1371
      | _ -> false
912✔
1372

1373
    let internal_module_type t =
1374
      let open Lang.ModuleType in
1,361✔
1375
      match t.id.iv with
UNCOV
1376
      | `ModuleType (_, name) when ModuleTypeName.is_hidden name -> true
×
1377
      | _ -> false
1,361✔
1378

1379
    let internal_module_substitution t =
1380
      let open Lang.ModuleSubstitution in
8✔
1381
      match t.id.iv with
UNCOV
1382
      | `Module (_, name) when ModuleName.is_hidden name -> true
×
1383
      | _ -> false
8✔
1384

1385
    let internal_module_type_substitution t =
1386
      let open Lang.ModuleTypeSubstitution in
8✔
1387
      match t.id.iv with
UNCOV
1388
      | `ModuleType (_, name) when ModuleTypeName.is_hidden name -> true
×
1389
      | _ -> false
8✔
1390

1391
    let rec signature (s : Lang.Signature.t) =
1392
      let rec loop l acc_items =
3,582✔
1393
        match l with
12,543✔
1394
        | [] -> List.rev acc_items
3,582✔
1395
        | item :: rest -> (
8,961✔
1396
            let continue (item : Item.t) = loop rest (item :: acc_items) in
7,838✔
1397
            match (item : Lang.Signature.item) with
1398
            | Module (_, m) when internal_module m -> loop rest acc_items
81✔
1399
            | Type (_, t) when internal_type t -> loop rest acc_items
1✔
1400
            | Value v when internal_value v -> loop rest acc_items
106✔
UNCOV
1401
            | ModuleType m when internal_module_type m -> loop rest acc_items
×
1402
            | ModuleSubstitution m when internal_module_substitution m ->
8✔
UNCOV
1403
                loop rest acc_items
×
1404
            | ModuleTypeSubstitution m when internal_module_type_substitution m
8✔
1405
              ->
UNCOV
1406
                loop rest acc_items
×
1407
            | ModuleTypeSubstitution m -> continue @@ module_type_substitution m
8✔
1408
            | Module (_, m) -> continue @@ module_ m
1,843✔
1409
            | ModuleType m -> continue @@ module_type m
1,361✔
1410
            | Class (_, c) -> continue @@ class_ c
166✔
1411
            | ClassType (_, c) -> continue @@ class_type c
67✔
1412
            | Include m -> continue @@ include_ m
290✔
1413
            | ModuleSubstitution m -> continue @@ module_substitution m
8✔
1414
            | TypeSubstitution t ->
23✔
1415
                continue @@ type_decl ~is_substitution:true (Ordinary, t)
23✔
1416
            | Type (r, t) -> continue @@ type_decl (r, t)
2,968✔
1417
            | TypExt e -> continue @@ extension e
126✔
1418
            | Exception e -> continue @@ exn e
66✔
1419
            | Value v -> continue @@ value v
912✔
1420
            | Open o ->
82✔
1421
                let items = Sectioning.comment_items o.doc.elements in
1422
                loop rest (List.rev_append items acc_items)
82✔
1423
            | Comment `Stop ->
57✔
1424
                let rest =
1425
                  List.skip_until rest ~p:(function
1426
                    | Lang.Signature.Comment `Stop -> true
49✔
1427
                    | _ -> false)
65✔
1428
                in
1429
                loop rest acc_items
57✔
1430
            | Comment (`Docs c) ->
796✔
1431
                let items = Sectioning.comment_items c.elements in
1432
                loop rest (List.rev_append items acc_items))
796✔
1433
      in
1434
      ((Lang.extract_signature_doc s).elements, loop s.items [])
3,582✔
1435

1436
    and functor_parameter :
1437
        Odoc_model.Lang.FunctorParameter.parameter -> DocumentedSrc.t =
1438
     fun arg ->
1439
      let open Odoc_model.Lang.FunctorParameter in
211✔
1440
      let name = Paths.Identifier.name arg.id in
1441
      let render_ty = arg.expr in
211✔
1442
      let modtyp =
1443
        mty_in_decl (arg.id :> Paths.Identifier.Signature.t) render_ty
1444
      in
1445
      let modname, mod_decl =
211✔
1446
        match expansion_of_module_type_expr arg.expr with
1447
        | None ->
×
1448
            let modname = O.txt (Paths.Identifier.name arg.id) in
×
UNCOV
1449
            (modname, O.documentedSrc modtyp)
×
1450
        | Some (expansion_doc, items) ->
211✔
1451
            let url = Url.Path.from_identifier arg.id in
1452
            let modname = path url [ inline @@ Text name ] in
211✔
1453
            let type_with_expansion =
211✔
1454
              let content =
1455
                make_expansion_page ~source_anchor:None url [ expansion_doc ]
1456
                  items
1457
              in
1458
              let summary = O.render modtyp in
211✔
1459
              let status = `Default in
211✔
1460
              let expansion =
1461
                O.documentedSrc
211✔
1462
                  (O.txt Syntax.Type.annotation_separator ++ O.keyword "sig")
211✔
1463
                @ DocumentedSrc.[ Subpage { content; status } ]
1464
                @ O.documentedSrc (O.keyword "end")
211✔
1465
              in
1466
              DocumentedSrc.
1467
                [
1468
                  Alternative
1469
                    (Expansion { status = `Default; summary; url; expansion });
1470
                ]
1471
            in
1472
            (modname, type_with_expansion)
1473
      in
1474
      O.documentedSrc (O.keyword "module" ++ O.txt " ")
211✔
1475
      @ O.documentedSrc modname @ mod_decl
211✔
1476

1477
    and module_substitution (t : Odoc_model.Lang.ModuleSubstitution.t) =
1478
      let name = Paths.Identifier.name t.id in
8✔
1479
      let path = Link.from_path (t.manifest :> Paths.Path.t) in
8✔
1480
      let content =
8✔
1481
        O.documentedSrc
1482
          (O.keyword "module" ++ O.txt " " ++ O.txt name ++ O.txt " :=" ++ O.sp
8✔
1483
         ++ path)
8✔
1484
      in
1485
      let attr = [ "module-substitution" ] in
8✔
1486
      let anchor = path_to_id t.id in
1487
      let doc = Comment.to_ir t.doc.elements in
8✔
1488
      Item.Declaration { attr; anchor; doc; content; source_anchor = None }
8✔
1489

1490
    and module_type_substitution (t : Odoc_model.Lang.ModuleTypeSubstitution.t)
1491
        =
1492
      let prefix =
8✔
1493
        O.keyword "module" ++ O.txt " " ++ O.keyword "type" ++ O.txt " "
8✔
1494
      in
1495
      let source_anchor = None in
8✔
1496
      let modname = Paths.Identifier.name t.id in
1497
      let modname, expansion_doc, mty =
8✔
1498
        module_type_manifest ~subst:true ~source_anchor modname t.id
1499
          t.doc.elements (Some t.manifest) prefix
1500
      in
1501
      let content =
8✔
1502
        O.documentedSrc (prefix ++ modname)
8✔
1503
        @ mty
1504
        @ O.documentedSrc
8✔
UNCOV
1505
            (if Syntax.Mod.close_tag_semicolon then O.txt ";" else O.noop)
×
1506
      in
1507
      let attr = [ "module-type" ] in
1508
      let anchor = path_to_id t.id in
1509
      let doc = Comment.synopsis ~decl_doc:t.doc.elements ~expansion_doc in
8✔
1510
      Item.Declaration { attr; anchor; doc; content; source_anchor }
1511

1512
    and simple_expansion :
1513
        Odoc_model.Lang.ModuleType.simple_expansion ->
1514
        Comment.Comment.elements * Item.t list =
1515
     fun t ->
1516
      let rec extract_functor_params
2,933✔
1517
          (f : Odoc_model.Lang.ModuleType.simple_expansion) =
1518
        match f with
3,152✔
1519
        | Signature sg -> (None, sg)
2,933✔
1520
        | Functor (p, expansion) ->
219✔
1521
            let add_to params =
1522
              match p with Unit -> params | Named p -> p :: params
8✔
1523
            in
1524
            let params, sg = extract_functor_params expansion in
1525
            let params = match params with None -> [] | Some p -> p in
36✔
1526
            (Some (add_to params), sg)
219✔
1527
      in
1528
      match extract_functor_params t with
1529
      | None, sg -> signature sg
2,750✔
1530
      | Some params, sg ->
183✔
1531
          let sg_doc, content = signature sg in
1532
          let params =
183✔
1533
            let decl_of_arg arg =
1534
              let content = functor_parameter arg in
211✔
1535
              let attr = [ "parameter" ] in
211✔
1536
              let anchor =
1537
                Some (Url.Anchor.from_identifier (arg.id :> Paths.Identifier.t))
211✔
1538
              in
1539
              let doc = [] in
1540
              [
1541
                Item.Declaration
1542
                  { content; anchor; attr; doc; source_anchor = None };
1543
              ]
1544
            in
1545
            List.concat_map decl_of_arg params
183✔
1546
          in
1547
          let prelude = mk_heading ~label:"parameters" "Parameters" :: params
183✔
1548
          and content = mk_heading ~label:"signature" "Signature" :: content in
183✔
1549
          (sg_doc, prelude @ content)
1550

1551
    and expansion_of_module_type_expr :
1552
        Odoc_model.Lang.ModuleType.expr ->
1553
        (Comment.Comment.elements * Item.t list) option =
1554
     fun t ->
1555
      let rec simple_expansion_of (t : Odoc_model.Lang.ModuleType.expr) =
3,218✔
1556
        match t with
3,437✔
1557
        | Path { p_expansion = None; _ }
379✔
1558
        | TypeOf { t_expansion = None; _ }
8✔
1559
        | With { w_expansion = None; _ }
×
UNCOV
1560
        | Strengthen { s_expansion = None; _ } ->
×
1561
            None
1562
        | Path { p_expansion = Some e; _ }
426✔
1563
        | TypeOf { t_expansion = Some e; _ }
56✔
1564
        | With { w_expansion = Some e; _ }
218✔
UNCOV
1565
        | Strengthen { s_expansion = Some e; _ } ->
×
1566
            Some e
1567
        | Signature sg -> Some (Signature sg)
2,131✔
1568
        | Functor (f_parameter, e) -> (
219✔
1569
            match simple_expansion_of e with
1570
            | Some e -> Some (Functor (f_parameter, e))
211✔
1571
            | None -> None)
8✔
1572
      in
1573
      match simple_expansion_of t with
1574
      | None -> None
387✔
1575
      | Some e -> Some (simple_expansion e)
2,831✔
1576

1577
    and module_ : Odoc_model.Lang.Module.t -> Item.t =
1578
     fun t ->
1579
      let modname = Paths.Identifier.name t.id in
1,843✔
1580
      let expansion =
1,843✔
1581
        match t.type_ with
1582
        | Alias (_, Some e) -> Some (simple_expansion e)
102✔
1583
        | Alias (_, None) -> None
186✔
1584
        | ModuleType e -> expansion_of_module_type_expr e
1,555✔
1585
      in
1586
      let source_anchor = source_anchor t.source_loc in
1587
      let modname, status, expansion, expansion_doc =
1,843✔
1588
        match expansion with
1589
        | None -> (O.txt modname, `Default, None, None)
322✔
1590
        | Some (expansion_doc, items) ->
1,521✔
1591
            let status =
1592
              match t.type_ with
1593
              | ModuleType (Signature _) -> `Inline
987✔
1594
              | _ -> `Default
534✔
1595
            in
1596
            let url = Url.Path.from_identifier t.id in
1597
            let link = path url [ inline @@ Text modname ] in
1,521✔
1598
            let page =
1,521✔
1599
              make_expansion_page ~source_anchor url
1600
                [ t.doc.elements; expansion_doc ]
1601
                items
1602
            in
1603
            (link, status, Some page, Some expansion_doc)
1,521✔
1604
      in
1605
      let intro = O.keyword "module" ++ O.txt " " ++ modname in
1,843✔
1606
      let summary = O.ignore intro ++ mdexpr_in_decl t.id t.type_ in
1,843✔
1607
      let modexpr =
1,843✔
1608
        attach_expansion ~status
1609
          (Syntax.Type.annotation_separator, "sig", "end")
1610
          expansion summary
1611
      in
1612
      let content =
1,843✔
1613
        O.documentedSrc intro @ modexpr
1,843✔
1614
        @ O.documentedSrc
1,843✔
UNCOV
1615
            (if Syntax.Mod.close_tag_semicolon then O.txt ";" else O.noop)
×
1616
      in
1617
      let attr = [ "module" ] in
1618
      let anchor = path_to_id t.id in
1619
      let doc = Comment.synopsis ~decl_doc:t.doc.elements ~expansion_doc in
1,843✔
1620
      Item.Declaration { attr; anchor; doc; content; source_anchor }
1621

1622
    and simple_expansion_in_decl (base : Paths.Identifier.Module.t) se =
1623
      let rec ty_of_se :
102✔
1624
          Lang.ModuleType.simple_expansion -> Lang.ModuleType.expr = function
1625
        | Signature sg -> Signature sg
102✔
UNCOV
1626
        | Functor (arg, sg) -> Functor (arg, ty_of_se sg)
×
1627
      in
1628
      mty_in_decl (base :> Paths.Identifier.Signature.t) (ty_of_se se)
102✔
1629

1630
    and mdexpr_in_decl (base : Paths.Identifier.Module.t) md =
1631
      let sig_dotdotdot =
1,843✔
1632
        O.txt Syntax.Type.annotation_separator
1,843✔
1633
        ++ O.cut ++ Syntax.Mod.open_tag ++ O.txt " ... " ++ Syntax.Mod.close_tag
1,843✔
1634
      in
1635
      match md with
1,843✔
1636
      | Alias (_, Some se) -> simple_expansion_in_decl base se
102✔
1637
      | Alias (p, _) when not Paths.Path.(is_hidden (p :> t)) ->
186✔
1638
          O.txt " =" ++ O.sp ++ mdexpr md
186✔
UNCOV
1639
      | Alias _ -> sig_dotdotdot
×
1640
      | ModuleType mt -> mty_in_decl (base :> Paths.Identifier.Signature.t) mt
1,555✔
1641

1642
    and mdexpr : Odoc_model.Lang.Module.decl -> text = function
1643
      | Alias (mod_path, _) -> Link.from_path (mod_path :> Paths.Path.t)
242✔
UNCOV
1644
      | ModuleType mt -> mty mt
×
1645

1646
    and module_type_manifest ~subst ~source_anchor modname id doc manifest
1647
        prefix =
1648
      let expansion =
1,369✔
1649
        match manifest with
1650
        | None -> None
128✔
1651
        | Some e -> expansion_of_module_type_expr e
1,241✔
1652
      in
1653
      let modname, expansion, expansion_doc =
1654
        match expansion with
1655
        | None -> (O.txt modname, None, None)
379✔
1656
        | Some (expansion_doc, items) ->
990✔
1657
            let url = Url.Path.from_identifier id in
1658
            let link = path url [ inline @@ Text modname ] in
990✔
1659
            let page =
990✔
1660
              make_expansion_page ~source_anchor url [ doc; expansion_doc ]
1661
                items
1662
            in
1663
            (link, Some page, Some expansion_doc)
990✔
1664
      in
1665
      let summary =
1666
        match manifest with
1667
        | None -> O.noop
128✔
1668
        | Some expr ->
1,241✔
1669
            O.ignore (prefix ++ modname)
1,241✔
1670
            ++ (if subst then O.txt " :=" ++ O.sp else O.txt " =" ++ O.sp)
8✔
1671
            ++ mty expr
1,241✔
1672
      in
1673
      ( modname,
1674
        expansion_doc,
1675
        attach_expansion (" = ", "sig", "end") expansion summary )
1,369✔
1676

1677
    and module_type (t : Odoc_model.Lang.ModuleType.t) =
1678
      let prefix =
1,361✔
1679
        O.keyword "module" ++ O.txt " " ++ O.keyword "type" ++ O.txt " "
1,361✔
1680
      in
1681
      let modname = Paths.Identifier.name t.id in
1,361✔
1682
      let source_anchor = source_anchor t.source_loc in
1,361✔
1683
      let modname, expansion_doc, mty =
1,361✔
1684
        module_type_manifest ~subst:false ~source_anchor modname t.id
1685
          t.doc.elements t.expr prefix
1686
      in
1687
      let content =
1,361✔
1688
        O.documentedSrc (prefix ++ modname)
1,361✔
1689
        @ mty
1690
        @ O.documentedSrc
1,361✔
UNCOV
1691
            (if Syntax.Mod.close_tag_semicolon then O.txt ";" else O.noop)
×
1692
      in
1693
      let attr = [ "module-type" ] in
1694
      let anchor = path_to_id t.id in
1695
      let doc = Comment.synopsis ~decl_doc:t.doc.elements ~expansion_doc in
1,361✔
1696
      Item.Declaration { attr; anchor; doc; content; source_anchor }
1697

1698
    and umty_hidden : Odoc_model.Lang.ModuleType.U.expr -> bool = function
1699
      | Path p -> Paths.Path.(is_hidden (p :> t))
403✔
1700
      | With (_, expr) -> umty_hidden expr
25✔
1701
      | TypeOf (ModPath m, _) | TypeOf (StructInclude m, _) ->
42✔
1702
          Paths.Path.(is_hidden (m :> t))
1703
      | Signature _ -> false
14✔
1704
      | Strengthen (expr, p, _) ->
×
UNCOV
1705
          umty_hidden expr || Paths.Path.(is_hidden (p :> t))
×
1706

1707
    and mty_hidden : Odoc_model.Lang.ModuleType.expr -> bool = function
1708
      | Path { p_path = mty_path; _ } -> Paths.Path.(is_hidden (mty_path :> t))
837✔
1709
      | With { w_expr; _ } -> umty_hidden w_expr
218✔
1710
      | TypeOf { t_desc = ModPath m; _ }
48✔
1711
      | TypeOf { t_desc = StructInclude m; _ } ->
16✔
1712
          Paths.Path.(is_hidden (m :> t))
1713
      | _ -> false
2,297✔
1714

1715
    and mty_with subs expr =
1716
      umty expr ++ O.sp ++ O.keyword "with" ++ O.txt " "
238✔
1717
      ++ O.list
238✔
1718
           ~sep:(O.cut ++ O.txt " " ++ O.keyword "and" ++ O.txt " ")
238✔
1719
           ~f:(fun x -> O.span (substitution x))
279✔
1720
           subs
1721

1722
    and mty_strengthen expr path =
1723
      umty expr ++ O.sp ++ O.keyword "with" ++ O.txt " "
×
UNCOV
1724
      ++ Link.from_path (path :> Paths.Path.t)
×
1725

1726
    and mty_typeof t_desc =
1727
      match t_desc with
154✔
1728
      | Odoc_model.Lang.ModuleType.ModPath m ->
90✔
1729
          O.keyword "module" ++ O.txt " " ++ O.keyword "type" ++ O.txt " "
90✔
1730
          ++ O.keyword "of" ++ O.txt " "
90✔
1731
          ++ Link.from_path (m :> Paths.Path.t)
90✔
1732
      | StructInclude m ->
64✔
1733
          O.keyword "module" ++ O.txt " " ++ O.keyword "type" ++ O.txt " "
64✔
1734
          ++ O.keyword "of" ++ O.txt " " ++ O.keyword "struct" ++ O.txt " "
64✔
1735
          ++ O.keyword "include" ++ O.txt " "
64✔
1736
          ++ Link.from_path (m :> Paths.Path.t)
64✔
1737
          ++ O.txt " " ++ O.keyword "end"
64✔
1738

1739
    and is_elidable_with_u : Odoc_model.Lang.ModuleType.U.expr -> bool =
1740
      function
1741
      | Path _ -> false
213✔
1742
      | Signature _ -> true
5✔
UNCOV
1743
      | With (_, expr) -> is_elidable_with_u expr
×
1744
      | TypeOf _ -> false
25✔
UNCOV
1745
      | Strengthen (expr, _, _) -> is_elidable_with_u expr
×
1746

1747
    and umty : Odoc_model.Lang.ModuleType.U.expr -> text =
1748
     fun m ->
1749
      match m with
527✔
1750
      | Path p -> Link.from_path (p :> Paths.Path.t)
403✔
1751
      | Signature _ ->
9✔
1752
          Syntax.Mod.open_tag ++ O.txt " ... " ++ Syntax.Mod.close_tag
9✔
1753
      | With (_, expr) when is_elidable_with_u expr ->
25✔
1754
          Syntax.Mod.open_tag ++ O.txt " ... " ++ Syntax.Mod.close_tag
5✔
1755
      | With (subs, expr) -> mty_with subs expr
20✔
1756
      | TypeOf (t_desc, _) -> mty_typeof t_desc
90✔
1757
      | Strengthen (expr, _, _) when is_elidable_with_u expr ->
×
1758
          Syntax.Mod.open_tag ++ O.txt " ... " ++ Syntax.Mod.close_tag
×
UNCOV
1759
      | Strengthen (expr, p, _) -> mty_strengthen expr (p :> Paths.Path.t)
×
1760

1761
    and mty : Odoc_model.Lang.ModuleType.expr -> text =
1762
     fun m ->
1763
      if mty_hidden m then
3,416✔
UNCOV
1764
        Syntax.Mod.open_tag ++ O.txt " ... " ++ Syntax.Mod.close_tag
×
1765
      else
1766
        match m with
3,416✔
1767
        | Path { p_path = mty_path; _ } ->
837✔
1768
            Link.from_path (mty_path :> Paths.Path.t)
1769
        | Functor (Unit, expr) ->
×
1770
            (if Syntax.Mod.functor_keyword then O.keyword "functor" else O.noop)
×
1771
            ++ O.span (O.txt " () " ++ Syntax.Type.arrow)
×
UNCOV
1772
            ++ O.sp ++ mty expr
×
1773
        | Functor (Named arg, expr) ->
48✔
1774
            let arg_expr = arg.expr in
1775
            let stop_before = expansion_of_module_type_expr arg_expr = None in
48✔
1776
            let name =
1777
              let open Odoc_model.Lang.FunctorParameter in
1778
              let name = Paths.Identifier.name arg.id in
1779
              let href =
48✔
1780
                Url.from_identifier ~stop_before (arg.id :> Paths.Identifier.t)
1781
              in
1782
              resolved href [ inline @@ Text name ]
48✔
1783
            in
UNCOV
1784
            (if Syntax.Mod.functor_keyword then O.keyword "functor" else O.noop)
×
1785
            ++ (O.box_hv @@ O.span
48✔
1786
               @@ O.txt " (" ++ name
48✔
1787
                  ++ O.txt Syntax.Type.annotation_separator
48✔
1788
                  ++ mty arg_expr ++ O.txt ")" ++ O.txt " " ++ Syntax.Type.arrow
48✔
1789
               )
1790
            ++ O.sp ++ mty expr
48✔
1791
        | With { w_expr; _ } when is_elidable_with_u w_expr ->
218✔
UNCOV
1792
            Syntax.Mod.open_tag ++ O.txt " ... " ++ Syntax.Mod.close_tag
×
1793
        | With { w_substitutions; w_expr; _ } ->
218✔
1794
            O.box_hv @@ mty_with w_substitutions w_expr
218✔
1795
        | TypeOf { t_desc; _ } -> mty_typeof t_desc
64✔
1796
        | Signature _ ->
2,249✔
1797
            Syntax.Mod.open_tag ++ O.txt " ... " ++ Syntax.Mod.close_tag
2,249✔
1798
        | Strengthen { s_expr; _ } when is_elidable_with_u s_expr ->
×
1799
            Syntax.Mod.open_tag ++ O.txt " ... " ++ Syntax.Mod.close_tag
×
1800
        | Strengthen { s_expr; s_path; _ } ->
×
UNCOV
1801
            O.box_hv @@ mty_strengthen s_expr (s_path :> Paths.Path.t)
×
1802

1803
    and mty_in_decl :
1804
        Paths.Identifier.Signature.t -> Odoc_model.Lang.ModuleType.expr -> text
1805
        =
1806
     fun base -> function
UNCOV
1807
      | (Path _ | Signature _ | With _ | TypeOf _ | Strengthen _) as m ->
×
1808
          O.txt Syntax.Type.annotation_separator ++ O.cut ++ mty m
1,868✔
1809
      | Functor _ as m when not Syntax.Mod.functor_contraction ->
171✔
UNCOV
1810
          O.txt Syntax.Type.annotation_separator ++ O.cut ++ mty m
×
1811
      | Functor (arg, expr) ->
171✔
1812
          let text_arg =
1813
            match arg with
1814
            | Unit -> O.txt "()"
8✔
1815
            | Named arg ->
163✔
1816
                let arg_expr = arg.expr in
1817
                let stop_before =
1818
                  expansion_of_module_type_expr arg_expr = None
163✔
1819
                in
1820
                let name =
1821
                  let open Odoc_model.Lang.FunctorParameter in
1822
                  let name = Paths.Identifier.name arg.id in
1823
                  let href =
163✔
1824
                    Url.from_identifier ~stop_before
1825
                      (arg.id :> Paths.Identifier.t)
1826
                  in
1827
                  resolved href [ inline @@ Text name ]
163✔
1828
                in
1829
                O.box_hv
163✔
1830
                @@ O.txt "(" ++ name
163✔
1831
                   ++ O.txt Syntax.Type.annotation_separator
163✔
1832
                   ++ O.cut ++ mty arg.expr ++ O.txt ")"
163✔
1833
          in
1834
          O.sp ++ text_arg ++ mty_in_decl base expr
171✔
1835

1836
    (* TODO : Centralize the list juggling for type parameters *)
1837
    and type_expr_in_subst td typath =
1838
      let typath = Link.from_fragment typath in
151✔
1839
      match td.Lang.TypeDecl.Equation.params with
151✔
1840
      | [] -> typath
125✔
1841
      | l -> Syntax.Type.handle_substitution_params typath (format_params l)
26✔
1842

1843
    and substitution : Odoc_model.Lang.ModuleType.substitution -> text =
1844
      function
1845
      | ModuleEq (frag_mod, md) ->
56✔
1846
          O.box_hv
1847
          @@ O.keyword "module" ++ O.txt " "
56✔
1848
             ++ Link.from_fragment (frag_mod :> Paths.Fragment.leaf)
56✔
1849
             ++ O.txt " =" ++ O.sp ++ mdexpr md
56✔
1850
      | ModuleTypeEq (frag_mty, md) ->
32✔
1851
          O.box_hv
1852
          @@ O.keyword "module" ++ O.txt " " ++ O.keyword "type" ++ O.txt " "
32✔
1853
             ++ Link.from_fragment (frag_mty :> Paths.Fragment.leaf)
32✔
1854
             ++ O.txt " =" ++ O.sp ++ mty md
32✔
1855
      | TypeEq (frag_typ, td) ->
104✔
1856
          O.box_hv
1857
          @@ O.keyword "type" ++ O.txt " "
104✔
1858
             ++ type_expr_in_subst td (frag_typ :> Paths.Fragment.leaf)
104✔
1859
             ++ fst (format_manifest td)
104✔
1860
             ++ format_constraints
104✔
1861
                  td.Odoc_model.Lang.TypeDecl.Equation.constraints
1862
      | ModuleSubst (frag_mod, mod_path) ->
24✔
1863
          O.box_hv
1864
          @@ O.keyword "module" ++ O.txt " "
24✔
1865
             ++ Link.from_fragment (frag_mod :> Paths.Fragment.leaf)
24✔
1866
             ++ O.txt " :=" ++ O.sp
24✔
1867
             ++ Link.from_path (mod_path :> Paths.Path.t)
24✔
1868
      | ModuleTypeSubst (frag_mty, md) ->
16✔
1869
          O.box_hv
1870
          @@ O.keyword "module" ++ O.txt " " ++ O.keyword "type" ++ O.txt " "
16✔
1871
             ++ Link.from_fragment (frag_mty :> Paths.Fragment.leaf)
16✔
1872
             ++ O.txt " :=" ++ O.sp ++ mty md
16✔
1873
      | TypeSubst (frag_typ, td) -> (
47✔
1874
          O.box_hv
1875
          @@ O.keyword "type" ++ O.txt " "
47✔
1876
             ++ type_expr_in_subst td (frag_typ :> Paths.Fragment.leaf)
47✔
1877
             ++ O.txt " :=" ++ O.sp
47✔
1878
             ++
47✔
1879
             match td.Lang.TypeDecl.Equation.manifest with
1880
             | None -> assert false (* cf loader/cmti *)
1881
             | Some te -> type_expr te)
47✔
1882

1883
    and include_ (t : Odoc_model.Lang.Include.t) =
1884
      let decl_hidden =
290✔
1885
        match t.decl with
UNCOV
1886
        | Alias p -> Paths.Path.(is_hidden (p :> t))
×
1887
        | ModuleType mty -> umty_hidden mty
290✔
1888
      in
1889
      let status = if decl_hidden then `Inline else t.status in
1✔
1890

1891
      let _, content = signature t.expansion.content in
1892
      let summary =
290✔
1893
        if decl_hidden then O.render (O.keyword "include" ++ O.txt " ...")
1✔
1894
        else
1895
          let include_decl =
289✔
1896
            match t.decl with
1897
            | Odoc_model.Lang.Include.Alias mod_path ->
×
UNCOV
1898
                Link.from_path (mod_path :> Paths.Path.t)
×
1899
            | ModuleType mt -> umty mt
289✔
1900
          in
1901
          O.render
289✔
1902
            (O.keyword "include" ++ O.txt " " ++ include_decl
289✔
UNCOV
1903
            ++ if Syntax.Mod.include_semicolon then O.keyword ";" else O.noop)
×
1904
      in
1905
      let content = { Include.content; status; summary } in
1906
      let attr = [ "include" ] in
1907
      let anchor = None in
1908
      let doc =
1909
        (* Documentation attached to includes behave differently than other
1910
           declarations, which show only the synopsis. We can't only show the
1911
           synopsis because no page is generated to render it and we'd loose
1912
           the full documentation.
1913
           The documentation from the expansion is not used. *)
1914
        Comment.to_ir t.doc.elements
1915
      in
1916
      Item.Include { attr; anchor; doc; content; source_anchor = None }
290✔
1917
  end
1918

1919
  open Module
1920

1921
  module Page : sig
1922
    val compilation_unit : Lang.Compilation_unit.t -> Document.t
1923

1924
    val page : Lang.Page.t -> Document.t
1925

1926
    val implementation :
1927
      Lang.Implementation.t ->
1928
      Syntax_highlighter.infos ->
1929
      string ->
1930
      Document.t list
1931
  end = struct
1932
    let pack : Lang.Compilation_unit.Packed.t -> Item.t list =
1933
     fun t ->
1934
      let f x =
×
UNCOV
1935
        let id = x.Lang.Compilation_unit.Packed.id in
×
1936
        let modname = Paths.Identifier.name id in
1937
        let md_def =
×
1938
          O.keyword "module" ++ O.txt " " ++ O.txt modname ++ O.txt " = "
×
UNCOV
1939
          ++ Link.from_path (x.path :> Paths.Path.t)
×
1940
        in
1941
        let content = O.documentedSrc md_def in
×
1942
        let anchor =
×
UNCOV
1943
          Some (Url.Anchor.from_identifier (id :> Paths.Identifier.t))
×
1944
        in
1945
        let attr = [ "modules" ] in
1946
        let doc = [] in
1947
        let decl = { Item.anchor; content; attr; doc; source_anchor = None } in
1948
        Item.Declaration decl
1949
      in
1950
      List.map f t
1951

1952
    let compilation_unit (t : Odoc_model.Lang.Compilation_unit.t) =
1953
      let url = Url.Path.from_identifier t.id in
359✔
1954
      let unit_doc, items =
359✔
1955
        match t.content with
1956
        | Module sign -> signature sign
359✔
UNCOV
1957
        | Pack packed -> ([], pack packed)
×
1958
      in
1959
      let source_anchor = source_anchor t.source_loc in
1960
      let page = make_expansion_page ~source_anchor url [ unit_doc ] items in
359✔
1961
      Document.Page page
359✔
1962

1963
    let page (t : Odoc_model.Lang.Page.t) =
1964
      (*let name =
1965
          match t.name.iv with `Page (_, name) | `LeafPage (_, name) -> name
1966
        in*)
1967
      (*let title = Odoc_model.Names.PageName.to_string name in*)
1968
      let url = Url.Path.from_identifier t.name in
69✔
1969
      let preamble, items = Sectioning.docs t.content.elements in
69✔
1970
      let source_anchor = None in
69✔
1971
      Document.Page { Page.preamble; items; url; source_anchor }
1972

1973
    let implementation (v : Odoc_model.Lang.Implementation.t) syntax_info
1974
        source_code =
1975
      match v.id with
28✔
UNCOV
1976
      | None -> []
×
1977
      | Some id ->
28✔
1978
          [
1979
            Document.Source_page
1980
              (Source_page.source id syntax_info v.source_info source_code);
28✔
1981
          ]
1982
  end
1983

1984
  include Page
1985

1986
  let type_expr = type_expr
1987

1988
  let record = record
1989

1990
  let unboxed_record = unboxed_record
1991
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