Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions lib/elixir/lib/module/types/descr.ex
Original file line number Diff line number Diff line change
Expand Up @@ -5256,6 +5256,17 @@ defmodule Module.Types.Descr do

# Takes a static tuple type and removes an index from it.
defp tuple_delete_static(%{tuple: bdd}, index) do
# Restrict to tuples that actually have `index` before projecting. Callers
# already guarantee this (via the is_proper_size? check), so the intersection
# is a semantic no-op, but it makes the element at `index` explicit in the
# positive literals. Without it, an open literal whose size lower bound is
# only carried by negations (e.g. {...} \ {} \ {term()}) projects that index
# to term_or_optional(); the optional marker then pairs with the full
# remaining bdd and the result over-approximates (e.g. admits the empty
# tuple), losing layout. This is the same optional-marker imprecision the
# projection machinery exhibits for tuple_fetch.
bdd = tuple_intersection(bdd, tuple_new(:open, List.duplicate(term(), index + 1)))

bdd =
bdd
|> tuple_bdd_to_dnf_with_negations()
Expand Down
22 changes: 22 additions & 0 deletions lib/elixir/test/elixir/module/types/descr_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -2000,6 +2000,28 @@ defmodule Module.Types.DescrTest do
assert dynamic(opt_union(tuple(), integer()))
|> tuple_delete_at(1)
|> equal?(dynamic(tuple_of_size_at_least(1)))

# The result must not depend on the representation. A size lower bound
# carried only by negations ({...} \ {} \ {term()} == open_tuple([term(),
# term()])) must delete to the same type as the canonical open tuple and
# must not over-approximate by admitting shorter tuples (e.g. the empty
# tuple).
canonical = open_tuple([term(), term()])

by_negation =
open_tuple([])
|> opt_difference(tuple([]))
|> opt_difference(tuple([term()]))

assert equal?(canonical, by_negation)

assert tuple_delete_at(by_negation, 0)
|> equal?(tuple_delete_at(canonical, 0))

assert tuple_delete_at(by_negation, 0)
|> equal?(open_tuple([term()]))

refute subtype?(tuple([]), tuple_delete_at(by_negation, 0))
end

test "tuple_insert_at" do
Expand Down