Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 27 additions & 17 deletions lib/elixir/lib/module/types/descr.ex
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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

Expand All @@ -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)
Expand All @@ -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)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is performing structural equality. Should we add a TODO saying this is a cheap optimization?


defp tuple_to_quoted(dnf) do
dnf
Expand Down Expand Up @@ -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
Expand Down
155 changes: 154 additions & 1 deletion lib/elixir/test/elixir/module/types/descr_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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()}, %{...}}"
Expand Down
Loading