Skip to content

Commit d215542

Browse files
committed
improvements to declaration provider
1 parent ef4631c commit d215542

File tree

3 files changed

+341
-83
lines changed

3 files changed

+341
-83
lines changed

apps/language_server/lib/language_server/providers/declaration/locator.ex

+188-24
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,14 @@ defmodule ElixirLS.LanguageServer.Providers.Declaration.Locator do
77
This is effectively the reverse of the "go to implementations" provider.
88
"""
99

10+
alias ElixirSense.Core.Binding
11+
alias ElixirSense.Core.SurroundContext
1012
alias ElixirSense.Core.Metadata
1113
alias ElixirSense.Core.Normalized.Code, as: NormalizedCode
1214
alias ElixirSense.Core.State
1315
alias ElixirLS.LanguageServer.Location
1416
alias ElixirSense.Core.Parser
17+
alias ElixirSense.Core.State.{ModFunInfo, SpecInfo}
1518

1619
require ElixirSense.Core.Introspection, as: Introspection
1720

@@ -41,33 +44,166 @@ defmodule ElixirLS.LanguageServer.Providers.Declaration.Locator do
4144
end
4245

4346
@doc false
44-
def find(_context, %State.Env{module: module} = env, metadata) do
45-
# Get the binding environment as in the other providers.
46-
# binding_env = Binding.from_env(env, metadata, context.begin)
47+
def find(context, %State.Env{module: module} = env, metadata) do
48+
binding_env = Binding.from_env(env, metadata, context.begin)
4749

48-
case env.function do
50+
type = SurroundContext.to_binding(context.context, module)
51+
52+
case type do
4953
nil ->
5054
nil
5155

52-
{fun, arity} ->
53-
# Get the behaviours (and possibly protocols) declared for the current module.
54-
behaviours = Metadata.get_module_behaviours(metadata, env, module)
55-
56-
# For each behaviour, if the current function is a callback for it,
57-
# try to find the callback’s declaration.
58-
locations =
59-
for behaviour <- behaviours,
60-
Introspection.is_callback(behaviour, fun, arity, metadata),
61-
location = get_callback_location(behaviour, fun, arity, metadata),
62-
location != nil do
63-
location
64-
end
65-
66-
case locations do
67-
[] -> nil
68-
[single] -> single
69-
multiple -> multiple
56+
{:keyword, _} ->
57+
nil
58+
59+
{:variable, variable, version} ->
60+
var_info = Metadata.find_var(metadata, variable, version, context.begin)
61+
62+
if var_info == nil do
63+
# find local call
64+
find_function(
65+
{nil, variable},
66+
context,
67+
env,
68+
metadata,
69+
binding_env
70+
)
7071
end
72+
73+
{:attribute, _attribute} ->
74+
nil
75+
76+
{module, function} ->
77+
find_function(
78+
{module, function},
79+
context,
80+
env,
81+
metadata,
82+
binding_env
83+
)
84+
end
85+
end
86+
87+
defp find_function(
88+
{{:variable, _, _} = type, function},
89+
context,
90+
env,
91+
metadata,
92+
binding_env
93+
) do
94+
case Binding.expand(binding_env, type) do
95+
{:atom, module} ->
96+
find_function(
97+
{{:atom, module}, function},
98+
context,
99+
env,
100+
metadata,
101+
binding_env
102+
)
103+
104+
_ ->
105+
nil
106+
end
107+
end
108+
109+
defp find_function(
110+
{{:attribute, _} = type, function},
111+
context,
112+
env,
113+
metadata,
114+
binding_env
115+
) do
116+
case Binding.expand(binding_env, type) do
117+
{:atom, module} ->
118+
find_function(
119+
{{:atom, module}, function},
120+
context,
121+
env,
122+
metadata,
123+
binding_env
124+
)
125+
126+
_ ->
127+
nil
128+
end
129+
end
130+
131+
defp find_function(
132+
{module, function},
133+
context,
134+
env,
135+
metadata,
136+
_binding_env
137+
) do
138+
m = get_module(module)
139+
140+
case {m, function}
141+
|> Introspection.actual_mod_fun(
142+
env,
143+
metadata.mods_funs_to_positions,
144+
metadata.types,
145+
context.begin,
146+
true
147+
) do
148+
{mod, fun, false, _} ->
149+
{line, column} = context.end
150+
call_arity = Metadata.get_call_arity(metadata, mod, fun, line, column) || :any
151+
152+
get_callback_location(env.module, fun, call_arity, metadata)
153+
154+
{mod, fun, true, :mod_fun} ->
155+
{line, column} = context.end
156+
call_arity = Metadata.get_call_arity(metadata, mod, fun, line, column) || :any
157+
158+
find_callback(mod, fun, call_arity, metadata, env)
159+
160+
_ ->
161+
nil
162+
end
163+
end
164+
165+
defp get_module({:atom, module}), do: module
166+
defp get_module(_), do: nil
167+
168+
def find_callback(mod, fun, arity, metadata, env) do
169+
# Get the behaviours (and possibly protocols) declared for the current module.
170+
behaviours = Metadata.get_module_behaviours(metadata, env, mod)
171+
172+
# For each behaviour, if the current function is a callback for it,
173+
# try to find the callback’s declaration.
174+
locations =
175+
for behaviour <- behaviours ++ [mod],
176+
Introspection.is_callback(behaviour, fun, arity, metadata),
177+
location = get_callback_location(behaviour, fun, arity, metadata),
178+
location != nil do
179+
location
180+
end
181+
182+
locations =
183+
if locations == [] do
184+
# check if function is overridable
185+
# NOTE we only go over local buffer defs. There is no way to tell if a remote def has been overridden.
186+
metadata.mods_funs_to_positions
187+
|> Enum.filter(fn
188+
{{^mod, ^fun, a}, %ModFunInfo{overridable: {true, _module_with_overridables}}}
189+
when Introspection.matches_arity?(a, arity) ->
190+
true
191+
192+
{_, _} ->
193+
false
194+
end)
195+
|> Enum.map(fn {_, %ModFunInfo{overridable: {true, module_with_overridables}}} ->
196+
# assume overridables are defined by __using__ macro
197+
get_function_location(module_with_overridables, :__using__, :any, metadata)
198+
end)
199+
else
200+
locations
201+
end
202+
203+
case locations do
204+
[] -> nil
205+
[single] -> single
206+
multiple -> multiple
71207
end
72208
end
73209

@@ -76,7 +212,8 @@ defmodule ElixirLS.LanguageServer.Providers.Declaration.Locator do
76212
# to trying to locate the source code.
77213
defp get_callback_location(behaviour, fun, arity, metadata) do
78214
case Enum.find(metadata.specs, fn
79-
{{^behaviour, ^fun, a}, _spec_info} ->
215+
{{^behaviour, ^fun, a}, %SpecInfo{kind: kind}}
216+
when kind in [:callback, :macrocallback] ->
80217
Introspection.matches_arity?(a, arity)
81218

82219
_ ->
@@ -91,7 +228,34 @@ defmodule ElixirLS.LanguageServer.Providers.Declaration.Locator do
91228

92229
%Location{
93230
file: nil,
94-
type: :callback,
231+
type: spec_info.kind,
232+
line: line,
233+
column: column,
234+
end_line: end_line,
235+
end_column: end_column
236+
}
237+
end
238+
end
239+
240+
defp get_function_location(mod, fun, arity, metadata) do
241+
fn_definition =
242+
Location.get_function_position_using_metadata(
243+
mod,
244+
fun,
245+
arity,
246+
metadata.mods_funs_to_positions
247+
)
248+
249+
case fn_definition do
250+
nil ->
251+
Location.find_mod_fun_source(mod, fun, arity)
252+
253+
%ModFunInfo{} = info ->
254+
{{line, column}, {end_line, end_column}} = Location.info_to_range(info)
255+
256+
%Location{
257+
file: nil,
258+
type: ModFunInfo.get_category(info),
95259
line: line,
96260
column: column,
97261
end_line: end_line,

apps/language_server/lib/language_server/providers/definition/locator.ex

+1-1
Original file line numberDiff line numberDiff line change
@@ -296,7 +296,7 @@ defmodule ElixirLS.LanguageServer.Providers.Definition.Locator do
296296
end
297297
end
298298

299-
def find_function(mod, fun, arity, metadata) do
299+
defp find_function(mod, fun, arity, metadata) do
300300
fn_definition =
301301
Location.get_function_position_using_metadata(
302302
mod,

0 commit comments

Comments
 (0)