Skip to content

Commit 4d4c15c

Browse files
committed
Optimization attempt for union of maps and tuples
1 parent a42f2e8 commit 4d4c15c

File tree

1 file changed

+71
-8
lines changed

1 file changed

+71
-8
lines changed

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

Lines changed: 71 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -220,9 +220,9 @@ defmodule Module.Types.Descr do
220220
defp union(:bitmap, v1, v2), do: v1 ||| v2
221221
defp union(:dynamic, v1, v2), do: dynamic_union(v1, v2)
222222
defp union(:list, v1, v2), do: list_union(v1, v2)
223-
defp union(:map, v1, v2), do: map_union(v1, v2)
223+
defp union(:map, v1, v2), do: map_or_tuple_union(v1, v2)
224224
defp union(:optional, 1, 1), do: 1
225-
defp union(:tuple, v1, v2), do: tuple_union(v1, v2)
225+
defp union(:tuple, v1, v2), do: map_or_tuple_union(v1, v2)
226226

227227
@doc """
228228
Computes the intersection of two descrs.
@@ -1278,8 +1278,75 @@ defmodule Module.Types.Descr do
12781278

12791279
defp map_only?(descr), do: empty?(Map.delete(descr, :map))
12801280

1281-
# Union is list concatenation
1282-
defp map_union(dnf1, dnf2), do: dnf1 ++ (dnf2 -- dnf1)
1281+
defp map_or_tuple_union(dnf1, dnf2) do
1282+
if optimized = maybe_optimize_union(dnf1, dnf2) do
1283+
optimized
1284+
else
1285+
# Union is list concatenation
1286+
# Removes duplicates in union, which should trickle to other operations.
1287+
# This is a cheap optimization that relies on structural equality.
1288+
dnf1 ++ (dnf2 -- dnf1)
1289+
end
1290+
end
1291+
1292+
defp maybe_optimize_union([{tag1, pos1, []}] = dnf1, [{tag2, pos2, []}] = dnf2) do
1293+
# Avoid the explosion for unions of very similar maps or tuples,
1294+
# by returning only one if it contains the other
1295+
# e.g. %{a: integer()} or %{..., a: term()} -> %{..., a: term()}
1296+
cond do
1297+
map_or_tuple_simple_subtype?(tag1, pos1, tag2, pos2) -> dnf2
1298+
map_or_tuple_simple_subtype?(tag2, pos2, tag1, pos1) -> dnf1
1299+
true -> nil
1300+
end
1301+
end
1302+
1303+
defp maybe_optimize_union(_, _), do: nil
1304+
1305+
defp map_or_tuple_simple_subtype?(_, _, :open, pos2) when pos2 == %{}, do: true
1306+
1307+
defp map_or_tuple_simple_subtype?(:closed, pos1, :closed, pos2)
1308+
when map_size(pos1) == map_size(pos2) do
1309+
all_key_simple_subtypes?(pos1, pos2)
1310+
end
1311+
1312+
defp map_or_tuple_simple_subtype?(_, pos1, :open, pos2) when map_size(pos1) >= map_size(pos2) do
1313+
all_key_simple_subtypes?(pos1, pos2)
1314+
end
1315+
1316+
defp map_or_tuple_simple_subtype?(_, _, _, _), do: false
1317+
1318+
defp all_key_simple_subtypes?(pos1, pos2) do
1319+
Enum.all?(pos1, fn {key, type1} ->
1320+
case pos2 do
1321+
%{^key => type2} -> simple_subtype?(type1, type2)
1322+
_ -> false
1323+
end
1324+
end)
1325+
end
1326+
1327+
defp simple_subtype?(_, :term), do: true
1328+
defp simple_subtype?(same, same), do: true
1329+
1330+
defp simple_subtype?(type1, type2) when map_size(type1) == 1 and map_size(type2) == 1 do
1331+
case {type1, type2} do
1332+
{%{atom: _}, %{atom: {:negation, neg}}} when neg == %{} ->
1333+
true
1334+
1335+
{%{bitmap: bitmap1}, %{bitmap: bitmap2}} ->
1336+
(bitmap1 &&& bitmap2) === bitmap2
1337+
1338+
{%{map: [{tag1, pos1, []}]}, %{map: [{tag2, pos2, []}]}} ->
1339+
map_or_tuple_simple_subtype?(tag1, pos1, tag2, pos2)
1340+
1341+
{%{tuple: [{tag1, pos1, []}]}, %{tuple: [{tag2, pos2, []}]}} ->
1342+
map_or_tuple_simple_subtype?(tag1, pos1, tag2, pos2)
1343+
1344+
_ ->
1345+
false
1346+
end
1347+
end
1348+
1349+
defp simple_subtype?(_, _), do: false
12831350

12841351
# Given two unions of maps, intersects each pair of maps.
12851352
defp map_intersection(dnf1, dnf2) do
@@ -2049,10 +2116,6 @@ defmodule Module.Types.Descr do
20492116
end
20502117
end
20512118

2052-
# Removes duplicates in union, which should trickle to other operations.
2053-
# This is a cheap optimization that relies on structural equality.
2054-
defp tuple_union(left, right), do: left ++ (right -- left)
2055-
20562119
defp tuple_to_quoted(dnf, opts) do
20572120
dnf
20582121
|> tuple_simplify()

0 commit comments

Comments
 (0)