Skip to content

Commit bb871d3

Browse files
committed
Add list normalization for printing
1 parent cb2e036 commit bb871d3

File tree

2 files changed

+50
-7
lines changed

2 files changed

+50
-7
lines changed

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

Lines changed: 34 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -864,7 +864,7 @@ defmodule Module.Types.Descr do
864864
defp list_tail_unfold(:term), do: @not_non_empty_list
865865
defp list_tail_unfold(other), do: Map.delete(other, :list)
866866

867-
defp list_union(dnf1, dnf2), do: dnf1 ++ dnf2
867+
defp list_union(dnf1, dnf2), do: dnf1 ++ (dnf2 -- dnf1)
868868

869869
defp list_intersection(dnf1, dnf2) do
870870
for {list_type1, last_type1, negs1} <- dnf1,
@@ -1076,12 +1076,39 @@ defmodule Module.Types.Descr do
10761076
end
10771077
end
10781078

1079-
# TODO: Eliminate empty lists from the union.
1080-
defp list_normalize(dnf), do: dnf
1081-
# Enum.filter(dnf, fn {list_type, last_type, negs} ->
1082-
# not Enum.any?(negs, fn neg -> subtype?(list_type, neg) end)
1083-
# end)
1084-
# end
1079+
# Eliminate empty lists from the union, and redundant types (that are subtypes of others,
1080+
# or that can be merged with others).
1081+
defp list_normalize(dnf) do
1082+
Enum.reduce(dnf, [], fn {lt, last, negs}, acc ->
1083+
if list_literal_empty?(lt, last, negs),
1084+
do: acc,
1085+
else: add_to_list_normalize(acc, lt, last, negs)
1086+
end)
1087+
end
1088+
1089+
defp list_literal_empty?(list_type, last_type, negations) do
1090+
empty?(list_type) or empty?(last_type) or
1091+
Enum.any?(negations, fn {neg_type, neg_last} ->
1092+
subtype?(list_type, neg_type) and subtype?(last_type, neg_last)
1093+
end)
1094+
end
1095+
1096+
# Inserts a list type into a list of non-subtype list types.
1097+
# If the {list_type, last_type} is a subtype of an existing type, the negs
1098+
# are added to that type.
1099+
# If one list member is a subtype of {list_type, last_type}, it is replaced
1100+
# and its negations are added to the new type.
1101+
# If the type of elements are the same, the last types are merged.
1102+
defp add_to_list_normalize([{t, l, n} | rest], list, last, negs) do
1103+
cond do
1104+
subtype?(list, t) and subtype?(last, l) -> [{t, l, n ++ negs} | rest]
1105+
subtype?(t, list) and subtype?(l, last) -> [{list, last, n ++ negs} | rest]
1106+
equal?(t, list) -> [{t, union(l, last), n ++ negs} | rest]
1107+
true -> [{t, l, n} | add_to_list_normalize(rest, list, last, negs)]
1108+
end
1109+
end
1110+
1111+
defp add_to_list_normalize([], list, last, negs), do: [{list, last, negs}]
10851112

10861113
## Dynamic
10871114
#

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

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1229,6 +1229,22 @@ defmodule Module.Types.DescrTest do
12291229

12301230
assert list(term(), term()) |> to_quoted_string() ==
12311231
"list(term(), term())"
1232+
1233+
# Test normalization
1234+
1235+
# Remove duplicates
1236+
assert union(list(integer()), list(integer())) |> to_quoted_string() == "list(integer())"
1237+
1238+
# Merge subtypes
1239+
assert union(list(float(), pid()), list(number(), pid())) |> to_quoted_string() ==
1240+
"list(float() or integer(), pid())"
1241+
1242+
# Merge last element types
1243+
assert union(list(atom([:ok]), integer()), list(atom([:ok]), float())) |> to_quoted_string() ==
1244+
"list(:ok, float() or integer())"
1245+
1246+
assert union(dynamic(list(integer(), float())), dynamic(list(integer(), pid())))
1247+
|> to_quoted_string() == "dynamic(list(integer(), float() or pid()))"
12321248
end
12331249

12341250
test "tuples" do

0 commit comments

Comments
 (0)