Skip to content

Commit 79c2842

Browse files
committed
Convert list_proper? into list_of
1 parent 0e3465d commit 79c2842

File tree

2 files changed

+113
-42
lines changed

2 files changed

+113
-42
lines changed

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

Lines changed: 95 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1879,45 +1879,113 @@ defmodule Module.Types.Descr do
18791879
defp list_top?(_), do: false
18801880

18811881
@doc """
1882-
Checks if a list type is a proper list (terminated by empty list).
1882+
Returns the element type of a list, assuming the list is proper.
1883+
1884+
It returns a two-element tuple. The first element dictates the
1885+
empty list type. The second element returns the value type.
1886+
1887+
{:static or :dynamic or nil, t() or nil}
1888+
1889+
If the value is `nil`, it means that component is missing.
1890+
However, both cannot be `nil` together. In such cases,
1891+
we return `:badproperlist`.
18831892
"""
1884-
def list_proper?(:term), do: false
1893+
def list_of(:term), do: :badproperlist
18851894

1886-
def list_proper?(descr) do
1887-
{dynamic, static} = Map.pop(descr, :dynamic, descr)
1895+
def list_of(descr) do
1896+
case :maps.take(:dynamic, descr) do
1897+
:error ->
1898+
with {empty_list, value} <- list_of_static(descr) do
1899+
if empty?(value) and empty_list == nil do
1900+
:badproperlist
1901+
else
1902+
{empty_list, value}
1903+
end
1904+
end
18881905

1889-
# A list is proper if it's either the empty list alone, or all non-empty
1890-
# list types have tails that are subtypes of empty list.
1891-
case static do
1892-
%{list: bdd} ->
1893-
Map.get(static, :bitmap, @bit_empty_list) == @bit_empty_list and
1894-
empty?(Map.drop(static, [:list, :bitmap])) and
1895-
list_bdd_to_pos_dnf(bdd)
1896-
|> Enum.all?(fn {_list, last, _negs} -> subtype?(last, @empty_list) end)
1906+
{dynamic, static} ->
1907+
with {empty_list, static_value} <- list_of_static(static) do
1908+
empty_list =
1909+
case dynamic do
1910+
%{bitmap: bitmap} when (bitmap &&& @bit_empty_list) != 0 -> :dynamic
1911+
_ -> empty_list
1912+
end
18971913

1898-
_ when static == %{bitmap: @bit_empty_list} ->
1899-
true
1914+
dynamic_value =
1915+
case dynamic do
1916+
:term ->
1917+
dynamic()
1918+
1919+
%{list: bdd} ->
1920+
Enum.reduce(list_bdd_to_pos_dnf(bdd), none(), fn {list, last, _negs}, acc ->
1921+
if last == @empty_list or subtype?(last, @empty_list) do
1922+
union(acc, list)
1923+
else
1924+
acc
1925+
end
1926+
end)
1927+
1928+
%{} ->
1929+
none()
1930+
end
19001931

1901-
%{} ->
1902-
# Dynamic requires only the empty list or a single proper list
1903-
empty?(static) and
1904-
case dynamic do
1905-
:term ->
1906-
true
1932+
if empty?(dynamic_value) do
1933+
# list_bdd_to_pos_dnf guarantees the lists actually exists,
1934+
# so we can match on none() rather than empty.
1935+
if empty_list == nil do
1936+
:badproperlist
1937+
else
1938+
{empty_list, nil}
1939+
end
1940+
else
1941+
{empty_list, union(static_value, dynamic(dynamic_value))}
1942+
end
1943+
end
1944+
end
1945+
end
19071946

1908-
%{bitmap: bitmap} when (bitmap &&& @bit_empty_list) != 0 ->
1909-
true
1947+
defp list_of_static(descr) do
1948+
case descr do
1949+
%{bitmap: @bit_empty_list} ->
1950+
case empty?(Map.drop(descr, [:bitmap, :list])) do
1951+
true -> list_of_static(descr, :static)
1952+
false -> :badproperlist
1953+
end
19101954

1911-
%{list: bdd} ->
1912-
list_bdd_to_pos_dnf(bdd)
1913-
|> Enum.any?(fn {_list, last, _negs} -> subtype?(last, @empty_list) end)
1955+
%{bitmap: _} ->
1956+
:badproperlist
19141957

1915-
%{} ->
1916-
false
1958+
%{} ->
1959+
case empty?(Map.delete(descr, :list)) do
1960+
true -> list_of_static(descr, nil)
1961+
false -> :badproperlist
1962+
end
1963+
end
1964+
end
1965+
1966+
# A list is proper if it's either the empty list alone, or all non-empty
1967+
# list types have tails that are subtypes of empty list.
1968+
defp list_of_static(%{list: bdd}, empty_list) do
1969+
try do
1970+
result =
1971+
Enum.reduce(list_bdd_to_pos_dnf(bdd), none(), fn {list, last, _negs}, acc ->
1972+
if last == @empty_list or subtype?(last, @empty_list) do
1973+
union(acc, list)
1974+
else
1975+
throw(:badproperlist)
19171976
end
1977+
end)
1978+
1979+
{empty_list, result}
1980+
catch
1981+
:badproperlist -> :badproperlist
19181982
end
19191983
end
19201984

1985+
defp list_of_static(%{}, empty_list) do
1986+
{empty_list, none()}
1987+
end
1988+
19211989
defp list_intersection(bdd_leaf(list1, last1), bdd_leaf(list2, last2)) do
19221990
try do
19231991
list = non_empty_intersection!(list1, list2)

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

Lines changed: 18 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1268,26 +1268,29 @@ defmodule Module.Types.DescrTest do
12681268
assert list_hd(non_empty_list(atom(), negation(list(term(), term())))) == {:ok, atom()}
12691269
end
12701270

1271-
test "list_proper?" do
1272-
refute list_proper?(term())
1273-
refute list_proper?(none())
1274-
assert list_proper?(empty_list())
1275-
assert list_proper?(non_empty_list(integer()))
1276-
refute list_proper?(non_empty_list(integer(), atom()))
1277-
refute list_proper?(non_empty_list(integer(), term()))
1278-
assert list_proper?(non_empty_list(integer(), list(term())))
1279-
refute list_proper?(list(integer()) |> union(list(integer(), integer())))
1280-
assert list_proper?(dynamic(list(integer())))
1281-
assert list_proper?(dynamic(list(integer(), atom())))
1282-
refute list_proper?(dynamic(non_empty_list(integer(), atom())))
1283-
1284-
# An empty list
1271+
test "list_of" do
1272+
assert list_of(term()) == :badproperlist
1273+
assert list_of(none()) == :badproperlist
1274+
assert list_of(empty_list()) == {:static, none()}
1275+
assert list_of(union(empty_list(), integer())) == :badproperlist
1276+
assert list_of(non_empty_list(integer())) == {nil, integer()}
1277+
assert list_of(non_empty_list(integer(), atom())) == :badproperlist
1278+
assert list_of(non_empty_list(integer(), term())) == :badproperlist
1279+
assert list_of(non_empty_list(integer(), list(term()))) == {nil, term()}
1280+
assert list_of(list(integer()) |> union(list(integer(), integer()))) == :badproperlist
1281+
assert list_of(list(integer()) |> union(integer())) == :badproperlist
1282+
assert list_of(dynamic(list(integer()))) == {:dynamic, dynamic(integer())}
1283+
assert list_of(dynamic(list(integer(), atom()))) == {:dynamic, nil}
1284+
assert list_of(dynamic(non_empty_list(integer(), atom()))) == :badproperlist
1285+
assert list_of(dynamic(union(empty_list(), integer()))) == {:dynamic, nil}
1286+
1287+
# A list that the difference resolves to nothing
12851288
list_with_tail =
12861289
non_empty_list(atom(), union(integer(), empty_list()))
12871290
|> difference(non_empty_list(atom([:ok]), integer()))
12881291
|> difference(non_empty_list(atom(), term()))
12891292

1290-
assert list_proper?(list_with_tail)
1293+
assert list_of(list_with_tail) == :badproperlist
12911294
end
12921295

12931296
test "list_tl" do

0 commit comments

Comments
 (0)