Skip to content

Commit 7abcb48

Browse files
authored
Add tuple_values to descr (#13964)
1 parent 7e1aaac commit 7abcb48

File tree

2 files changed

+121
-0
lines changed

2 files changed

+121
-0
lines changed

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

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1981,6 +1981,94 @@ defmodule Module.Types.Descr do
19811981
end)
19821982
end
19831983

1984+
def tuple_values(descr) do
1985+
case :maps.take(:dynamic, descr) do
1986+
:error ->
1987+
if tuple_only?(descr) do
1988+
process_tuples_values(Map.get(descr, :tuple, []))
1989+
else
1990+
:badtuple
1991+
end
1992+
1993+
{dynamic, static} ->
1994+
if tuple_only?(static) and descr_key?(dynamic, :tuple) do
1995+
dynamic(process_tuples_values(Map.get(dynamic, :tuple, [])))
1996+
|> union(process_tuples_values(Map.get(static, :tuple, [])))
1997+
else
1998+
:badtuple
1999+
end
2000+
end
2001+
end
2002+
2003+
defp process_tuples_values(dnf) do
2004+
Enum.reduce(dnf, none(), fn {tag, elements, negs}, acc ->
2005+
union(tuple_values(tag, elements, negs), acc)
2006+
end)
2007+
end
2008+
2009+
defp tuple_values(tag, elements, []) do
2010+
cond do
2011+
Enum.any?(elements, &empty?/1) -> none()
2012+
tag == :open -> term()
2013+
tag == :closed -> Enum.reduce(elements, none(), &union/2)
2014+
end
2015+
end
2016+
2017+
defp tuple_values(_tag, _elements, [{:open, []} | _]), do: none()
2018+
2019+
defp tuple_values(tag, elements, [{neg_tag, neg_elements} | negs]) do
2020+
n = length(elements)
2021+
m = length(neg_elements)
2022+
2023+
if (tag == :closed and n < m) or (neg_tag == :closed and n > m) do
2024+
tuple_values(tag, elements, negs)
2025+
else
2026+
# Those two functions eliminate the negations, transforming into
2027+
# a union of tuples to compute their values.
2028+
values_elements([], tag, elements, neg_elements, negs)
2029+
|> union(values_size(n, m, tag, elements, neg_tag, negs))
2030+
end
2031+
end
2032+
2033+
# This means that there are no more neg_elements to subtract -- end the recursion.
2034+
defp values_elements(_acc, _tag, _elements, [], _), do: none()
2035+
2036+
# Eliminates negations according to tuple content.
2037+
# Subtracts each element of a negative tuple to build a new tuple with the difference.
2038+
# Example: {number(), atom()} and not {float(), :foo} contains types {integer(), :foo}
2039+
# as well as {float(), atom() and not :foo}
2040+
# Same process as tuple_elements_empty?
2041+
defp values_elements(acc, tag, elements, [neg_type | neg_elements], negs) do
2042+
{ty, elements} = List.pop_at(elements, 0, term())
2043+
diff = difference(ty, neg_type)
2044+
2045+
if empty?(diff) do
2046+
none()
2047+
else
2048+
tuple_values(tag, Enum.reverse(acc, [diff | elements]), negs)
2049+
end
2050+
|> union(values_elements([ty | acc], tag, elements, neg_elements, negs))
2051+
end
2052+
2053+
# Eliminates negations according to size
2054+
# Example: {integer(), ...} and not {term(), term(), ...} contains {integer()}
2055+
defp values_size(n, m, tag, elements, neg_tag, negs) do
2056+
if tag == :closed do
2057+
none()
2058+
else
2059+
n..(m - 1)//1
2060+
|> Enum.map(&tuple_values(:closed, tuple_fill(elements, &1), negs))
2061+
|> Enum.reduce(none(), &union/2)
2062+
|> union(
2063+
if neg_tag == :open do
2064+
none()
2065+
else
2066+
tuple_values(tag, tuple_fill(elements, m + 1), negs)
2067+
end
2068+
)
2069+
end
2070+
end
2071+
19842072
defp tuple_pop_index(tag, elements, index) do
19852073
case List.pop_at(elements, index) do
19862074
{nil, _} -> {tag_to_type(tag), %{tuple: [{tag, elements, []}]}}

lib/elixir/test/elixir/module/types/descr_test.exs

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -855,6 +855,39 @@ defmodule Module.Types.DescrTest do
855855
|> equal?(dynamic(open_tuple([term(), boolean()])))
856856
end
857857

858+
test "tuple_values" do
859+
assert tuple_values(integer()) == :badtuple
860+
assert tuple_values(tuple([])) == none()
861+
assert tuple_values(tuple()) == term()
862+
assert tuple_values(open_tuple([integer()])) == term()
863+
assert tuple_values(tuple([integer(), atom()])) == union(integer(), atom())
864+
865+
assert tuple_values(union(tuple([float(), pid()]), tuple([reference()]))) ==
866+
union(float(), union(pid(), reference()))
867+
868+
assert tuple_values(difference(tuple([number(), atom()]), tuple([float(), term()]))) ==
869+
union(integer(), atom())
870+
871+
assert union(tuple([atom([:ok])]), open_tuple([integer()]))
872+
|> difference(open_tuple([term(), term()]))
873+
|> tuple_values() == union(atom([:ok]), integer())
874+
875+
assert tuple_values(difference(tuple([number(), atom()]), tuple([float(), atom([:ok])]))) ==
876+
union(number(), atom())
877+
878+
assert tuple_values(dynamic(tuple())) == dynamic()
879+
assert tuple_values(dynamic(tuple([integer()]))) == dynamic(integer())
880+
881+
assert tuple_values(union(dynamic(tuple([integer()])), tuple([atom()]))) ==
882+
union(dynamic(integer()), atom())
883+
884+
assert tuple_values(union(dynamic(tuple()), integer())) == :badtuple
885+
assert tuple_values(dynamic(union(integer(), tuple([atom()])))) == dynamic(atom())
886+
887+
assert tuple_values(union(dynamic(tuple([integer()])), tuple([integer()])))
888+
|> equal?(integer())
889+
end
890+
858891
test "map_fetch" do
859892
assert map_fetch(term(), :a) == :badmap
860893
assert map_fetch(union(open_map(), integer()), :a) == :badmap

0 commit comments

Comments
 (0)