Skip to content

Commit 37e13d0

Browse files
authored
Better LSP hover interaction in stack-or-heap (#116)
* add tests of behaviors we plan to change Signed-off-by: David Vulakh <[email protected]> * implement [let]-bound function support Signed-off-by: David Vulakh <[email protected]> * implement restricted constructor location gated behind [-lsp-compat] flag Signed-off-by: David Vulakh <[email protected]> * clean up reported location for let-bound functions report the entire value binding when not in the lsp-compat regime also move all the lsp-compat tests to a separate file to group them together Signed-off-by: David Vulakh <[email protected]> * sundry cleanup clean up some artifacts of intermediate states to make the total PR diff cleaner Signed-off-by: David Vulakh <[email protected]> * pr comments Signed-off-by: David Vulakh <[email protected]> * make fmt Signed-off-by: David Vulakh <[email protected]> --------- Signed-off-by: David Vulakh <[email protected]>
1 parent 25f1a7f commit 37e13d0

File tree

8 files changed

+285
-39
lines changed

8 files changed

+285
-39
lines changed

src/analysis/stack_or_heap_enclosing.ml

Lines changed: 55 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -10,20 +10,43 @@ type stack_or_heap =
1010

1111
type stack_or_heap_enclosings = (Location.t * stack_or_heap) list
1212

13-
let from_nodes ~pos ~path =
14-
let aux (_env, node, _tail) =
13+
let from_nodes ~lsp_compat ~pos ~path =
14+
let[@tail_mod_cons] rec with_parents = function
15+
| node :: parent :: rest ->
16+
(node, Some parent) :: with_parents (parent :: rest)
17+
| [ node ] -> [ (node, None) ]
18+
| [] -> []
19+
in
20+
let cursor_is_inside ({ loc_start; loc_end; _ } : Location.t) =
21+
Lexing.compare_pos pos loc_start >= 0 && Lexing.compare_pos pos loc_end <= 0
22+
in
23+
let aux (node, parent) =
1524
let open Browse_raw in
16-
let ret mode_result = Some (Mbrowse.node_loc node, mode_result) in
17-
let ret_alloc alloc_mode = ret (Alloc_mode alloc_mode) in
18-
let ret_no_alloc reason = ret (No_alloc { reason }) in
19-
let ret_maybe_alloc reason = function
20-
| Some alloc_mode -> ret_alloc alloc_mode
21-
| None -> ret_no_alloc reason
25+
let ret ?(loc = Mbrowse.node_loc node) mode_result =
26+
Some (loc, mode_result)
27+
in
28+
let ret_alloc ?loc alloc_mode = ret ?loc (Alloc_mode alloc_mode) in
29+
let ret_no_alloc ?loc reason = ret ?loc (No_alloc { reason }) in
30+
let ret_maybe_alloc ?loc reason = function
31+
| Some alloc_mode -> ret_alloc ?loc alloc_mode
32+
| None -> ret_no_alloc ?loc reason
2233
in
23-
match node with
24-
| Expression { exp_desc; _ } -> (
34+
match (node, parent) with
35+
| ( Pattern { pat_desc = Tpat_var _; _ },
36+
Some
37+
(Value_binding
38+
{ vb_expr = { exp_desc = Texp_function { alloc_mode; _ }; _ };
39+
vb_loc;
40+
_
41+
}) ) ->
42+
(* The location that most sensibly corresponds to the "allocation" is the entire
43+
value binding. However, the LSP hover at this point will describe just the
44+
pattern, so we don't override the location in the [lsp_compat] regime. *)
45+
let loc = if lsp_compat then None else Some vb_loc in
46+
ret ?loc (Alloc_mode alloc_mode.mode)
47+
| Expression { exp_desc; _ }, _ -> (
2548
match exp_desc with
26-
| Texp_function { alloc_mode; body; _ } ->
49+
| Texp_function { alloc_mode; body; _ } -> (
2750
let body_loc =
2851
(* A function expression is often in a non-obvious way the nearest enclosing
2952
allocating expression. To avoid confusion, we only consider a function
@@ -51,26 +74,30 @@ let from_nodes ~pos ~path =
5174
}
5275
| [] -> None)
5376
in
54-
let cursor_is_inside_function_body =
55-
match body_loc with
56-
| None -> false
57-
| Some { loc_start; loc_end; loc_ghost = _ } ->
58-
Lexing.compare_pos pos loc_start >= 0
59-
&& Lexing.compare_pos pos loc_end <= 0
60-
in
61-
if cursor_is_inside_function_body then None
62-
else ret (Alloc_mode alloc_mode.mode)
77+
match body_loc with
78+
| Some loc when cursor_is_inside loc -> None
79+
| _ -> ret (Alloc_mode alloc_mode.mode))
6380
| Texp_array (_, _, _, alloc_mode) -> ret (Alloc_mode alloc_mode.mode)
64-
| Texp_construct (_, { cstr_repr; _ }, args, maybe_alloc_mode) -> (
81+
| Texp_construct
82+
({ loc; txt = _lident }, { cstr_repr; _ }, args, maybe_alloc_mode)
83+
-> (
84+
let loc =
85+
(* The location of the "allocation" here is the entire expression, but the LSP
86+
hover for a constructor reports information just for the constructor (not the
87+
entire [Texp_construct] expression), so we override the location in the
88+
[lsp_compat] regime. *)
89+
if lsp_compat && cursor_is_inside loc then Some loc else None
90+
in
6591
match maybe_alloc_mode with
66-
| Some alloc_mode -> ret (Alloc_mode alloc_mode.mode)
92+
| Some alloc_mode -> ret ?loc (Alloc_mode alloc_mode.mode)
6793
| None -> (
6894
match args with
69-
| [] -> ret_no_alloc "constructor without arguments"
95+
| [] -> ret_no_alloc ?loc "constructor without arguments"
7096
| _ :: _ -> (
7197
match cstr_repr with
72-
| Variant_unboxed -> ret_no_alloc "unboxed constructor"
73-
| Variant_extensible | Variant_boxed _ -> ret Unexpected_no_alloc)))
98+
| Variant_unboxed -> ret_no_alloc ?loc "unboxed constructor"
99+
| Variant_extensible | Variant_boxed _ ->
100+
ret ?loc Unexpected_no_alloc)))
74101
| Texp_record { representation; alloc_mode = maybe_alloc_mode; _ } -> (
75102
match (maybe_alloc_mode, representation) with
76103
| _, Record_inlined _ -> None
@@ -90,4 +117,6 @@ let from_nodes ~pos ~path =
90117
| _ -> None)
91118
| _ -> None
92119
in
93-
List.filter_map ~f:aux path
120+
path
121+
|> List.map ~f:(fun (_, node, _) -> node)
122+
|> with_parents |> List.filter_map ~f:aux

src/analysis/stack_or_heap_enclosing.mli

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ type stack_or_heap =
2727
type stack_or_heap_enclosings = (Location.t * stack_or_heap) list
2828

2929
val from_nodes :
30+
lsp_compat:bool ->
3031
pos:Lexing.position ->
3132
path:(Env.t * Browse_raw.node * Query_protocol.is_tail_position) list ->
3233
stack_or_heap_enclosings

src/commands/new_commands.ml

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -708,6 +708,8 @@ let all_commands =
708708
of expressions known not to allocate, give \"unknown (does your code \
709709
contain a type error?)\". As suggested by the message, this should \
710710
only occur if the input does not typecheck.\n\n\
711+
`-lsp-compat` can be used to change the locations reported for better \
712+
LSP hover interaction.\n\n\
711713
`-index` can be used to print only one \"stack-or-heap\".\n\n\
712714
The result is returned as a list of:\n\
713715
```javascript\n\
@@ -719,21 +721,31 @@ let all_commands =
719721
```"
720722
~spec:
721723
[ arg "-position" "<position> Position to complete"
722-
(marg_position (fun pos (expr, cursor, _pos, index) ->
723-
(expr, cursor, pos, index)));
724+
(marg_position (fun pos (expr, cursor, _pos, lsp_compat, index) ->
725+
(expr, cursor, pos, lsp_compat, index)));
726+
optional "-lsp-compat"
727+
"<bool> Report ranges that are less accurate but work better with \
728+
LSP hover"
729+
(Marg.param "bool"
730+
(fun lsp_compat (expr, cursor, pos, _lsp_compat, index) ->
731+
match bool_of_string lsp_compat with
732+
| lsp_compat -> (expr, cursor, pos, lsp_compat, index)
733+
| exception _ -> failwith "lsp_compat should be a bool"));
724734
optional "-index" "<int> Only print type of <index>'th result"
725-
(Marg.param "int" (fun index (expr, cursor, pos, _index) ->
735+
(Marg.param "int"
736+
(fun index (expr, cursor, pos, lsp_compat, _index) ->
726737
match int_of_string index with
727-
| index -> (expr, cursor, pos, Some index)
738+
| index -> (expr, cursor, pos, lsp_compat, Some index)
728739
| exception _ -> failwith "index should be an integer"))
729740
]
730-
~default:("", -1, `None, None)
741+
~default:("", -1, `None, false, None)
731742
begin
732-
fun buffer (_, _, pos, index) ->
743+
fun buffer (_, _, pos, lsp_compat, index) ->
733744
match pos with
734745
| `None -> failwith "-position <pos> is mandatory"
735746
| #Msource.position as pos ->
736-
run buffer (Query_protocol.Stack_or_heap_enclosing (pos, index))
747+
run buffer
748+
(Query_protocol.Stack_or_heap_enclosing (pos, lsp_compat, index))
737749
end;
738750
command "type-enclosing"
739751
~doc:

src/commands/query_json.ml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,12 +56,13 @@ let dump (type a) : a t -> json =
5656
| Type_expr (expr, pos) ->
5757
mk "type-expression"
5858
[ ("expression", `String expr); ("position", mk_position pos) ]
59-
| Stack_or_heap_enclosing (pos, index) ->
59+
| Stack_or_heap_enclosing (pos, lsp_compat, index) ->
6060
mk "stack-or-heap-enclosing"
6161
[ ( "index",
6262
match index with
6363
| None -> `String "all"
6464
| Some n -> `Int n );
65+
("lsp-compat", `Bool lsp_compat);
6566
("position", mk_position pos)
6667
]
6768
| Type_enclosing (opt_cursor, pos, index) ->

src/frontend/query_commands.ml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -264,7 +264,7 @@ let dispatch pipeline (type a) : a Query_protocol.t -> a = function
264264
let context = Context.Expr in
265265
ignore (Type_utils.type_in_env ~verbosity ~context env ppf source : bool);
266266
to_string ()
267-
| Stack_or_heap_enclosing (pos, index) ->
267+
| Stack_or_heap_enclosing (pos, lsp_compat, index) ->
268268
let typer = Mpipeline.typer_result pipeline in
269269

270270
(* Optimise allocations only on programs that have type-checked. *)
@@ -292,7 +292,7 @@ let dispatch pipeline (type a) : a Query_protocol.t -> a = function
292292
| browse -> Browse_misc.annotate_tail_calls browse
293293
in
294294

295-
let result = Stack_or_heap_enclosing.from_nodes ~pos ~path in
295+
let result = Stack_or_heap_enclosing.from_nodes ~lsp_compat ~pos ~path in
296296

297297
let all_results =
298298
List.mapi result ~f:(fun i (loc, text) ->

src/frontend/query_protocol.ml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,7 @@ end
180180
type _ t =
181181
| Type_expr (* *) : string * Msource.position -> string t
182182
| Stack_or_heap_enclosing (* *) :
183-
Msource.position * int option
183+
Msource.position * bool * int option
184184
-> (Location.t * [ `String of string | `Index of int ]) list t
185185
| Type_enclosing (* *) :
186186
(string * int) option * Msource.position * int option
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
(* Cursor on the constructor itself (we treat this case specially to improve LSP
2+
compatibility) *)
3+
4+
let f g x y =
5+
let z = x + y in
6+
Some (g z)
7+
(* ^ *)
8+
;;
9+
10+
let f g x y =
11+
let z = x + y in
12+
exclave_ Some (g z)
13+
(* ^ *)
14+
;;
15+
16+
let f g x y =
17+
let z = Some (g x) in
18+
(* ^ *)
19+
y
20+
;;
21+
22+
(* Pattern of a [let]-bound function (we treat this case specially to improve LSP
23+
compatibility) *)
24+
25+
let f g x y =
26+
(* ^ *)
27+
let z = x + y in
28+
exclave_ Some (g z)
29+
and h g x y =
30+
(* ^ *)
31+
let z = x + y in
32+
exclave_ Some (g z)
33+
;;
34+
35+
let ignore (local_ _) = ()
36+
37+
let () =
38+
let f g x y =
39+
(* ^ *)
40+
let z = x + y in
41+
exclave_ Some (g z)
42+
and h g x y =
43+
(* ^ *)
44+
let z = x + y in
45+
exclave_ Some (g z)
46+
in
47+
ignore f;
48+
ignore h
49+
50+
(* Ensure other [let]-bound patterns aren't treated this way *)
51+
52+
let x = Some 5
53+
(* ^ *)

0 commit comments

Comments
 (0)