Skip to content

Commit bc4f917

Browse files
committed
Add singletons
1 parent 9721759 commit bc4f917

2 files changed

Lines changed: 113 additions & 16 deletions

File tree

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

Lines changed: 62 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -270,7 +270,7 @@ defmodule Module.Types.Descr do
270270
end
271271

272272
defp unwrap_domain_tuple(%{tuple: bdd} = descr, transform) when map_size(descr) == 1 do
273-
tuple_normalize(bdd) |> Enum.map(transform)
273+
tuple_bdd_to_dnf(bdd) |> Enum.map(transform)
274274
end
275275

276276
defp unwrap_domain_tuple(descr, _transform) when descr == %{}, do: []
@@ -604,6 +604,56 @@ defmodule Module.Types.Descr do
604604
defp empty_key?(:tuple, value), do: tuple_empty?(value)
605605
defp empty_key?(_, _value), do: false
606606

607+
@doc """
608+
Returns if the type is a singleton.
609+
"""
610+
def singleton?(:term), do: false
611+
def singleton?(descr), do: static_singleton?(Map.get(descr, :dynamic, descr))
612+
613+
defp static_singleton?(:term), do: false
614+
defp static_singleton?(%{optional: _}), do: false
615+
defp static_singleton?(%{list: _}), do: false
616+
defp static_singleton?(%{fun: _}), do: false
617+
defp static_singleton?(descr), do: each_singleton?(descr, [:atom, :bitmap, :map, :tuple], false)
618+
619+
defp each_singleton?(descr, [key | keys], acc) do
620+
case descr do
621+
%{^key => value} ->
622+
case each_singleton?(key, value) do
623+
true when acc == true -> false
624+
true -> each_singleton?(descr, keys, true)
625+
false -> false
626+
:empty -> each_singleton?(descr, keys, acc)
627+
end
628+
629+
%{} ->
630+
each_singleton?(descr, keys, acc)
631+
end
632+
end
633+
634+
defp each_singleton?(_descr, [], acc), do: acc
635+
636+
# Implement for each type
637+
defp each_singleton?(:bitmap, bitmap), do: bitmap == @bit_empty_list
638+
639+
defp each_singleton?(:atom, atoms), do: match?({:union, set} when map_size(set) == 1, atoms)
640+
641+
defp each_singleton?(:tuple, bdd) do
642+
case tuple_bdd_to_dnf(bdd) do
643+
[] -> :empty
644+
[{:closed, entries}] -> Enum.all?(entries, &static_singleton?/1)
645+
_ -> false
646+
end
647+
end
648+
649+
defp each_singleton?(:map, bdd) do
650+
case map_bdd_to_dnf(bdd) do
651+
[] -> :empty
652+
[{:closed, fields, _negs}] -> Enum.all?(fields, fn {_, v} -> static_singleton?(v) end)
653+
_ -> false
654+
end
655+
end
656+
607657
@doc """
608658
Converts a descr to its quoted representation.
609659
@@ -3850,15 +3900,6 @@ defmodule Module.Types.Descr do
38503900
end)
38513901
end
38523902

3853-
# Use heuristics to normalize a map bdd for pretty printing.
3854-
defp map_normalize(bdd) do
3855-
map_bdd_to_dnf(bdd)
3856-
|> Enum.map(fn {tag, fields, negs} ->
3857-
map_eliminate_while_negs_decrease(tag, fields, negs)
3858-
end)
3859-
|> map_fusion()
3860-
end
3861-
38623903
# Continue to eliminate negations while length of list of negs decreases
38633904
defp map_eliminate_while_negs_decrease(tag, fields, []), do: {tag, fields, []}
38643905

@@ -3950,7 +3991,12 @@ defmodule Module.Types.Descr do
39503991
end
39513992

39523993
defp map_to_quoted(bdd, opts) do
3953-
map_normalize(bdd)
3994+
bdd
3995+
|> map_bdd_to_dnf()
3996+
|> Enum.map(fn {tag, fields, negs} ->
3997+
map_eliminate_while_negs_decrease(tag, fields, negs)
3998+
end)
3999+
|> map_fusion()
39544000
|> Enum.map(&map_each_to_quoted(&1, opts))
39554001
end
39564002

@@ -4472,14 +4518,14 @@ defmodule Module.Types.Descr do
44724518
end
44734519

44744520
defp tuple_to_quoted(bdd, opts) do
4475-
tuple_normalize(bdd)
4521+
tuple_bdd_to_dnf(bdd)
44764522
|> tuple_fusion()
44774523
|> Enum.map(&tuple_literal_to_quoted(&1, opts))
44784524
end
44794525

44804526
# Transforms a bdd into a union of tuples with no negations.
44814527
# Note: it is important to compose the results with tuple_dnf_union/2 to avoid duplicates
4482-
defp tuple_normalize(bdd) do
4528+
defp tuple_bdd_to_dnf(bdd) do
44834529
bdd_to_dnf(bdd)
44844530
|> Enum.reduce([], fn {positive_tuples, negative_tuples}, acc ->
44854531
case non_empty_tuple_literals_intersection(positive_tuples) do
@@ -4639,7 +4685,7 @@ defmodule Module.Types.Descr do
46394685
end
46404686

46414687
defp tuple_get(bdd, index) do
4642-
tuple_normalize(bdd)
4688+
tuple_bdd_to_dnf(bdd)
46434689
|> Enum.reduce(none(), fn
46444690
{tag, elements}, acc -> Enum.at(elements, index, tuple_tag_to_type(tag)) |> union(acc)
46454691
end)
@@ -4670,7 +4716,7 @@ defmodule Module.Types.Descr do
46704716
end
46714717

46724718
defp process_tuples_values(bdd) do
4673-
tuple_normalize(bdd)
4719+
tuple_bdd_to_dnf(bdd)
46744720
|> Enum.reduce(none(), fn {tag, elements}, acc ->
46754721
cond do
46764722
Enum.any?(elements, &empty?/1) -> none()
@@ -4808,7 +4854,7 @@ defmodule Module.Types.Descr do
48084854
defp tuple_of_size_at_least_static?(descr, index) do
48094855
case descr do
48104856
%{tuple: bdd} ->
4811-
tuple_normalize(bdd)
4857+
tuple_bdd_to_dnf(bdd)
48124858
|> Enum.all?(fn {_, elements} -> length(elements) >= index end)
48134859

48144860
%{} ->

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

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1222,6 +1222,57 @@ defmodule Module.Types.DescrTest do
12221222
end
12231223
end
12241224

1225+
describe "singleton?" do
1226+
test "non-singleton?" do
1227+
refute singleton?(term())
1228+
refute singleton?(none())
1229+
refute singleton?(dynamic())
1230+
refute singleton?(integer())
1231+
refute singleton?(float())
1232+
refute singleton?(pid())
1233+
refute singleton?(reference())
1234+
refute singleton?(fun(1))
1235+
refute singleton?(non_empty_list(atom([:foo])))
1236+
end
1237+
1238+
@disguised_empty_map closed_map(key: atom([:value]))
1239+
|> difference(open_map(key: atom(), optional: if_set(atom())))
1240+
1241+
test "atoms" do
1242+
assert singleton?(atom([:foo]))
1243+
refute singleton?(atom([:foo, :bar]))
1244+
assert singleton?(atom([:foo]) |> union(@disguised_empty_map))
1245+
refute singleton?(atom() |> difference(atom([:foo])))
1246+
end
1247+
1248+
test "empty list" do
1249+
assert singleton?(empty_list())
1250+
refute singleton?(non_empty_list(term()))
1251+
refute singleton?(union(empty_list(), atom([:foo])))
1252+
assert singleton?(union(empty_list(), @disguised_empty_map))
1253+
end
1254+
1255+
test "maps" do
1256+
assert singleton?(empty_map())
1257+
assert singleton?(closed_map(key: atom([:value])))
1258+
assert singleton?(closed_map(key: atom([:value])) |> union(@disguised_empty_map))
1259+
refute singleton?(closed_map(key: binary()))
1260+
refute singleton?(closed_map(key: if_set(atom([:value]))))
1261+
refute singleton?(open_map())
1262+
refute singleton?(open_map(key: atom([:value])))
1263+
refute singleton?(union(closed_map(key: atom([:value])), closed_map(other: atom([:value]))))
1264+
end
1265+
1266+
test "tuples" do
1267+
assert singleton?(tuple([]))
1268+
assert singleton?(tuple([atom([:foo])]))
1269+
refute singleton?(tuple([binary()]))
1270+
refute singleton?(open_tuple([]))
1271+
refute singleton?(union(tuple([atom([:value])]), tuple([atom([:other_value])])))
1272+
refute singleton?(union(tuple([atom([:value])]), closed_map(other: atom([:value]))))
1273+
end
1274+
end
1275+
12251276
describe "projections" do
12261277
test "booleaness" do
12271278
for type <- [none(), open_map(), negation(boolean()), difference(atom(), boolean())] do

0 commit comments

Comments
 (0)