Skip to content

Commit aec5d53

Browse files
committed
Optimize tuple_fetch, closes #15154
1 parent 445895c commit aec5d53

1 file changed

Lines changed: 92 additions & 8 deletions

File tree

lib/elixir/lib/module/types/descr.ex

Lines changed: 92 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2823,9 +2823,6 @@ defmodule Module.Types.Descr do
28232823
{fields_from_reverse_list(fields), domain, dynamic?}
28242824
end
28252825

2826-
defp tuple_tag_to_type(:open), do: term_or_optional()
2827-
defp tuple_tag_to_type(:closed), do: not_set()
2828-
28292826
# Gets the default type associated to atom keys in a map.
28302827
defp map_key_tag_to_type(:open), do: term_or_optional()
28312828
defp map_key_tag_to_type(:closed), do: not_set()
@@ -5481,18 +5478,105 @@ defmodule Module.Types.Descr do
54815478
defp tuple_fetch_static(descr, index) when is_integer(index) do
54825479
case descr do
54835480
:term -> {true, term()}
5484-
%{tuple: tuple} -> tuple_get(tuple, index) |> pop_optional_static()
5481+
%{tuple: bdd_leaf(tag, elements)} -> tuple_fetch_element(elements, index, tag)
5482+
%{tuple: bdd} -> tuple_bdd_fetch_static(bdd, index)
54855483
%{} -> {false, none()}
54865484
end
54875485
end
54885486

5489-
defp tuple_get(bdd, index) do
5490-
tuple_bdd_to_dnf_no_negations(bdd)
5491-
|> Enum.reduce(none(), fn
5492-
{tag, elements}, acc -> Enum.at(elements, index, tuple_tag_to_type(tag)) |> union(acc)
5487+
defp tuple_bdd_fetch_static(bdd, index) do
5488+
bdd
5489+
|> tuple_bdd_to_dnf_with_negations()
5490+
|> Enum.reduce({false, none()}, fn
5491+
# Optimization: if there are no negatives
5492+
{tag, elements, []}, {acc_optional?, acc_descr} ->
5493+
{optional?, descr} = tuple_fetch_element(elements, index, tag)
5494+
{optional? or acc_optional?, union(descr, acc_descr)}
5495+
5496+
{tag, elements, negs}, acc ->
5497+
{_, value, bdd} = tuple_take_element(elements, index, tag)
5498+
5499+
negs
5500+
|> tuple_split_negative(index, value, bdd)
5501+
|> Enum.reduce(acc, fn {value, _}, {acc_optional?, acc_descr} ->
5502+
{optional?, descr} = pop_optional_static(value)
5503+
{optional? or acc_optional?, union(descr, acc_descr)}
5504+
end)
54935505
end)
5506+
catch
5507+
:open -> {true, term()}
54945508
end
54955509

5510+
# Remove negatives:
5511+
# {t, s} \ {t₁, s₁} = {t \ t₁, s} ∪ {t ∩ t₁, s \ s₁}
5512+
defp tuple_split_negative(negs, index, value, bdd) do
5513+
Enum.reduce(negs, [{value, bdd}], fn
5514+
{:open, []}, _acc ->
5515+
throw(:empty)
5516+
5517+
{neg_tag, neg_elements}, acc ->
5518+
{found?, neg_value, neg_bdd} = tuple_take_element(neg_elements, index, neg_tag)
5519+
5520+
if not found? and neg_tag == :open do
5521+
# In case the tuple is open, t \ t₁ is always empty,
5522+
# t ∩ t₁ is always t, so we just need to deal with the bdd.
5523+
Enum.reduce(acc, [], fn {value, bdd}, acc ->
5524+
diff_bdd = tuple_difference(bdd, neg_bdd)
5525+
5526+
if tuple_empty?(diff_bdd) do
5527+
acc
5528+
else
5529+
[{value, diff_bdd} | acc]
5530+
end
5531+
end)
5532+
else
5533+
Enum.reduce(acc, [], fn {value, bdd}, acc ->
5534+
# If the negative tag is closed, then they are likely disjoint,
5535+
# so we can drastically cut down the amount of operations.
5536+
if neg_tag == :closed and tuple_empty?(tuple_intersection(bdd, neg_bdd)) do
5537+
[{value, bdd} | acc]
5538+
else
5539+
intersection_value = intersection(value, neg_value)
5540+
5541+
if empty?(intersection_value) do
5542+
[{value, bdd} | acc]
5543+
else
5544+
diff_bdd = tuple_difference(bdd, neg_bdd)
5545+
5546+
if tuple_empty?(diff_bdd) do
5547+
prepend_pair_unless_empty_diff(value, neg_value, bdd, acc)
5548+
else
5549+
acc = [{intersection_value, diff_bdd} | acc]
5550+
prepend_pair_unless_empty_diff(value, neg_value, bdd, acc)
5551+
end
5552+
end
5553+
end
5554+
end)
5555+
end
5556+
end)
5557+
catch
5558+
:empty -> []
5559+
end
5560+
5561+
defp tuple_fetch_element([], _, :open), do: {true, term()}
5562+
defp tuple_fetch_element([], _, :closed), do: {true, none()}
5563+
defp tuple_fetch_element([h | _], 0, _tag), do: {false, h}
5564+
defp tuple_fetch_element([_ | t], i, tag), do: tuple_fetch_element(t, i - 1, tag)
5565+
5566+
defp tuple_take_element(elements, index, tag) do
5567+
case do_tuple_take_element(elements, index, []) do
5568+
:error -> {false, tuple_tag_to_type(tag), tuple_new(tag, elements)}
5569+
{value, elements} -> {true, value, tuple_new(tag, elements)}
5570+
end
5571+
end
5572+
5573+
defp do_tuple_take_element([], _, _), do: :error
5574+
defp do_tuple_take_element([h | t], 0, acc), do: {h, Enum.reverse(acc, t)}
5575+
defp do_tuple_take_element([h | t], i, acc), do: do_tuple_take_element(t, i - 1, [h | acc])
5576+
5577+
defp tuple_tag_to_type(:open), do: term_or_optional()
5578+
defp tuple_tag_to_type(:closed), do: none()
5579+
54965580
@doc """
54975581
Returns all of the values that are part of a tuple.
54985582
"""

0 commit comments

Comments
 (0)