Skip to content

Commit 8973686

Browse files
authored
Merge pull request #1173 from Octachron/fix_type_naming_in_414
Fix the type_expr loader in OCaml 4.14 and later
2 parents e394650 + 3c89807 commit 8973686

File tree

6 files changed

+106
-46
lines changed

6 files changed

+106
-46
lines changed

CHANGES.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,9 @@
5757
- Fix wrong links to standalone comments in search results (#1118, @panglesd)
5858
- Remove duplicated or unwanted comments (@Julow, #1133)
5959
This could happen with inline includes.
60+
- Fix misprinting of type variables from ml files for OCaml 4.14 and later
61+
(multiple occurences of the same type variable could be named differently)
62+
(@octachron, #1173)
6063

6164

6265
# 2.4.0

src/loader/cmi.ml

Lines changed: 69 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,13 @@ module Paths = Odoc_model.Paths
2727

2828
module Compat = struct
2929
#if OCAML_VERSION >= (4, 14, 0)
30+
(** this is the type on which physical equality is meaningful *)
31+
type repr_type_node = Types.transient_expr
32+
33+
(** repr has morally type [type_expr -> repr_type_node] in all OCaml
34+
versions *)
35+
let repr x = Transient_expr.repr x
36+
3037
let get_desc = Types.get_desc
3138
let get_row_name = Types.row_name
3239
let row_field_repr = Types.row_field_repr
@@ -35,30 +42,40 @@ module Compat = struct
3542
let row_closed = Types.row_closed
3643
let row_fields = Types.row_fields
3744
let field_public = Types.Fpublic
38-
let repr x = x
3945
let self_type = Btype.self_type
4046
let csig_self x = x.Types.csig_self
4147
let row_repr x = x
4248
let concr_mem = Types.Meths.mem
4349
let csig_concr x = x.Types.csig_meths
50+
let eq_type = Types.eq_type
51+
let invisible_wrap ty = newty2 ~level:Btype.generic_level (Ttuple [ty])
4452
#else
45-
let get_desc x = x.Types.desc
53+
type repr_type_node = Types.type_expr
54+
let repr = Btype.repr
55+
let get_desc x = (repr x).Types.desc
4656
let get_row_name x = x.Types.row_name
4757
let row_field_repr = Btype.row_field_repr
4858
let field_kind_repr = Btype.field_kind_repr
4959
let static_row_repr x = Btype.static_row (Btype.row_repr x)
5060
let row_closed x = x.Types.row_closed
5161
let row_fields x = x.Types.row_fields
5262
let field_public = Types.Fpresent
53-
let repr = Btype.repr
5463
let self_type = Ctype.self_type
5564
let csig_self x = Btype.repr x.Types.csig_self
5665
let row_repr = Btype.row_repr
5766
let concr_mem = Types.Concr.mem
5867
let csig_concr x = x.Types.csig_concr
68+
let eq_type x y = x == y || repr x == repr y
69+
70+
(** Create a new node pointing to [ty] that is printed in the same way as
71+
[ty]*)
72+
let invisible_wrap ty =
73+
Btype.(newty2 generic_level (Ttuple [ty]))
5974
#endif
6075
end
6176

77+
let proxy ty = Compat.(repr (Btype.proxy ty))
78+
6279
let opt_map f = function
6380
| None -> None
6481
| Some x -> Some (f x)
@@ -87,7 +104,10 @@ let read_label lbl =
87104

88105
(* Handle type variable names *)
89106

90-
let used_names = ref []
107+
(** To identify equal type node for type variables, we need a map from the
108+
representative type node to names. Otherwise, equivalent variables would end
109+
up with distinct names *)
110+
let used_names : (Compat.repr_type_node * string) list ref = ref []
91111
let name_counter = ref 0
92112
let reserved_names = ref []
93113

@@ -119,25 +139,27 @@ let fresh_name base =
119139
done;
120140
!current_name
121141

122-
let name_of_type (ty : Types.type_expr) =
142+
let name_of_type_repr (ty : Compat.repr_type_node) =
123143
try
124144
List.assq ty !used_names
125145
with Not_found ->
126146
let base =
127-
match Compat.get_desc ty with
147+
match ty.desc with
128148
| Tvar (Some name) | Tunivar (Some name) -> name
129149
| _ -> next_name ()
130150
in
131151
let name = fresh_name base in
132152
if name <> "_" then used_names := (ty, name) :: !used_names;
133153
name
134154

155+
let name_of_type ty = name_of_type_repr (Compat.repr ty)
156+
135157
let remove_names tyl =
136158
used_names := List.filter (fun (ty,_) -> not (List.memq ty tyl)) !used_names
137159

138160
(* Handle recursive types and shared row variables *)
139161

140-
let aliased = ref []
162+
let aliased: Compat.repr_type_node list ref = ref []
141163
let used_aliases = ref []
142164

143165
let reset_aliased () = aliased := []; used_aliases := []
@@ -149,20 +171,21 @@ let aliasable (ty : Types.type_expr) =
149171
| Tvar _ | Tunivar _ | Tpoly _ -> false
150172
| _ -> true
151173

152-
let add_alias ty =
153-
let px = Btype.proxy ty in
174+
let add_alias_proxy px =
154175
if not (List.memq px !aliased) then begin
155176
aliased := px :: !aliased;
156-
match Compat.get_desc px with
177+
match px.desc with
157178
| Tvar name | Tunivar name -> reserve_name name
158179
| _ -> ()
159180
end
160181

161-
let used_alias (px : Types.type_expr) = List.memq px !used_aliases
182+
let add_alias ty = add_alias_proxy (proxy ty)
183+
184+
let used_alias (px : Compat.repr_type_node) = List.memq px !used_aliases
162185

163-
let use_alias (px : Types.type_expr) = used_aliases := px :: !used_aliases
186+
let use_alias (px : Compat.repr_type_node) = used_aliases := px :: !used_aliases
164187

165-
let visited_rows = ref []
188+
let visited_rows: Compat.repr_type_node list ref = ref []
166189

167190
let reset_visited_rows () = visited_rows := []
168191

@@ -191,9 +214,8 @@ let namable_row row =
191214

192215
let mark_type ty =
193216
let rec loop visited ty =
194-
let ty = Compat.repr ty in
195-
let px = Btype.proxy ty in
196-
if List.memq px visited && aliasable ty then add_alias px else
217+
let px = proxy ty in
218+
if List.memq px visited && aliasable ty then add_alias_proxy px else
197219
let visited = px :: visited in
198220
match Compat.get_desc ty with
199221
| Tvar name -> reserve_name name
@@ -204,7 +226,7 @@ let mark_type ty =
204226
| Tconstr(_, tyl, _) ->
205227
List.iter (loop visited) tyl
206228
| Tvariant row ->
207-
if is_row_visited px then add_alias px else
229+
if is_row_visited px then add_alias_proxy px else
208230
begin
209231
if not (Compat.static_row_repr row) then visit_row px;
210232
match Compat.get_row_name row with
@@ -214,7 +236,7 @@ let mark_type ty =
214236
Btype.iter_row (loop visited) row
215237
end
216238
| Tobject (fi, nm) ->
217-
if is_row_visited px then add_alias px else
239+
if is_row_visited px then add_alias_proxy px else
218240
begin
219241
visit_object ty px;
220242
match !nm with
@@ -268,31 +290,34 @@ let mark_value_description vd =
268290
mark_type vd.val_type
269291

270292
let mark_type_parameter param =
271-
add_alias param;
293+
let px = proxy param in
294+
add_alias_proxy px;
272295
mark_type param;
273-
if aliasable param then use_alias (Btype.proxy param)
296+
if aliasable param then use_alias px
274297

275298
#if OCAML_VERSION<(4,13,0)
276-
let tsubst x = Tsubst x
277299
let tvar_none ty = ty.desc <- Tvar None
278300
#elif OCAML_VERSION < (4,14,0)
279-
let tsubst x = Tsubst(x,None)
280301
let tvar_none ty = Types.Private_type_expr.set_desc ty (Tvar None)
281302
#else
282-
let tsubst x = Tsubst(x,None)
283303
let tvar_none ty = Types.Transient_expr.(set_desc (coerce ty) (Tvar None))
284304
#endif
285305

286-
let prepare_type_parameters params manifest =
306+
let wrap_constrained_params tyl =
287307
let params =
288308
List.fold_left
289-
(fun params param ->
290-
let param = Compat.repr param in
291-
if List.memq param params then Btype.newgenty (tsubst param) :: params
292-
else param :: params)
293-
[] params
294-
in
295-
let params = List.rev params in
309+
(fun tyl ty ->
310+
if List.exists (Compat.eq_type ty) tyl
311+
then Compat.invisible_wrap ty :: tyl
312+
else ty :: tyl)
313+
(* Two parameters might be identical due to a constraint but we need to
314+
print them differently in order to make the output syntactically valid.
315+
We use [Ttuple [ty]] because it is printed as [ty]. *)
316+
[] tyl
317+
in List.rev params
318+
319+
let prepare_type_parameters params manifest =
320+
let params = wrap_constrained_params params in
296321
begin match manifest with
297322
| Some ty ->
298323
let vars = Ctype.free_variables ty in
@@ -366,22 +391,22 @@ let mark_exception ext =
366391
let rec mark_class_type params = function
367392
| Cty_constr (_, tyl, cty) ->
368393
let sty = Compat.self_type cty in
369-
if is_row_visited (Btype.proxy sty)
394+
if is_row_visited (proxy sty)
370395
|| List.exists aliasable params
371396
|| List.exists (Ctype.deep_occur sty) tyl
372397
then mark_class_type params cty
373398
else List.iter mark_type tyl
374399
| Cty_signature sign ->
375400
let sty = Compat.csig_self sign in
376-
let px = Btype.proxy sty in
377-
if is_row_visited px then add_alias sty
401+
let px = proxy sty in
402+
if is_row_visited px then add_alias_proxy px
378403
else visit_row px;
379404
let (fields, _) =
380405
Ctype.flatten_fields (Ctype.object_fields sign.csig_self)
381406
in
382407
List.iter (fun (_, _, ty) -> mark_type ty) fields;
383408
Vars.iter (fun _ (_, _, ty) -> mark_type ty) sign.csig_vars;
384-
if is_aliased sty && aliasable sty then use_alias px
409+
if is_aliased px && aliasable sty then use_alias px
385410
| Cty_arrow (_, ty, cty) ->
386411
mark_type ty;
387412
mark_class_type params cty
@@ -398,8 +423,7 @@ let mark_class_declaration cld =
398423

399424
let rec read_type_expr env typ =
400425
let open TypeExpr in
401-
let typ = Compat.repr typ in
402-
let px = Btype.proxy typ in
426+
let px = proxy typ in
403427
if used_alias px then Var (name_of_type typ)
404428
else begin
405429
let alias =
@@ -418,7 +442,7 @@ let rec read_type_expr env typ =
418442
| Tarrow(lbl, arg, res, _) ->
419443
let arg =
420444
if Btype.is_optional lbl then
421-
match Compat.get_desc (Compat.repr arg) with
445+
match Compat.get_desc arg with
422446
| Tconstr(_option, [arg], _) -> read_type_expr env arg
423447
| _ -> assert false
424448
else read_type_expr env arg
@@ -439,7 +463,7 @@ let rec read_type_expr env typ =
439463
| Tpoly (typ, []) -> read_type_expr env typ
440464
| Tpoly (typ, tyl) ->
441465
let tyl = List.map Compat.repr tyl in
442-
let vars = List.map name_of_type tyl in
466+
let vars = List.map name_of_type_repr tyl in
443467
let typ = read_type_expr env typ in
444468
remove_names tyl;
445469
Poly(vars, typ)
@@ -540,8 +564,7 @@ and read_row env _px row =
540564
and read_object env fi nm =
541565
let open TypeExpr in
542566
let open TypeExpr.Object in
543-
let fi = Compat.repr fi in
544-
let px = Btype.proxy fi in
567+
let px = proxy fi in
545568
if used_alias px then Var (name_of_type fi)
546569
else begin
547570
use_alias px;
@@ -816,14 +839,14 @@ let read_instance_variable env parent (name, mutable_, virtual_, typ) =
816839
ClassSignature.InstanceVariable {id; doc; mutable_; virtual_; type_}
817840

818841
let read_self_type sty =
819-
let sty = Compat.repr sty in
820-
if not (is_aliased sty) then None
821-
else Some (TypeExpr.Var (name_of_type (Btype.proxy sty)))
842+
let px = proxy sty in
843+
if not (is_aliased px) then None
844+
else Some (TypeExpr.Var (name_of_type_repr px))
822845

823846
let rec read_class_signature env parent params =
824847
let open ClassType in function
825848
| Cty_constr(p, _, cty) ->
826-
if is_row_visited (Btype.proxy (Compat.self_type cty))
849+
if is_row_visited (proxy (Compat.self_type cty))
827850
|| List.exists aliasable params
828851
then read_class_signature env parent params cty
829852
else begin
@@ -902,7 +925,7 @@ let rec read_class_type env parent params =
902925
| Cty_arrow(lbl, arg, cty) ->
903926
let arg =
904927
if Btype.is_optional lbl then
905-
match Compat.get_desc (Compat.repr arg) with
928+
match Compat.get_desc arg with
906929
| Tconstr(path, [arg], _)
907930
when OCamlPath.same path Predef.path_option ->
908931
read_type_expr env arg

test/generators/cases/bugs.ml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,5 @@ let foo (type a) ?(bar : a opt) () = ()
33
(** Triggers an assertion failure when
44
{:https://github.com/ocaml/odoc/issues/101} is not fixed. *)
55

6+
let repeat x y = (x, y, x, y)
7+
(** Renders as [val repeat : 'a -> 'b -> 'c * 'd * 'e * 'f] before https://github.com/ocaml/odoc/pull/1173 *)

test/generators/html/Bugs.html

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,29 @@ <h1>Module <code><span>Bugs</span></code></h1>
4040
</p>
4141
</div>
4242
</div>
43+
<div class="odoc-spec">
44+
<div class="spec value anchored" id="val-repeat">
45+
<a href="#val-repeat" class="anchor"></a>
46+
<code>
47+
<span><span class="keyword">val</span> repeat :
48+
<span><span class="type-var">'a</span>
49+
<span class="arrow">&#45;&gt;</span>
50+
</span>
51+
<span><span class="type-var">'b</span>
52+
<span class="arrow">&#45;&gt;</span>
53+
</span> <span class="type-var">'a</span> *
54+
<span class="type-var">'b</span> * <span class="type-var">'a</span>
55+
* <span class="type-var">'b</span>
56+
</span>
57+
</code>
58+
</div>
59+
<div class="spec-doc">
60+
<p>Renders as
61+
<code>val repeat : 'a -&gt; 'b -&gt; 'c * 'd * 'e * 'f</code> before
62+
https://github.com/ocaml/odoc/pull/1173
63+
</p>
64+
</div>
65+
</div>
4366
</div>
4467
</body>
4568
</html>

test/generators/latex/Bugs.tex

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,7 @@ \section{Module \ocamlinlinecode{Bugs}}\label{module-Bugs}%
22
\label{module-Bugs-type-opt}\ocamlcodefragment{\ocamltag{keyword}{type} 'a opt = \ocamltag{type-var}{'a} option}\\
33
\label{module-Bugs-val-foo}\ocamlcodefragment{\ocamltag{keyword}{val} foo : \ocamltag{optlabel}{?bar}:\ocamltag{type-var}{'a} \ocamltag{arrow}{$\rightarrow$} unit \ocamltag{arrow}{$\rightarrow$} unit}\begin{ocamlindent}Triggers an assertion failure when \href{https://github.com/ocaml/odoc/issues/101}{https://github.com/ocaml/odoc/issues/101}\footnote{\url{https://github.com/ocaml/odoc/issues/101}} is not fixed.\end{ocamlindent}%
44
\medbreak
5+
\label{module-Bugs-val-repeat}\ocamlcodefragment{\ocamltag{keyword}{val} repeat : \ocamltag{type-var}{'a} \ocamltag{arrow}{$\rightarrow$} \ocamltag{type-var}{'b} \ocamltag{arrow}{$\rightarrow$} \ocamltag{type-var}{'a} * \ocamltag{type-var}{'b} * \ocamltag{type-var}{'a} * \ocamltag{type-var}{'b}}\begin{ocamlindent}Renders as \ocamlinlinecode{val repeat : 'a -> 'b -> 'c * 'd * 'e * 'f} before https://github.com/ocaml/odoc/pull/1173\end{ocamlindent}%
6+
\medbreak
57

68

test/generators/man/Bugs.3o

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,4 +23,11 @@ https://github\.com/ocaml/odoc/issues/101
2323
.UE
2424
is not fixed\.
2525
.nf
26+
.sp
27+
\f[CB]val\fR repeat : \f[CB]'a\fR \f[CB]\->\fR \f[CB]'b\fR \f[CB]\->\fR \f[CB]'a\fR * \f[CB]'b\fR * \f[CB]'a\fR * \f[CB]'b\fR
28+
.fi
29+
.br
30+
.ti +2
31+
Renders as val repeat : 'a -> 'b -> 'c * 'd * 'e * 'f before https://github\.com/ocaml/odoc/pull/1173
32+
.nf
2633

0 commit comments

Comments
 (0)