From eab3b4d0111d7aa10c2a6029a5fa3520d928f2de Mon Sep 17 00:00:00 2001 From: Guillaume Duboc Date: Tue, 12 Nov 2024 18:11:29 +0100 Subject: [PATCH] Improve perf of tuple_difference --- lib/elixir/lib/module/types/descr.ex | 44 +++-- .../test/elixir/module/types/descr_test.exs | 155 +++++++++++++++++- 2 files changed, 181 insertions(+), 18 deletions(-) diff --git a/lib/elixir/lib/module/types/descr.ex b/lib/elixir/lib/module/types/descr.ex index 74200c4cf8f..472296169c7 100644 --- a/lib/elixir/lib/module/types/descr.ex +++ b/lib/elixir/lib/module/types/descr.ex @@ -1792,10 +1792,8 @@ defmodule Module.Types.Descr do {tag2, elements2, negs2} <- dnf2, reduce: [] do acc -> - try do - {tag, fields} = tuple_literal_intersection(tag1, elements1, tag2, elements2) - [{tag, fields, negs1 ++ negs2} | acc] - catch + case tuple_literal_intersection(tag1, elements1, tag2, elements2) do + {tag, fields} -> [{tag, fields, negs1 ++ negs2} | acc] :empty -> acc end end @@ -1811,13 +1809,21 @@ defmodule Module.Types.Descr do cond do (tag1 == :closed and n < m) or (tag2 == :closed and n > m) -> - throw(:empty) + :empty tag1 == :open and tag2 == :open -> - {:open, zip_non_empty_intersection!(elements1, elements2, [])} + try do + {:open, zip_non_empty_intersection!(elements1, elements2, [])} + catch + :empty -> :empty + end true -> - {:closed, zip_non_empty_intersection!(elements1, elements2, [])} + try do + {:closed, zip_non_empty_intersection!(elements1, elements2, [])} + catch + :empty -> :empty + end end end @@ -1832,14 +1838,17 @@ defmodule Module.Types.Descr do defp tuple_difference(dnf1, dnf2) do Enum.reduce(dnf2, dnf1, fn {tag2, elements2, negs2}, dnf1 -> Enum.reduce(dnf1, [], fn {tag1, elements1, negs1}, acc -> - acc = [{tag1, elements1, [{tag2, elements2} | negs1]}] ++ acc - - Enum.reduce(negs2, acc, fn {neg_tag2, neg_elements2}, acc -> - try do - {tag, fields} = tuple_literal_intersection(tag1, elements1, neg_tag2, neg_elements2) - [{tag, fields, negs1}] ++ acc - catch - :empty -> acc + # Prune negations that have no values in common + acc = + case tuple_literal_intersection(tag1, elements1, tag2, elements2) do + :empty -> [{tag1, elements1, negs1}] ++ acc + _ -> [{tag1, elements1, [{tag2, elements2} | negs1]}] ++ acc + end + + Enum.reduce(negs2, acc, fn {neg_tag2, neg_elements2}, inner_acc -> + case tuple_literal_intersection(tag1, elements1, neg_tag2, neg_elements2) do + :empty -> inner_acc + {tag, fields} -> [{tag, fields, negs1} | inner_acc] end end) end) @@ -1850,7 +1859,8 @@ defmodule Module.Types.Descr do end end - defp tuple_union(left, right), do: left ++ right + # Removes duplicates in union, which should trickle to other operations. + defp tuple_union(left, right), do: left ++ (right -- left) defp tuple_to_quoted(dnf) do dnf @@ -1902,7 +1912,7 @@ defmodule Module.Types.Descr do Enum.all?(dnf, fn {tag, pos, negs} -> tuple_empty?(tag, pos, negs) end) end - # No negations, so not empty + # No negations, so not empty unless there's an empty type defp tuple_empty?(_, pos, []), do: Enum.any?(pos, &empty?/1) # Open empty negation makes it empty defp tuple_empty?(_, _, [{:open, []} | _]), do: true diff --git a/lib/elixir/test/elixir/module/types/descr_test.exs b/lib/elixir/test/elixir/module/types/descr_test.exs index 3e74d5d2aae..acc52c20c35 100644 --- a/lib/elixir/test/elixir/module/types/descr_test.exs +++ b/lib/elixir/test/elixir/module/types/descr_test.exs @@ -334,6 +334,159 @@ defmodule Module.Types.DescrTest do assert difference(tuple(), open_tuple([term(), term()])) |> equal?(union(tuple([term()]), tuple([]))) + + # Large difference with no duplicates + descr1 = %{ + dynamic: %{ + atom: {:union, %{reset: [], ignored: []}}, + tuple: [ + {:closed, [%{atom: {:union, %{font_style: []}}}, %{atom: {:union, %{italic: []}}}], + []} + ] + } + } + + descr2 = %{ + dynamic: %{ + atom: {:union, %{reset: [], ignored: []}}, + tuple: [ + {:closed, [%{atom: {:union, %{font_weight: []}}}, %{atom: {:union, %{bold: []}}}], + []}, + {:closed, [%{atom: {:union, %{font_style: []}}}, %{atom: {:union, %{italic: []}}}], + []}, + {:closed, [%{atom: {:union, %{font_weight: []}}}, %{atom: {:union, %{clear: []}}}], + []}, + {:closed, + [ + %{atom: {:union, %{text_decoration: []}}}, + %{atom: {:union, %{clear_decoration: []}}} + ], []}, + {:closed, + [ + %{atom: {:union, %{text_decoration: []}}}, + %{atom: {:union, %{remove_decoration: []}}} + ], []}, + {:closed, + [ + %{atom: {:union, %{text_decoration: []}}}, + %{atom: {:union, %{undo_decoration: []}}} + ], []}, + {:closed, + [ + %{atom: {:union, %{text_decoration: []}}}, + %{atom: {:union, %{default_decoration: []}}} + ], []}, + {:closed, + [ + %{atom: {:union, %{foreground_color: []}}}, + %{atom: {:union, %{clear_foreground: []}}} + ], []}, + {:closed, + [ + %{atom: {:union, %{foreground_color: []}}}, + %{atom: {:union, %{remove_foreground: []}}} + ], []}, + {:closed, + [ + %{atom: {:union, %{foreground_color: []}}}, + %{atom: {:union, %{undo_foreground: []}}} + ], []}, + {:closed, + [ + %{atom: {:union, %{foreground_color: []}}}, + %{atom: {:union, %{default_foreground: []}}} + ], []}, + {:closed, + [ + %{atom: {:union, %{background_color: []}}}, + %{atom: {:union, %{clear_background: []}}} + ], []}, + {:closed, + [ + %{atom: {:union, %{background_color: []}}}, + %{atom: {:union, %{remove_background: []}}} + ], []}, + {:closed, + [ + %{atom: {:union, %{background_color: []}}}, + %{atom: {:union, %{undo_background: []}}} + ], []}, + {:closed, + [ + %{atom: {:union, %{background_color: []}}}, + %{atom: {:union, %{default_background: []}}} + ], []}, + {:closed, + [ + %{atom: {:union, %{text_decoration: []}}}, + %{atom: {:union, %{overline: []}}} + ], []}, + {:closed, + [ + %{atom: {:union, %{text_decoration: []}}}, + %{atom: {:union, %{clear_text: []}}} + ], []}, + {:closed, + [ + %{atom: {:union, %{text_decoration: []}}}, + %{atom: {:union, %{remove_text: []}}} + ], []}, + {:closed, + [ + %{atom: {:union, %{text_decoration: []}}}, + %{atom: {:union, %{undo_text: []}}} + ], []}, + {:closed, + [ + %{atom: {:union, %{text_decoration: []}}}, + %{atom: {:union, %{default_text: []}}} + ], []}, + {:closed, + [ + %{atom: {:union, %{foreground_color: []}}}, + %{atom: {:union, %{clear_fg: []}}} + ], []}, + {:closed, + [ + %{atom: {:union, %{foreground_color: []}}}, + %{atom: {:union, %{remove_fg: []}}} + ], []}, + {:closed, + [ + %{atom: {:union, %{foreground_color: []}}}, + %{atom: {:union, %{undo_fg: []}}} + ], []}, + {:closed, + [ + %{atom: {:union, %{foreground_color: []}}}, + %{atom: {:union, %{default_fg: []}}} + ], []}, + {:closed, + [ + %{atom: {:union, %{background_color: []}}}, + %{atom: {:union, %{clear_bg: []}}} + ], []}, + {:closed, + [ + %{atom: {:union, %{background_color: []}}}, + %{atom: {:union, %{remove_bg: []}}} + ], []}, + {:closed, + [ + %{atom: {:union, %{background_color: []}}}, + %{atom: {:union, %{undo_bg: []}}} + ], []}, + {:closed, + [ + %{atom: {:union, %{background_color: []}}}, + %{atom: {:union, %{default_bg: []}}} + ], []} + ] + } + } + + assert subtype?(descr1, descr2) + refute subtype?(descr2, descr1) end test "map" do @@ -1243,7 +1396,7 @@ defmodule Module.Types.DescrTest do "{integer(), atom()} or {atom(), ...}" assert difference(tuple([integer(), atom()]), open_tuple([atom()])) |> to_quoted_string() == - "{integer(), atom()} and not {atom(), ...}" + "{integer(), atom()}" assert tuple([closed_map(a: integer()), open_map()]) |> to_quoted_string() == "{%{a: integer()}, %{...}}"