Make sure error messages contain all relevant information

We have recently changed the compiler to emit a warning
for each undefined function with a generic error at the
end saying the module could not be defined.

Unfortunately, some tools like IDEs and Phoenix Reloader
give higher priority to errors over warnings, which means
an error would be shown but without any actionable
information.

This commit changes it so the first undefined function is
an error and all following ones are warnings.
This commit is contained in:
José Valim
2019-12-23 11:47:21 +01:00
parent 004c8baa44
commit 8b82564ebd
3 changed files with 83 additions and 105 deletions
+4 -8
View File
@@ -109,9 +109,9 @@ ensure_no_undefined_local(File, Module, All) ->
[] -> ok;
List ->
[{FirstMeta, _, _} | _] = Sorted = lists:sort(List),
[elixir_errors:form_warn(Meta, File, ?MODULE, {Error, Tuple}) || {Meta, Tuple, Error} <- Sorted],
elixir_errors:form_error(FirstMeta, File, ?MODULE, {invalid_or_undefined_calls, Module}),
[{FirstMeta, FirstTuple, FirstError} | Rest] = lists:sort(List),
[elixir_errors:form_warn(Meta, File, ?MODULE, {Error, Tuple}) || {Meta, Tuple, Error} <- lists:reverse(Rest)],
elixir_errors:form_error(FirstMeta, File, ?MODULE, {FirstError, FirstTuple}),
ok
end
end).
@@ -139,8 +139,4 @@ format_error({undefined_function, {F, A}}) ->
io_lib:format("undefined function ~ts/~B", [F, A]);
format_error({incorrect_dispatch, {F, A}}) ->
io_lib:format("cannot invoke macro ~ts/~B before its definition", [F, A]);
format_error({invalid_or_undefined_calls, Module}) ->
io_lib:format("cannot define module ~ts, undefined or invalid function calls found",
[elixir_aliases:inspect(Module)]).
io_lib:format("cannot invoke macro ~ts/~B before its definition", [F, A]).
+69 -83
View File
@@ -375,20 +375,18 @@ defmodule Kernel.ErrorsTest do
end
'''
assert capture_io(:stderr, fn ->
assert_eval_raise CompileError,
~r"nofile:4: cannot define module Kernel.ErrorsTest.ClauseWithDefaults5",
~C'''
defmodule Kernel.ErrorsTest.ClauseWithDefaults5 do
def hello(
foo,
bar \\ foo()
)
assert_eval_raise CompileError,
~r"nofile:4: undefined function foo/0",
~C'''
defmodule Kernel.ErrorsTest.ClauseWithDefaults5 do
def hello(
foo,
bar \\ foo()
)
def hello(foo, bar), do: foo + bar
end
'''
end) =~ "undefined function foo/0"
def hello(foo, bar), do: foo + bar
end
'''
end
test "different defs with defaults" do
@@ -410,9 +408,20 @@ defmodule Kernel.ErrorsTest do
end
test "undefined function" do
assert_eval_raise CompileError,
~r"hello.ex:4: undefined function bar/0",
'''
defmodule Kernel.ErrorsTest.BadForm do
@file "hello.ex"
def foo do
bar()
end
end
'''
assert capture_io(:stderr, fn ->
assert_eval_raise CompileError,
~r"nofile:3: cannot define module Kernel.ErrorsTest.BadForm",
~r"nofile:3: undefined function bar/1",
'''
defmodule Kernel.ErrorsTest.BadForm do
def foo do
@@ -422,35 +431,20 @@ defmodule Kernel.ErrorsTest do
end
end
'''
end) =~ ~r"undefined function bar/1.*undefined function baz/2"s
end) =~ "undefined function baz/2"
assert capture_io(:stderr, fn ->
assert_eval_raise CompileError,
~r"hello.ex:4: cannot define module Kernel.ErrorsTest.BadForm",
'''
defmodule Kernel.ErrorsTest.BadForm do
@file "hello.ex"
def foo do
bar()
end
end
'''
end) =~ "undefined function bar/0"
assert_eval_raise CompileError, ~r"nofile:8: undefined function baz/0", '''
defmodule Sample do
def foo do
bar()
end
assert capture_io(:stderr, fn ->
assert_eval_raise CompileError, ~r"nofile:8: cannot define module Sample", '''
defmodule Sample do
def foo do
bar()
end
defoverridable [foo: 0]
def foo do
baz()
end
end
'''
end) =~ "undefined function baz/0"
defoverridable [foo: 0]
def foo do
baz()
end
end
'''
end
test "undefined non-local function" do
@@ -781,54 +775,46 @@ defmodule Kernel.ErrorsTest do
end
test "macro invoked before its definition" do
assert capture_io(:stderr, fn ->
assert_eval_raise CompileError,
~r"nofile:2: cannot define module Kernel.ErrorsTest.IncorrectMacroDispatch",
'''
defmodule Kernel.ErrorsTest.IncorrectMacroDispatch do
def foo, do: bar()
defmacro bar, do: :bar
end
'''
end) =~ "cannot invoke macro bar/0 before its definition"
assert_eval_raise CompileError,
~r"nofile:2: cannot invoke macro bar/0 before its definition",
'''
defmodule Kernel.ErrorsTest.IncorrectMacroDispatch do
def foo, do: bar()
defmacro bar, do: :bar
end
'''
assert capture_io(:stderr, fn ->
assert_eval_raise CompileError,
~r"nofile:2: cannot define module Kernel.ErrorsTest.IncorrectMacropDispatch",
'''
defmodule Kernel.ErrorsTest.IncorrectMacropDispatch do
def foo, do: bar()
defmacrop bar, do: :ok
end
'''
end) =~ "cannot invoke macro bar/0 before its definition"
assert_eval_raise CompileError,
~r"nofile:2: cannot invoke macro bar/0 before its definition",
'''
defmodule Kernel.ErrorsTest.IncorrectMacropDispatch do
def foo, do: bar()
defmacrop bar, do: :ok
end
'''
assert capture_io(:stderr, fn ->
assert_eval_raise CompileError,
~r"nofile:2: cannot define module Kernel.ErrorsTest.IncorrectMacroDispatch",
'''
defmodule Kernel.ErrorsTest.IncorrectMacroDispatch do
defmacro bar(a) when is_atom(a), do: bar([a])
end
'''
end) =~ "cannot invoke macro bar/1 before its definition"
assert_eval_raise CompileError,
~r"nofile:2: cannot invoke macro bar/1 before its definition",
'''
defmodule Kernel.ErrorsTest.IncorrectMacroDispatch do
defmacro bar(a) when is_atom(a), do: bar([a])
end
'''
end
test "macro captured before its definition" do
assert capture_io(:stderr, fn ->
assert_eval_raise CompileError,
~r"nofile:3: cannot define module Kernel.ErrorsTest.IncorrectMacroDispatch.Capture",
'''
defmodule Kernel.ErrorsTest.IncorrectMacroDispatch.Capture do
def foo do
predicate = &is_ok/1
Enum.any?([:ok, :error, :foo], predicate)
end
assert_eval_raise CompileError,
~r"nofile:3: cannot invoke macro is_ok/1 before its definition",
'''
defmodule Kernel.ErrorsTest.IncorrectMacroDispatch.Capture do
def foo do
predicate = &is_ok/1
Enum.any?([:ok, :error, :foo], predicate)
end
defmacro is_ok(atom), do: atom == :ok
end
'''
end) =~ "cannot invoke macro is_ok/1 before its definition"
defmacro is_ok(atom), do: atom == :ok
end
'''
end
test "function definition with alias" do
+10 -14
View File
@@ -3,8 +3,6 @@ Code.require_file("../test_helper.exs", __DIR__)
defmodule Kernel.GuardTest do
use ExUnit.Case, async: true
import ExUnit.CaptureIO
describe "defguard(p) usage" do
defmodule GuardsInMacros do
defguard is_foo(atom) when atom == :foo
@@ -163,19 +161,17 @@ defmodule Kernel.GuardTest do
end
end
assert capture_io(:stderr, fn ->
assert_raise CompileError,
~r"cannot define module Kernel.GuardTest.IntegerPrivateFunctionUtils,",
fn ->
defmodule IntegerPrivateFunctionUtils do
import IntegerPrivateGuards
assert_raise CompileError,
~r"undefined function is_even/1",
fn ->
defmodule IntegerPrivateFunctionUtils do
import IntegerPrivateGuards
def is_even_and_small?(value) do
if is_even(value) and value <= 100, do: true, else: false
end
end
end
end) =~ "undefined function is_even/1"
def is_even_and_small?(value) do
if is_even(value) and value <= 100, do: true, else: false
end
end
end
end
test "requires a proper macro name" do