Skip to content

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

Merged
josevalim merged 2 commits intoelixir-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 intoelixir-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
Copy link
Copy Markdown
Member

gldubc commented Apr 30, 2026

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

Copy link
Copy Markdown
Member

@gldubc gldubc left a comment

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