diff --git a/lib/elixir/lib/protocol.ex b/lib/elixir/lib/protocol.ex index 1e4c3911ad6..df6d9df1632 100644 --- a/lib/elixir/lib/protocol.ex +++ b/lib/elixir/lib/protocol.ex @@ -614,10 +614,12 @@ defmodule Protocol do {impl_for!, definitions} = List.keytake(definitions, {:impl_for!, 1}, 0) {struct_impl_for, definitions} = List.keytake(definitions, {:struct_impl_for, 1}, 0) + protocol_funs = get_protocol_functions(protocol_def) + protocol_def = change_protocol(protocol_def, types) impl_for = change_impl_for(impl_for, protocol, types) struct_impl_for = change_struct_impl_for(struct_impl_for, protocol, types, structs) - new_signatures = new_signatures(definitions, protocol, types) + new_signatures = new_signatures(definitions, protocol_funs, protocol, types) definitions = [protocol_def, impl_for, impl_for!, struct_impl_for] ++ definitions signatures = Enum.into(new_signatures, signatures) @@ -628,7 +630,7 @@ defmodule Protocol do end end - defp new_signatures(definitions, protocol, types) do + defp new_signatures(definitions, protocol_funs, protocol, types) do alias Module.Types.Descr clauses = @@ -666,9 +668,10 @@ defmodule Protocol do end new_signatures = - for {{fun, arity}, :def, _, _} <- definitions do + for {{_fun, arity} = fun_arity, :def, _, _} <- definitions, + fun_arity in protocol_funs do rest = List.duplicate(Descr.term(), arity - 1) - {{fun, arity}, {:strong, nil, [{[domain | rest], Descr.dynamic()}]}} + {fun_arity, {:strong, nil, [{[domain | rest], Descr.dynamic()}]}} end [ @@ -677,6 +680,13 @@ defmodule Protocol do ] ++ new_signatures end + defp get_protocol_functions({_name, _kind, _meta, clauses}) do + Enum.find_value(clauses, fn + {_meta, [:functions], [], clauses} -> clauses + _ -> nil + end) || raise "could not find protocol functions" + end + defp change_protocol({_name, _kind, meta, clauses}, types) do clauses = Enum.map(clauses, fn diff --git a/lib/elixir/test/elixir/fixtures/consolidation/sample.ex b/lib/elixir/test/elixir/fixtures/consolidation/sample.ex index ee7edea3fbc..b1530e20a52 100644 --- a/lib/elixir/test/elixir/fixtures/consolidation/sample.ex +++ b/lib/elixir/test/elixir/fixtures/consolidation/sample.ex @@ -4,4 +4,9 @@ defprotocol Protocol.ConsolidationTest.Sample do @deprecated "Reason" @spec ok(t) :: boolean def ok(term) + + # Not a protocol function. While this is not "officially" supported, + # it does happen in practice, so we need to make sure we preserve + # its signature. + Kernel.def(regular_fun(term), do: term + 1) end diff --git a/lib/elixir/test/elixir/protocol/consolidation_test.exs b/lib/elixir/test/elixir/protocol/consolidation_test.exs index 64f35f88e6a..9a209961521 100644 --- a/lib/elixir/test/elixir/protocol/consolidation_test.exs +++ b/lib/elixir/test/elixir/protocol/consolidation_test.exs @@ -234,6 +234,12 @@ defmodule Protocol.ConsolidationTest do assert %{{:ok, 1} => %{sig: {:strong, nil, clauses}}} = exports assert clauses == [{[none()], dynamic()}] end + + test "handles regular function definitions" do + exports = exports(sample_binary()) + + assert %{{:regular_fun, 1} => %{sig: :none}} = exports + end end test "consolidation errors on missing BEAM files" do