Skip to content

Commit aac2e68

Browse files
cknittclaude
andcommitted
Show a functor's parameters on hover
Why: Editor analysis models a functor by the structure of its result, so hover on a functor showed its body as if it were a plain module, and nothing said it takes arguments. The result structure now records the parameters, and hover prints them in front of the body. Diagnostic modified (hover on a functor): Example: module type Ord = { type t let compare: (t, t) => int } module MakeSet = (O: Ord) => { type t = array<O.t> let add = (s: t, x: O.t) => s->Array.concat([x]) } Before (rescript): tests/analysis_tests/tests/src/HoverFunctor.res module MakeSet: { type t = array<O.t> let add: (t, O.t) => array<O.t> } After (rescript): tests/analysis_tests/tests/src/HoverFunctor.res module MakeSet: (O: Ord) => { type t = array<O.t> let add: (t, O.t) => array<O.t> } Review: Why not add a functor case to the analysis module type? Completion, references and path resolution all treat a functor as its result and would each need the new case; only hover displays the parameters, so they are recorded on the result structure. Review: What does hover show for an applied functor? A plain module: an application is modelled by its signature, so `module IntSet = MakeSet(IntOrd)` shows no parameters, and a partial application such as `Pair(IntOrd)` shows only the remaining one. Fixes: #9071 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Kbf3gLMawVgtmiKy3TMBc3 Signed-off-by: Christoph Knittel <ck@cca.io>
1 parent c989f87 commit aac2e68

6 files changed

Lines changed: 144 additions & 8 deletions

File tree

‎analysis/src/hover.ml‎

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ open Shared_types
22

33
module String_set = Set.Make (String)
44

5-
let show_module_top_level ~docstring ~is_type ~name
5+
let show_module_top_level ?(functor_params = []) ~docstring ~is_type ~name
66
(top_level : Module.item list) =
77
let contents =
88
top_level
@@ -22,6 +22,9 @@ let show_module_top_level ~docstring ~is_type ~name
2222
Markdown.code_block
2323
("module "
2424
^ (if is_type then "type " ^ name ^ " = " else name ^ ": ")
25+
^ (match functor_params with
26+
| [] -> ""
27+
| params -> "(" ^ String.concat ", " params ^ ") => ")
2528
^ "{" ^ "\n" ^ contents ^ "\n}")
2629
in
2730
let doc =
@@ -39,13 +42,13 @@ let rec show_module ~state ~docstring ~(file : File.t) ~package ~name
3942
match declared with
4043
| None ->
4144
show_module_top_level ~docstring ~is_type:false ~name file.structure.items
42-
| Some {item = Structure {items}; module_path} ->
45+
| Some {item = Structure {items; functor_params}; module_path} ->
4346
let is_type =
4447
match module_path with
4548
| ExportedModule {is_type} -> is_type
4649
| _ -> false
4750
in
48-
show_module_top_level ~docstring ~is_type ~name items
51+
show_module_top_level ~functor_params ~docstring ~is_type ~name items
4952
| Some ({item = Constraint (_moduleItem, module_type_item)} as declared) ->
5053
(* show the interface *)
5154
show_module ~state ~docstring ~file ~name ~package

‎analysis/src/process_cmt.ml‎

Lines changed: 46 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,35 @@ let map_record_field {Types.ld_id; ld_type; ld_attributes; ld_optional} =
3535
deprecated = Process_attributes.find_deprecated_attribute ld_attributes;
3636
}
3737

38+
(* A functor parameter's module type, on one line for hover. *)
39+
let module_type_to_string (mty : Types.module_type) =
40+
match mty with
41+
| Mty_ident path | Mty_alias (_, path) -> Printtyp.string_of_path path
42+
| Mty_signature signature ->
43+
let items =
44+
Printtyp.tree_of_signature signature
45+
|> List.map (fun item ->
46+
Res_outcome_printer.print_out_sig_item_doc item
47+
|> Res_doc.to_string ~width:Res_printer.default_print_width)
48+
in
49+
"{" ^ String.concat "; " items ^ "}"
50+
| Mty_functor _ -> "{...}"
51+
52+
(* A functor parameter as hover shows it, such as [O: Ord]; a generative
53+
functor's [*] parameter shows as nothing, giving [() => ...]. *)
54+
let functor_param ~name (mty : Types.module_type option) =
55+
match (name, mty) with
56+
| "*", _ -> ""
57+
| _, None -> name
58+
| _, Some mty -> name ^ ": " ^ module_type_to_string mty
59+
60+
let rec add_functor_param param (m : Module.t) : Module.t =
61+
match m with
62+
| Structure s -> Structure {s with functor_params = param :: s.functor_params}
63+
| Constraint (m1, m2) ->
64+
Constraint (add_functor_param param m1, add_functor_param param m2)
65+
| Ident _ -> m
66+
3867
let rec for_type_signature_item ~(env : Shared_types.Env.t)
3968
~(exported : Exported.t) (item : Types.signature_item) =
4069
match item with
@@ -174,16 +203,24 @@ and for_type_signature ~name ~env signature =
174203
(fun item items -> for_type_signature_item ~env ~exported item @ items)
175204
signature []
176205
in
177-
{Module.name; docstring = []; exported; items; deprecated = None}
206+
{
207+
Module.name;
208+
docstring = [];
209+
exported;
210+
items;
211+
deprecated = None;
212+
functor_params = [];
213+
}
178214

179215
and for_type_module ~name ~env module_type =
180216
match module_type with
181217
| Types.Mty_ident path -> Ident path
182218
| Mty_alias (_ (* 402 *), path) -> Ident path
183219
| Mty_signature signature ->
184220
Structure (for_type_signature ~name ~env signature)
185-
| Mty_functor (_argIdent, _argType, result_type) ->
221+
| Mty_functor (arg_ident, arg_type, result_type) ->
186222
for_type_module ~name ~env result_type
223+
|> add_functor_param (functor_param ~name:(Ident.name arg_ident) arg_type)
187224

188225
(* Stamps given to renamed identifiers start here, far above the stamps
189226
in a typed tree, so that they never meet one of them. *)
@@ -453,7 +490,7 @@ let for_signature ~name ~env sig_items =
453490
in
454491
let docstring = attrs_to_docstring attributes in
455492
let deprecated = Process_attributes.find_deprecated_attribute attributes in
456-
{Module.name; docstring; exported; items; deprecated}
493+
{Module.name; docstring; exported; items; deprecated; functor_params = []}
457494

458495
let rec get_module_path mod_desc =
459496
match mod_desc with
@@ -701,7 +738,11 @@ and for_module ~env (mod_expr : Typedtree.module_expr) module_name =
701738
~extent:arg_name.loc ~stamp ~module_path:NotVisible false []
702739
in
703740
Stamps.add_module env.stamps stamp declared);
704-
for_module ~env result_expr module_name
741+
let param =
742+
functor_param ~name:arg_name.txt
743+
(Option.map (fun (t : Typedtree.module_type) -> t.mty_type) maybe_type)
744+
in
745+
for_module ~env result_expr module_name |> add_functor_param param
705746
| Tmod_apply (functor_, _arg, _coercion) ->
706747
(* The functor's body has the parameter's types; the application's
707748
signature has the argument's. Its items keep the identifiers of the
@@ -779,7 +820,7 @@ and for_structure ~name ~env str_items =
779820
in
780821
let docstring = attrs_to_docstring attributes in
781822
let deprecated = Process_attributes.find_deprecated_attribute attributes in
782-
{Module.name; docstring; exported; items; deprecated}
823+
{Module.name; docstring; exported; items; deprecated; functor_params = []}
783824

784825
let file_for_cmt_infos ~module_name ~uri
785826
({cmt_modname; cmt_annots} : Cmt_format.cmt_infos) =

‎analysis/src/shared_types.ml‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,8 @@ module Module = struct
148148
exported: Exported.t;
149149
items: item list;
150150
deprecated: string option;
151+
functor_params: string list;
152+
(** the parameters, such as [O: Ord], when this is a functor's result *)
151153
}
152154

153155
and t = Ident of Path.t | Structure of structure | Constraint of t * t
@@ -290,6 +292,7 @@ module File = struct
290292
exported = Exported.init ();
291293
items = [];
292294
deprecated = None;
295+
functor_params = [];
293296
};
294297
}
295298
end
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
- Hover on a functor shows its parameters, such as `module MakeSet: (O: Ord) => {...}`, instead of only its body.
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
module type Ord = {
2+
type t
3+
let compare: (t, t) => int
4+
}
5+
6+
module MakeSet = (O: Ord) => {
7+
// ^hov
8+
type t = array<O.t>
9+
let add = (s: t, x: O.t) => s->Array.concat([x])
10+
}
11+
12+
module Pair = (A: Ord, B: Ord) => {
13+
// ^hov
14+
type t = (A.t, B.t)
15+
}
16+
17+
module Inline = (X: {
18+
// ^hov
19+
let x: int
20+
}) => {
21+
let y = X.x
22+
}
23+
24+
module IntOrd = {
25+
type t = int
26+
let compare = (a: int, b) => a - b
27+
}
28+
29+
// An applied functor is a plain module
30+
module IntSet = MakeSet(IntOrd)
31+
// ^hov
32+
33+
// A partially applied functor keeps its remaining parameter
34+
module PairInt = Pair(IntOrd)
35+
// ^hov
36+
37+
module Counter = () => {
38+
// ^hov
39+
let count = ref(0)
40+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
Hover src/HoverFunctor.res 5:9
2+
{
3+
"contents": {
4+
"kind": "markdown",
5+
"value": "```rescript\nmodule MakeSet: (O: Ord) => {\n type t = array<O.t>\n let add: (t, O.t) => array<O.t>\n}\n```"
6+
}
7+
}
8+
9+
Hover src/HoverFunctor.res 11:7
10+
{
11+
"contents": {
12+
"kind": "markdown",
13+
"value": "```rescript\nmodule Pair: (A: Ord, B: Ord) => {\n type t = (A.t, B.t)\n}\n```"
14+
}
15+
}
16+
17+
Hover src/HoverFunctor.res 16:9
18+
{
19+
"contents": {
20+
"kind": "markdown",
21+
"value": "```rescript\nmodule Inline: (X: {let x: int}) => {\n let y: int\n}\n```"
22+
}
23+
}
24+
25+
Hover src/HoverFunctor.res 29:8
26+
{
27+
"contents": {
28+
"kind": "markdown",
29+
"value": "```rescript\nmodule IntSet: {\n type t = array<IntOrd.t>\n let add: (t, IntOrd.t) => array<IntOrd.t>\n}\n```"
30+
}
31+
}
32+
33+
Hover src/HoverFunctor.res 33:8
34+
{
35+
"contents": {
36+
"kind": "markdown",
37+
"value": "```rescript\nmodule PairInt: (B: Ord) => {\n type t = (IntOrd.t, B.t)\n}\n```"
38+
}
39+
}
40+
41+
Hover src/HoverFunctor.res 36:9
42+
{
43+
"contents": {
44+
"kind": "markdown",
45+
"value": "```rescript\nmodule Counter: () => {\n let count: ref<int>\n}\n```"
46+
}
47+
}
48+

0 commit comments

Comments
 (0)