Skip to content

Fix incorrect guard type refinement with length/map_size and != operator - #15327

Merged
josevalim merged 2 commits into
elixir-lang:mainfrom
lukaszsamson:ls-fix-guard-size-refinement
Apr 30, 2026
Merged

Fix incorrect guard type refinement with length/map_size and != operator#15327
josevalim merged 2 commits into
elixir-lang:mainfrom
lukaszsamson:ls-fix-guard-size-refinement

Conversation

@lukaszsamson

Copy link
Copy Markdown
Contributor

Previously empty_list() and empty_map were returned for length(x) != n and map_size(x) != n when n > 0. Now the types are widened to list() and open_map(). List/map of size exactly n when n > 0 cannot be expressed in the type system. The problem does not affect tuples as their sizes are encoded in types

Fixes #15326

Previously `empty_list()` and `empty_map` were returned for `length(x) != n` and `map_size(x) != n` when n > 0. Now the types are widened to `list()` and `open_map()`
assert typecheck!([x], not (0 != length(x)), x) == dynamic(empty_list())

assert typecheck!([x], length(x) != 1, x) == dynamic(list(term()))
assert typecheck!([x], length(x) == 1, x) == dynamic(list(term()))

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This cannot be an empty list, right?

@gldubc

gldubc commented Apr 30, 2026

Copy link
Copy Markdown
Member

This is a regression: they weren't tested explicitly previously, but things like length(x) == 1 should rightfully deduce for x the type of a non empty list.

These tests should succeed:

assert typecheck!([x], length(x) == 1, x) == dynamic(non_empty_list(term()))
assert typecheck!([x], not (length(x) != 1), x) == dynamic(non_empty_list(term()))

assert typecheck!([x], 1 == length(x), x) == dynamic(non_empty_list(term()))

assert typecheck!([x], map_size(x) == 1, x) ==
               dynamic(open_map() |> difference(empty_map()))
assert typecheck!([x], not (map_size(x) != 1), x) ==  dynamic(open_map() |> difference(empty_map()))

assert typecheck!([x], 1 == map_size(x), x) ==  dynamic(open_map() |> difference(empty_map()))

@josevalim

Copy link
Copy Markdown
Member

Yes, we need a few additional tests and cases!

@lukaszsamson

Copy link
Copy Markdown
Contributor Author

Tightened refinement to non_empty_list/non_empty_map for length/map_size(x) == n when n > 0. Added all the test cases suggested by @gldubc

@gldubc gldubc left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's good now!

@josevalim
josevalim merged commit f4e1b34 into elixir-lang:main Apr 30, 2026
15 checks passed
@josevalim

Copy link
Copy Markdown
Member

💚 💙 💜 💛 ❤️

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

Invalid type guard type refinement with != operator

3 participants