Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
58 changes: 54 additions & 4 deletions lib/elixir/lib/module/behaviour.ex
Original file line number Diff line number Diff line change
Expand Up @@ -181,8 +181,14 @@ defmodule Module.Behaviour do
behaviour not in behaviours ->
{:error, {:behaviour_not_declared, behaviour}}

not Code.ensure_loaded?(behaviour) ->
{:error, {:module_not_defined, behaviour}}

not behaviour_defined?(callbacks, behaviour) ->
{:error, {:behaviour_not_defined, behaviour}}

true ->
{:error, {:behaviour_not_defined, behaviour, callbacks}}
{:error, {:callback_not_defined, behaviour, callbacks}}
end
end

Expand Down Expand Up @@ -215,6 +221,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 +408,45 @@ defmodule Module.Behaviour do
]
end

defp format_warning({:behaviour_not_defined, callback, kind, behaviour, callbacks}) do
defp format_warning({:module_not_defined, callback, kind, behaviour}) do
behaviour_string = inspect(behaviour)

[
"got \"@impl ",
inspect(behaviour),
behaviour_string,
"\" for ",
format_definition(kind, callback),
" but module ",
behaviour_string,
" does not exist"
]
end

defp format_warning({:behaviour_not_defined, callback, kind, behaviour}) do
behaviour_string = inspect(behaviour)

[
"got \"@impl ",
behaviour_string,
"\" for ",
format_definition(kind, callback),
" but behaviour ",
behaviour_string,
" does not exist"
]
end

defp format_warning({:callback_not_defined, callback, kind, behaviour, callbacks}) do
behaviour_string = inspect(behaviour)

[
"got \"@impl ",
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
32 changes: 23 additions & 9 deletions lib/elixir/test/elixir/kernel/impl_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -122,18 +122,32 @@ defmodule Kernel.ImplTest do
end
end

test "warns for undefined value" do
test "warns about undefined module" do
assert capture_err(fn ->
Code.eval_string("""
defmodule Kernel.ImplTest.ImplAttributes do
@behaviour :abc
@behaviour Abc

@impl :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"
"got \"@impl Abc\" for function foo/0 but module Abc does not exist"
end

test "warns about undefined behaviour" do
assert capture_err(fn ->
Code.eval_string("""
defmodule Kernel.ImplTest.ImplAttributes do
@behaviour Enum

@impl Enum
def foo(), do: :ok
end
""")
end) =~
"got \"@impl Enum\" for function foo/0 but behaviour Enum does not exist"
end

test "warns for callbacks without impl and @impl has been set before" do
Expand Down Expand Up @@ -315,7 +329,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 +342,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 +355,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 +369,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 +523,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