Skip to content

Commit

Permalink
fix(completions): work in guards (#475)
Browse files Browse the repository at this point in the history
  • Loading branch information
mhanberg authored May 14, 2024
1 parent 59e57ce commit e0573ab
Show file tree
Hide file tree
Showing 2 changed files with 61 additions and 40 deletions.
72 changes: 32 additions & 40 deletions priv/monkey/_next_ls_private_compiler.ex
Original file line number Diff line number Diff line change
Expand Up @@ -1425,39 +1425,36 @@ if Version.match?(System.version(), ">= 1.17.0-dev") do
end
end

defp expand_macro(_meta, Kernel, type, [{name, _, params}, [{_, block}]], _callback, state, env)
when type in [:def, :defp] and is_tuple(block) and is_atom(name) and is_list(params) do
{_, state, penv} =
for p <- params, reduce: {nil, state, env} do
{_, state, penv} ->
expand_pattern(p, state, penv)
defp expand_macro(_meta, Kernel, type, args, _callback, state, env)
when type in [:def, :defmacro, :defp, :defmacrop] do
# extract the name, params, guards, and blocks
{name, params, guards, blocks} =
case args do
[{:when, _, [{name, _, params} | guards]} | maybe_blocks] ->
{name, params, guards, maybe_blocks}

[{name, _, params} | maybe_blocks] ->
{name, params, [], maybe_blocks}
end

{res, state, _env} = expand(block, state, penv)

arity = length(List.wrap(params))
functions = Map.update(state.functions, env.module, [{name, arity}], &Keyword.put_new(&1, name, arity))
{res, put_in(state.functions, functions), env}
end

defp expand_macro(_meta, Kernel, type, [{name, _, params}, block], _callback, state, env)
when type in [:defmacro, :defmacrop] do
{_res, state, penv} = expand(params, state, env)
{res, state, _env} = expand(block, state, penv)
blocks = List.first(blocks, [])

arity = length(List.wrap(params))
macros = Map.update(state.macros, env.module, [{name, arity}], &Keyword.put_new(&1, name, arity))
{res, put_in(state.macros, macros), env}
end

defp expand_macro(_meta, Kernel, type, [{name, _, params}, blocks], _callback, state, env)
when type in [:def, :defp] and is_atom(name) and is_list(params) and is_list(blocks) do
# collect the environment from the parameters
# parameters are always patterns
{_, state, penv} =
for p <- params, reduce: {nil, state, env} do
{_, state, penv} ->
expand_pattern(p, state, penv)
end

# expand guards, which includes the env from params
{_, state, _} =
for guard <- guards, reduce: {nil, state, penv} do
{_, state, env} ->
expand(guard, state, env)
end

# expand the blocks, there could be `:do`, `:after`, `:catch`, etc
{blocks, state} =
for {type, block} <- blocks, reduce: {[], state} do
{acc, state} ->
Expand All @@ -1467,26 +1464,21 @@ if Version.match?(System.version(), ">= 1.17.0-dev") do

arity = length(List.wrap(params))

functions = Map.update(state.functions, env.module, [{name, arity}], &Keyword.put_new(&1, name, arity))
{Enum.reverse(blocks), put_in(state.functions, functions), env}
end

defp expand_macro(_meta, Kernel, type, [{_name, _, params}, blocks], _callback, state, env)
when type in [:def, :defp] and is_list(params) and is_list(blocks) do
{_, state, penv} =
for p <- params, reduce: {nil, state, env} do
{_, state, penv} ->
expand_pattern(p, state, penv)
# determine which key to save this function in state
state_key =
case type do
type when type in [:def, :defp] -> :functions
type when type in [:defmacro, :defmacrop] -> :macros
end

{blocks, state} =
for {type, block} <- blocks, reduce: {[], state} do
{acc, state} ->
{res, state, _env} = expand(block, state, penv)
{[{type, res} | acc], state}
funcs =
if is_atom(name) do
Map.update(state[state_key], env.module, [{name, arity}], &Keyword.put_new(&1, name, arity))
else
state[state_key]
end

{Enum.reverse(blocks), state, env}
{Enum.reverse(blocks), put_in(state[state_key], funcs), env}
end

defp expand_macro(meta, Kernel, :@, [{name, _, p}] = args, callback, state, env) when is_list(p) do
Expand Down
29 changes: 29 additions & 0 deletions test/next_ls/completions_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -809,4 +809,33 @@ defmodule NextLS.CompletionsTest do
assert_match %{"kind" => 6, "label" => "items"} in results
assert_match %{"kind" => 6, "label" => "item"} not in results
end

test "parameters are available inside guards", %{client: client, foo: foo} do
uri = uri(foo)

did_open(client, foo, """
defmodule Foo do
def run(items) when is_list(i
end
""")

request client, %{
method: "textDocument/completion",
id: 2,
jsonrpc: "2.0",
params: %{
textDocument: %{
uri: uri
},
position: %{
line: 1,
character: 31
}
}
}

assert_result 2, results

assert_match %{"kind" => 6, "label" => "items"} in results
end
end

0 comments on commit e0573ab

Please sign in to comment.