Skip to content

Commit f2fbcef

Browse files
committed
Fix nested canonical modules
Fundamentally this just needs us to reresolve the paths when figuring out the shortest canonical path to use, but this is a little tricky to get right.
1 parent 0b22e72 commit f2fbcef

File tree

2 files changed

+150
-61
lines changed

2 files changed

+150
-61
lines changed

src/xref2/component.ml

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -594,7 +594,10 @@ module Fmt = struct
594594
sg.items;
595595
Format.fprintf ppf "@] (removed=[%a])" removed_item_list sg.removed
596596

597-
and option pp ppf x =
597+
and option :
598+
type a.
599+
(Format.formatter -> a -> unit) -> Format.formatter -> a option -> unit =
600+
fun pp ppf x ->
598601
match x with
599602
| Some x -> Format.fprintf ppf "Some(%a)" pp x
600603
| None -> Format.fprintf ppf "None"
@@ -697,7 +700,10 @@ module Fmt = struct
697700
| Alias (p, _) -> Format.fprintf ppf "= %a" module_path p
698701
| ModuleType mt -> Format.fprintf ppf ": %a" module_type_expr mt
699702

700-
and module_ ppf m = Format.fprintf ppf "%a" module_decl m.type_
703+
and module_ ppf m =
704+
Format.fprintf ppf "%a (canonical=%a)" module_decl m.type_
705+
(option model_path)
706+
(m.canonical :> Odoc_model.Paths.Path.t option)
701707

702708
and simple_expansion ppf (m : ModuleType.simple_expansion) =
703709
match m with

src/xref2/tools.ml

Lines changed: 142 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -1179,39 +1179,63 @@ and reresolve_module_gpath :
11791179
( reresolve_module_gpath env functor_path,
11801180
reresolve_module_gpath env argument_path )
11811181
| `Module (parent, name) -> `Module (reresolve_module_gpath env parent, name)
1182-
| `Alias (p1, `Resolved p2) ->
1183-
`Alias
1184-
( reresolve_module_gpath env p1,
1185-
`Resolved (reresolve_module_gpath env p2) )
11861182
| `Alias (p1, p2) ->
11871183
let dest' = reresolve_module_gpath env p1 in
1188-
let p2' =
1189-
if
1190-
Odoc_model.Paths.Path.Resolved.Module.is_hidden
1191-
~weak_canonical_test:false dest'
1192-
then
1193-
let cp2 = Component.Of_Lang.(module_path (empty ()) p2) in
1194-
match
1195-
resolve_module env ~mark_substituted:false ~add_canonical:true cp2
1196-
with
1197-
| Ok (p2', _) ->
1198-
Lang_of.(
1199-
Path.module_ (empty ()) (`Resolved (reresolve_module env p2')))
1200-
| Error _ -> p2
1201-
else p2
1202-
in
1203-
`Alias (dest', p2')
1184+
if
1185+
Odoc_model.Paths.Path.Resolved.Module.is_hidden
1186+
~weak_canonical_test:false dest'
1187+
then
1188+
let cp2 = Component.Of_Lang.(module_path (empty ()) p2) in
1189+
match
1190+
resolve_module env ~mark_substituted:false ~add_canonical:true cp2
1191+
with
1192+
| Ok (`Alias (_, _, Some p3), _) ->
1193+
let p = reresolve_module env p3 in
1194+
Lang_of.(Path.resolved_module (empty ()) p)
1195+
| _ -> `Alias (dest', p2)
1196+
else `Alias (dest', p2)
12041197
| `Subst (p1, p2) ->
12051198
`Subst (reresolve_module_type_gpath env p1, reresolve_module_gpath env p2)
12061199
| `Hidden p ->
12071200
let p' = reresolve_module_gpath env p in
12081201
`Hidden p'
1209-
| `Canonical (p, (`Resolved _ as p2)) ->
1210-
`Canonical (reresolve_module_gpath env p, p2)
1202+
| `Canonical (p, `Resolved p2) ->
1203+
`Canonical
1204+
(reresolve_module_gpath env p, `Resolved (reresolve_module_gpath env p2))
12111205
| `Canonical (p, p2) ->
12121206
`Canonical (reresolve_module_gpath env p, handle_canonical_module env p2)
12131207
| `OpaqueModule m -> `OpaqueModule (reresolve_module_gpath env m)
12141208

1209+
and strip_canonical :
1210+
c:Odoc_model.Paths.Path.Module.t ->
1211+
Cpath.Resolved.module_ ->
1212+
Cpath.Resolved.module_ =
1213+
fun ~c path ->
1214+
match path with
1215+
| `Canonical (x, y) when y = c -> strip_canonical ~c x
1216+
| `Canonical (x, y) -> `Canonical (strip_canonical ~c x, y)
1217+
| `Alias (x, y, z) -> `Alias (strip_canonical ~c x, y, z)
1218+
| `Subst (x, y) -> `Subst (x, strip_canonical ~c y)
1219+
| `Hidden x -> `Hidden (strip_canonical ~c x)
1220+
| `OpaqueModule x -> `OpaqueModule (strip_canonical ~c x)
1221+
| `Substituted x -> `Substituted (strip_canonical ~c x)
1222+
| `Gpath p -> `Gpath (strip_canonical_gpath ~c p)
1223+
| `Local _ | `Apply _ | `Module _ -> path
1224+
1225+
and strip_canonical_gpath :
1226+
c:Odoc_model.Paths.Path.Module.t ->
1227+
Odoc_model.Paths.Path.Resolved.Module.t ->
1228+
Odoc_model.Paths.Path.Resolved.Module.t =
1229+
fun ~c path ->
1230+
match path with
1231+
| `Canonical (x, y) when y = c -> strip_canonical_gpath ~c x
1232+
| `Canonical (x, y) -> `Canonical (strip_canonical_gpath ~c x, y)
1233+
| `Alias (x, y) -> `Alias (strip_canonical_gpath ~c x, y)
1234+
| `Subst (x, y) -> `Subst (x, strip_canonical_gpath ~c y)
1235+
| `Hidden x -> `Hidden (strip_canonical_gpath ~c x)
1236+
| `OpaqueModule x -> `OpaqueModule (strip_canonical_gpath ~c x)
1237+
| `Apply _ | `Module _ | `Identifier _ -> path
1238+
12151239
and reresolve_module : Env.t -> Cpath.Resolved.module_ -> Cpath.Resolved.module_
12161240
=
12171241
fun env path ->
@@ -1223,42 +1247,89 @@ and reresolve_module : Env.t -> Cpath.Resolved.module_ -> Cpath.Resolved.module_
12231247
`Apply
12241248
(reresolve_module env functor_path, reresolve_module env argument_path)
12251249
| `Module (parent, name) -> `Module (reresolve_parent env parent, name)
1226-
| `Alias (p1, `Resolved p2, p3) ->
1227-
`Alias (reresolve_module env p1, `Resolved (reresolve_module env p2), p3)
1228-
| `Alias (p1, p2, p3) ->
1250+
| `Alias (p1, p2, p3opt) ->
12291251
let dest' = reresolve_module env p1 in
12301252
if Cpath.is_resolved_module_hidden ~weak_canonical_test:false dest' then
1231-
match
1232-
resolve_module env ~mark_substituted:false ~add_canonical:true p2
1233-
with
1234-
| Ok (`Alias (_, _, Some p3), _) -> reresolve_module env p3
1235-
| _ -> `Alias (dest', p2, p3)
1236-
else `Alias (dest', p2, p3)
1253+
match p3opt with
1254+
| Some p3 -> reresolve_module env p3
1255+
| None -> (
1256+
match
1257+
resolve_module env ~mark_substituted:false ~add_canonical:true p2
1258+
with
1259+
| Ok (`Alias (_, _, Some p3), _) -> reresolve_module env p3
1260+
| _ -> `Alias (dest', p2, None))
1261+
else `Alias (dest', p2, p3opt)
12371262
| `Subst (p1, p2) ->
12381263
`Subst (reresolve_module_type env p1, reresolve_module env p2)
12391264
| `Hidden p ->
12401265
let p' = reresolve_module env p in
12411266
`Hidden p'
12421267
| `Canonical (p, `Resolved p2) ->
1243-
`Canonical (reresolve_module env p, `Resolved p2)
1268+
let cp2 = Component.Of_Lang.(resolved_module_path (empty ()) p2) in
1269+
let cp2' = reresolve_module env cp2 in
1270+
let p2' = Lang_of.(Path.resolved_module (empty ()) cp2') in
1271+
`Canonical (reresolve_module env p, `Resolved p2')
12441272
| `Canonical (p, p2) -> (
12451273
match handle_canonical_module env p2 with
12461274
| `Resolved _ as r -> `Canonical (p, r)
12471275
| r -> `Canonical (reresolve_module env p, r))
12481276
| `OpaqueModule m -> `OpaqueModule (reresolve_module env m)
12491277

12501278
and handle_canonical_module_real env p2 =
1251-
let strip_alias : Cpath.Resolved.module_ -> Cpath.Resolved.module_ =
1252-
fun x -> match x with `Alias (_, _, Some p) -> p | _ -> x
1279+
(* Canonical paths are always fully qualified, but this isn't
1280+
necessarily good for rendering, as the full path would
1281+
always be written out whenever a canonical module path is
1282+
encountered.
1283+
1284+
Instead the intent of this code is to try to find the shortest
1285+
path that still correctly references the canonical module.
1286+
1287+
It works by starting with the fully qualified path, e.g.
1288+
A.B.C.D where A is a root module. It then makes the list
1289+
of possibilities (A).B.C.D (A.B).C.D (A.B.C).D and (A.B.C.D)
1290+
where brackets represent the part that's an identifier.
1291+
It then resolved each one in turn and calculates the
1292+
identifier of the resolved path. The shortest path that
1293+
has the same identifier as the fully-qualified path is
1294+
chosen as the canonical path.
1295+
1296+
When doing this, we end up resolving each possibility.
1297+
Additionally, we need to 'reresolve' - resolve the canonical
1298+
references - while we're doing this. This is because the
1299+
parent parts of the resolved path can contain aliases and
1300+
canonical paths themselves which require resolving in order
1301+
to check the identifier is the same.
1302+
1303+
However, we first need to strip off any alias/canonical paths
1304+
in the resolved module, as we want the identifier for the
1305+
module itself, not any aliased module, and the canonical path
1306+
_ought_ to be the same as the one we're _currently_ resolving
1307+
anyway, so we'd end up looping forever. Note that it's not
1308+
sufficient to simply ask not to add on the canonical paths
1309+
at this point (ie, ~add_canonical=false) as the alias chain
1310+
may include modules that have already been resolved and hence
1311+
have canonical constructors in their resolved paths.
1312+
*)
1313+
1314+
(* [strip p] strips the top-level aliases and canonical paths from
1315+
the path [p]. Any aliases/canonicals in parents are left as is. *)
1316+
let strip : Cpath.Resolved.module_ -> Cpath.Resolved.module_ =
1317+
fun x ->
1318+
match x with `Alias (_, _, Some p) -> strip_canonical ~c:p2 p | _ -> x
12531319
in
1320+
1321+
(* Resolve the path, then 'reresolve', making sure to strip off the
1322+
top-level alias and canonicals to avoid looping forever *)
12541323
let resolve env p =
12551324
resolve_module env ~mark_substituted:false ~add_canonical:false p
1256-
>>= fun (p, m) -> Ok (strip_alias p, m)
1325+
>>= fun (p, m) -> Ok (reresolve_module env (strip p), m)
12571326
in
1327+
12581328
let lang_of cpath =
12591329
(Lang_of.(Path.resolved_module (empty ()) cpath)
12601330
:> Odoc_model.Paths.Path.Resolved.t)
12611331
in
1332+
12621333
let cp2 = Component.Of_Lang.(module_path (empty ()) p2) in
12631334
match canonical_helper env resolve lang_of c_mod_poss cp2 with
12641335
| None -> p2
@@ -1284,30 +1355,42 @@ and handle_canonical_module_real env p2 =
12841355
let expanded =
12851356
match m.type_ with
12861357
| Component.Module.Alias (_, Some _) -> true
1287-
| Alias (`Resolved p, None) ->
1288-
(* we're an alias - check to see if we're marked as the canonical path.
1289-
If not, check for an alias chain with us as canonical in it... *)
1290-
let rec check m =
1291-
match m.Component.Module.canonical with
1292-
| Some p ->
1293-
p = p2
1294-
(* The canonical path is the same one we're trying to resolve *)
1295-
| None -> (
1296-
match m.type_ with
1297-
| Component.Module.Alias (`Resolved p, _) -> (
1298-
match lookup_module ~mark_substituted:false env p with
1299-
| Error _ -> false
1300-
| Ok m ->
1301-
let m = Component.Delayed.get m in
1302-
check m)
1303-
| _ -> false)
1304-
in
1305-
let self_canonical () = check m in
1306-
let hidden =
1307-
Cpath.is_resolved_module_hidden ~weak_canonical_test:true p
1308-
in
1309-
hidden || self_canonical ()
1310-
| Alias (_, _) -> false
1358+
| Alias (p, None) -> (
1359+
match
1360+
resolve_module ~mark_substituted:false ~add_canonical:false env p
1361+
with
1362+
| Ok (rp, _) ->
1363+
(* we're an alias - check to see if we're marked as the canonical path.
1364+
If not, check for an alias chain with us as canonical in it... *)
1365+
let rec check m =
1366+
match m.Component.Module.canonical with
1367+
| Some p ->
1368+
p = p2
1369+
(* The canonical path is the same one we're trying to resolve *)
1370+
| None -> (
1371+
match m.type_ with
1372+
| Component.Module.Alias (p, _) -> (
1373+
match
1374+
resolve_module ~mark_substituted:false
1375+
~add_canonical:false env p
1376+
with
1377+
| Ok (rp, _) -> (
1378+
match
1379+
lookup_module ~mark_substituted:false env rp
1380+
with
1381+
| Error _ -> false
1382+
| Ok m ->
1383+
let m = Component.Delayed.get m in
1384+
check m)
1385+
| _ -> false)
1386+
| _ -> false)
1387+
in
1388+
let self_canonical () = check m in
1389+
let hidden =
1390+
Cpath.is_resolved_module_hidden ~weak_canonical_test:true rp
1391+
in
1392+
hidden || self_canonical ()
1393+
| _ -> false)
13111394
| ModuleType _ -> true
13121395
in
13131396
let cpath =

0 commit comments

Comments
 (0)