diff --git a/lib/elixir/lib/module/types/descr.ex b/lib/elixir/lib/module/types/descr.ex index bf63ca6d6c7..5150a70d57d 100644 --- a/lib/elixir/lib/module/types/descr.ex +++ b/lib/elixir/lib/module/types/descr.ex @@ -5278,6 +5278,9 @@ defmodule Module.Types.Descr do {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