Skip to content

Commit 4ad0749

Browse files
authored
GotoDef returning multiple definitions for atoms (#1338)
Allowing for more flexibility when dealing with Erlang atoms. Hence, whenever the user tries to navigate to a definition, they will be able to choose which definition to navigate to. Tasks: T123303743
1 parent 637022b commit 4ad0749

14 files changed

+219
-109
lines changed

apps/els_lsp/priv/code_navigation/src/code_navigation.erl

+10
Original file line numberDiff line numberDiff line change
@@ -122,3 +122,13 @@ macro_b(_X, _Y) ->
122122

123123
function_mb() ->
124124
?MACRO_B(m, b).
125+
126+
code_navigation() -> code_navigation.
127+
128+
code_navigation(X) -> X.
129+
130+
multiple_instances_same_file() -> {code_navigation, [simple_list], "abc"}.
131+
132+
code_navigation_extra(X, Y, Z) -> [code_navigation_extra, X, Y, Z].
133+
134+
multiple_instances_diff_file() -> code_navigation_extra.

apps/els_lsp/src/els_call_hierarchy_provider.erl

+1-1
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ application_to_item(Uri, Application) ->
9090
#{id := Id} = Application,
9191
Name = els_utils:function_signature(Id),
9292
case els_code_navigation:goto_definition(Uri, Application) of
93-
{ok, DefUri, DefPOI} ->
93+
{ok, [{DefUri, DefPOI} | _]} ->
9494
DefRange = maps:get(range, DefPOI),
9595
Data = #{poi => DefPOI},
9696
{ok, els_call_hierarchy_item:new(Name, DefUri, DefRange, DefRange, Data)};

apps/els_lsp/src/els_code_navigation.erl

+63-38
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
%%==============================================================================
2525

2626
-spec goto_definition(uri(), els_poi:poi()) ->
27-
{ok, uri(), els_poi:poi()} | {error, any()}.
27+
{ok, [{uri(), els_poi:poi()}]} | {error, any()}.
2828
goto_definition(
2929
Uri,
3030
Var = #{kind := variable}
@@ -33,7 +33,7 @@ goto_definition(
3333
%% first occurrence of the variable in variable scope.
3434
case find_in_scope(Uri, Var) of
3535
[Var | _] -> {error, already_at_definition};
36-
[POI | _] -> {ok, Uri, POI};
36+
[POI | _] -> {ok, [{Uri, POI}]};
3737
% Probably due to parse error
3838
[] -> {error, nothing_in_scope}
3939
end;
@@ -46,7 +46,7 @@ goto_definition(
4646
Kind =:= import_entry
4747
->
4848
case els_utils:find_module(M) of
49-
{ok, Uri} -> find(Uri, function, {F, A});
49+
{ok, Uri} -> defs_to_res(find(Uri, function, {F, A}));
5050
{error, Error} -> {error, Error}
5151
end;
5252
goto_definition(
@@ -60,27 +60,26 @@ goto_definition(
6060
%% try to find local function first
6161
%% fall back to bif search if unsuccessful
6262
case find(Uri, function, {F, A}) of
63-
{error, Error} ->
63+
[] ->
6464
case is_imported_bif(Uri, F, A) of
6565
true ->
6666
goto_definition(Uri, POI#{id := {erlang, F, A}});
6767
false ->
68-
{error, Error}
68+
{error, not_found}
6969
end;
7070
Result ->
71-
Result
71+
defs_to_res(Result)
7272
end;
7373
goto_definition(
7474
Uri,
75-
#{kind := atom, id := Id} = POI
75+
#{kind := atom, id := Id}
7676
) ->
77-
%% Two interesting cases for atoms: testcases and modules.
78-
%% Testcases are functions with arity 1, so we first look for a function
79-
%% with the same name and arity 1 in the local scope
80-
%% If we can't find it, we hope that the atom refers to a module.
81-
case find(Uri, function, {Id, 1}) of
82-
{error, _Error} -> goto_definition(Uri, POI#{kind := module});
83-
Else -> Else
77+
%% Two interesting cases for atoms: functions and modules.
78+
%% We return all function defs with any arity combined with module defs.
79+
DefsFun = find(Uri, function, {Id, any_arity}),
80+
case els_utils:find_module(Id) of
81+
{ok, ModUri} -> defs_to_res(DefsFun ++ find(ModUri, module, Id));
82+
{error, _Error} -> defs_to_res(DefsFun)
8483
end;
8584
goto_definition(
8685
_Uri,
@@ -90,7 +89,7 @@ goto_definition(
9089
Kind =:= module
9190
->
9291
case els_utils:find_module(Module) of
93-
{ok, Uri} -> find(Uri, module, Module);
92+
{ok, Uri} -> defs_to_res(find(Uri, module, Module));
9493
{error, Error} -> {error, Error}
9594
end;
9695
goto_definition(
@@ -101,37 +100,37 @@ goto_definition(
101100
} = POI
102101
) ->
103102
case find(Uri, define, Define) of
104-
{error, not_found} ->
103+
[] ->
105104
goto_definition(Uri, POI#{id => MacroName});
106105
Else ->
107-
Else
106+
defs_to_res(Else)
108107
end;
109108
goto_definition(Uri, #{kind := macro, id := Define}) ->
110-
find(Uri, define, Define);
109+
defs_to_res(find(Uri, define, Define));
111110
goto_definition(Uri, #{kind := record_expr, id := Record}) ->
112-
find(Uri, record, Record);
111+
defs_to_res(find(Uri, record, Record));
113112
goto_definition(Uri, #{kind := record_field, id := {Record, Field}}) ->
114-
find(Uri, record_def_field, {Record, Field});
113+
defs_to_res(find(Uri, record_def_field, {Record, Field}));
115114
goto_definition(_Uri, #{kind := Kind, id := Id}) when
116115
Kind =:= include;
117116
Kind =:= include_lib
118117
->
119118
case els_utils:find_header(els_utils:filename_to_atom(Id)) of
120-
{ok, Uri} -> {ok, Uri, beginning()};
119+
{ok, Uri} -> {ok, [{Uri, beginning()}]};
121120
{error, Error} -> {error, Error}
122121
end;
123122
goto_definition(_Uri, #{kind := type_application, id := {M, T, A}}) ->
124123
case els_utils:find_module(M) of
125-
{ok, Uri} -> find(Uri, type_definition, {T, A});
124+
{ok, Uri} -> defs_to_res(find(Uri, type_definition, {T, A}));
126125
{error, Error} -> {error, Error}
127126
end;
128127
goto_definition(Uri, #{kind := Kind, id := {T, A}}) when
129128
Kind =:= type_application; Kind =:= export_type_entry
130129
->
131-
find(Uri, type_definition, {T, A});
130+
defs_to_res(find(Uri, type_definition, {T, A}));
132131
goto_definition(_Uri, #{kind := parse_transform, id := Module}) ->
133132
case els_utils:find_module(Module) of
134-
{ok, Uri} -> find(Uri, module, Module);
133+
{ok, Uri} -> defs_to_res(find(Uri, module, Module));
135134
{error, Error} -> {error, Error}
136135
end;
137136
goto_definition(_Filename, _) ->
@@ -153,15 +152,19 @@ is_imported_bif(_Uri, F, A) ->
153152
true
154153
end.
155154

155+
-spec defs_to_res([{uri(), els_poi:poi()}]) -> {ok, [{uri(), els_poi:poi()}]} | {error, not_found}.
156+
defs_to_res([]) -> {error, not_found};
157+
defs_to_res(Defs) -> {ok, Defs}.
158+
156159
-spec find(uri() | [uri()], els_poi:poi_kind(), any()) ->
157-
{ok, uri(), els_poi:poi()} | {error, not_found}.
160+
[{uri(), els_poi:poi()}].
158161
find(UriOrUris, Kind, Data) ->
159162
find(UriOrUris, Kind, Data, sets:new()).
160163

161164
-spec find(uri() | [uri()], els_poi:poi_kind(), any(), sets:set(binary())) ->
162-
{ok, uri(), els_poi:poi()} | {error, not_found}.
165+
[{uri(), els_poi:poi()}].
163166
find([], _Kind, _Data, _AlreadyVisited) ->
164-
{error, not_found};
167+
[];
165168
find([Uri | Uris0], Kind, Data, AlreadyVisited) ->
166169
case sets:is_element(Uri, AlreadyVisited) of
167170
true ->
@@ -185,24 +188,46 @@ find(Uri, Kind, Data, AlreadyVisited) ->
185188
any(),
186189
sets:set(binary())
187190
) ->
188-
{ok, uri(), els_poi:poi()} | {error, any()}.
191+
[{uri(), els_poi:poi()}].
189192
find_in_document([Uri | Uris0], Document, Kind, Data, AlreadyVisited) ->
190193
POIs = els_dt_document:pois(Document, [Kind]),
191-
case [POI || #{id := Id} = POI <- POIs, Id =:= Data] of
194+
Defs = [POI || #{id := Id} = POI <- POIs, Id =:= Data],
195+
{AllDefs, MultipleDefs} =
196+
case Data of
197+
{_, any_arity} when Kind =:= function ->
198+
%% Including defs with any arity
199+
AnyArity = [
200+
POI
201+
|| #{id := {F, _}} = POI <- POIs, Kind =:= function, Data =:= {F, any_arity}
202+
],
203+
{AnyArity, true};
204+
_ ->
205+
{Defs, false}
206+
end,
207+
case AllDefs of
192208
[] ->
193209
case maybe_imported(Document, Kind, Data) of
194-
{ok, U, P} ->
195-
{ok, U, P};
196-
{error, not_found} ->
210+
[] ->
197211
find(
198212
lists:usort(include_uris(Document) ++ Uris0),
199213
Kind,
200214
Data,
201215
AlreadyVisited
202-
)
216+
);
217+
Else ->
218+
Else
203219
end;
204220
Definitions ->
205-
{ok, Uri, hd(els_poi:sort(Definitions))}
221+
SortedDefs = els_poi:sort(Definitions),
222+
case MultipleDefs of
223+
true ->
224+
%% This will be the case only when the user tries to
225+
%% navigate to the definition of an atom
226+
[{Uri, POI} || POI <- SortedDefs];
227+
false ->
228+
%% In the general case, we return only one def
229+
[{Uri, hd(SortedDefs)}]
230+
end
206231
end.
207232

208233
-spec include_uris(els_dt_document:item()) -> [uri()].
@@ -223,20 +248,20 @@ beginning() ->
223248

224249
%% @doc check for a match in any of the module imported functions.
225250
-spec maybe_imported(els_dt_document:item(), els_poi:poi_kind(), any()) ->
226-
{ok, uri(), els_poi:poi()} | {error, not_found}.
251+
[{uri(), els_poi:poi()}].
227252
maybe_imported(Document, function, {F, A}) ->
228253
POIs = els_dt_document:pois(Document, [import_entry]),
229254
case [{M, F, A} || #{id := {M, FP, AP}} <- POIs, FP =:= F, AP =:= A] of
230255
[] ->
231-
{error, not_found};
256+
[];
232257
[{M, F, A} | _] ->
233258
case els_utils:find_module(M) of
234259
{ok, Uri0} -> find(Uri0, function, {F, A});
235-
{error, not_found} -> {error, not_found}
260+
{error, not_found} -> []
236261
end
237262
end;
238263
maybe_imported(_Document, _Kind, _Data) ->
239-
{error, not_found}.
264+
[].
240265

241266
-spec find_in_scope(uri(), els_poi:poi()) -> [els_poi:poi()].
242267
find_in_scope(Uri, #{kind := variable, id := VarId, range := VarRange}) ->

apps/els_lsp/src/els_crossref_diagnostics.erl

+1-1
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,7 @@ has_definition(
142142
lager_definition(Level, Arity);
143143
has_definition(POI, #{uri := Uri}) ->
144144
case els_code_navigation:goto_definition(Uri, POI) of
145-
{ok, _Uri, _POI} ->
145+
{ok, _Defs} ->
146146
true;
147147
{error, _Error} ->
148148
false

apps/els_lsp/src/els_definition_provider.erl

+9-3
Original file line numberDiff line numberDiff line change
@@ -40,13 +40,19 @@ handle_request({definition, Params}) ->
4040
{response, GoTo}
4141
end.
4242

43-
-spec goto_definition(uri(), [els_poi:poi()]) -> map() | null.
43+
-spec goto_definition(uri(), [els_poi:poi()]) -> [map()] | null.
4444
goto_definition(_Uri, []) ->
4545
null;
4646
goto_definition(Uri, [POI | Rest]) ->
4747
case els_code_navigation:goto_definition(Uri, POI) of
48-
{ok, DefUri, #{range := Range}} ->
49-
#{uri => DefUri, range => els_protocol:range(Range)};
48+
{ok, Definitions} ->
49+
lists:map(
50+
fun({DefUri, DefPOI}) ->
51+
#{range := Range} = DefPOI,
52+
#{uri => DefUri, range => els_protocol:range(Range)}
53+
end,
54+
Definitions
55+
);
5056
_ ->
5157
goto_definition(Uri, Rest)
5258
end.

apps/els_lsp/src/els_docs.erl

+2-2
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ docs(Uri, #{kind := Kind, id := {F, A}}) when
5959
function_docs('local', M, F, A);
6060
docs(Uri, #{kind := macro, id := Name} = POI) ->
6161
case els_code_navigation:goto_definition(Uri, POI) of
62-
{ok, DefUri, #{data := #{args := Args, value_range := ValueRange}}} when
62+
{ok, [{DefUri, #{data := #{args := Args, value_range := ValueRange}}}]} when
6363
is_list(Args); is_atom(Name)
6464
->
6565
NameStr = macro_signature(Name, Args),
@@ -73,7 +73,7 @@ docs(Uri, #{kind := macro, id := Name} = POI) ->
7373
end;
7474
docs(Uri, #{kind := record_expr} = POI) ->
7575
case els_code_navigation:goto_definition(Uri, POI) of
76-
{ok, DefUri, #{data := #{value_range := ValueRange}}} ->
76+
{ok, [{DefUri, #{data := #{value_range := ValueRange}}}]} ->
7777
ValueText = get_valuetext(DefUri, ValueRange),
7878

7979
[{code_line, ValueText}];

apps/els_lsp/src/els_references_provider.erl

+2-2
Original file line numberDiff line numberDiff line change
@@ -109,9 +109,9 @@ find_references(Uri, Poi = #{kind := Kind}) when
109109
Kind =:= type_application
110110
->
111111
case els_code_navigation:goto_definition(Uri, Poi) of
112-
{ok, DefUri, DefPoi} ->
112+
{ok, [{DefUri, DefPoi}]} ->
113113
find_references(DefUri, DefPoi);
114-
{error, _} ->
114+
_ ->
115115
%% look for references only in the current document
116116
uri_pois_to_locations(
117117
find_scoped_references_for_def(Uri, Poi)

apps/els_lsp/src/els_rename_provider.erl

+1-1
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ workspace_edits(Uri, [#{kind := Kind} = POI | _], NewName) when
114114
Kind =:= type_application
115115
->
116116
case els_code_navigation:goto_definition(Uri, POI) of
117-
{ok, DefUri, DefPOI} ->
117+
{ok, [{DefUri, DefPOI}]} ->
118118
#{changes => changes(DefUri, DefPOI, NewName)};
119119
_ ->
120120
null

apps/els_lsp/src/els_unused_includes_diagnostics.erl

+3-3
Original file line numberDiff line numberDiff line change
@@ -98,16 +98,16 @@ update_unused(Acc, _Graph, _Uri, _POIs = []) ->
9898
update_unused(Acc, Graph, Uri, [POI | POIs]) ->
9999
NewAcc =
100100
case els_code_navigation:goto_definition(Uri, POI) of
101-
{ok, DefinitionUri, _DefinitionPOI} when DefinitionUri =:= Uri ->
101+
{ok, [{DefinitionUri, _DefinitionPOI} | _]} when DefinitionUri =:= Uri ->
102102
Acc;
103-
{ok, DefinitionUri, _DefinitionPOI} ->
103+
{ok, [{DefinitionUri, _DefinitionPOI} | _]} ->
104104
case digraph:get_path(Graph, DefinitionUri, Uri) of
105105
false ->
106106
Acc;
107107
Path ->
108108
Acc -- Path
109109
end;
110-
{error, _Reason} ->
110+
_ ->
111111
Acc
112112
end,
113113
update_unused(NewAcc, Graph, Uri, POIs).

apps/els_lsp/test/els_code_lens_SUITE.erl

+1-1
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ default_lenses(Config) ->
9696
],
9797
lists:usort(Commands)
9898
),
99-
?assertEqual(40, length(Commands)),
99+
?assertEqual(50, length(Commands)),
100100
ok.
101101

102102
-spec server_info(config()) -> ok.

apps/els_lsp/test/els_completion_SUITE.erl

+6-1
Original file line numberDiff line numberDiff line change
@@ -665,7 +665,12 @@ functions_arity(Config) ->
665665
{<<"function_p">>, 1},
666666
{<<"function_q">>, 0},
667667
{<<"macro_b">>, 2},
668-
{<<"function_mb">>, 0}
668+
{<<"function_mb">>, 0},
669+
{<<"code_navigation">>, 0},
670+
{<<"code_navigation">>, 1},
671+
{<<"multiple_instances_same_file">>, 0},
672+
{<<"code_navigation_extra">>, 3},
673+
{<<"multiple_instances_diff_file">>, 0}
669674
],
670675
ExpectedCompletion =
671676
[

0 commit comments

Comments
 (0)