Skip to content

Commit 4b27cc0

Browse files
authored
Merge pull request #61 from lukaszsamson/ls-defs-metadata
Use metadata in Introspection.actual_mod_fun
2 parents 7982c48 + 6064b0f commit 4b27cc0

16 files changed

+462
-169
lines changed

lib/alchemist/helpers/module_info.ex

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -68,25 +68,28 @@ defmodule Alchemist.Helpers.ModuleInfo do
6868
defp get_module_funs(module) do
6969
case Code.ensure_loaded(module) do
7070
{:module, _} ->
71-
(:functions
72-
|> module.module_info()
73-
|> filter_module_funs) ++
74-
module.__info__(:macros)
71+
module.module_info(:functions)
72+
|> Enum.reduce([], fn {f, a} = fun, acc ->
73+
case Atom.to_string(f) do
74+
"-" <> _ ->
75+
# skip anonymous/private
76+
acc
77+
78+
"MACRO-" <> macro_name ->
79+
# extract macro name
80+
[{String.to_atom(macro_name), a} | acc]
81+
82+
_ ->
83+
# normal public fun
84+
[fun | acc]
85+
end
86+
end)
7587

7688
_otherwise ->
7789
[]
7890
end
7991
end
8092

81-
defp filter_module_funs(list) do
82-
for(
83-
fun = {f, _a} <- list,
84-
!(f |> Atom.to_string() |> String.starts_with?(["MACRO-", "-"]))
85-
) do
86-
fun
87-
end
88-
end
89-
9093
defp all_functions(list) do
9194
for {fun, arities} <- list do
9295
for arity <- arities, {fun, arity} not in @builtin_functions do

lib/elixir_sense.ex

Lines changed: 33 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,9 @@ defmodule ElixirSense do
5252
scope: scope
5353
} = Metadata.get_env(metadata, line)
5454

55-
{actual_subject, docs} = Docs.all(subject, imports, aliases, module, scope)
55+
{actual_subject, docs} =
56+
Docs.all(subject, imports, aliases, module, scope, metadata.mods_funs)
57+
5658
%{subject: subject, actual_subject: actual_subject, docs: docs}
5759
end
5860

@@ -95,6 +97,7 @@ defmodule ElixirSense do
9597
module,
9698
vars,
9799
buffer_file_metadata.mods_funs_to_positions,
100+
buffer_file_metadata.mods_funs,
98101
calls
99102
)
100103
end
@@ -372,21 +375,34 @@ defmodule ElixirSense do
372375
"""
373376
@spec references(String.t(), pos_integer, pos_integer) :: [References.reference_info()]
374377
def references(code, line, column) do
375-
{subject, {line, col}} = Source.subject_with_position(code, line, column)
376-
377-
buffer_file_metadata = Parser.parse_string(code, true, true, line)
378-
379-
%State.Env{
380-
imports: imports,
381-
aliases: aliases,
382-
module: module,
383-
scope: scope,
384-
scope_id: scope_id
385-
} = Metadata.get_env(buffer_file_metadata, line)
386-
387-
vars = buffer_file_metadata.vars_info_per_scope_id[scope_id] |> Map.values()
388-
arity = Metadata.get_call_arity(buffer_file_metadata, line, col)
389-
390-
References.find(subject, arity, imports, aliases, module, scope, vars)
378+
case Source.subject_with_position(code, line, column) do
379+
{subject, {line, col}} ->
380+
buffer_file_metadata = Parser.parse_string(code, true, true, line)
381+
382+
%State.Env{
383+
imports: imports,
384+
aliases: aliases,
385+
module: module,
386+
scope: scope,
387+
scope_id: scope_id
388+
} = Metadata.get_env(buffer_file_metadata, line)
389+
390+
vars = buffer_file_metadata.vars_info_per_scope_id[scope_id] |> Map.values()
391+
arity = Metadata.get_call_arity(buffer_file_metadata, line, col)
392+
393+
References.find(
394+
subject,
395+
arity,
396+
imports,
397+
aliases,
398+
module,
399+
scope,
400+
vars,
401+
buffer_file_metadata.mods_funs
402+
)
403+
404+
_ ->
405+
[]
406+
end
391407
end
392408
end

lib/elixir_sense/core/introspection.ex

Lines changed: 62 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -606,19 +606,75 @@ defmodule ElixirSense.Core.Introspection do
606606
end
607607
end
608608

609-
def actual_mod_fun(mod_fun, imports, aliases, current_module) do
609+
defp maybe_expand_alias(nil, _aliases), do: nil
610+
611+
defp maybe_expand_alias(mod, aliases) do
612+
case Enum.find(aliases, fn {a, _m} -> a == mod end) do
613+
nil -> mod
614+
{_a, m} -> m
615+
end
616+
end
617+
618+
def actual_mod_fun({nil, nil}, _, _, _, _), do: {nil, nil}
619+
620+
def actual_mod_fun(mod_fun = {mod, fun}, imports, aliases, current_module, mods_funs) do
610621
with {nil, nil} <- find_kernel_function(mod_fun),
611-
{nil, nil} <- find_imported_function(mod_fun, imports),
612-
{nil, nil} <- find_aliased_function(mod_fun, aliases),
613-
{nil, nil} <- find_function_in_module(mod_fun),
614-
{nil, nil} <- find_builtin_type(mod_fun),
615-
{nil, nil} <- find_function_in_current_module(mod_fun, current_module) do
622+
{nil, nil} <-
623+
find_metadata_function(
624+
{maybe_expand_alias(mod, aliases), fun},
625+
current_module,
626+
imports,
627+
mods_funs
628+
),
629+
{nil, nil} <- find_builtin_type(mod_fun) do
616630
mod_fun
617631
else
618632
new_mod_fun -> new_mod_fun
619633
end
620634
end
621635

636+
defp has_type?(mod, type) do
637+
ElixirSense.Core.Normalized.Typespec.get_types(mod)
638+
|> Enum.any?(fn {_kind, {name, _def, _args}} -> name == type end)
639+
end
640+
641+
defp find_metadata_function({nil, fun}, current_module, imports, mods_funs) do
642+
mods = [current_module | imports]
643+
644+
case Enum.find(mods, fn mod ->
645+
find_metadata_function({mod, fun}, current_module, imports, mods_funs) != {nil, nil}
646+
end) do
647+
nil -> {nil, nil}
648+
mod -> {mod, fun}
649+
end
650+
end
651+
652+
defp find_metadata_function({mod, nil}, _current_module, _imports, mods_funs) do
653+
if Map.has_key?(mods_funs, mod) or match?({:module, _}, Code.ensure_loaded(mod)) do
654+
{mod, nil}
655+
else
656+
{nil, nil}
657+
end
658+
end
659+
660+
defp find_metadata_function({mod, fun}, _current_module, _imports, mods_funs) do
661+
# TODO types from metadata
662+
found_in_metadata =
663+
case mods_funs[mod] do
664+
nil ->
665+
false
666+
667+
funs ->
668+
Enum.any?(funs, fn {{f, _a}, _} -> f == fun end)
669+
end
670+
671+
if found_in_metadata or ModuleInfo.has_function?(mod, fun) or has_type?(mod, fun) do
672+
{mod, fun}
673+
else
674+
{nil, nil}
675+
end
676+
end
677+
622678
defp find_builtin_type({nil, fun}) do
623679
if ElixirSense.Core.BuiltinTypes.builtin_type?(fun) do
624680
{nil, fun}
@@ -648,50 +704,6 @@ defmodule ElixirSense.Core.Introspection do
648704
{nil, nil}
649705
end
650706

651-
defp find_imported_function({nil, fun}, imports) do
652-
case imports |> Enum.find(&ModuleInfo.has_function?(&1, fun)) do
653-
nil -> {nil, nil}
654-
mod -> {mod, fun}
655-
end
656-
end
657-
658-
defp find_imported_function({_mod, _fun}, _imports) do
659-
{nil, nil}
660-
end
661-
662-
defp find_aliased_function({nil, _fun}, _aliases) do
663-
{nil, nil}
664-
end
665-
666-
defp find_aliased_function({mod, fun}, aliases) do
667-
if elixir_module?(mod) do
668-
module =
669-
mod
670-
|> Module.split()
671-
|> ModuleInfo.expand_alias(aliases)
672-
673-
{module, fun}
674-
else
675-
{nil, nil}
676-
end
677-
end
678-
679-
defp find_function_in_module({mod, fun}) do
680-
if elixir_module?(mod) && ModuleInfo.has_function?(mod, fun) do
681-
{mod, fun}
682-
else
683-
{nil, nil}
684-
end
685-
end
686-
687-
defp find_function_in_current_module({nil, fun}, current_module) do
688-
{current_module, fun}
689-
end
690-
691-
defp find_function_in_current_module(_, _) do
692-
{nil, nil}
693-
end
694-
695707
def elixir_module?(module) when is_atom(module) do
696708
module == Module.concat(Elixir, module)
697709
end

lib/elixir_sense/core/metadata_builder.ex

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -647,7 +647,7 @@ defmodule ElixirSense.Core.MetadataBuilder do
647647
case concat_module_expression(state, module_expression) do
648648
{:ok, module} ->
649649
state
650-
|> add_call_to_line({module, call, length(params)}, line, col)
650+
|> add_call_to_line({module, call, length(params)}, line, col + 1)
651651
|> add_current_env_to_line(line)
652652

653653
:error ->
@@ -664,7 +664,7 @@ defmodule ElixirSense.Core.MetadataBuilder do
664664
module = get_current_module(state)
665665

666666
state
667-
|> add_call_to_line({module, call, length(params)}, line, col)
667+
|> add_call_to_line({module, call, length(params)}, line, col + 1)
668668
|> add_current_env_to_line(line)
669669
|> result(ast)
670670
end
@@ -675,7 +675,7 @@ defmodule ElixirSense.Core.MetadataBuilder do
675675
)
676676
when is_atom(module) and is_call(call, params) do
677677
state
678-
|> add_call_to_line({module, call, length(params)}, line, col)
678+
|> add_call_to_line({module, call, length(params)}, line, col + 1)
679679
|> add_current_env_to_line(line)
680680
|> result(ast)
681681
end

lib/elixir_sense/core/source.ex

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@ defmodule ElixirSense.Core.Source do
137137
|> Enum.at(0)
138138
|> String.reverse()
139139

140-
{subject, {line, col - String.length(last_part)}}
140+
{subject, {line, col - String.length(last_part) + 1}}
141141
end
142142
end
143143

lib/elixir_sense/providers/definition.ex

Lines changed: 32 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,9 @@ defmodule ElixirSense.Providers.Definition do
2525
@doc """
2626
Finds out where a module, function, macro or variable was defined.
2727
"""
28-
@spec find(String.t(), [module], [{module, module}], module, [%VarInfo{}], map, map) ::
28+
@spec find(String.t(), [module], [{module, module}], module, [%VarInfo{}], map, map, map) ::
2929
%Location{}
30-
def find(subject, imports, aliases, module, vars, mods_funs, calls) do
30+
def find(subject, imports, aliases, module, vars, mods_funs_to_positions, mods_funs, calls) do
3131
var_info =
3232
unless subject_is_call?(subject, calls) do
3333
vars |> Enum.find(fn %VarInfo{name: name} -> to_string(name) == subject end)
@@ -40,7 +40,7 @@ defmodule ElixirSense.Providers.Definition do
4040
_ ->
4141
subject
4242
|> Source.split_module_and_func(module, aliases)
43-
|> find_function_or_module(mods_funs, module, imports, aliases)
43+
|> find_function_or_module(mods_funs_to_positions, mods_funs, module, imports, aliases)
4444
end
4545
end
4646

@@ -54,41 +54,36 @@ defmodule ElixirSense.Providers.Definition do
5454
end) != nil
5555
end
5656

57-
defp find_function_or_module({nil, nil}, _mods_funs, current_module, imports, aliases) do
58-
{nil, nil}
59-
|> Introspection.actual_mod_fun(imports, aliases, current_module)
60-
|> find_source(current_module)
61-
end
62-
63-
defp find_function_or_module({module, function}, mods_funs, current_module, imports, aliases)
64-
when is_atom(function) do
65-
# TODO arity info would be useful here
66-
# TODO support local typespecs
67-
68-
fun_module =
69-
case module do
70-
nil -> current_module
71-
mod when is_atom(mod) -> mod
72-
end
57+
defp find_function_or_module(
58+
{module, function},
59+
mods_funs_to_positions,
60+
mods_funs,
61+
current_module,
62+
imports,
63+
aliases
64+
) do
65+
case {module, function}
66+
|> Introspection.actual_mod_fun(imports, aliases, current_module, mods_funs) do
67+
{nil, nil} ->
68+
%Location{found: false}
7369

74-
case mods_funs[{fun_module, function, nil}] do
75-
nil ->
76-
# module or function not found in buffer metadata, try introspection
77-
{module, function}
78-
|> Introspection.actual_mod_fun(imports, aliases, current_module)
79-
|> find_source(current_module)
80-
81-
%{positions: positions} ->
82-
# for simplicity take first position here
83-
[{line, column} | _] = positions
84-
85-
%Location{
86-
found: true,
87-
file: nil,
88-
type: fun_to_type(function),
89-
line: line,
90-
column: column
91-
}
70+
{mod, fun} ->
71+
case mods_funs_to_positions[{mod, fun, nil}] do
72+
nil ->
73+
{mod, fun} |> find_source(current_module)
74+
75+
%{positions: positions} ->
76+
# for simplicity take first position here
77+
[{line, column} | _] = positions
78+
79+
%Location{
80+
found: true,
81+
file: nil,
82+
type: fun_to_type(function),
83+
line: line,
84+
column: column
85+
}
86+
end
9287
end
9388
end
9489

lib/elixir_sense/providers/docs.ex

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,13 @@ defmodule ElixirSense.Providers.Docs do
66
alias ElixirSense.Core.State
77
alias ElixirSense.Core.Source
88

9-
@spec all(String.t(), [module], [{module, module}], module, State.scope()) ::
9+
@spec all(String.t(), [module], [{module, module}], module, State.scope(), map) ::
1010
{actual_mod_fun :: String.t(), docs :: Introspection.docs()}
11-
def all(subject, imports, aliases, module, scope) do
11+
def all(subject, imports, aliases, module, scope, mods_funs) do
1212
mod_fun =
1313
subject
1414
|> Source.split_module_and_func(module, aliases)
15-
|> Introspection.actual_mod_fun(imports, aliases, module)
15+
|> Introspection.actual_mod_fun(imports, aliases, module, mods_funs)
1616

1717
{mod_fun_to_string(mod_fun), Introspection.get_all_docs(mod_fun, scope)}
1818
end

0 commit comments

Comments
 (0)