Skip to content

Use metadata in Introspection.actual_mod_fun #61

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 15 commits into from
Nov 26, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 16 additions & 13 deletions lib/alchemist/helpers/module_info.ex
Original file line number Diff line number Diff line change
Expand Up @@ -68,25 +68,28 @@ defmodule Alchemist.Helpers.ModuleInfo do
defp get_module_funs(module) do
case Code.ensure_loaded(module) do
{:module, _} ->
(:functions
|> module.module_info()
|> filter_module_funs) ++
module.__info__(:macros)
module.module_info(:functions)
|> Enum.reduce([], fn {f, a} = fun, acc ->
case Atom.to_string(f) do
"-" <> _ ->
# skip anonymous/private
acc

"MACRO-" <> macro_name ->
# extract macro name
[{String.to_atom(macro_name), a} | acc]

_ ->
# normal public fun
[fun | acc]
end
end)

_otherwise ->
[]
end
end

defp filter_module_funs(list) do
for(
fun = {f, _a} <- list,
!(f |> Atom.to_string() |> String.starts_with?(["MACRO-", "-"]))
) do
fun
end
end

defp all_functions(list) do
for {fun, arities} <- list do
for arity <- arities, {fun, arity} not in @builtin_functions do
Expand Down
50 changes: 33 additions & 17 deletions lib/elixir_sense.ex
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,9 @@ defmodule ElixirSense do
scope: scope
} = Metadata.get_env(metadata, line)

{actual_subject, docs} = Docs.all(subject, imports, aliases, module, scope)
{actual_subject, docs} =
Docs.all(subject, imports, aliases, module, scope, metadata.mods_funs)

%{subject: subject, actual_subject: actual_subject, docs: docs}
end

Expand Down Expand Up @@ -95,6 +97,7 @@ defmodule ElixirSense do
module,
vars,
buffer_file_metadata.mods_funs_to_positions,
buffer_file_metadata.mods_funs,
calls
)
end
Expand Down Expand Up @@ -372,21 +375,34 @@ defmodule ElixirSense do
"""
@spec references(String.t(), pos_integer, pos_integer) :: [References.reference_info()]
def references(code, line, column) do
{subject, {line, col}} = Source.subject_with_position(code, line, column)

buffer_file_metadata = Parser.parse_string(code, true, true, line)

%State.Env{
imports: imports,
aliases: aliases,
module: module,
scope: scope,
scope_id: scope_id
} = Metadata.get_env(buffer_file_metadata, line)

vars = buffer_file_metadata.vars_info_per_scope_id[scope_id] |> Map.values()
arity = Metadata.get_call_arity(buffer_file_metadata, line, col)

References.find(subject, arity, imports, aliases, module, scope, vars)
case Source.subject_with_position(code, line, column) do
{subject, {line, col}} ->
buffer_file_metadata = Parser.parse_string(code, true, true, line)

%State.Env{
imports: imports,
aliases: aliases,
module: module,
scope: scope,
scope_id: scope_id
} = Metadata.get_env(buffer_file_metadata, line)

vars = buffer_file_metadata.vars_info_per_scope_id[scope_id] |> Map.values()
arity = Metadata.get_call_arity(buffer_file_metadata, line, col)

References.find(
subject,
arity,
imports,
aliases,
module,
scope,
vars,
buffer_file_metadata.mods_funs
)

_ ->
[]
end
end
end
112 changes: 62 additions & 50 deletions lib/elixir_sense/core/introspection.ex
Original file line number Diff line number Diff line change
Expand Up @@ -606,19 +606,75 @@ defmodule ElixirSense.Core.Introspection do
end
end

def actual_mod_fun(mod_fun, imports, aliases, current_module) do
defp maybe_expand_alias(nil, _aliases), do: nil

defp maybe_expand_alias(mod, aliases) do
case Enum.find(aliases, fn {a, _m} -> a == mod end) do
nil -> mod
{_a, m} -> m
end
end

def actual_mod_fun({nil, nil}, _, _, _, _), do: {nil, nil}

def actual_mod_fun(mod_fun = {mod, fun}, imports, aliases, current_module, mods_funs) do
with {nil, nil} <- find_kernel_function(mod_fun),
{nil, nil} <- find_imported_function(mod_fun, imports),
{nil, nil} <- find_aliased_function(mod_fun, aliases),
{nil, nil} <- find_function_in_module(mod_fun),
{nil, nil} <- find_builtin_type(mod_fun),
{nil, nil} <- find_function_in_current_module(mod_fun, current_module) do
{nil, nil} <-
find_metadata_function(
{maybe_expand_alias(mod, aliases), fun},
current_module,
imports,
mods_funs
),
{nil, nil} <- find_builtin_type(mod_fun) do
mod_fun
else
new_mod_fun -> new_mod_fun
end
end

defp has_type?(mod, type) do
ElixirSense.Core.Normalized.Typespec.get_types(mod)
|> Enum.any?(fn {_kind, {name, _def, _args}} -> name == type end)
end

defp find_metadata_function({nil, fun}, current_module, imports, mods_funs) do
mods = [current_module | imports]

case Enum.find(mods, fn mod ->
find_metadata_function({mod, fun}, current_module, imports, mods_funs) != {nil, nil}
end) do
nil -> {nil, nil}
mod -> {mod, fun}
end
end

defp find_metadata_function({mod, nil}, _current_module, _imports, mods_funs) do
if Map.has_key?(mods_funs, mod) or match?({:module, _}, Code.ensure_loaded(mod)) do
{mod, nil}
else
{nil, nil}
end
end

defp find_metadata_function({mod, fun}, _current_module, _imports, mods_funs) do
# TODO types from metadata
found_in_metadata =
case mods_funs[mod] do
nil ->
false

funs ->
Enum.any?(funs, fn {{f, _a}, _} -> f == fun end)
end

if found_in_metadata or ModuleInfo.has_function?(mod, fun) or has_type?(mod, fun) do
{mod, fun}
else
{nil, nil}
end
end

defp find_builtin_type({nil, fun}) do
if ElixirSense.Core.BuiltinTypes.builtin_type?(fun) do
{nil, fun}
Expand Down Expand Up @@ -648,50 +704,6 @@ defmodule ElixirSense.Core.Introspection do
{nil, nil}
end

defp find_imported_function({nil, fun}, imports) do
case imports |> Enum.find(&ModuleInfo.has_function?(&1, fun)) do
nil -> {nil, nil}
mod -> {mod, fun}
end
end

defp find_imported_function({_mod, _fun}, _imports) do
{nil, nil}
end

defp find_aliased_function({nil, _fun}, _aliases) do
{nil, nil}
end

defp find_aliased_function({mod, fun}, aliases) do
if elixir_module?(mod) do
module =
mod
|> Module.split()
|> ModuleInfo.expand_alias(aliases)

{module, fun}
else
{nil, nil}
end
end

defp find_function_in_module({mod, fun}) do
if elixir_module?(mod) && ModuleInfo.has_function?(mod, fun) do
{mod, fun}
else
{nil, nil}
end
end

defp find_function_in_current_module({nil, fun}, current_module) do
{current_module, fun}
end

defp find_function_in_current_module(_, _) do
{nil, nil}
end

def elixir_module?(module) when is_atom(module) do
module == Module.concat(Elixir, module)
end
Expand Down
6 changes: 3 additions & 3 deletions lib/elixir_sense/core/metadata_builder.ex
Original file line number Diff line number Diff line change
Expand Up @@ -647,7 +647,7 @@ defmodule ElixirSense.Core.MetadataBuilder do
case concat_module_expression(state, module_expression) do
{:ok, module} ->
state
|> add_call_to_line({module, call, length(params)}, line, col)
|> add_call_to_line({module, call, length(params)}, line, col + 1)
|> add_current_env_to_line(line)

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

state
|> add_call_to_line({module, call, length(params)}, line, col)
|> add_call_to_line({module, call, length(params)}, line, col + 1)
|> add_current_env_to_line(line)
|> result(ast)
end
Expand All @@ -675,7 +675,7 @@ defmodule ElixirSense.Core.MetadataBuilder do
)
when is_atom(module) and is_call(call, params) do
state
|> add_call_to_line({module, call, length(params)}, line, col)
|> add_call_to_line({module, call, length(params)}, line, col + 1)
|> add_current_env_to_line(line)
|> result(ast)
end
Expand Down
2 changes: 1 addition & 1 deletion lib/elixir_sense/core/source.ex
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ defmodule ElixirSense.Core.Source do
|> Enum.at(0)
|> String.reverse()

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

Expand Down
69 changes: 32 additions & 37 deletions lib/elixir_sense/providers/definition.ex
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,9 @@ defmodule ElixirSense.Providers.Definition do
@doc """
Finds out where a module, function, macro or variable was defined.
"""
@spec find(String.t(), [module], [{module, module}], module, [%VarInfo{}], map, map) ::
@spec find(String.t(), [module], [{module, module}], module, [%VarInfo{}], map, map, map) ::
%Location{}
def find(subject, imports, aliases, module, vars, mods_funs, calls) do
def find(subject, imports, aliases, module, vars, mods_funs_to_positions, mods_funs, calls) do
var_info =
unless subject_is_call?(subject, calls) do
vars |> Enum.find(fn %VarInfo{name: name} -> to_string(name) == subject end)
Expand All @@ -40,7 +40,7 @@ defmodule ElixirSense.Providers.Definition do
_ ->
subject
|> Source.split_module_and_func(module, aliases)
|> find_function_or_module(mods_funs, module, imports, aliases)
|> find_function_or_module(mods_funs_to_positions, mods_funs, module, imports, aliases)
end
end

Expand All @@ -54,41 +54,36 @@ defmodule ElixirSense.Providers.Definition do
end) != nil
end

defp find_function_or_module({nil, nil}, _mods_funs, current_module, imports, aliases) do
{nil, nil}
|> Introspection.actual_mod_fun(imports, aliases, current_module)
|> find_source(current_module)
end

defp find_function_or_module({module, function}, mods_funs, current_module, imports, aliases)
when is_atom(function) do
# TODO arity info would be useful here
# TODO support local typespecs

fun_module =
case module do
nil -> current_module
mod when is_atom(mod) -> mod
end
defp find_function_or_module(
{module, function},
mods_funs_to_positions,
mods_funs,
current_module,
imports,
aliases
) do
case {module, function}
|> Introspection.actual_mod_fun(imports, aliases, current_module, mods_funs) do
{nil, nil} ->
%Location{found: false}

case mods_funs[{fun_module, function, nil}] do
nil ->
# module or function not found in buffer metadata, try introspection
{module, function}
|> Introspection.actual_mod_fun(imports, aliases, current_module)
|> find_source(current_module)

%{positions: positions} ->
# for simplicity take first position here
[{line, column} | _] = positions

%Location{
found: true,
file: nil,
type: fun_to_type(function),
line: line,
column: column
}
{mod, fun} ->
case mods_funs_to_positions[{mod, fun, nil}] do
nil ->
{mod, fun} |> find_source(current_module)

%{positions: positions} ->
# for simplicity take first position here
[{line, column} | _] = positions

%Location{
found: true,
file: nil,
type: fun_to_type(function),
line: line,
column: column
}
end
end
end

Expand Down
6 changes: 3 additions & 3 deletions lib/elixir_sense/providers/docs.ex
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,13 @@ defmodule ElixirSense.Providers.Docs do
alias ElixirSense.Core.State
alias ElixirSense.Core.Source

@spec all(String.t(), [module], [{module, module}], module, State.scope()) ::
@spec all(String.t(), [module], [{module, module}], module, State.scope(), map) ::
{actual_mod_fun :: String.t(), docs :: Introspection.docs()}
def all(subject, imports, aliases, module, scope) do
def all(subject, imports, aliases, module, scope, mods_funs) do
mod_fun =
subject
|> Source.split_module_and_func(module, aliases)
|> Introspection.actual_mod_fun(imports, aliases, module)
|> Introspection.actual_mod_fun(imports, aliases, module, mods_funs)

{mod_fun_to_string(mod_fun), Introspection.get_all_docs(mod_fun, scope)}
end
Expand Down
Loading