From 7feaf6e871933dce3e65d26191e9f8c24728d5d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Thu, 16 Jul 2026 15:39:59 +0200 Subject: [PATCH 1/2] Optimize tuple emptiness checks Short-circuit disjoint and subtype tuple elements before exploring the general difference branches. Assisted-by: Codex:GPT-5.6 --- lib/elixir/lib/module/types/descr.ex | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/lib/elixir/lib/module/types/descr.ex b/lib/elixir/lib/module/types/descr.ex index 00552078cc4..457f5458657 100644 --- a/lib/elixir/lib/module/types/descr.ex +++ b/lib/elixir/lib/module/types/descr.ex @@ -5001,15 +5001,22 @@ defmodule Module.Types.Descr do # Handles the case where {tag, elements} is an open tuple, like {:open, []} {ty, elements} = List.pop_at(elements, 0, term()) - # In this case, there is no intersection between the positive and this negative. - # So we should just "go next" - diff = bare_difference(ty, neg_type) meet = bare_intersection(ty, neg_type) - (empty_seen?(diff, seen) or - tuple_line_empty?(tag, Enum.reverse(acc_meet, [diff | elements]), negs, seen)) and - (empty_seen?(meet, seen) or - tuple_elements_empty?([meet | acc_meet], tag, elements, neg_elements, negs, seen)) + if empty_seen?(meet, seen) do + # This negative tuple is disjoint from the current line at this element. + tuple_line_empty?(tag, Enum.reverse(acc_meet, [ty | elements]), negs, seen) + else + diff = bare_difference(ty, neg_type) + + if empty_seen?(diff, seen) do + # The element is a subtype of the negative element, so their intersection is ty. + tuple_elements_empty?([ty | acc_meet], tag, elements, neg_elements, negs, seen) + else + tuple_line_empty?(tag, Enum.reverse(acc_meet, [diff | elements]), negs, seen) and + tuple_elements_empty?([meet | acc_meet], tag, elements, neg_elements, negs, seen) + end + end end # Determines if the set difference is empty when: From bc34a8f4b80c878293cfe385b2e609a8069684d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Thu, 16 Jul 2026 15:59:07 +0200 Subject: [PATCH 2/2] Optimize maps too --- lib/elixir/lib/module/types/descr.ex | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/lib/elixir/lib/module/types/descr.ex b/lib/elixir/lib/module/types/descr.ex index 457f5458657..6ae3eed9a28 100644 --- a/lib/elixir/lib/module/types/descr.ex +++ b/lib/elixir/lib/module/types/descr.ex @@ -4432,13 +4432,22 @@ defmodule Module.Types.Descr do end defp map_line_meet_empty?(key, type, neg_type, t1, t2, tag, neg_tag, acc_meet, negs, seen) do - diff = bare_difference(type, neg_type) meet = bare_intersection(type, neg_type) - (empty_seen?(diff, seen) or - map_line_empty?(tag, Enum.reverse(acc_meet, [{key, diff} | t1]), negs, seen)) and - (empty_seen?(meet, seen) or - map_line_meet_empty?(t1, t2, tag, neg_tag, [{key, meet} | acc_meet], negs, seen)) + if empty_seen?(meet, seen) do + # This negative map is disjoint from the current line at this field. + map_line_empty?(tag, Enum.reverse(acc_meet, [{key, type} | t1]), negs, seen) + else + diff = bare_difference(type, neg_type) + + if empty_seen?(diff, seen) do + # The field is a subtype of the negative field, so their intersection is type. + map_line_meet_empty?(t1, t2, tag, neg_tag, [{key, type} | acc_meet], negs, seen) + else + map_line_empty?(tag, Enum.reverse(acc_meet, [{key, diff} | t1]), negs, seen) and + map_line_meet_empty?(t1, t2, tag, neg_tag, [{key, meet} | acc_meet], negs, seen) + end + end end defp map_line_fields_empty?(