diff --git a/lib/elixir/lib/module/types/descr.ex b/lib/elixir/lib/module/types/descr.ex index 426d6051665..5150a70d57d 100644 --- a/lib/elixir/lib/module/types/descr.ex +++ b/lib/elixir/lib/module/types/descr.ex @@ -5098,10 +5098,8 @@ defmodule Module.Types.Descr do zip_empty_intersection?(elements, neg_elements) do [{tag, elements}] else - tuple_dnf_union( - tuple_elim_size(n, m, tag, elements, neg_tag), + tuple_elim_size(n, m, tag, elements, neg_tag) ++ tuple_elim_content([], tag, elements, neg_elements) - ) end end @@ -5179,19 +5177,6 @@ defmodule Module.Types.Descr do end) end - # Prefer the smaller on the left - defp tuple_dnf_union(dnf1, dnf2) do - # Union of tuple DNFs is just concatenation, - # but we do our best to remove duplicates. - with [tuple1] <- dnf1, - [tuple2] <- dnf2, - optimized when optimized != nil <- maybe_optimize_tuple_union(tuple1, tuple2) do - [optimized] - else - _ -> dnf1 ++ (dnf2 -- dnf1) - end - end - defp tuple_union( bdd_leaf(tag1, elements1) = tuple1, bdd_leaf(tag2, elements2) = tuple2 @@ -5285,23 +5270,17 @@ defmodule Module.Types.Descr do end # Transforms a bdd into a union of tuples with no negations. - # Note: it is important to compose the results with - # tuple_dnf_union/2 to avoid duplicates defp tuple_bdd_to_dnf_no_negations(bdd) do bdd_to_dnf(bdd) - |> Enum.reduce([], fn {pos, negs}, acc -> + |> Enum.flat_map(fn {pos, negs} -> case non_empty_tuple_literals_intersection(pos) do - :empty -> - acc - - {tag, elements} -> - if tuple_line_empty?(tag, elements, negs) do - acc - else - tuple_eliminate_negations(tag, elements, negs) |> tuple_dnf_union(acc) - end + :empty -> [] + {tag, elements} -> tuple_eliminate_negations(tag, elements, negs) end end) + # We want to avoid each_singleton? from failing, + # so we remove contiguous duplicates (cheaper than uniq) + |> Enum.dedup() end defp tuple_bdd_to_dnf_with_negations(bdd) do diff --git a/lib/elixir/test/elixir/module/types/descr_test.exs b/lib/elixir/test/elixir/module/types/descr_test.exs index 2779810e30a..a931fda63fc 100644 --- a/lib/elixir/test/elixir/module/types/descr_test.exs +++ b/lib/elixir/test/elixir/module/types/descr_test.exs @@ -1422,6 +1422,16 @@ defmodule Module.Types.DescrTest do refute singleton?(open_tuple([])) refute singleton?(union(tuple([atom([:value])]), tuple([atom([:other_value])]))) refute singleton?(union(tuple([atom([:value])]), closed_map(other: atom([:value])))) + + # Both BDD lines produce the same singleton tuple, so the tuple DNF must not duplicate it. + a = tuple([union(integer(), atom([:ok])), atom([:x])]) + b = tuple([integer(), atom([:x, :y])]) + c = tuple([integer(), union(atom([:x]), binary())]) + + t = union(difference(a, b), difference(a, c)) + # Semantically t ~= {:ok, :x}, confirmed by equal? + assert equal?(t, tuple([atom([:ok]), atom([:x])])) + assert singleton?(t) end end