Skip to content

Commit 04d3010

Browse files
authored
fix(completions): imports inside blocks that generate functions (#423)
The `test/2` macro is an example of this Closes #420
1 parent d62809e commit 04d3010

File tree

3 files changed

+134
-36
lines changed

3 files changed

+134
-36
lines changed

Diff for: lib/next_ls.ex

+1
Original file line numberDiff line numberDiff line change
@@ -646,6 +646,7 @@ defmodule NextLS do
646646
|> then(fn
647647
{:ok, ast} -> ast
648648
{:error, ast, _} -> ast
649+
{:error, :no_fuel_remaining} -> nil
649650
end)
650651

651652
{:ok, {_, _, _, macro_env}} = Runtime.expand(runtime, ast, Path.basename(uri))

Diff for: priv/monkey/_next_ls_private_compiler.ex

+41-33
Original file line numberDiff line numberDiff line change
@@ -1217,52 +1217,46 @@ if Version.match?(System.version(), ">= 1.17.0-dev") do
12171217
{arg, state, env} = expand(arg, state, env)
12181218
{opts, state, env} = expand_directive_opts(opts, state, env)
12191219

1220-
case arg do
1221-
{:__aliases__, _, _} ->
1222-
# An actual compiler would raise if the alias fails.
1223-
case Macro.Env.define_alias(env, meta, arg, [trace: false] ++ opts) do
1224-
{:ok, env} -> {arg, state, env}
1225-
{:error, _} -> {arg, state, env}
1226-
end
1227-
1228-
_ ->
1229-
{node, state, env}
1220+
if is_atom(arg) do
1221+
# An actual compiler would raise if the alias fails.
1222+
case Macro.Env.define_alias(env, meta, arg, [trace: false] ++ opts) do
1223+
{:ok, env} -> {arg, state, env}
1224+
{:error, _} -> {arg, state, env}
1225+
end
1226+
else
1227+
{node, state, env}
12301228
end
12311229
end
12321230

12331231
defp expand({:require, meta, [arg, opts]} = node, state, env) do
12341232
{arg, state, env} = expand(arg, state, env)
12351233
{opts, state, env} = expand_directive_opts(opts, state, env)
12361234

1237-
case arg do
1238-
{:__aliases__, _, _} ->
1239-
# An actual compiler would raise if the module is not defined or if the require fails.
1240-
case Macro.Env.define_require(env, meta, arg, [trace: false] ++ opts) do
1241-
{:ok, env} -> {arg, state, env}
1242-
{:error, _} -> {arg, state, env}
1243-
end
1244-
1245-
_ ->
1246-
{node, state, env}
1235+
if is_atom(arg) do
1236+
# An actual compiler would raise if the module is not defined or if the require fails.
1237+
case Macro.Env.define_require(env, meta, arg, [trace: false] ++ opts) do
1238+
{:ok, env} -> {arg, state, env}
1239+
{:error, _} -> {arg, state, env}
1240+
end
1241+
else
1242+
{node, state, env}
12471243
end
12481244
end
12491245

12501246
defp expand({:import, meta, [arg, opts]} = node, state, env) do
12511247
{arg, state, env} = expand(arg, state, env)
12521248
{opts, state, env} = expand_directive_opts(opts, state, env)
12531249

1254-
case arg do
1255-
{:__aliases__, _, _} ->
1256-
# An actual compiler would raise if the module is not defined or if the import fails.
1257-
with true <- is_atom(arg) and Code.ensure_loaded?(arg),
1258-
{:ok, env} <- Macro.Env.define_import(env, meta, arg, [trace: false] ++ opts) do
1259-
{arg, state, env}
1260-
else
1261-
_ -> {arg, state, env}
1262-
end
1263-
1264-
_ ->
1265-
{node, state, env}
1250+
if is_atom(arg) do
1251+
# An actual compiler would raise if the module is not defined or if the import fails.
1252+
with true <- is_atom(arg) and Code.ensure_loaded?(arg),
1253+
{:ok, env} <- Macro.Env.define_import(env, meta, arg, [trace: false] ++ opts) do
1254+
{arg, state, env}
1255+
else
1256+
_ -> {arg, state, env}
1257+
end
1258+
else
1259+
{node, state, env}
12661260
end
12671261
end
12681262

@@ -1444,6 +1438,20 @@ if Version.match?(System.version(), ">= 1.17.0-dev") do
14441438
{Enum.reverse(blocks), put_in(state.functions, functions), env}
14451439
end
14461440

1441+
defp expand_macro(_meta, Kernel, type, [{_name, _, params}, blocks], _callback, state, env)
1442+
when type in [:def, :defp] and is_list(params) and is_list(blocks) do
1443+
{blocks, state} =
1444+
for {type, block} <- blocks, reduce: {[], state} do
1445+
{acc, state} ->
1446+
{res, state, _env} = expand(block, state, env)
1447+
{[{type, res} | acc], state}
1448+
end
1449+
1450+
arity = length(List.wrap(params))
1451+
1452+
{Enum.reverse(blocks), state, env}
1453+
end
1454+
14471455
defp expand_macro(meta, Kernel, :@, [{name, _, p}] = args, callback, state, env) when is_list(p) do
14481456
state = update_in(state.attrs, &[to_string(name) | &1])
14491457
expand_macro_callback(meta, Kernel, :@, args, callback, state, env)
@@ -1527,7 +1535,7 @@ if Version.match?(System.version(), ">= 1.17.0-dev") do
15271535
{Enum.reverse(acc), state, env}
15281536
end
15291537

1530-
defp expand_list([h | t], state, env, acc) do
1538+
defp expand_list([h | t] = list, state, env, acc) do
15311539
{h, state, env} = expand(h, state, env)
15321540
expand_list(t, state, env, [h | acc])
15331541
end

Diff for: test/next_ls/completions_test.exs

+92-3
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,20 @@ defmodule NextLS.CompletionsTest do
55
import GenLSP.Test
66
import NextLS.Support.Utils
77

8+
defmacrop assert_match({:in, _, [left, right]}) do
9+
quote do
10+
assert Enum.any?(unquote(right), fn x ->
11+
match?(unquote(left), x)
12+
end),
13+
"""
14+
failed to find a match inside of list
15+
16+
left: #{unquote(Macro.to_string(left))}
17+
right: #{inspect(unquote(right), pretty: true)}
18+
"""
19+
end
20+
end
21+
822
@moduletag tmp_dir: true, root_paths: ["my_proj"]
923

1024
setup %{tmp_dir: tmp_dir} do
@@ -34,7 +48,7 @@ defmodule NextLS.CompletionsTest do
3448
baz = Path.join(cwd, "my_proj/lib/baz.ex")
3549

3650
File.write!(baz, """
37-
defmodule Foo.Bar.Baz do
51+
defmodule Foo.Bing.Baz do
3852
def run() do
3953
:ok
4054
end
@@ -361,12 +375,47 @@ defmodule NextLS.CompletionsTest do
361375
]
362376
end
363377

378+
test "aliases in document", %{client: client, foo: foo} do
379+
uri = uri(foo)
380+
381+
did_open(client, foo, """
382+
defmodule Foo do
383+
alias Foo.Bing
384+
385+
def run() do
386+
B
387+
end
388+
end
389+
""")
390+
391+
request client, %{
392+
method: "textDocument/completion",
393+
id: 2,
394+
jsonrpc: "2.0",
395+
params: %{
396+
textDocument: %{
397+
uri: uri
398+
},
399+
position: %{
400+
line: 4,
401+
character: 5
402+
}
403+
}
404+
}
405+
406+
assert_result 2, results
407+
408+
assert_match(
409+
%{"data" => _, "documentation" => _, "insertText" => "Bing", "kind" => 9, "label" => "Bing"} in results
410+
)
411+
end
412+
364413
test "inside alias special form", %{client: client, foo: foo} do
365414
uri = uri(foo)
366415

367416
did_open(client, foo, """
368417
defmodule Foo do
369-
alias Foo.Bar.
418+
alias Foo.Bing.
370419
371420
def run() do
372421
:ok
@@ -390,7 +439,47 @@ defmodule NextLS.CompletionsTest do
390439
}
391440

392441
assert_result 2, [
393-
%{"data" => _, "documentation" => _, "insertText" => "Baz", "kind" => 9, "label" => "Baz"}
442+
%{"data" => _, "documentation" => _, "insertText" => "Bing", "kind" => 9, "label" => "Bing"}
394443
]
395444
end
445+
446+
test "import functions appear", %{client: client, foo: foo} do
447+
uri = uri(foo)
448+
449+
did_open(client, foo, """
450+
defmodule Foo do
451+
use ExUnit.Case
452+
import ExUnit.CaptureLog
453+
454+
test "foo" do
455+
cap
456+
end
457+
end
458+
""")
459+
460+
request client, %{
461+
method: "textDocument/completion",
462+
id: 2,
463+
jsonrpc: "2.0",
464+
params: %{
465+
textDocument: %{
466+
uri: uri
467+
},
468+
position: %{
469+
line: 5,
470+
character: 7
471+
}
472+
}
473+
}
474+
475+
assert_result 2, results
476+
477+
assert_match(
478+
%{"data" => _, "documentation" => _, "insertText" => "capture_log", "kind" => 3, "label" => "capture_log/1"} in results
479+
)
480+
481+
assert_match(
482+
%{"data" => _, "documentation" => _, "insertText" => "capture_log", "kind" => 3, "label" => "capture_log/2"} in results
483+
)
484+
end
396485
end

0 commit comments

Comments
 (0)