Ignore additional callbacks in protocol implementation, closes #15456

This commit is contained in:
José Valim
2026-06-12 21:01:28 +02:00
parent 3bebe1599a
commit 1938d8a957
2 changed files with 46 additions and 2 deletions
+2 -2
View File
@@ -136,8 +136,8 @@ defmodule Module.Types do
defp impl_for(attrs) do
case List.keyfind(attrs, :__impl__, 0) do
{:__impl__, [protocol: protocol, for: for]} ->
if Code.ensure_loaded?(protocol) and function_exported?(protocol, :behaviour_info, 1) do
{for, protocol.behaviour_info(:callbacks)}
if Code.ensure_loaded?(protocol) and function_exported?(protocol, :__protocol__, 1) do
{for, protocol.__protocol__(:functions)}
else
nil
end
@@ -154,6 +154,50 @@ defmodule Module.Types.IntegrationTest do
assert itself_arg.(Itself.Unknown) == dynamic(open_map(__struct__: atom([Unknown])))
end
test "ignores additional callbacks on implementations" do
files = %{
"p.ex" => """
defmodule InjectCallback do
defmacro __before_compile__(_env) do
quote do
@callback extra() :: term()
end
end
end
defprotocol Injected do
@before_compile InjectCallback
def f(x)
end
defimpl Injected, for: Atom do
def f(_), do: :ok
def extra(), do: :extra
end
defprotocol Explicit do
@callback extra() :: term()
def f(x)
end
defimpl Explicit, for: Atom do
def f(_), do: :ok
def extra(), do: :extra
end
"""
}
assert capture_compile_warnings(files, []) == """
warning: cannot define @callback extra/0 inside protocol, use def/1 to outline your protocol definition
│
20 │ @callback extra() :: term()
│ ~~~~~~~~~~~~~~~~~~~~~~~~~~~
│
└─ p.ex:20: Explicit (module)
"""
end
end
describe "type checking" do