Skip to content

Commit 17f782e

Browse files
authored
Fix tuple_insert_static crash on non-normalized empty type (#15542)
Closes #15541.
1 parent 6fda428 commit 17f782e

2 files changed

Lines changed: 39 additions & 23 deletions

File tree

lib/elixir/lib/module/types/descr.ex

Lines changed: 25 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -5630,31 +5630,33 @@ defmodule Module.Types.Descr do
56305630
end
56315631
end
56325632

5633-
defp tuple_insert_static(descr, _, _) when descr == @none, do: none()
5634-
5635-
defp tuple_insert_static(descr, index, type) do
5636-
Map.update!(descr, :tuple, fn bdd ->
5637-
if tuple_bdd_positive?(bdd) do
5638-
# A pure disjunction of leaves: the insert distributes over the union, so
5639-
# we rewrite each leaf in place (preserving the structure callers assert on).
5640-
bdd_map(bdd, fn bdd_leaf(tag, elements) ->
5641-
tuple_insert_leaf(tag, elements, index, type)
5642-
end)
5643-
else
5644-
# The bdd carries negations and/or implicit `:bdd_top` positive paths
5645-
# (e.g. from `tuple_difference(open_tuple([]), _) -> bdd_negation`).
5646-
# `bdd_map` rewrites only explicit leaves, so it would skip the implicit
5647-
# top (losing the insert) and wrongly transform negated leaves. Expand to
5648-
# the exact negation-free positive DNF first, then insert into each leaf.
5649-
bdd
5650-
|> tuple_bdd_to_dnf_no_negations()
5651-
|> Enum.reduce(:bdd_bot, fn {tag, elements}, acc ->
5652-
tuple_union(tuple_insert_leaf(tag, elements, index, type), acc)
5653-
end)
5654-
end
5655-
end)
5633+
defp tuple_insert_static(%{tuple: bdd} = descr, index, type) do
5634+
%{
5635+
descr
5636+
| tuple:
5637+
if tuple_bdd_positive?(bdd) do
5638+
# A pure disjunction of leaves: the insert distributes over the union, so
5639+
# we rewrite each leaf in place (preserving the structure callers assert on).
5640+
bdd_map(bdd, fn bdd_leaf(tag, elements) ->
5641+
tuple_insert_leaf(tag, elements, index, type)
5642+
end)
5643+
else
5644+
# The bdd carries negations and/or implicit `:bdd_top` positive paths
5645+
# (e.g. from `tuple_difference(open_tuple([]), _) -> bdd_negation`).
5646+
# `bdd_map` rewrites only explicit leaves, so it would skip the implicit
5647+
# top (losing the insert) and wrongly transform negated leaves. Expand to
5648+
# the exact negation-free positive DNF first, then insert into each leaf.
5649+
bdd
5650+
|> tuple_bdd_to_dnf_no_negations()
5651+
|> Enum.reduce(:bdd_bot, fn {tag, elements}, acc ->
5652+
tuple_union(tuple_insert_leaf(tag, elements, index, type), acc)
5653+
end)
5654+
end
5655+
}
56565656
end
56575657

5658+
defp tuple_insert_static(_descr, _index, _type), do: none()
5659+
56585660
# Inserts `type` at `index` into a single tuple literal. If the tuple is open,
56595661
# `List.insert_at` needs the tuple filled with `term()` up to `index` first.
56605662
# Closed tuples of an incorrect size are cancelled before reaching here (the

lib/elixir/test/elixir/module/types/descr_test.exs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2199,6 +2199,20 @@ defmodule Module.Types.DescrTest do
21992199
# Errors must propagate even when the inserted value is dynamic
22002200
assert tuple_insert_at(integer(), 0, dynamic()) == :badtuple
22012201
assert tuple_insert_at(tuple([atom([:ok])]), 2, dynamic()) == :badindex
2202+
2203+
# Must not crash when a gradual descr's static part is a non-normalized
2204+
# empty (semantically empty but syntactically present) non-tuple component.
2205+
a1 = opt_union(dynamic(), non_empty_list(integer(), none()))
2206+
assert equal?(a1, dynamic())
2207+
2208+
assert tuple_insert_at(a1, 1, atom([:x]))
2209+
|> equal?(tuple_insert_at(dynamic(), 1, atom([:x])))
2210+
2211+
a2 = opt_union(tuple([dynamic()]), non_empty_list(none()))
2212+
assert equal?(a2, tuple([dynamic()]))
2213+
2214+
assert tuple_insert_at(a2, 1, atom([:x]))
2215+
|> equal?(tuple_insert_at(tuple([dynamic()]), 1, atom([:x])))
22022216
end
22032217

22042218
test "tuple_replace_at" do

0 commit comments

Comments
 (0)