Skip to content

Commit

Permalink
Resolve external source links
Browse files Browse the repository at this point in the history
Result of shape reduction might not have an uid attached or might have
an uid that doesn't correspond to a local location.
Instead of letting shape reduction load external cmts, use the model to
load locations already computed by other compilation units.

Allow source locations to be partially resolved during compile. The
not fully reduced shape is converted into a path and can be resolved
later.

Technically, this can be done during the compile step and the Lang types
could be unchanged. It's done in Link to avoid adding stronger
dependencies between compiles, even though this fits currently defined
dependencies.

Co-authored-by: Paul-Elliot <peada@free.fr>
  • Loading branch information
2 people authored and jonludlam committed Mar 14, 2023
1 parent d4eb5c5 commit 1d79ac2
Show file tree
Hide file tree
Showing 19 changed files with 264 additions and 61 deletions.
11 changes: 6 additions & 5 deletions src/document/url.ml
Original file line number Diff line number Diff line change
Expand Up @@ -370,11 +370,12 @@ module Anchor = struct
Error (Unexpected_anchor "core_type label parent")
| { iv = `Type (gp, _); _ } -> mk ~kind:`Section gp str_name)

let source_file_from_identifier ~ext root loc =
let kind = `SourceLine in
let page = Path.source_file_from_identifier ~ext root in
let anchor = Printf.sprintf "%s" loc in
Some { page; anchor; kind }
let source_file_from_identifier ~ext root = function
| Odoc_model.Lang.Locations.Unresolved _ -> None
| Resolved { anchor } ->
let kind = `SourceLine in
let page = Path.source_file_from_identifier ~ext root in
Some { page; anchor; kind }

let polymorphic_variant ~type_ident elt =
let name_of_type_constr te =
Expand Down
5 changes: 4 additions & 1 deletion src/html/html_source.ml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,10 @@ let html_of_doc docs =
span
~a:[ a_id (Printf.sprintf "L%d" l); a_class [ "source_line" ] ]
children
| Local_jmp (Occurence lbl) -> a ~a:[ a_href ("#" ^ lbl) ] children
| Local_jmp (Occurence (Resolved { anchor })) ->
a ~a:[ a_href ("#" ^ anchor) ] children
| Local_jmp (Occurence (Unresolved _)) ->
a ~a:[ a_class [ "xref-unresolved" ] ] children
| Local_jmp (Def lbl) -> span ~a:[ a_id lbl ] children)
in
span ~a:[] @@ List.map doc_to_html docs
Expand Down
8 changes: 5 additions & 3 deletions src/loader/cmt.ml
Original file line number Diff line number Diff line change
Expand Up @@ -26,15 +26,17 @@ open Odoc_model.Lang
module Env = Ident_env

let read_locations_impl parent impl =
let open Locations in
let source_parent =
match Identifier.root parent with
| Some sp -> (sp :> Identifier.Module.t)
| None -> assert false
and impl = match impl with
and impl =
match impl with
| None -> None
| Some impl -> Some (Uid.string_of_uid impl)
| Some impl -> Some (Resolved { anchor = Uid.string_of_uid impl })
in
{ Locations.source_parent; impl; intf = None }
{ source_parent; impl; intf = None }

let read_core_type env ctyp =
Cmi.read_type_expr env ctyp.ctyp_type
Expand Down
11 changes: 6 additions & 5 deletions src/loader/local_jmp.ml
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#if OCAML_VERSION >= (4, 14, 0)

open Odoc_model.Lang.Locations
open Odoc_model.Lang.Source_code.Info

let pos_of_loc loc = (loc.Location.loc_start.pos_cnum, loc.loc_end.pos_cnum)
Expand All @@ -13,7 +14,7 @@ module Local_analysis = struct
let extract_id id =
match id with
| Path.Pident id ->
let uniq = Ident.unique_name id in
let uniq = Resolved { anchor = Ident.unique_name id } in
poses := (Occurence uniq, pos_of_loc exp_loc) :: !poses
| _ -> ()
in
Expand Down Expand Up @@ -46,10 +47,10 @@ module Global_analysis = struct
match Shape.Uid.Tbl.find_opt uid_to_loc value_description.val_uid with
| None -> ()
| Some _ ->
poses :=
( Occurence (string_of_uid value_description.val_uid),
pos_of_loc exp_loc )
:: !poses)
let uid =
Resolved { anchor = string_of_uid value_description.val_uid }
in
poses := (Occurence uid, pos_of_loc exp_loc) :: !poses)
| _ -> ()
end

Expand Down
66 changes: 55 additions & 11 deletions src/loader/lookup_def.ml
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
#if OCAML_VERSION >= (4, 14, 0)

open Odoc_model
open Odoc_model.Paths
open Odoc_model.Names
module Kind = Shape.Sig_component_kind

type t = {
uid_to_loc : Location.t Shape.Uid.Tbl.t;
impl_shape : Shape.t;
}
let ( >>= ) m f = match m with Some x -> f x | None -> None

type t = { uid_to_loc : Location.t Shape.Uid.Tbl.t; impl_shape : Shape.t }

(** Project an identifier into a shape. *)
let rec project_id :
Expand Down Expand Up @@ -47,25 +47,69 @@ let rec project_id :
(* Not represented in shapes. *)
None

let item_exp item : string * Shape.Sig_component_kind.t =
(* This function is missing in compiler-libs. *)
Obj.magic item

module MkId = Identifier.Mk

let rec shape_to_module_path shape =
match shape.Shape.desc with
| Var _ | Struct _ | Leaf -> None
| Abs (_, parent) -> shape_to_module_path parent
| App (left, _) -> shape_to_module_path left
| Proj (parent, item) -> (
shape_to_module_path parent >>= fun parent ->
let name, kind = item_exp item in
match kind with
| Module -> Some (`Dot (parent, name))
| Module_type | Value | Type | Extension_constructor | Class | Class_type
->
None)
| Comp_unit name -> Some (`Root name)

let shape_to_unresolved shape =
match shape.Shape.desc with
| Proj (parent, item) ->
shape_to_module_path parent >>= fun parent ->
let name, kind = item_exp item in
Some
(match kind with
| Value -> `Value (parent, name)
| Type -> `Type (`Dot (parent, name))
| Module -> `Module (`Dot (parent, name))
| Module_type -> `ModuleType (`Dot (parent, name))
| Extension_constructor -> `Extension (parent, name)
| Class -> `Class (parent, name)
| Class_type -> `ClassType (`Dot (parent, name)))
| _ ->
(shape_to_module_path shape :> Path.Module.t option) >>= fun m ->
Some (`Module m)

let lookup_def impl_shape id =
match project_id impl_shape.impl_shape id with
| None -> None
| Some query -> (
let result = Shape.local_reduce query in
match result.uid with
| None -> None
| Some uid ->
if Shape.Uid.Tbl.mem impl_shape.uid_to_loc uid
then Some (Uid.string_of_uid (Uid.of_shape_uid uid))
else None)
| Some uid when Shape.Uid.Tbl.mem impl_shape.uid_to_loc uid ->
(* Check whether [uid] has a location in the current unit.
[result.uid] might be [Some] but point to something in an other
compilation unit, that would need to be resolved again. *)
let anchor = Uid.string_of_uid (Uid.of_shape_uid uid) in
Some (Lang.Locations.Resolved { anchor })
| _ -> (
match shape_to_unresolved result with
| Some id -> Some (Lang.Locations.Unresolved id)
| None -> None))

let of_cmt (cmt : Cmt_format.cmt_infos) =
match cmt.cmt_impl_shape with
| Some impl_shape ->
Some ({ uid_to_loc = cmt.cmt_uid_to_loc; impl_shape })
| Some impl_shape -> Some { uid_to_loc = cmt.cmt_uid_to_loc; impl_shape }
| None -> None

#else

type t = unit

let lookup_def () _id = None
Expand Down
11 changes: 10 additions & 1 deletion src/model/lang.ml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,16 @@
open Paths

module Locations = struct
type uid = string
type unresolved =
[ `Value of Path.Module.t * string
| `Type of Path.Type.t
| `Module of Path.Module.t
| `ModuleType of Path.ModuleType.t
| `Extension of Path.Module.t * string
| `Class of Path.Module.t * string
| `ClassType of Path.ClassType.t ]

type uid = Resolved of { anchor : string } | Unresolved of unresolved

type t = {
source_parent : Identifier.Module.t;
Expand Down
18 changes: 17 additions & 1 deletion src/model_desc/lang_desc.ml
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,26 @@ let location = To_string (fun loc -> Format.asprintf "%a" Location_.pp loc)

let locations =
let open Lang.Locations in
let unresolved_to_path : Lang.Locations.unresolved -> _ = function
| `Value (parent, name) -> ("value", `Dot (parent, name))
| `Type p -> ("type", (p :> Paths.Path.t))
| `Module p -> ("module", (p :> Paths.Path.t))
| `ModuleType p -> ("module_type", (p :> Paths.Path.t))
| `Extension (parent, name) -> ("extension", `Dot (parent, name))
| `Class (parent, name) -> ("class", `Dot (parent, name))
| `ClassType p -> ("class_type", (p :> Paths.Path.t))
in
let uid =
Variant
(function
| Resolved { anchor } -> C ("Resolved", anchor, string)
| Unresolved x ->
C ("Unresolved", unresolved_to_path x, Pair (string, path)))
in
Record
[
F ("source_parent", (fun t -> t.source_parent), identifier);
F ("impl", (fun t -> t.impl), Option string);
F ("impl", (fun t -> t.impl), Option uid);
F ("intf", (fun t -> t.intf), Option location);
]

Expand Down
5 changes: 2 additions & 3 deletions src/xref2/env.ml
Original file line number Diff line number Diff line change
Expand Up @@ -425,9 +425,8 @@ let lookup_root_module name env =
result

let lookup_def id env =
match env.resolver with
| Some r -> r.lookup_def (id :> Paths.Identifier.Any.t)
| None -> None
let id = (id :> Paths.Identifier.Any.t) in
match env.resolver with Some r -> r.lookup_def id | None -> None

let lookup_page name env =
match env.resolver with None -> None | Some r -> r.lookup_page name
Expand Down
6 changes: 5 additions & 1 deletion src/xref2/env.mli
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,11 @@ type resolver = {
open_units : string list;
lookup_unit : string -> lookup_unit_result;
lookup_page : string -> lookup_page_result;
lookup_def : Identifier.t -> string option;
lookup_def : Identifier.t -> Lang.Locations.uid option;
(** Lookup the source code location from an identifier. Returns
[Some (Unresolved _)] when the location is not available in the unit
being compiled but could be resolved by loading more modules. Returns
[None] when no location can be found. *)
}

type lookup_type =
Expand Down
29 changes: 16 additions & 13 deletions src/xref2/find.ml
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,13 @@ let filter_in_sig sg f =
in
inner f sg.Signature.items

(** Returns the last element of a list. Used to implement [_unambiguous]
functions. *)
let rec disambiguate = function
| [ x ] -> Some x
| [] -> None
| _ :: tl -> disambiguate tl

let module_in_sig sg name =
find_in_sig sg (function
| Signature.Module (id, _, m) when N.module_ id = name ->
Expand All @@ -104,14 +111,6 @@ let type_in_sig sg name =
Some (`FClassType (N.class_type' id, c))
| _ -> None)

let class_in_sig sg name =
find_in_sig sg (function
| Signature.Class (id, _, c) when N.class_ id = name ->
Some (`FClass (N.class' id, c))
| Signature.ClassType (id, _, c) when N.class_type id = name ->
Some (`FClassType (N.class_type' id, c))
| _ -> None)

type removed_type =
[ `FType_removed of TypeName.t * TypeExpr.t * TypeDecl.Equation.t ]

Expand Down Expand Up @@ -157,11 +156,6 @@ let careful_type_in_sig sg name =
| Some _ as x -> x
| None -> removed_type_in_sig sg name

let careful_class_in_sig sg name =
match class_in_sig sg name with
| Some _ as x -> x
| None -> removed_type_in_sig sg name

let datatype_in_sig sg name =
find_in_sig sg (function
| Signature.Type (id, _, t) when N.type_ id = name ->
Expand All @@ -176,6 +170,13 @@ let class_in_sig sg name =
Some (`FClassType (N.class_type' id, c))
| _ -> None)

let class_in_sig_unambiguous sg name = disambiguate (class_in_sig sg name)

let careful_class_in_sig sg name =
match class_in_sig_unambiguous sg name with
| Some _ as x -> x
| None -> removed_type_in_sig sg name

let any_in_type (typ : TypeDecl.t) name =
let rec find_cons = function
| ({ TypeDecl.Constructor.name = name'; _ } as cons) :: _ when name' = name
Expand Down Expand Up @@ -265,6 +266,8 @@ let value_in_sig sg name =
Some (`FValue (N.typed_value id, Delayed.get m))
| _ -> None)

let value_in_sig_unambiguous sg name = disambiguate (value_in_sig sg name)

let label_in_sig sg name =
filter_in_sig sg (function
| Signature.Comment (`Docs d) -> any_in_comment d name
Expand Down
6 changes: 6 additions & 0 deletions src/xref2/find.mli
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,12 @@ val any_in_type_in_sig : Signature.t -> string -> any_in_type_in_sig list

val any_in_class_signature : ClassSignature.t -> string -> any_in_class_sig list

(** Disambiguated lookups, returns the last match. *)

val class_in_sig_unambiguous : Signature.t -> string -> class_ option

val value_in_sig_unambiguous : Signature.t -> string -> value option

(** Lookup removed items *)

type removed_type =
Expand Down
8 changes: 7 additions & 1 deletion src/xref2/link.ml
Original file line number Diff line number Diff line change
Expand Up @@ -318,7 +318,13 @@ and open_ env parent = function

let locations env (locs : Locations.t) =
let source_parent = Env.lookup_source_parent env locs.source_parent in
{ locs with source_parent }
match locs.impl with
| Some (Unresolved unresolved) -> (
(* Uid couldn't be resolved locally, resolve again using the model. *)
match Locations_tools.lookup_loc env unresolved with
| Some locs -> locs
| None -> { locs with source_parent; impl = None })
| Some (Resolved _) | None -> { locs with source_parent }

let rec unit env t =
let open Compilation_unit in
Expand Down
Loading

0 comments on commit 1d79ac2

Please sign in to comment.