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
10 changes: 8 additions & 2 deletions lib/elixir/lib/module/types/descr.ex
Original file line number Diff line number Diff line change
Expand Up @@ -1603,15 +1603,21 @@ defmodule Module.Types.Descr do

defp fun_normalize(%{fun: {:union, bdds}}, arity) do
case :maps.take(arity, bdds) do
{bdd, _rest} ->
{bdd, rest} ->
{domain, arrows} =
Enum.reduce(fun_bdd_to_pos_dnf(arity, bdd), {term(), []}, fn pos_funs,
{domain, arrows} ->
{bare_intersection(domain, fetch_domain(pos_funs)), [pos_funs | arrows]}
end)

if arrows == [] do
{:badarity, :maps.keys(bdds)}
# The function is empty at the requested arity. Report the *other*
# arities (never the called one, which would be self-contradictory),
# or :badfun when there are none, i.e. the function is empty.
case :maps.keys(rest) do
[] -> :badfun
other -> {:badarity, other}
end
else
{:ok, domain, arrows}
end
Expand Down
22 changes: 22 additions & 0 deletions lib/elixir/test/elixir/module/types/descr_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -1378,6 +1378,28 @@ defmodule Module.Types.DescrTest do
assert fun_apply(fun_mixed, [integer()]) == {:badarity, [1, 2]}
assert fun_apply(fun_mixed, [integer(), atom()]) == {:badarity, [2, 1]}

# A function that is empty at the called arity is :badfun, not a
# self-contradictory {:badarity, [called_arity]}.
empty_fun =
opt_difference(fun([opt_union(integer(), float())], atom()), fun([integer()], atom()))

assert empty?(empty_fun)
assert fun_apply(empty_fun, [integer()]) == :badfun

# When the function is empty at the called arity but usable at another, the
# badarity must list only the *other* arity, never the (empty) called one.
# `empty_fun` keeps a structured empty arity-1 entry, which union preserves,
# so `usable_at_2` is equal? to a plain 2-arity function yet carries it.
usable_at_2 = opt_union(empty_fun, fun([integer(), atom()], boolean()))

refute empty?(usable_at_2)
assert equal?(usable_at_2, fun([integer(), atom()], boolean()))

# Congruent with the normalized form, and excludes the empty arity 1.
assert fun_apply(usable_at_2, [integer()]) == {:badarity, [2]}
assert fun_apply(fun([integer(), atom()], boolean()), [integer()]) == {:badarity, [2]}
assert fun_apply(usable_at_2, [integer(), atom()]) == {:ok, boolean()}

# Function intersection tests (no overlap)
fun0 = opt_intersection(fun([integer()], atom()), fun([float()], binary()))
assert fun_apply(fun0, [integer()]) == {:ok, atom()}
Expand Down
Loading