Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Prevent crash on zero arity def in protocol #14191

Merged
merged 7 commits into from
Jan 17, 2025
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
18 changes: 14 additions & 4 deletions lib/elixir/lib/protocol.ex
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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 =
Expand Down Expand Up @@ -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

[
Expand All @@ -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
Expand Down
5 changes: 5 additions & 0 deletions lib/elixir/test/elixir/fixtures/consolidation/sample.ex
Original file line number Diff line number Diff line change
Expand Up @@ -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
6 changes: 6 additions & 0 deletions lib/elixir/test/elixir/protocol/consolidation_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading