Skip to content

Commit

Permalink
Add error span for unknown local call
Browse files Browse the repository at this point in the history
  • Loading branch information
viniciusmuller authored and josevalim committed Oct 7, 2023
1 parent a4c700b commit 47d7067
Show file tree
Hide file tree
Showing 5 changed files with 62 additions and 27 deletions.
36 changes: 18 additions & 18 deletions lib/elixir/lib/module/locals_tracker.ex
Original file line number Diff line number Diff line change
Expand Up @@ -102,12 +102,21 @@ defmodule Module.LocalsTracker do
@doc """
Collect undefined functions based on local calls and existing definitions.
"""
def collect_undefined_locals({set, bag}, all_defined) do
def collect_undefined_locals({set, bag}, all_defined, file) do
undefined =
for {pair, _, meta, _} <- all_defined,
{local, position, macro_dispatch?} <- out_neighbours(bag, {:local, pair}),
error = undefined_local_error(set, local, macro_dispatch?),
do: {pair, build_meta(position, meta), local, error}
{{local_name, _} = local, position, macro_dispatch?} <-
out_neighbours(bag, {:local, pair}),
error = undefined_local_error(set, local, macro_dispatch?) do
file =
case Keyword.get(meta, :file) do
{keep_file, _keep_line} -> keep_file
nil -> file
end

meta = build_meta(position, local_name)
{pair, meta, file, local, error}
end

:lists.usort(undefined)
end
Expand Down Expand Up @@ -212,21 +221,12 @@ defmodule Module.LocalsTracker do
end

defp get_line(meta), do: Keyword.get(meta, :line)
defp get_position(meta), do: {get_line(meta), meta[:column]}

defp get_position(meta) do
{get_line(meta), meta[:column]}
end

defp build_meta(nil, _meta), do: []

# We need to transform any file annotation in the function
# definition into a keep annotation that is used by the
# error handling system in order to respect line/file.
defp build_meta(position, meta) do
case {position, Keyword.get(meta, :file)} do
{{line, _col}, {file, _}} -> [keep: {file, line}]
{{line, nil}, nil} -> [line: line]
{{line, col}, nil} -> [line: line, column: col]
defp build_meta(position, function_name) do
case position do
{line, nil} -> [line: line]
{line, col} -> :elixir_env.calculate_span([line: line, column: col], function_name)
end
end

Expand Down
4 changes: 2 additions & 2 deletions lib/elixir/src/elixir_locals.erl
Original file line number Diff line number Diff line change
Expand Up @@ -99,8 +99,8 @@ ensure_no_import_conflict(Module, All, E) ->

ensure_no_undefined_local(Module, All, E) ->
if_tracker(Module, [], fun(Tracker) ->
[elixir_errors:module_error(Meta, E#{function := Function}, ?MODULE, {Error, Tuple, Module})
|| {Function, Meta, Tuple, Error} <- ?tracker:collect_undefined_locals(Tracker, All)],
[elixir_errors:module_error(Meta, E#{function := Function, file := File}, ?MODULE, {Error, Tuple, Module})
|| {Function, Meta, File, Tuple, Error} <- ?tracker:collect_undefined_locals(Tracker, All, ?key(E, file))],
ok
end).

Expand Down
31 changes: 31 additions & 0 deletions lib/elixir/test/elixir/kernel/diagnostics_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -889,6 +889,37 @@ defmodule Kernel.DiagnosticsTest do
purge(Sample)
end

@tag :tmp_dir
test "shows span for unknown local function calls", %{tmp_dir: tmp_dir} do
path = make_relative_tmp(tmp_dir, "unknown_local_function_call.ex")

source = """
defmodule Sample do
@file "#{path}"
def foo do
_result = unknown_func_call!(:hello!)
end
end
"""

File.write!(path, source)

expected = """
error: undefined function unknown_func_call!/1 (expected Sample to define such a function or for it to be imported, but none are available)
5 │ _result = unknown_func_call!(:hello!)
│ ^^^^^^^^^^^^^^^^^^
└─ #{path}:5:15: Sample.foo/0
"""

assert capture_compile(source) == expected
after
purge(Sample)
end

@tag :tmp_dir
test "line + column", %{tmp_dir: tmp_dir} do
path = make_relative_tmp(tmp_dir, "error_line_column.ex")
Expand Down
2 changes: 1 addition & 1 deletion lib/elixir/test/elixir/kernel/errors_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ defmodule Kernel.ErrorsTest do
test "undefined function" do
assert_compile_error(
[
"hello.ex:4: ",
"hello.ex:4:5: ",
"undefined function bar/0 (expected Kernel.ErrorsTest.BadForm to define such a function or for it to be imported, but none are available)"
],
~c"""
Expand Down
16 changes: 10 additions & 6 deletions lib/elixir/test/elixir/module/locals_tracker_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,12 @@ defmodule Module.LocalsTrackerTest do
test "preserves column information on retrieval", config do
D.add_local(config[:ref], {:public, 1}, {:private, 1}, [line: 1, column: 1], false)

undefined = D.collect_undefined_locals(config[:ref], @used)
assert undefined == [{{:public, 1}, [line: 1, column: 1], {:private, 1}, :undefined_function}]
undefined = D.collect_undefined_locals(config[:ref], @used, "foo.exs")

assert undefined == [
{{:public, 1}, [span: {1, 8}, line: 1, column: 1], "foo.exs", {:private, 1},
:undefined_function}
]
end

test "private definitions with unused default arguments", config do
Expand Down Expand Up @@ -80,8 +84,8 @@ defmodule Module.LocalsTrackerTest do
test "undefined functions are marked as so", config do
D.add_local(config[:ref], {:public, 1}, {:private, 1}, [line: 1], false)

undefined = D.collect_undefined_locals(config[:ref], @used)
assert undefined == [{{:public, 1}, [line: 1], {:private, 1}, :undefined_function}]
undefined = D.collect_undefined_locals(config[:ref], @used, "foo.exs")
assert undefined == [{{:public, 1}, [line: 1], "foo.exs", {:private, 1}, :undefined_function}]
end

### Incorrect dispatches
Expand All @@ -93,8 +97,8 @@ defmodule Module.LocalsTrackerTest do

D.add_local(config[:ref], {:public, 1}, {:macro, 1}, [line: 5], false)

undefined = D.collect_undefined_locals(config[:ref], definitions)
assert undefined == [{{:public, 1}, [line: 5], {:macro, 1}, :incorrect_dispatch}]
undefined = D.collect_undefined_locals(config[:ref], definitions, "foo.exs")
assert undefined == [{{:public, 1}, [line: 5], "foo.exs", {:macro, 1}, :incorrect_dispatch}]
end

## Defaults
Expand Down

0 comments on commit 47d7067

Please sign in to comment.