From 8b82564ebd58a3e650ca1293a580ba5cc7a4019d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Mon, 23 Dec 2019 11:45:27 +0100 Subject: [PATCH] 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. --- lib/elixir/src/elixir_locals.erl | 12 +- lib/elixir/test/elixir/kernel/errors_test.exs | 152 ++++++++---------- lib/elixir/test/elixir/kernel/guard_test.exs | 24 ++- 3 files changed, 83 insertions(+), 105 deletions(-) diff --git a/lib/elixir/src/elixir_locals.erl b/lib/elixir/src/elixir_locals.erl index 0966f9114d..476e220b9c 100644 --- a/lib/elixir/src/elixir_locals.erl +++ b/lib/elixir/src/elixir_locals.erl @@ -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]). \ No newline at end of file diff --git a/lib/elixir/test/elixir/kernel/errors_test.exs b/lib/elixir/test/elixir/kernel/errors_test.exs index d5f2de4553..8adc0870aa 100644 --- a/lib/elixir/test/elixir/kernel/errors_test.exs +++ b/lib/elixir/test/elixir/kernel/errors_test.exs @@ -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 diff --git a/lib/elixir/test/elixir/kernel/guard_test.exs b/lib/elixir/test/elixir/kernel/guard_test.exs index 36700ca456..19f607b63d 100644 --- a/lib/elixir/test/elixir/kernel/guard_test.exs +++ b/lib/elixir/test/elixir/kernel/guard_test.exs @@ -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