Skip to content

Commit

Permalink
Fix locate command in presence of let punning (#96)
Browse files Browse the repository at this point in the history
* Modify locate behavior with let punning

* Add tests

* Add tests

* Improve test output

* Add broken occurrences tests

* Remove redundant ?attrs:None

* Cleanup compare_nodes function

* Stop using <<<

* Remove usage of <<
  • Loading branch information
liam923 authored Sep 24, 2024
1 parent bc9398f commit 742896d
Show file tree
Hide file tree
Showing 15 changed files with 12,929 additions and 12,535 deletions.
4 changes: 2 additions & 2 deletions src/analysis/context.ml
Original file line number Diff line number Diff line change
Expand Up @@ -132,12 +132,12 @@ let inspect_expression ~cursor ~lid e : t =
| _ ->
Expr

let inspect_browse_tree ~cursor lid browse : t option =
let inspect_browse_tree ?let_pun_behavior ~cursor lid browse : t option =
log ~title:"inspect_context" "current node is: [%s]"
(String.concat ~sep:"|" (
List.map ~f:(Mbrowse.print ()) browse
));
match Mbrowse.enclosing cursor browse with
match Mbrowse.enclosing ?let_pun_behavior cursor browse with
| [] ->
log ~title:"inspect_context"
"no enclosing around: %a" Lexing.print_position cursor;
Expand Down
1 change: 1 addition & 0 deletions src/analysis/context.mli
Original file line number Diff line number Diff line change
Expand Up @@ -55,4 +55,5 @@ val to_string : t -> string
breaking the context inference.
*)
val inspect_browse_tree :
?let_pun_behavior:Mbrowse.Let_pun_behavior.t ->
cursor:Std.Lexing.position -> Longident.t -> Mbrowse.t list -> t option
8 changes: 4 additions & 4 deletions src/analysis/locate.ml
Original file line number Diff line number Diff line change
Expand Up @@ -836,7 +836,7 @@ let from_path ~config ~env ~local_defs ~namespace path =
| None -> `Not_in_env (Path.name path)
| Some decl -> from_path ~config ~env ~local_defs ~decl path

let infer_namespace ?namespaces ~pos lid browse is_label =
let infer_namespace ?let_pun_behavior ?namespaces ~pos lid browse is_label =
match namespaces with
| Some nss ->
if not is_label
Expand All @@ -850,7 +850,7 @@ let infer_namespace ?namespaces ~pos lid browse is_label =
`Error `Missing_labels_namespace
)
| None ->
match Context.inspect_browse_tree ~cursor:pos lid [browse], is_label with
match Context.inspect_browse_tree ?let_pun_behavior ~cursor:pos lid [browse], is_label with
| None, _ ->
log ~title:"from_string" "already at origin, doing nothing" ;
`Error `At_origin
Expand All @@ -864,13 +864,13 @@ let infer_namespace ?namespaces ~pos lid browse is_label =
"dropping inferred context, it is not precise enough";
`Ok [ `Labels ]

let from_string ~config ~env ~local_defs ~pos ?namespaces path =
let from_string ~config ~env ~local_defs ~pos ?let_pun_behavior ?namespaces path =
File_switching.reset ();
let browse = Mbrowse.of_typedtree local_defs in
let lid = Type_utils.parse_longident path in
let from_lid lid =
let ident, is_label = Longident.keep_suffix lid in
match infer_namespace ?namespaces ~pos lid browse is_label with
match infer_namespace ?let_pun_behavior ?namespaces ~pos lid browse is_label with
| `Error e -> e
| `Ok nss ->
log ~title:"from_string"
Expand Down
1 change: 1 addition & 0 deletions src/analysis/locate.mli
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ val from_string
-> env:Env.t
-> local_defs:Mtyper.typedtree
-> pos:Lexing.position
-> ?let_pun_behavior:Mbrowse.Let_pun_behavior.t
-> ?namespaces:Env_lookup.Namespace.inferred_basic list
-> string
-> [> `File_not_found of string
Expand Down
3 changes: 2 additions & 1 deletion src/frontend/query_commands.ml
Original file line number Diff line number Diff line change
Expand Up @@ -595,6 +595,7 @@ let dispatch pipeline (type a) : a Query_protocol.t -> a =
let typer = Mpipeline.typer_result pipeline in
let local_defs = Mtyper.get_typedtree typer in
let pos = Mpipeline.get_lexing_pos pipeline pos in
let let_pun_behavior = Mbrowse.Let_pun_behavior.Prefer_expression in
let env, _ = Mbrowse.leaf_node (Mtyper.node_at typer pos) in
let path =
match patho with
Expand All @@ -614,7 +615,7 @@ let dispatch pipeline (type a) : a Query_protocol.t -> a =
traverse_aliases = true
}
in
begin match Locate.from_string ~config ~env ~local_defs ~pos path with
begin match Locate.from_string ~config ~env ~local_defs ~pos ~let_pun_behavior path with
| `Found { file; location; _ } ->
Locate.log ~title:"result"
"found: %s" file;
Expand Down
75 changes: 57 additions & 18 deletions src/kernel/mbrowse.ml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,12 @@ open Browse_raw
type node = Browse_raw.node
type t = (Env.t * node) list

module Let_pun_behavior = struct
type t =
| Prefer_expression
| Prefer_pattern
end

let node_of_binary_part = Browse_raw.node_of_binary_part

let fold_node f env t acc =
Expand Down Expand Up @@ -71,7 +77,6 @@ let node_loc node = approximate_loc Browse_raw.node_real_loc node
let node_merlin_loc node = approximate_loc Browse_raw.node_merlin_loc node

let leaf_node = List.hd
let leaf_loc t = node_loc (snd (leaf_node t))

let drop_leaf t =
match t with
Expand Down Expand Up @@ -112,20 +117,34 @@ let select_leafs pos root =
(try traverse root with Exit -> ());
!branches

let compare_locations pos l1 l2 =
module Favorability = struct
type t =
| Neutral
| Unfavored

let based_on_ghostliness (loc : Location.t) =
match loc.loc_ghost with
| true -> Unfavored
| false -> Neutral
end
type node_loc = { loc : Location.t; favorability : Favorability.t }

let compare_locations pos (l1 : node_loc) (l2 : node_loc) =
let t2_first = +1 in
let t1_first = -1 in
match
Location_aux.compare_pos pos l1,
Location_aux.compare_pos pos l2
Location_aux.compare_pos pos l1.loc,
Location_aux.compare_pos pos l2.loc
with
| 0, 0 ->
(* Cursor inside both locations: favor non-ghost closer to the end *)
begin match l1.Location.loc_ghost, l2.Location.loc_ghost with
| true, false -> 1
| false, true -> -1
(* Cursor inside both locations:
If one is unfavored, favor the other one.
Otherwise, favor the one closer to the end *)
begin match l1.favorability, l2.favorability with
| Unfavored, Neutral -> 1
| Neutral, Unfavored -> -1
| _ ->
Lexing.compare_pos l1.Location.loc_end l2.Location.loc_end
Lexing.compare_pos l1.loc.loc_end l2.loc.loc_end
end
(* Cursor inside one location: it has priority *)
| 0, _ -> t1_first
Expand All @@ -135,25 +154,45 @@ let compare_locations pos l1 l2 =
| n, m when m > 0 && n < 0 -> t2_first
(* Cursor is after both, select the closest one *)
| _, _ ->
Lexing.compare_pos l2.Location.loc_end l1.Location.loc_end
Lexing.compare_pos l2.loc.loc_end l1.loc.loc_end

let compare_nodes ?(let_pun_behavior = Let_pun_behavior.Prefer_pattern) pos (n1, loc1) (n2, loc2) =
let loc_with_favorability node (loc : Location.t) : node_loc =
let is_punned = Browse_raw.has_attr ~name:Builtin_attributes.merlin_let_punned node in
let favorability : Favorability.t =
match is_punned, node, let_pun_behavior with
| true, Expression _, Prefer_expression -> Neutral
| true, Expression _, Prefer_pattern -> Unfavored
| true, Pattern _, Prefer_expression -> Unfavored
| true, Pattern _, Prefer_pattern -> Neutral
| _ -> Favorability.based_on_ghostliness loc
in
{ loc = node_loc node; favorability }
in
compare_locations pos (loc_with_favorability n1 loc1) (loc_with_favorability n2 loc2)

let best_node pos = function
let best_node ?let_pun_behavior pos = function
| [] -> []
| init :: xs ->
let f acc x =
if compare_locations pos (leaf_loc acc) (leaf_loc x) <= 0
let leaf_with_loc leaf =
let _, node = leaf_node leaf in
let loc = node_loc node in
node, loc
in
if compare_nodes ?let_pun_behavior pos (leaf_with_loc acc) (leaf_with_loc x) <= 0
then acc
else x
in
List.fold_left ~f ~init xs

let enclosing pos roots =
match best_node pos roots with
let enclosing ?let_pun_behavior pos roots =
match best_node ?let_pun_behavior pos roots with
| [] -> []
| root -> best_node pos (select_leafs pos root)
| root -> best_node ?let_pun_behavior pos (select_leafs pos root)

let deepest_before pos roots =
match enclosing pos roots with
let deepest_before ?let_pun_behavior pos roots =
match enclosing ?let_pun_behavior pos roots with
| [] -> []
| root ->
let rec aux path =
Expand All @@ -165,7 +204,7 @@ let deepest_before pos roots =
Location_aux.compare_pos pos loc = 0 ||
Lexing.compare_pos loc.Location.loc_end loc0.Location.loc_end = 0
then match acc with
| Some (_,loc',_) when compare_locations pos loc' loc <= 0 -> acc
| Some (_,loc',node') when compare_nodes pos (node', loc') (node, loc) <= 0 -> acc
| Some _ | None -> Some (env,loc,node)
else acc
in
Expand Down
17 changes: 14 additions & 3 deletions src/kernel/mbrowse.mli
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,12 @@ open Std
type node = Browse_raw.node
type t = (Env.t * node) list

module Let_pun_behavior : sig
type t =
| Prefer_expression
| Prefer_pattern
end

val fold_node : (Env.t -> Browse_raw.node -> 'a -> 'a) ->
Env.t -> Browse_raw.node -> 'a -> 'a
val node_loc : Browse_raw.node -> Location.t
Expand All @@ -43,13 +49,18 @@ val drop_leaf : t -> t option
* through:
* foo bar (baz :: tail) <cursor>
* asking for node from cursor position will return context of "tail".
* Returns the matching node and all its ancestors or the empty list. *)
val deepest_before : Lexing.position -> t list -> t
* Returns the matching node and all its ancestors or the empty list.
[let_pun_behavior] dictates whether to prefer the expression or pattern node in a
punned let expression. The default is [Prefer_pattern] *)
val deepest_before : ?let_pun_behavior:Let_pun_behavior.t -> Lexing.position -> t list -> t


val select_open_node : t -> (Path.t * Longident.t * t) option

val enclosing : Lexing.position -> t list -> t
(** [let_pun_behavior] dictates whether to prefer the expression or pattern node in a
punned let expression. The default is [Prefer_pattern] *)
val enclosing : ?let_pun_behavior:Let_pun_behavior.t -> Lexing.position -> t list -> t

val of_structure : Typedtree.structure -> t
val of_signature : Typedtree.signature -> t
Expand Down
4 changes: 2 additions & 2 deletions src/kernel/mtyper.ml
Original file line number Diff line number Diff line change
Expand Up @@ -284,7 +284,7 @@ let get_index t = t.index

let get_stamp t = t.stamp

let node_at ?(skip_recovered=false) t pos_cursor =
let node_at ?(skip_recovered=false) ?let_pun_behavior t pos_cursor =
let node = Mbrowse.of_typedtree (get_typedtree t) in
log ~title:"node_at" "Node: %s" (Mbrowse.print () node);
let rec select = function
Expand All @@ -294,7 +294,7 @@ let node_at ?(skip_recovered=false) t pos_cursor =
when Mbrowse.is_recovered node' -> select ancestors
| l -> l
in
match Mbrowse.deepest_before pos_cursor [node] with
match Mbrowse.deepest_before ?let_pun_behavior pos_cursor [node] with
| [] -> [get_env t, Browse_raw.Dummy]
| path when skip_recovered -> select path
| path ->
Expand Down
2 changes: 1 addition & 1 deletion src/kernel/mtyper.mli
Original file line number Diff line number Diff line change
Expand Up @@ -59,4 +59,4 @@ val get_cache_stat : result -> typer_cache_stats
* inside x definition.
*)
val node_at :
?skip_recovered:bool -> result -> Lexing.position -> Mbrowse.t
?skip_recovered:bool -> ?let_pun_behavior:Mbrowse.Let_pun_behavior.t -> result -> Lexing.position -> Mbrowse.t
4 changes: 4 additions & 0 deletions src/ocaml/parsing/builtin_attributes.ml
Original file line number Diff line number Diff line change
Expand Up @@ -1023,3 +1023,7 @@ let get_tracing_probe_payload (payload : Parsetree.payload) =
| _ -> Error ()
in
Ok { name; name_loc; enabled_at_init; arg }

(* Merlin specific *)

let merlin_let_punned = "merlin.let-punned"
7 changes: 7 additions & 0 deletions src/ocaml/parsing/builtin_attributes.mli
Original file line number Diff line number Diff line change
Expand Up @@ -326,3 +326,10 @@ type tracing_probe =
*)
val get_tracing_probe_payload :
Parsetree.payload -> (tracing_probe, unit) result

(* Merlin specific *)

(** The name of the attribute used to identify punned let expressions. When a let
expression is punned, an attribute with this name is added to the pattern and
expression nodes by the parser. *)
val merlin_let_punned : string
Loading

0 comments on commit 742896d

Please sign in to comment.