diff --git a/lib/elixir/lib/exception.ex b/lib/elixir/lib/exception.ex index 3347c250e85..7837372f5b0 100644 --- a/lib/elixir/lib/exception.ex +++ b/lib/elixir/lib/exception.ex @@ -640,8 +640,26 @@ defmodule UndefinedFunctionError do end def message(%{reason: :"module could not be loaded", module: module, function: function, arity: arity}) do + suffix = + case :code.get_object_code(module) do + {_, _, path} -> + case :beam_lib.info(path) do + info when is_list(info) -> + alt_module = Keyword.get(info, :module) + exports = exports_for(alt_module) + if {function, arity} in exports do + ". Did you mean:\n\n * " <> + Exception.format_mfa(alt_module, function, arity) <> "\n" + else + ". Did you mean #{inspect alt_module}?" + end + _ -> "" + end + _ -> "" + end "function " <> Exception.format_mfa(module, function, arity) <> - " is undefined (module #{inspect module} is not available)" + " is undefined (module #{inspect module} is not available)" <> + suffix end def message(%{reason: :"function not exported", module: module, function: function, arity: arity, exports: exports}) do diff --git a/lib/elixir/test/elixir/exception_test.exs b/lib/elixir/test/elixir/exception_test.exs index 3da381289c8..ad8257447c2 100644 --- a/lib/elixir/test/elixir/exception_test.exs +++ b/lib/elixir/test/elixir/exception_test.exs @@ -387,6 +387,13 @@ defmodule ExceptionTest do * get_cookie/0 * set_cookie/2 """ + assert %UndefinedFunctionError{module: Genserver, function: :start_link, arity: 3} |> message == """ + function Genserver.start_link/3 is undefined (module Genserver is not available). Did you mean: + + * GenServer.start_link/3 + """ + assert %UndefinedFunctionError{module: Genserver, function: :start_link, arity: 1} |> message == + "function Genserver.start_link/1 is undefined (module Genserver is not available). Did you mean GenServer?" end test "UndefinedFunctionError when the mfa is a macro but require wasn't called" do diff --git a/lib/mix/lib/mix/tasks/xref.ex b/lib/mix/lib/mix/tasks/xref.ex index 92373188382..4b0d5fd00ba 100644 --- a/lib/mix/lib/mix/tasks/xref.ex +++ b/lib/mix/lib/mix/tasks/xref.ex @@ -240,8 +240,12 @@ defmodule Mix.Tasks.Xref do end defp format_warning(file, {lines, :unknown_module, module, function, arity, _}) do - ["function ", Exception.format_mfa(module, function, arity), - " is undefined (module #{inspect module} is not available)\n" | format_file_lines(file, lines)] + message = + [module: module, function: function, arity: arity, reason: :"module could not be loaded"] + |> UndefinedFunctionError.exception() + |> Exception.message() + + [message, "\n" | format_file_lines(file, lines)] end defp format_file_lines(file, [line]) do diff --git a/lib/mix/test/mix/tasks/xref_test.exs b/lib/mix/test/mix/tasks/xref_test.exs index f803bc6e17f..5f3e68a5189 100644 --- a/lib/mix/test/mix/tasks/xref_test.exs +++ b/lib/mix/test/mix/tasks/xref_test.exs @@ -63,6 +63,21 @@ defmodule Mix.Tasks.XrefTest do """ end + test "warnings: reports module case mismatch" do + assert_warnings """ + defmodule A do + def a, do: Genserver.start_link(A, [], []) + end + """, """ + warning: function Genserver.start_link/3 is undefined (module Genserver is not available). Did you mean: + + * GenServer.start_link/3 + + lib/a.ex:2 + + """ + end + test "warnings: reports missing captures" do assert_warnings """ defmodule A do