Skip to content

Commit b0124d0

Browse files
committed
implement restricted constructor location
gated behind [-lsp-compat] flag Signed-off-by: David Vulakh <[email protected]>
1 parent edd8ff3 commit b0124d0

File tree

7 files changed

+162
-35
lines changed

7 files changed

+162
-35
lines changed

src/analysis/stack_or_heap_enclosing.ml

Lines changed: 27 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -10,19 +10,24 @@ type stack_or_heap =
1010

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

13-
let from_nodes ~pos ~path =
13+
let from_nodes ~lsp_compat ~pos ~path =
1414
let[@tail_mod_cons] rec tails = function
1515
| hd :: tl -> (hd, tl) :: tails tl
1616
| [] -> []
1717
in
18+
let cursor_is_inside ({ loc_start; loc_end; _ } : Location.t) =
19+
Lexing.compare_pos pos loc_start >= 0 && Lexing.compare_pos pos loc_end <= 0
20+
in
1821
let aux node parent =
1922
let open Browse_raw in
20-
let ret mode_result = Some (Mbrowse.node_loc node, mode_result) in
21-
let ret_alloc alloc_mode = ret (Alloc_mode alloc_mode) in
22-
let ret_no_alloc reason = ret (No_alloc { reason }) in
23-
let ret_maybe_alloc reason = function
24-
| Some alloc_mode -> ret_alloc alloc_mode
25-
| None -> ret_no_alloc reason
23+
let ret ?(loc = Mbrowse.node_loc node) mode_result =
24+
Some (loc, mode_result)
25+
in
26+
let ret_alloc ?loc alloc_mode = ret ?loc (Alloc_mode alloc_mode) in
27+
let ret_no_alloc ?loc reason = ret ?loc (No_alloc { reason }) in
28+
let ret_maybe_alloc ?loc reason = function
29+
| Some alloc_mode -> ret_alloc ?loc alloc_mode
30+
| None -> ret_no_alloc ?loc reason
2631
in
2732
match (node, parent) with
2833
| ( Pattern { pat_desc = Tpat_var _; _ },
@@ -32,7 +37,7 @@ let from_nodes ~pos ~path =
3237
) -> ret (Alloc_mode alloc_mode.mode)
3338
| Expression { exp_desc; _ }, _ -> (
3439
match exp_desc with
35-
| Texp_function { alloc_mode; body; _ } ->
40+
| Texp_function { alloc_mode; body; _ } -> (
3641
let body_loc =
3742
(* A function expression is often in a non-obvious way the nearest enclosing
3843
allocating expression. To avoid confusion, we only consider a function
@@ -60,26 +65,26 @@ let from_nodes ~pos ~path =
6065
}
6166
| [] -> None)
6267
in
63-
let cursor_is_inside_function_body =
64-
match body_loc with
65-
| None -> false
66-
| Some { loc_start; loc_end; loc_ghost = _ } ->
67-
Lexing.compare_pos pos loc_start >= 0
68-
&& Lexing.compare_pos pos loc_end <= 0
69-
in
70-
if cursor_is_inside_function_body then None
71-
else ret (Alloc_mode alloc_mode.mode)
68+
match body_loc with
69+
| Some loc when cursor_is_inside loc -> None
70+
| _ -> ret (Alloc_mode alloc_mode.mode))
7271
| Texp_array (_, _, _, alloc_mode) -> ret (Alloc_mode alloc_mode.mode)
73-
| Texp_construct (_, { cstr_repr; _ }, args, maybe_alloc_mode) -> (
72+
| Texp_construct
73+
({ loc; txt = _lident }, { cstr_repr; _ }, args, maybe_alloc_mode)
74+
-> (
75+
let loc =
76+
if lsp_compat && cursor_is_inside loc then Some loc else None
77+
in
7478
match maybe_alloc_mode with
75-
| Some alloc_mode -> ret (Alloc_mode alloc_mode.mode)
79+
| Some alloc_mode -> ret ?loc (Alloc_mode alloc_mode.mode)
7680
| None -> (
7781
match args with
78-
| [] -> ret_no_alloc "constructor without arguments"
82+
| [] -> ret_no_alloc ?loc "constructor without arguments"
7983
| _ :: _ -> (
8084
match cstr_repr with
81-
| Variant_unboxed -> ret_no_alloc "unboxed constructor"
82-
| Variant_extensible | Variant_boxed _ -> ret Unexpected_no_alloc)))
85+
| Variant_unboxed -> ret_no_alloc ?loc "unboxed constructor"
86+
| Variant_extensible | Variant_boxed _ ->
87+
ret ?loc Unexpected_no_alloc)))
8388
| Texp_record { representation; alloc_mode = maybe_alloc_mode; _ } -> (
8489
match (maybe_alloc_mode, representation) with
8590
| _, Record_inlined _ -> None

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

tests/test-dirs/stack-or-heap.t/run.t

Lines changed: 110 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,12 +43,13 @@ escape characters in string literals, so we use the revert-newlines script.
4343
> file=$1
4444
> position=$2
4545
> index=$3
46+
> trailing=$4
4647
> line=$(echo "$position" | cut -d ':' -f 1)
4748
> col=$(echo "$position" | cut -d ':' -f 2)
4849
> highlight_range "$file" $line $(expr $col - 1) $line $col
4950
> merlin=$(
5051
> $MERLIN single stack-or-heap-enclosing -position "$position" -verbosity "$verbosity" \
51-
> -filename "$file" < "$file" | revert-newlines
52+
> -filename "$file" $trailing < "$file" | revert-newlines
5253
> )
5354
> echo
5455
> if [ "$(echo "$merlin" | jq ".value[$index]")" != null ]
@@ -71,7 +72,7 @@ escape characters in string literals, so we use the revert-newlines script.
7172
> do
7273
> for i in $(seq 0 $until)
7374
> do
74-
> run "$orig_file.tmp.ml" $lc $i
75+
> run "$orig_file.tmp.ml" $lc $i "$3"
7576
> done
7677
> done
7778
> rm "$orig_file.tmp.ml"
@@ -184,6 +185,113 @@ escape characters in string literals, so we use the revert-newlines script.
184185

185186
"not an allocation (unboxed constructor)"
186187

188+
189+
$ run_annotated_file constructors.ml 1 "-lsp-compat true"
190+
| Some (g z)
191+
| ^
192+
193+
| Some (g z)
194+
| ^^^^^^^^^^
195+
196+
"heap"
197+
198+
| exclave_ Some (g z)
199+
| ^
200+
201+
| exclave_ Some (g z)
202+
| ^^^^^^^^^^
203+
204+
"stack"
205+
206+
| let z = Some (g x) in
207+
| ^
208+
209+
| let z = Some (g x) in
210+
| ^^^^^^^^^^
211+
212+
"stack"
213+
214+
| Some (g z)
215+
| ^
216+
217+
| Some (g z)
218+
| ^^^^
219+
220+
"heap"
221+
222+
| exclave_ Some (g z)
223+
| ^
224+
225+
| exclave_ Some (g z)
226+
| ^^^^
227+
228+
"stack"
229+
230+
| let z = Some (g x) in
231+
| ^
232+
233+
| let z = Some (g x) in
234+
| ^^^^
235+
236+
"stack"
237+
238+
| None
239+
| ^
240+
241+
| None
242+
| ^^^^
243+
244+
"not an allocation (constructor without arguments)"
245+
246+
| exclave_ None
247+
| ^
248+
249+
| exclave_ None
250+
| ^^^^
251+
252+
"not an allocation (constructor without arguments)"
253+
254+
| f (Some x);
255+
| ^
256+
257+
| f (Some x);
258+
| ^^^^^^^^
259+
260+
"stack"
261+
262+
| f (local_ Some x);
263+
| ^
264+
265+
| f (local_ Some x);
266+
| ^^^^^^^^^^^^^^^
267+
268+
"stack"
269+
270+
| f (Some x)
271+
| ^
272+
273+
| f (Some x)
274+
| ^^^^^^^^
275+
276+
"heap"
277+
278+
|let g x = f (Some x) [@nontail]
279+
| ^
280+
281+
|let g x = f (Some x) [@nontail]
282+
| ^^^^^^^^
283+
284+
"stack"
285+
286+
| Box (g z)
287+
| ^
288+
289+
| Box (g z)
290+
| ^^^^^^^^^
291+
292+
"not an allocation (unboxed constructor)"
293+
294+
187295
(II) Variants
188296

189297
$ run_annotated_file variants.ml

0 commit comments

Comments
 (0)