@@ -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+
889969let 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
0 commit comments