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

ocaml / odoc / 3192

21 May 2026 11:44AM UTC coverage: 71.012% (-0.1%) from 71.138%
3192

push

github

jonludlam
OxCaml: Add a test for kind abbreviation

10443 of 14706 relevant lines covered (71.01%)

5873.66 hits per line

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

80.45
/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 Type_expression : sig
308
    val type_expr : ?needs_parentheses:bool -> Lang.TypeExpr.t -> text
309

310
    val format_type_path :
311
      delim:[ `parens | `brackets ] -> Lang.TypeExpr.t list -> text -> text
312

313
    val kind_annotation :
314
      ?needs_parentheses:bool -> Odoc_model.Lang.Kind.t -> text
315

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

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

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

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

437
    and kind_annotation ?(needs_parentheses = false)
×
438
        (k : Odoc_model.Lang.Kind.t) =
439
      let enclose_parens_if_needed res =
×
440
        if needs_parentheses then enclose ~l:"(" res ~r:")" else res
×
441
      in
442
      match k with
443
      | Default -> O.noop
×
444
      | Abbreviation frag ->
×
445
          O.txt (Link.render_fragment_any (frag :> Paths.Fragment.t))
×
446
      | Mod (base, modes) ->
×
447
          let res =
448
            kind_annotation ~needs_parentheses:true base
×
449
            ++ O.txt " " ++ O.keyword "mod"
×
450
            ++ O.txt (" " ^ String.concat ~sep:" " modes)
×
451
          in
452
          enclose_parens_if_needed res
×
453
      | With (base, ty, modalities) ->
×
454
          let res =
455
            kind_annotation ~needs_parentheses:true base
×
456
            ++ O.txt " " ++ O.keyword "with" ++ O.txt " " ++ type_expr ty
×
457
            ++
458
            match modalities with
459
            | [] -> O.noop
×
460
            | mods -> O.txt " @@ " ++ O.txt (String.concat ~sep:" " mods)
×
461
          in
462
          enclose_parens_if_needed res
×
463
      | Kind_of ty ->
×
464
          let res = O.keyword "kind_of_" ++ O.txt " " ++ type_expr ty in
×
465
          enclose_parens_if_needed res
×
466
      | Product ks ->
×
467
          let res =
468
            O.list ks ~sep:(O.txt " & ") ~f:(fun k ->
×
469
                kind_annotation ~needs_parentheses:true k)
×
470
          in
471
          enclose_parens_if_needed res
×
472

473
    and with_kind_annotation kind base =
474
      match kind with
648✔
475
      | Odoc_model.Lang.Kind.Default -> base
648✔
476
      | k -> O.txt "(" ++ base ++ O.txt " : " ++ kind_annotation k ++ O.txt ")"
×
477

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

556
    and package_path pkg =
557
      Link.from_path (pkg.path :> Paths.Path.t)
24✔
558
      ++
559
      match pkg.substitutions with
560
      | [] -> O.noop
16✔
561
      | fst :: lst ->
8✔
562
          O.sp
563
          ++ O.box_hv (O.keyword "with" ++ O.txt " " ++ package_subst fst)
8✔
564
          ++ O.list lst ~f:(fun s ->
8✔
565
                 O.cut
8✔
566
                 ++ (O.box_hv
8✔
567
                    @@ O.txt " " ++ O.keyword "and" ++ O.txt " "
8✔
568
                       ++ package_subst s))
8✔
569

570
    and package_subst
571
        ((frag_typ, te) : Paths.Fragment.Type.t * Odoc_model.Lang.TypeExpr.t) :
572
        text =
573
      let typath = Link.from_fragment (frag_typ :> Paths.Fragment.leaf) in
16✔
574
      O.keyword "type" ++ O.txt " " ++ typath ++ O.txt " =" ++ O.sp
16✔
575
      ++ type_expr te
16✔
576
  end
577

578
  open Type_expression
579

580
  (* Also handles constructor declarations for exceptions and extensible
581
     variants, and exposes a few helpers used in formatting classes and signature
582
     constraints. *)
583
  module Type_declaration : sig
584
    val type_decl :
585
      ?is_substitution:bool ->
586
      Lang.Signature.recursive * Lang.TypeDecl.t ->
587
      Item.t
588

589
    val extension : Lang.Extension.t -> Item.t
590

591
    val record : Lang.TypeDecl.Field.t list -> DocumentedSrc.one list
592

593
    val unboxed_record :
594
      Lang.TypeDecl.UnboxedField.t list -> DocumentedSrc.one list
595

596
    val exn : Lang.Exception.t -> Item.t
597

598
    val format_params :
599
      ?delim:[ `parens | `brackets ] -> Lang.TypeDecl.param list -> text
600

601
    val format_manifest :
602
      ?is_substitution:bool ->
603
      ?compact_variants:bool ->
604
      Lang.TypeDecl.Equation.t ->
605
      text * bool
606

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

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

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

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

761
    let extension_constructor (t : Odoc_model.Lang.Extension.Constructor.t) =
762
      let id = (t.id :> Paths.Identifier.t) in
154✔
763
      let url = Url.from_identifier ~stop_before:true id in
764
      let anchor = Some url in
154✔
765
      let attrs = [ "def"; "variant"; Url.Anchor.string_of_kind url.kind ] in
154✔
766
      let code = O.documentedSrc (O.txt "| ") @ constructor id t.args t.res in
154✔
767
      let doc = Comment.to_ir t.doc.elements in
768
      let markers = Syntax.Comment.markers in
154✔
769
      DocumentedSrc.Nested { anchor; attrs; code; doc; markers }
770

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

798
    let exn (t : Odoc_model.Lang.Exception.t) =
799
      let cstr = constructor (t.id :> Paths.Identifier.t) t.args t.res in
66✔
800
      let content =
66✔
801
        O.documentedSrc (O.keyword "exception" ++ O.txt " ")
66✔
802
        @ cstr
803
        @ O.documentedSrc
66✔
804
            (if Syntax.Type.Exception.semicolon then O.txt ";" else O.noop)
×
805
      in
806
      let attr = [ "exception" ] in
807
      let anchor = path_to_id t.id in
808
      let doc = Comment.to_ir t.doc.elements in
66✔
809
      let source_anchor = source_anchor t.source_loc in
66✔
810
      Item.Declaration { attr; anchor; doc; content; source_anchor }
66✔
811

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

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

926
    let format_constraints constraints =
927
      O.list constraints ~f:(fun (t1, t2) ->
3,103✔
928
          O.sp
104✔
929
          ++ (O.box_hv
104✔
930
             @@ O.keyword "constraint" ++ O.sp
104✔
931
                ++ O.box_hv_no_indent (type_expr t1)
104✔
932
                ++ O.txt " =" ++ O.sp
104✔
933
                ++ O.box_hv_no_indent (type_expr t2)))
104✔
934

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

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

1042
  open Type_declaration
1043

1044
  module Value : sig
1045
    val value : Lang.Value.t -> Item.t
1046
  end = struct
1047
    let value (t : Odoc_model.Lang.Value.t) =
1048
      let extra_attr, semicolon =
912✔
1049
        match t.value with
1050
        | Abstract -> ([], Syntax.Value.semicolon)
888✔
1051
        | External _ -> ([ "external" ], Syntax.Type.External.semicolon)
24✔
1052
      in
1053
      let name = Paths.Identifier.name t.id in
1054
      let content =
912✔
1055
        O.documentedSrc
1056
          (O.box_hv
912✔
1057
          @@ O.keyword Syntax.Value.variable_keyword
912✔
1058
             ++ O.txt " " ++ O.txt name
912✔
1059
             ++ O.txt Syntax.Type.annotation_separator
912✔
1060
             ++ O.cut ++ type_expr t.type_
912✔
1061
             ++ if semicolon then O.txt ";" else O.noop)
×
1062
      in
1063
      let attr = [ "value" ] @ extra_attr in
912✔
1064
      let anchor = path_to_id t.id in
1065
      let doc = Comment.to_ir t.doc.elements in
912✔
1066
      let source_anchor = source_anchor t.source_loc in
912✔
1067
      Item.Declaration { attr; anchor; doc; content; source_anchor }
912✔
1068
  end
1069

1070
  open Value
1071

1072
  (* This chunk of code is responsible for sectioning list of items
1073
     according to headings by extracting headings as Items.
1074

1075
     TODO: This sectioning would be better done as a pass on the model directly.
1076
  *)
1077
  module Sectioning : sig
1078
    open Odoc_model
1079

1080
    val comment_items : Comment.elements -> Item.t list
1081

1082
    val docs : Comment.elements -> Item.t list * Item.t list
1083
  end = struct
1084
    let take_until_heading_or_end (docs : Odoc_model.Comment.elements) =
1085
      let content, _, rest =
693✔
1086
        Doctree.Take.until docs ~classify:(fun b ->
1087
            match b.Location.value with
1,481✔
1088
            | `Heading _ -> Stop_and_keep
202✔
1089
            | #Odoc_model.Comment.attached_block_element as doc ->
1,279✔
1090
                let content = Comment.attached_block_element doc in
1091
                Accum content)
1,279✔
1092
      in
1093
      (content, rest)
693✔
1094

1095
    let comment_items (input0 : Odoc_model.Comment.elements) =
1096
      let rec loop input_comment acc =
963✔
1097
        match input_comment with
2,493✔
1098
        | [] -> List.rev acc
963✔
1099
        | element :: input_comment -> (
1,530✔
1100
            match element.Location.value with
1101
            | `Heading h ->
837✔
1102
                let item = Comment.heading h in
1103
                loop input_comment (item :: acc)
837✔
1104
            | _ ->
693✔
1105
                let content, input_comment =
1106
                  take_until_heading_or_end (element :: input_comment)
1107
                in
1108
                let item = Item.Text content in
693✔
1109
                loop input_comment (item :: acc))
1110
      in
1111
      loop input0 []
1112

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

1137
  module Class : sig
1138
    val class_ : Lang.Class.t -> Item.t
1139

1140
    val class_type : Lang.ClassType.t -> Item.t
1141
  end = struct
1142
    let class_type_expr (cte : Odoc_model.Lang.ClassType.expr) =
1143
      match cte with
249✔
1144
      | Constr (path, args) ->
58✔
1145
          let link = Link.from_path (path :> Paths.Path.t) in
1146
          format_type_path ~delim:`brackets args link
58✔
1147
      | Signature _ ->
191✔
1148
          Syntax.Class.open_tag ++ O.txt " ... " ++ Syntax.Class.close_tag
191✔
1149

1150
    let method_ (t : Odoc_model.Lang.Method.t) =
1151
      let name = Paths.Identifier.name t.id in
90✔
1152
      let virtual_ =
90✔
1153
        if t.virtual_ then O.keyword "virtual" ++ O.txt " " else O.noop
8✔
1154
      in
1155
      let private_ =
1156
        if t.private_ then O.keyword "private" ++ O.txt " " else O.noop
8✔
1157
      in
1158
      let content =
1159
        O.documentedSrc
1160
          (O.keyword "method" ++ O.txt " " ++ private_ ++ virtual_ ++ O.txt name
90✔
1161
          ++ O.txt Syntax.Type.annotation_separator
90✔
1162
          ++ type_expr t.type_)
90✔
1163
      in
1164
      let attr = [ "method" ] in
90✔
1165
      let anchor = path_to_id t.id in
1166
      let doc = Comment.to_ir t.doc.elements in
90✔
1167
      Item.Declaration { attr; anchor; doc; content; source_anchor = None }
90✔
1168

1169
    let instance_variable (t : Odoc_model.Lang.InstanceVariable.t) =
1170
      let name = Paths.Identifier.name t.id in
17✔
1171
      let virtual_ =
17✔
1172
        if t.virtual_ then O.keyword "virtual" ++ O.txt " " else O.noop
8✔
1173
      in
1174
      let mutable_ =
1175
        if t.mutable_ then O.keyword "mutable" ++ O.txt " " else O.noop
8✔
1176
      in
1177
      let content =
1178
        O.documentedSrc
1179
          (O.keyword "val" ++ O.txt " " ++ mutable_ ++ virtual_ ++ O.txt name
17✔
1180
          ++ O.txt Syntax.Type.annotation_separator
17✔
1181
          ++ type_expr t.type_)
17✔
1182
      in
1183
      let attr = [ "value"; "instance-variable" ] in
17✔
1184
      let anchor = path_to_id t.id in
1185
      let doc = Comment.to_ir t.doc.elements in
17✔
1186
      Item.Declaration { attr; anchor; doc; content; source_anchor = None }
17✔
1187

1188
    let inherit_ (ih : Lang.ClassSignature.Inherit.t) =
1189
      let cte =
16✔
1190
        match ih.expr with
1191
        | Signature _ -> assert false (* Bold. *)
1192
        | cty -> cty
16✔
1193
      in
1194
      let content =
1195
        O.documentedSrc (O.keyword "inherit" ++ O.txt " " ++ class_type_expr cte)
16✔
1196
      in
1197
      let attr = [ "inherit" ] in
16✔
1198
      let anchor = None in
1199
      let doc = Comment.to_ir ih.doc.elements in
1200
      Item.Declaration { attr; anchor; doc; content; source_anchor = None }
16✔
1201

1202
    let constraint_ (cst : Lang.ClassSignature.Constraint.t) =
1203
      let content =
8✔
1204
        O.documentedSrc (format_constraints [ (cst.left, cst.right) ])
8✔
1205
      in
1206
      let attr = [] in
8✔
1207
      let anchor = None in
1208
      let doc = Comment.to_ir cst.doc.elements in
1209
      Item.Declaration { attr; anchor; doc; content; source_anchor = None }
8✔
1210

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

1236
    let rec class_decl (cd : Odoc_model.Lang.Class.decl) =
1237
      match cd with
182✔
1238
      | ClassType expr -> class_type_expr expr
166✔
1239
      (* TODO: factorize the following with [type_expr] *)
1240
      | Arrow (None, src, dst) ->
16✔
1241
          O.span
16✔
1242
            (type_expr ~needs_parentheses:true src
16✔
1243
            ++ O.txt " " ++ Syntax.Type.arrow)
16✔
1244
          ++ O.txt " " ++ class_decl dst
16✔
1245
      | Arrow (Some (RawOptional _ as lbl), _src, dst) ->
×
1246
          O.span
×
1247
            (O.box_hv
×
1248
            @@ label lbl ++ O.txt ":"
×
1249
               ++ tag "error" (O.txt "???")
×
1250
               ++ O.txt " " ++ Syntax.Type.arrow)
×
1251
          ++ O.sp ++ class_decl dst
×
1252
      | Arrow (Some lbl, src, dst) ->
×
1253
          O.span
×
1254
            (label lbl ++ O.txt ":"
×
1255
            ++ type_expr ~needs_parentheses:true src
×
1256
            ++ O.txt " " ++ Syntax.Type.arrow)
×
1257
          ++ O.txt " " ++ class_decl dst
×
1258

1259
    let class_ (t : Odoc_model.Lang.Class.t) =
1260
      let name = Paths.Identifier.name t.id in
166✔
1261
      let params =
166✔
1262
        match t.params with
1263
        | [] -> O.noop
142✔
1264
        | _ :: _ as params -> format_params ~delim:`brackets params ++ O.txt " "
24✔
1265
      in
1266
      let virtual_ =
1267
        if t.virtual_ then O.keyword "virtual" ++ O.txt " " else O.noop
24✔
1268
      in
1269

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

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

1339
  open Class
1340

1341
  module Module : sig
1342
    val signature : Lang.Signature.t -> Comment.Comment.elements * Item.t list
1343
    (** Returns [header_doc, content]. *)
1344
  end = struct
1345
    let internal_module m =
1346
      let open Lang.Module in
1,924✔
1347
      match m.id.iv with
1348
      | `Module (_, name) when ModuleName.is_hidden name -> true
81✔
1349
      | _ -> false
1,843✔
1350

1351
    let internal_type t =
1352
      let open Lang.TypeDecl in
2,969✔
1353
      match t.id.iv with
1354
      | `Type (_, name) when TypeName.is_hidden name -> true
1✔
1355
      | _ -> false
2,968✔
1356

1357
    let internal_value v =
1358
      let open Lang.Value in
1,018✔
1359
      match v.id.iv with
1360
      | `Value (_, name) when ValueName.is_hidden name -> true
106✔
1361
      | _ -> false
912✔
1362

1363
    let internal_module_type t =
1364
      let open Lang.ModuleType in
1,361✔
1365
      match t.id.iv with
1366
      | `ModuleType (_, name) when ModuleTypeName.is_hidden name -> true
×
1367
      | _ -> false
1,361✔
1368

1369
    let internal_module_substitution t =
1370
      let open Lang.ModuleSubstitution in
8✔
1371
      match t.id.iv with
1372
      | `Module (_, name) when ModuleName.is_hidden name -> true
×
1373
      | _ -> false
8✔
1374

1375
    let internal_module_type_substitution t =
1376
      let open Lang.ModuleTypeSubstitution in
8✔
1377
      match t.id.iv with
1378
      | `ModuleType (_, name) when ModuleTypeName.is_hidden name -> true
×
1379
      | _ -> false
8✔
1380

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

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

1467
    and module_substitution (t : Odoc_model.Lang.ModuleSubstitution.t) =
1468
      let name = Paths.Identifier.name t.id in
8✔
1469
      let path = Link.from_path (t.manifest :> Paths.Path.t) in
8✔
1470
      let content =
8✔
1471
        O.documentedSrc
1472
          (O.keyword "module" ++ O.txt " " ++ O.txt name ++ O.txt " :=" ++ O.sp
8✔
1473
         ++ path)
8✔
1474
      in
1475
      let attr = [ "module-substitution" ] in
8✔
1476
      let anchor = path_to_id t.id in
1477
      let doc = Comment.to_ir t.doc.elements in
8✔
1478
      Item.Declaration { attr; anchor; doc; content; source_anchor = None }
8✔
1479

1480
    and module_type_substitution (t : Odoc_model.Lang.ModuleTypeSubstitution.t)
1481
        =
1482
      let prefix =
8✔
1483
        O.keyword "module" ++ O.txt " " ++ O.keyword "type" ++ O.txt " "
8✔
1484
      in
1485
      let source_anchor = None in
8✔
1486
      let modname = Paths.Identifier.name t.id in
1487
      let modname, expansion_doc, mty =
8✔
1488
        module_type_manifest ~subst:true ~source_anchor modname t.id
1489
          t.doc.elements (Some t.manifest) prefix
1490
      in
1491
      let content =
8✔
1492
        O.documentedSrc (prefix ++ modname)
8✔
1493
        @ mty
1494
        @ O.documentedSrc
8✔
1495
            (if Syntax.Mod.close_tag_semicolon then O.txt ";" else O.noop)
×
1496
      in
1497
      let attr = [ "module-type" ] in
1498
      let anchor = path_to_id t.id in
1499
      let doc = Comment.synopsis ~decl_doc:t.doc.elements ~expansion_doc in
8✔
1500
      Item.Declaration { attr; anchor; doc; content; source_anchor }
1501

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

1541
    and expansion_of_module_type_expr :
1542
        Odoc_model.Lang.ModuleType.expr ->
1543
        (Comment.Comment.elements * Item.t list) option =
1544
     fun t ->
1545
      let rec simple_expansion_of (t : Odoc_model.Lang.ModuleType.expr) =
3,218✔
1546
        match t with
3,437✔
1547
        | Path { p_expansion = None; _ }
379✔
1548
        | TypeOf { t_expansion = None; _ }
8✔
1549
        | With { w_expansion = None; _ }
×
1550
        | Strengthen { s_expansion = None; _ } ->
×
1551
            None
1552
        | Path { p_expansion = Some e; _ }
426✔
1553
        | TypeOf { t_expansion = Some e; _ }
56✔
1554
        | With { w_expansion = Some e; _ }
218✔
1555
        | Strengthen { s_expansion = Some e; _ } ->
×
1556
            Some e
1557
        | Signature sg -> Some (Signature sg)
2,131✔
1558
        | Functor (f_parameter, e) -> (
219✔
1559
            match simple_expansion_of e with
1560
            | Some e -> Some (Functor (f_parameter, e))
211✔
1561
            | None -> None)
8✔
1562
      in
1563
      match simple_expansion_of t with
1564
      | None -> None
387✔
1565
      | Some e -> Some (simple_expansion e)
2,831✔
1566

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

1612
    and simple_expansion_in_decl (base : Paths.Identifier.Module.t) se =
1613
      let rec ty_of_se :
102✔
1614
          Lang.ModuleType.simple_expansion -> Lang.ModuleType.expr = function
1615
        | Signature sg -> Signature sg
102✔
1616
        | Functor (arg, sg) -> Functor (arg, ty_of_se sg)
×
1617
      in
1618
      mty_in_decl (base :> Paths.Identifier.Signature.t) (ty_of_se se)
102✔
1619

1620
    and mdexpr_in_decl (base : Paths.Identifier.Module.t) md =
1621
      let sig_dotdotdot =
1,843✔
1622
        O.txt Syntax.Type.annotation_separator
1,843✔
1623
        ++ O.cut ++ Syntax.Mod.open_tag ++ O.txt " ... " ++ Syntax.Mod.close_tag
1,843✔
1624
      in
1625
      match md with
1,843✔
1626
      | Alias (_, Some se) -> simple_expansion_in_decl base se
102✔
1627
      | Alias (p, _) when not Paths.Path.(is_hidden (p :> t)) ->
186✔
1628
          O.txt " =" ++ O.sp ++ mdexpr md
186✔
1629
      | Alias _ -> sig_dotdotdot
×
1630
      | ModuleType mt -> mty_in_decl (base :> Paths.Identifier.Signature.t) mt
1,555✔
1631

1632
    and mdexpr : Odoc_model.Lang.Module.decl -> text = function
1633
      | Alias (mod_path, _) -> Link.from_path (mod_path :> Paths.Path.t)
242✔
1634
      | ModuleType mt -> mty mt
×
1635

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

1667
    and module_type (t : Odoc_model.Lang.ModuleType.t) =
1668
      let prefix =
1,361✔
1669
        O.keyword "module" ++ O.txt " " ++ O.keyword "type" ++ O.txt " "
1,361✔
1670
      in
1671
      let modname = Paths.Identifier.name t.id in
1,361✔
1672
      let source_anchor = source_anchor t.source_loc in
1,361✔
1673
      let modname, expansion_doc, mty =
1,361✔
1674
        module_type_manifest ~subst:false ~source_anchor modname t.id
1675
          t.doc.elements t.expr prefix
1676
      in
1677
      let content =
1,361✔
1678
        O.documentedSrc (prefix ++ modname)
1,361✔
1679
        @ mty
1680
        @ O.documentedSrc
1,361✔
1681
            (if Syntax.Mod.close_tag_semicolon then O.txt ";" else O.noop)
×
1682
      in
1683
      let attr = [ "module-type" ] in
1684
      let anchor = path_to_id t.id in
1685
      let doc = Comment.synopsis ~decl_doc:t.doc.elements ~expansion_doc in
1,361✔
1686
      Item.Declaration { attr; anchor; doc; content; source_anchor }
1687

1688
    and umty_hidden : Odoc_model.Lang.ModuleType.U.expr -> bool = function
1689
      | Path p -> Paths.Path.(is_hidden (p :> t))
403✔
1690
      | With (_, expr) -> umty_hidden expr
25✔
1691
      | TypeOf (ModPath m, _) | TypeOf (StructInclude m, _) ->
42✔
1692
          Paths.Path.(is_hidden (m :> t))
1693
      | Signature _ -> false
14✔
1694
      | Strengthen (expr, p, _) ->
×
1695
          umty_hidden expr || Paths.Path.(is_hidden (p :> t))
×
1696

1697
    and mty_hidden : Odoc_model.Lang.ModuleType.expr -> bool = function
1698
      | Path { p_path = mty_path; _ } -> Paths.Path.(is_hidden (mty_path :> t))
837✔
1699
      | With { w_expr; _ } -> umty_hidden w_expr
218✔
1700
      | TypeOf { t_desc = ModPath m; _ }
48✔
1701
      | TypeOf { t_desc = StructInclude m; _ } ->
16✔
1702
          Paths.Path.(is_hidden (m :> t))
1703
      | _ -> false
2,297✔
1704

1705
    and mty_with subs expr =
1706
      umty expr ++ O.sp ++ O.keyword "with" ++ O.txt " "
238✔
1707
      ++ O.list
238✔
1708
           ~sep:(O.cut ++ O.txt " " ++ O.keyword "and" ++ O.txt " ")
238✔
1709
           ~f:(fun x -> O.span (substitution x))
279✔
1710
           subs
1711

1712
    and mty_strengthen expr path =
1713
      umty expr ++ O.sp ++ O.keyword "with" ++ O.txt " "
×
1714
      ++ Link.from_path (path :> Paths.Path.t)
×
1715

1716
    and mty_typeof t_desc =
1717
      match t_desc with
154✔
1718
      | Odoc_model.Lang.ModuleType.ModPath m ->
90✔
1719
          O.keyword "module" ++ O.txt " " ++ O.keyword "type" ++ O.txt " "
90✔
1720
          ++ O.keyword "of" ++ O.txt " "
90✔
1721
          ++ Link.from_path (m :> Paths.Path.t)
90✔
1722
      | StructInclude m ->
64✔
1723
          O.keyword "module" ++ O.txt " " ++ O.keyword "type" ++ O.txt " "
64✔
1724
          ++ O.keyword "of" ++ O.txt " " ++ O.keyword "struct" ++ O.txt " "
64✔
1725
          ++ O.keyword "include" ++ O.txt " "
64✔
1726
          ++ Link.from_path (m :> Paths.Path.t)
64✔
1727
          ++ O.txt " " ++ O.keyword "end"
64✔
1728

1729
    and is_elidable_with_u : Odoc_model.Lang.ModuleType.U.expr -> bool =
1730
      function
1731
      | Path _ -> false
213✔
1732
      | Signature _ -> true
5✔
1733
      | With (_, expr) -> is_elidable_with_u expr
×
1734
      | TypeOf _ -> false
25✔
1735
      | Strengthen (expr, _, _) -> is_elidable_with_u expr
×
1736

1737
    and umty : Odoc_model.Lang.ModuleType.U.expr -> text =
1738
     fun m ->
1739
      match m with
527✔
1740
      | Path p -> Link.from_path (p :> Paths.Path.t)
403✔
1741
      | Signature _ ->
9✔
1742
          Syntax.Mod.open_tag ++ O.txt " ... " ++ Syntax.Mod.close_tag
9✔
1743
      | With (_, expr) when is_elidable_with_u expr ->
25✔
1744
          Syntax.Mod.open_tag ++ O.txt " ... " ++ Syntax.Mod.close_tag
5✔
1745
      | With (subs, expr) -> mty_with subs expr
20✔
1746
      | TypeOf (t_desc, _) -> mty_typeof t_desc
90✔
1747
      | Strengthen (expr, _, _) when is_elidable_with_u expr ->
×
1748
          Syntax.Mod.open_tag ++ O.txt " ... " ++ Syntax.Mod.close_tag
×
1749
      | Strengthen (expr, p, _) -> mty_strengthen expr (p :> Paths.Path.t)
×
1750

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

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

1826
    (* TODO : Centralize the list juggling for type parameters *)
1827
    and type_expr_in_subst td typath =
1828
      let typath = Link.from_fragment typath in
151✔
1829
      match td.Lang.TypeDecl.Equation.params with
151✔
1830
      | [] -> typath
125✔
1831
      | l -> Syntax.Type.handle_substitution_params typath (format_params l)
26✔
1832

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

1873
    and include_ (t : Odoc_model.Lang.Include.t) =
1874
      let decl_hidden =
290✔
1875
        match t.decl with
1876
        | Alias p -> Paths.Path.(is_hidden (p :> t))
×
1877
        | ModuleType mty -> umty_hidden mty
290✔
1878
      in
1879
      let status = if decl_hidden then `Inline else t.status in
1✔
1880

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

1909
  open Module
1910

1911
  module Page : sig
1912
    val compilation_unit : Lang.Compilation_unit.t -> Document.t
1913

1914
    val page : Lang.Page.t -> Document.t
1915

1916
    val implementation :
1917
      Lang.Implementation.t ->
1918
      Syntax_highlighter.infos ->
1919
      string ->
1920
      Document.t list
1921
  end = struct
1922
    let pack : Lang.Compilation_unit.Packed.t -> Item.t list =
1923
     fun t ->
1924
      let f x =
×
1925
        let id = x.Lang.Compilation_unit.Packed.id in
×
1926
        let modname = Paths.Identifier.name id in
1927
        let md_def =
×
1928
          O.keyword "module" ++ O.txt " " ++ O.txt modname ++ O.txt " = "
×
1929
          ++ Link.from_path (x.path :> Paths.Path.t)
×
1930
        in
1931
        let content = O.documentedSrc md_def in
×
1932
        let anchor =
×
1933
          Some (Url.Anchor.from_identifier (id :> Paths.Identifier.t))
×
1934
        in
1935
        let attr = [ "modules" ] in
1936
        let doc = [] in
1937
        let decl = { Item.anchor; content; attr; doc; source_anchor = None } in
1938
        Item.Declaration decl
1939
      in
1940
      List.map f t
1941

1942
    let compilation_unit (t : Odoc_model.Lang.Compilation_unit.t) =
1943
      let url = Url.Path.from_identifier t.id in
359✔
1944
      let unit_doc, items =
359✔
1945
        match t.content with
1946
        | Module sign -> signature sign
359✔
1947
        | Pack packed -> ([], pack packed)
×
1948
      in
1949
      let source_anchor = source_anchor t.source_loc in
1950
      let page = make_expansion_page ~source_anchor url [ unit_doc ] items in
359✔
1951
      Document.Page page
359✔
1952

1953
    let page (t : Odoc_model.Lang.Page.t) =
1954
      (*let name =
1955
          match t.name.iv with `Page (_, name) | `LeafPage (_, name) -> name
1956
        in*)
1957
      (*let title = Odoc_model.Names.PageName.to_string name in*)
1958
      let url = Url.Path.from_identifier t.name in
69✔
1959
      let preamble, items = Sectioning.docs t.content.elements in
69✔
1960
      let source_anchor = None in
69✔
1961
      Document.Page { Page.preamble; items; url; source_anchor }
1962

1963
    let implementation (v : Odoc_model.Lang.Implementation.t) syntax_info
1964
        source_code =
1965
      match v.id with
28✔
1966
      | None -> []
×
1967
      | Some id ->
28✔
1968
          [
1969
            Document.Source_page
1970
              (Source_page.source id syntax_info v.source_info source_code);
28✔
1971
          ]
1972
  end
1973

1974
  include Page
1975

1976
  let type_expr = type_expr
1977

1978
  let record = record
1979

1980
  let unboxed_record = unboxed_record
1981
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