-
Notifications
You must be signed in to change notification settings - Fork 3.5k
Add typechecker handling for put_elem
#15377
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
8757699
7e2005a
b8289d6
af6b56a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -1808,6 +1808,25 @@ defmodule Module.Types.DescrTest do | |
| assert dynamic(union(tuple(), integer())) | ||
| |> tuple_delete_at(1) | ||
| |> equal?(dynamic(tuple_of_size_at_least(1))) | ||
|
|
||
| # Deleting beyond a dynamic fixed-size tuple must fail with :badindex. | ||
| # In particular, deleting index N from a dynamic N-tuple must NOT | ||
| # silently return the original tuple type (off-by-one pruning bug). | ||
| assert tuple_delete_at(dynamic(tuple([atom([:ok]), term()])), 2) == :badindex | ||
| assert tuple_delete_at(dynamic(tuple([atom([:a]), atom([:b])])), 2) == :badindex | ||
| assert tuple_delete_at(dynamic(tuple([atom([:ok])])), 1) == :badindex | ||
|
|
||
| # Deletion is not injective at the deleted position; the negative | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Tests in 1819-1829 do not assert any new behaviour added in this PR but were failing pre #15376
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You can get rid of them, this was tested in the PR. |
||
| # constraint at that position must not be preserved. | ||
| assert difference(tuple([atom(), atom()]), tuple([atom([:a]), term()])) | ||
| |> tuple_delete_at(0) | ||
| |> equal?(tuple([atom()])) | ||
|
|
||
| # Negative constraints at positions OTHER than the deleted one are | ||
| # preserved (after shifting). | ||
| assert difference(tuple([atom(), atom()]), tuple([atom([:a]), term()])) | ||
| |> tuple_delete_at(1) | ||
| |> equal?(difference(tuple([atom()]), tuple([atom([:a])]))) | ||
| end | ||
|
|
||
| test "tuple_insert_at" do | ||
|
|
@@ -1881,6 +1900,126 @@ defmodule Module.Types.DescrTest do | |
| assert dynamic(union(tuple(), integer())) | ||
| |> tuple_insert_at(1, boolean()) | ||
| |> equal?(dynamic(open_tuple([term(), boolean()]))) | ||
|
|
||
| # Errors must propagate even when the inserted value is dynamic | ||
| assert tuple_insert_at(integer(), 0, dynamic()) == :badtuple | ||
| assert tuple_insert_at(term(), 0, dynamic()) == :badtuple | ||
| assert tuple_insert_at(tuple([atom([:ok])]), 2, dynamic()) == :badindex | ||
| assert tuple_insert_at(tuple([atom([:ok])]), -1, dynamic()) == :badindex | ||
|
|
||
| # Out-of-bounds insertions into a dynamic fixed-size tuple must fail | ||
| # with :badindex (rather than silently producing dynamic(none())). | ||
| assert tuple_insert_at(dynamic(tuple([atom([:ok]), term()])), 3, binary()) == | ||
| :badindex | ||
|
|
||
| assert tuple_insert_at(dynamic(tuple([atom([:ok])])), 2, binary()) == :badindex | ||
|
|
||
| # Even at index 0 (where the size constraint is vacuous) the dynamic | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Tests in 1917-1925 do not assert any new behaviour added in this PR but were failing pre #15376
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Also tested in the PR, can remove. :) |
||
| # upper bound must still be intersected with the tuple domain so that | ||
| # non-tuple alternatives are pruned. `Tuple.insert_at/3` always returns | ||
| # a tuple, so an integer alternative cannot survive the operation. | ||
| assert tuple_insert_at(dynamic(term()), 0, boolean()) == | ||
| dynamic(open_tuple([boolean()])) | ||
|
|
||
| assert tuple_insert_at(dynamic(union(tuple(), integer())), 0, boolean()) == | ||
| dynamic(open_tuple([boolean()])) | ||
| end | ||
|
|
||
| test "tuple_replace_at" do | ||
| assert tuple_replace_at(tuple([integer(), atom()]), 2, boolean()) == :badindex | ||
| assert tuple_replace_at(tuple([integer(), atom()]), -1, boolean()) == :badindex | ||
| assert tuple_replace_at(empty_tuple(), 0, boolean()) == :badindex | ||
| assert tuple_replace_at(integer(), 0, boolean()) == :badtuple | ||
| assert tuple_replace_at(term(), 0, boolean()) == :badtuple | ||
| assert tuple_replace_at(tuple([none()]), 0, boolean()) == :badtuple | ||
|
|
||
| # Out-of-bounds in a union | ||
| assert union(tuple([integer(), atom()]), tuple([float()])) | ||
| |> tuple_replace_at(1, boolean()) == :badindex | ||
|
|
||
| # Test replacing an element in a closed tuple | ||
| assert tuple_replace_at(tuple([integer(), atom(), boolean()]), 1, float()) == | ||
| tuple([integer(), float(), boolean()]) | ||
|
|
||
| # Test replacing the first element of a closed tuple | ||
| assert tuple_replace_at(tuple([integer(), atom()]), 0, boolean()) == | ||
| tuple([boolean(), atom()]) | ||
|
|
||
| # Test replacing the last element of a closed tuple | ||
| assert tuple_replace_at(tuple([integer(), atom()]), 1, boolean()) == | ||
| tuple([integer(), boolean()]) | ||
|
|
||
| # Test replacing in an open tuple | ||
| assert tuple_replace_at(open_tuple([integer(), atom(), boolean()]), 1, float()) == | ||
| open_tuple([integer(), float(), boolean()]) | ||
|
|
||
| # Test replacing with a dynamic type | ||
| assert tuple_replace_at(tuple([integer(), atom()]), 1, dynamic()) == | ||
| dynamic(tuple([integer(), term()])) | ||
|
|
||
| # Test replacing in a dynamic tuple | ||
| assert tuple_replace_at(dynamic(tuple([integer(), atom()])), 1, boolean()) == | ||
| dynamic(tuple([integer(), boolean()])) | ||
|
|
||
| # Test replacing in a union of tuples | ||
| assert tuple_replace_at(union(tuple([integer()]), tuple([atom()])), 0, boolean()) == | ||
| tuple([boolean()]) | ||
|
|
||
| # Test replacing in an intersection of tuples | ||
| assert intersection(tuple([integer(), atom()]), tuple([term(), boolean()])) | ||
| |> tuple_replace_at(1, float()) == tuple([integer(), float()]) | ||
|
|
||
| # Test replacing in a difference of tuples | ||
| assert difference(tuple([integer(), atom(), boolean()]), tuple([term(), term()])) | ||
| |> tuple_replace_at(1, float()) | ||
| |> equal?(tuple([integer(), float(), boolean()])) | ||
|
|
||
| # Replacing in a difference where the negation actually constrains the | ||
| # positive (not just by arity). The replaced position drops its negative | ||
| # constraint, the other positions keep theirs. | ||
| assert difference(tuple([atom(), atom()]), tuple([atom([:a]), term()])) | ||
| |> tuple_replace_at(0, boolean()) | ||
| |> equal?(tuple([boolean(), atom()])) | ||
|
|
||
| assert difference(tuple([atom(), atom()]), tuple([atom([:a]), term()])) | ||
| |> tuple_replace_at(1, boolean()) | ||
| |> equal?(difference(tuple([atom(), boolean()]), tuple([atom([:a]), boolean()]))) | ||
|
|
||
| # Errors must propagate even when the replacement value is dynamic | ||
| assert tuple_replace_at(integer(), 0, dynamic()) == :badtuple | ||
| assert tuple_replace_at(term(), 0, dynamic()) == :badtuple | ||
| assert tuple_replace_at(tuple([atom([:ok])]), 1, dynamic()) == :badindex | ||
| assert tuple_replace_at(empty_tuple(), 0, dynamic()) == :badindex | ||
|
|
||
| # Out-of-bounds writes to a dynamic fixed-size tuple must fail with :badindex | ||
| assert tuple_replace_at(dynamic(tuple([atom([:ok]), term()])), 2, binary()) == | ||
| :badindex | ||
|
|
||
| assert tuple_replace_at(dynamic(tuple([atom([:ok])])), 1, binary()) == :badindex | ||
|
|
||
| # Test replacing in a complex union involving dynamic | ||
| assert union(tuple([integer(), atom()]), dynamic(tuple([float(), binary()]))) | ||
| |> tuple_replace_at(1, boolean()) | ||
| |> equal?( | ||
| union( | ||
| tuple([integer(), boolean()]), | ||
| dynamic(tuple([float(), boolean()])) | ||
| ) | ||
| ) | ||
|
|
||
| # Successfully replacing at position `index` in a tuple means that the dynamic | ||
| # values that succeed are intersected with tuples of size at least `index + 1` | ||
| assert dynamic(tuple()) | ||
| |> tuple_replace_at(0, boolean()) | ||
| |> equal?(dynamic(open_tuple([boolean()]))) | ||
|
|
||
| assert dynamic(term()) | ||
| |> tuple_replace_at(0, boolean()) | ||
| |> equal?(dynamic(open_tuple([boolean()]))) | ||
|
|
||
| assert dynamic(union(tuple(), integer())) | ||
| |> tuple_replace_at(1, boolean()) | ||
| |> equal?(dynamic(open_tuple([term(), boolean()]))) | ||
| end | ||
|
|
||
| test "tuple_values" do | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder if dropping the static part is right here.
insert_elementdoes drop it whiledelete_elementpreservesThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
insert_elementshould be changed to preserve it, this is a loss of precisionThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It sounds like something for a follow up PR
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can take care of it.