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:
@@ -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]).
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user