Optimize tuple and map emptiness checks (#15619)

Short-circuit disjoint and subtype tuple elements
before exploring the general difference branches.

Assisted-by: Codex:GPT-5.6
This commit is contained in:
José Valim
2026-07-16 21:27:06 +02:00
committed by GitHub
parent 51ac6a5c0f
commit 59e257d3ca
+28 -12
View File
@@ -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?(
@@ -5001,15 +5010,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: