Skip to content

Commit e278734

Browse files
authored
Correctly traverse patterns when itering for docs #1572
from voodoos/iter-on-whole-pattern-to-find-uids
2 parents 306af71 + f837bdf commit e278734

File tree

3 files changed

+134
-86
lines changed

3 files changed

+134
-86
lines changed

CHANGES.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ unreleased
1212
jump-to-definition'q behavior (#1563)
1313
- Improve locate's behavior in various ill-typed expressions (#1546, fixes
1414
#1567 and partially #1543)
15+
- Correctly traverse patterns when looking for docs in the typedtree (#1572)
1516
+ test suite
1617
- Add multiple tests for locate over ill-typed expressions (#1546)
1718

src/analysis/locate.ml

Lines changed: 110 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -796,10 +796,13 @@ let from_string ~config ~env ~local_defs ~pos ?namespaces switch path =
796796
a uid-based search and return the attached comment in the attributes.
797797
This is a more sound way to get documentation than resorting on the
798798
[Ocamldoc.associate_comment] heuristic *)
799-
let doc_from_uid ~config ~comp_unit uid =
800-
let exception Found of Typedtree.attributes in
799+
(* In a future release of OCaml the cmt's uid_to_loc table will contain
800+
fragments of the typedtree that might be used to get the docstrings without
801+
relying on this iteration *)
802+
let find_doc_attributes_in_typedtree ~config ~comp_unit uid =
803+
let exception Found_attributes of Typedtree.attributes in
801804
let test elt_uid attributes =
802-
if Shape.Uid.equal uid elt_uid then raise (Found attributes)
805+
if Shape.Uid.equal uid elt_uid then raise (Found_attributes attributes)
803806
in
804807
let iterator =
805808
let first_item = ref true in
@@ -813,14 +816,14 @@ let doc_from_uid ~config ~comp_unit uid =
813816
The module docstring must be the first signature or structure item *)
814817
signature_item = (fun sub ({ sig_desc; _} as si) ->
815818
begin match sig_desc, !first_item, uid_is_comp_unit with
816-
| Tsig_attribute attr, true, true -> raise (Found [attr])
819+
| Tsig_attribute attr, true, true -> raise (Found_attributes [attr])
817820
| _, false, true -> raise Not_found
818821
| _, _, _ -> first_item := false end;
819822
Tast_iterator.default_iterator.signature_item sub si);
820823

821824
structure_item = (fun sub ({ str_desc; _} as sti) ->
822825
begin match str_desc, !first_item, uid_is_comp_unit with
823-
| Tstr_attribute attr, true, true -> raise (Found [attr])
826+
| Tstr_attribute attr, true, true -> raise (Found_attributes [attr])
824827
| _, false, true -> raise Not_found
825828
| _, _, _ -> first_item := false end;
826829
Tast_iterator.default_iterator.structure_item sub sti);
@@ -834,25 +837,34 @@ let doc_from_uid ~config ~comp_unit uid =
834837
Tast_iterator.default_iterator.type_declaration sub td);
835838

836839
value_binding = (fun sub ({ vb_pat; vb_attributes; _ } as vb) ->
837-
begin match vb_pat.pat_desc with
838-
| Tpat_var (id, _) ->
839-
begin try
840-
let vd = Env.find_value (Pident id) env in
841-
test vd.val_uid vb_attributes
842-
with Not_found -> () end
843-
| _ -> () end;
840+
let pat_var_iter ~f pat =
841+
let rec aux pat =
842+
let open Typedtree in
843+
match pat.pat_desc with
844+
| Tpat_var (id, _) -> f id
845+
| Tpat_alias (pat, _, _)
846+
| Tpat_variant (_, Some pat, _)
847+
| Tpat_lazy pat
848+
| Tpat_or (pat, _, _) ->
849+
aux pat
850+
| Tpat_tuple pats
851+
| Tpat_construct (_, _, pats, _)
852+
| Tpat_array pats ->
853+
List.iter ~f:aux pats
854+
| Tpat_record (pats, _) ->
855+
List.iter ~f:(fun (_, _, pat) -> aux pat) pats
856+
| _ -> ()
857+
in
858+
aux pat
859+
in
860+
pat_var_iter vb_pat ~f:(fun id ->
861+
try
862+
let vd = Env.find_value (Pident id) env in
863+
test vd.val_uid vb_attributes
864+
with Not_found -> ());
844865
Tast_iterator.default_iterator.value_binding sub vb)
845866
}
846867
in
847-
let parse_attributes attrs =
848-
let open Parsetree in
849-
try Some (List.find_map attrs ~f:(fun attr ->
850-
if List.exists ["ocaml.doc"; "ocaml.text"]
851-
~f:(String.equal attr.attr_name.txt)
852-
then Ast_helper.extract_str_payload attr.attr_payload
853-
else None))
854-
with Not_found -> None
855-
in
856868
let typedtree =
857869
log ~title:"doc_from_uid" "Loading the cmt for unit %S" comp_unit;
858870
match load_cmt ~config comp_unit `MLI with
@@ -879,38 +891,86 @@ let doc_from_uid ~config ~comp_unit uid =
879891
| _ -> () end;
880892
`No_documentation
881893
with
882-
| Found attrs ->
883-
log ~title:"doc_from_uid" "Found attributes for this uid";
894+
| Found_attributes attrs ->
895+
log ~title:"doc_from_uid" "Found attributes for this uid";
896+
let parse_attributes attrs =
897+
let open Parsetree in
898+
try Some (List.find_map attrs ~f:(fun attr ->
899+
if List.exists ["ocaml.doc"; "ocaml.text"]
900+
~f:(String.equal attr.attr_name.txt)
901+
then Ast_helper.extract_str_payload attr.attr_payload
902+
else None))
903+
with Not_found -> None
904+
in
884905
begin match parse_attributes attrs with
885906
| Some (doc, _) -> `Found (doc |> String.trim)
886907
| None -> `No_documentation end
887908
| Not_found -> `No_documentation
888909

910+
let doc_from_uid ~config ~loc uid =
911+
begin match uid with
912+
| Some (Shape.Uid.Item { comp_unit; _ } as uid)
913+
| Some (Shape.Uid.Compilation_unit comp_unit as uid)
914+
when Env.get_unit_name () <> comp_unit ->
915+
log ~title:"get_doc" "the doc (%a) you're looking for is in another
916+
compilation unit (%s)"
917+
Logger.fmt (fun fmt -> Shape.Uid.print fmt uid) comp_unit;
918+
(match find_doc_attributes_in_typedtree ~config ~comp_unit uid with
919+
| `Found doc -> `Found_doc doc
920+
| `No_documentation ->
921+
(* We fallback on the legacy heuristic to handle some unproper
922+
doc placement. See test [unattached-comment.t] *)
923+
`Found_loc loc)
924+
| _ ->
925+
(* Uid based search doesn't works in the current CU since Merlin's parser
926+
does not attach doc comments to the typedtree *)
927+
`Found_loc loc
928+
end
929+
930+
let doc_from_comment_list ~local_defs ~buffer_comments loc =
931+
(* When the doc we look for is in the current buffer or if search by uid
932+
has failed we use an alternative heuristic since Merlin's pure parser
933+
does not poulates doc attributes in the typedtree. *)
934+
let comments =
935+
match File_switching.where_am_i () with
936+
| None ->
937+
log ~title:"get_doc" "Using reader's comment (current buffer)";
938+
buffer_comments
939+
| Some cmt_path ->
940+
log ~title:"get_doc" "File switching: actually in %s" cmt_path;
941+
let {Cmt_cache. cmt_infos; _ } = Cmt_cache.read cmt_path in
942+
cmt_infos.Cmt_format.cmt_comments
943+
in
944+
log ~title:"get_doc" "%a" Logger.fmt (fun fmt ->
945+
Format.fprintf fmt "looking around %a inside: [\n"
946+
Location.print_loc !last_location;
947+
List.iter comments ~f:(fun (c, l) ->
948+
Format.fprintf fmt " (%S, %a);\n" c
949+
Location.print_loc l);
950+
Format.fprintf fmt "]\n"
951+
);
952+
let browse = Mbrowse.of_typedtree local_defs in
953+
let (_, deepest_before) =
954+
Mbrowse.(leaf_node @@ deepest_before loc.Location.loc_start [browse])
955+
in
956+
(* based on https://v2.ocaml.org/manual/doccomments.html#ss:label-comments: *)
957+
let after_only = begin match deepest_before with
958+
| Browse_raw.Constructor_declaration _ -> true
959+
(* The remaining `true` cases are currently not reachable *)
960+
| Label_declaration _ | Record_field _ | Row_field _ -> true
961+
| _ -> false
962+
end in
963+
match
964+
Ocamldoc.associate_comment ~after_only comments loc !last_location
965+
with
966+
| None, _ -> `No_documentation
967+
| Some doc, _ -> `Found doc
968+
889969
let get_doc ~config ~env ~local_defs ~comments ~pos =
890970
File_switching.reset ();
891-
let from_uid ~loc uid =
892-
begin match uid with
893-
| Some (Shape.Uid.Item { comp_unit; _ } as uid)
894-
| Some (Shape.Uid.Compilation_unit comp_unit as uid)
895-
when Env.get_unit_name () <> comp_unit ->
896-
log ~title:"get_doc" "the doc (%a) you're looking for is in another
897-
compilation unit (%s)"
898-
Logger.fmt (fun fmt -> Shape.Uid.print fmt uid) comp_unit;
899-
(match doc_from_uid ~config ~comp_unit uid with
900-
| `Found doc -> `Found_doc doc
901-
| `No_documentation ->
902-
(* We fallback on the legacy heuristic to handle some unproper
903-
doc placement. See test [unattached-comment.t] *)
904-
`Found loc)
905-
| _ ->
906-
(* Uid based search doesn't works in the current CU since Merlin's parser
907-
does not attach doc comments to the typedtree *)
908-
`Found loc
909-
end
910-
in
911971
fun path ->
912972
let_ref last_location Location.none @@ fun () ->
913-
match
973+
let doc_from_uid_result =
914974
match path with
915975
| `Completion_entry (namespace, path, _loc) ->
916976
log ~title:"get_doc" "completion: looking for the doc of '%a'"
@@ -921,7 +981,7 @@ let get_doc ~config ~env ~local_defs ~comments ~pos =
921981
let loc : Location.t =
922982
{ loc_start = pos; loc_end = pos; loc_ghost = true }
923983
in
924-
from_uid ~loc uid
984+
doc_from_uid ~config ~loc uid
925985
| (`Builtin |`Not_in_env _|`File_not_found _|`Not_found _)
926986
as otherwise -> otherwise
927987
end
@@ -932,53 +992,17 @@ let get_doc ~config ~env ~local_defs ~comments ~pos =
932992
let loc : Location.t =
933993
{ loc_start = pos; loc_end = pos; loc_ghost = true }
934994
in
935-
from_uid ~loc uid
995+
doc_from_uid ~config ~loc uid
936996
| `At_origin | `Missing_labels_namespace -> `No_documentation
937997
| `Builtin _ -> `Builtin
938998
| (`Not_in_env _ | `Not_found _ |`File_not_found _ )
939999
as otherwise -> otherwise
9401000
end
941-
with
1001+
in
1002+
match doc_from_uid_result with
9421003
| `Found_doc doc -> `Found doc
943-
| `Found loc ->
944-
(* When the doc we look for is in the current buffer or if search by uid
945-
has failed we use an alternative heuristic since Merlin's pure parser
946-
does not poulates doc attributes in the typedtree. *)
947-
let comments =
948-
match File_switching.where_am_i () with
949-
| None ->
950-
log ~title:"get_doc" "Using reader's comment (current buffer)";
951-
comments
952-
| Some cmt_path ->
953-
log ~title:"get_doc" "File switching: actually in %s" cmt_path;
954-
let {Cmt_cache. cmt_infos; _ } = Cmt_cache.read cmt_path in
955-
cmt_infos.Cmt_format.cmt_comments
956-
in
957-
log ~title:"get_doc" "%a" Logger.fmt (fun fmt ->
958-
Format.fprintf fmt "looking around %a inside: [\n"
959-
Location.print_loc !last_location;
960-
List.iter comments ~f:(fun (c, l) ->
961-
Format.fprintf fmt " (%S, %a);\n" c
962-
Location.print_loc l);
963-
Format.fprintf fmt "]\n"
964-
);
965-
let browse = Mbrowse.of_typedtree local_defs in
966-
let (_, deepest_before) =
967-
Mbrowse.(leaf_node @@ deepest_before loc.loc_start [browse])
968-
in
969-
(* based on https://v2.ocaml.org/manual/doccomments.html#ss:label-comments: *)
970-
let after_only = begin match deepest_before with
971-
| Browse_raw.Constructor_declaration _ -> true
972-
(* The remaining `true` cases are currently not reachable *)
973-
| Label_declaration _ | Record_field _ | Row_field _ -> true
974-
| _ -> false
975-
end in
976-
begin match
977-
Ocamldoc.associate_comment ~after_only comments loc !last_location
978-
with
979-
| None, _ -> `No_documentation
980-
| Some doc, _ -> `Found doc
981-
end
1004+
| `Found_loc loc ->
1005+
doc_from_comment_list ~local_defs ~buffer_comments:comments loc
9821006
| `Builtin ->
9831007
begin match path with
9841008
| `User_input path -> `Builtin path
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
$ cat >main.ml <<EOF
2+
> let _ = Lib.y
3+
> EOF
4+
5+
$ cat >lib.ml <<EOF
6+
> (** doc for all node *)
7+
> let x, y = 2, 3
8+
> EOF
9+
10+
$ $OCAMLC -c -bin-annot lib.ml main.ml
11+
12+
$ $MERLIN single document -position 1:12 \
13+
> -log-file - 2>log \
14+
> -filename main.ml <main.ml
15+
{
16+
"class": "return",
17+
"value": "doc for all node",
18+
"notifications": []
19+
}
20+
21+
We should not rely on the heuristic to get that comment
22+
$ cat log | grep -A 2 "looking around"
23+
[1]

0 commit comments

Comments
 (0)