diff --git a/lib/elixir/lib/module/behaviour.ex b/lib/elixir/lib/module/behaviour.ex index f436596b75..42e76baf22 100644 --- a/lib/elixir/lib/module/behaviour.ex +++ b/lib/elixir/lib/module/behaviour.ex @@ -181,8 +181,16 @@ defmodule Module.Behaviour do behaviour not in behaviours -> {:error, {:behaviour_not_declared, behaviour}} + not Code.ensure_loaded?(behaviour) -> + # Module does not exist, but we have already warned about it. + {:ok, []} + + not behaviour_defined?(callbacks, behaviour) -> + # Module does not define behaviour, but we have already warned about it. + {:ok, []} + true -> - {:error, {:behaviour_not_defined, behaviour, callbacks}} + {:error, {:callback_not_defined, behaviour, callbacks}} end end @@ -215,6 +223,18 @@ defmodule Module.Behaviour do end end + # Determines whether there is at least one callback defined for the given behaviour. + # If not, that means that the behaviour has not been defined. + defp behaviour_defined?(callbacks, behaviour) do + callbacks + |> Map.values() + |> List.flatten() + |> Enum.any?(fn + {_kind, ^behaviour, _optional?} -> true + {_kind, _behaviour, _optional?} -> false + end) + end + defp warn_missing_impls(%{callbacks: callbacks} = context, _impl_contexts, _defs) when map_size(callbacks) == 0 do context @@ -390,13 +410,17 @@ defmodule Module.Behaviour do ] end - defp format_warning({:behaviour_not_defined, callback, kind, behaviour, callbacks}) do + defp format_warning({:callback_not_defined, callback, kind, behaviour, callbacks}) do + behaviour_string = inspect(behaviour) + [ "got \"@impl ", - inspect(behaviour), + behaviour_string, "\" for ", format_definition(kind, callback), - " but this behaviour does not specify such callback", + " but ", + behaviour_string, + " does not specify such callback", known_callbacks(callbacks) ] end diff --git a/lib/elixir/test/elixir/kernel/impl_test.exs b/lib/elixir/test/elixir/kernel/impl_test.exs index a998b6d8a9..617f1a77eb 100644 --- a/lib/elixir/test/elixir/kernel/impl_test.exs +++ b/lib/elixir/test/elixir/kernel/impl_test.exs @@ -122,18 +122,44 @@ defmodule Kernel.ImplTest do end end - test "warns for undefined value" do - assert capture_err(fn -> - Code.eval_string(""" - defmodule Kernel.ImplTest.ImplAttributes do - @behaviour :abc + test "warns about undefined module, but does not warn at @impl line" do + capture_err = + capture_err(fn -> + Code.eval_string(""" + defmodule Kernel.ImplTest.ImplAttributes do + @behaviour Abc - @impl :abc - def foo(), do: :ok - end - """) - end) =~ - "got \"@impl :abc\" for function foo/0 but this behaviour does not specify such callback. There are no known callbacks" + @impl Abc + def foo(), do: :ok + end + """) + end) + + assert capture_err =~ + "@behaviour Abc does not exist (in module Kernel.ImplTest.ImplAttributes)" + + refute capture_err =~ + "got \"@impl Abc\"" + end + + test "warns about undefined behaviour, but does not warn at @impl line" do + capture_err = + capture_err(fn -> + Code.eval_string(""" + defmodule Kernel.ImplTest.ImplAttributes do + @behaviour Enum + + @impl Enum + def foo(), do: :ok + end + """) + end) + + assert capture_err =~ + "module Enum is not a behaviour (in module Kernel.ImplTest.ImplAttributes)" + + refute capture_err =~ + "got \"@impl Abc\"" end test "warns for callbacks without impl and @impl has been set before" do @@ -315,7 +341,7 @@ defmodule Kernel.ImplTest do end """) end) =~ - "got \"@impl Kernel.ImplTest.Behaviour\" for function bar/0 but this behaviour does not specify such callback" + "got \"@impl Kernel.ImplTest.Behaviour\" for function bar/0 but Kernel.ImplTest.Behaviour does not specify such callback" end test "warns for @impl module with macro callback name not in behaviour" do @@ -328,7 +354,7 @@ defmodule Kernel.ImplTest do end """) end) =~ - "got \"@impl Kernel.ImplTest.MacroBehaviour\" for macro foo/0 but this behaviour does not specify such callback" + "got \"@impl Kernel.ImplTest.MacroBehaviour\" for macro foo/0 but Kernel.ImplTest.MacroBehaviour does not specify such callback" end test "warns for @impl module with macro callback kind not in behaviour" do @@ -341,7 +367,7 @@ defmodule Kernel.ImplTest do end """) end) =~ - "got \"@impl Kernel.ImplTest.MacroBehaviour\" for function foo/0 but this behaviour does not specify such callback" + "got \"@impl Kernel.ImplTest.MacroBehaviour\" for function foo/0 but Kernel.ImplTest.MacroBehaviour does not specify such callback" end test "warns for @impl module and callback belongs to another known module" do @@ -355,7 +381,7 @@ defmodule Kernel.ImplTest do end """) end) =~ - "got \"@impl Kernel.ImplTest.Behaviour\" for function bar/0 but this behaviour does not specify such callback" + "got \"@impl Kernel.ImplTest.Behaviour\" for function bar/0 but Kernel.ImplTest.Behaviour does not specify such callback" end test "warns for @impl module and callback belongs to another unknown module" do @@ -509,7 +535,7 @@ defmodule Kernel.ImplTest do end """) end) =~ - "got \"@impl Kernel.ImplTest.MacroBehaviour\" for function foo/0 but this behaviour does not specify such callback" + "got \"@impl Kernel.ImplTest.MacroBehaviour\" for function foo/0 but Kernel.ImplTest.MacroBehaviour does not specify such callback" end test "warns only for non-generated functions in non-generated @impl" do