Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 28 additions & 4 deletions lib/elixir/lib/module/behaviour.ex
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
58 changes: 42 additions & 16 deletions lib/elixir/test/elixir/kernel/impl_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down
Loading