From 75bad3f0563725670bca118f6c2bd5121deddb90 Mon Sep 17 00:00:00 2001 From: Lukasz Samson Date: Thu, 30 Apr 2026 13:30:54 +0200 Subject: [PATCH 1/2] Fix incorrect guard type refinement with length/map_size and != operator 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()` --- lib/elixir/lib/module/types/apply.ex | 18 ++++++---- .../test/elixir/module/types/pattern_test.exs | 35 +++++++++++++++++++ 2 files changed, 47 insertions(+), 6 deletions(-) diff --git a/lib/elixir/lib/module/types/apply.ex b/lib/elixir/lib/module/types/apply.ex index 063bc01b92..43358e0adb 100644 --- a/lib/elixir/lib/module/types/apply.ex +++ b/lib/elixir/lib/module/types/apply.ex @@ -590,17 +590,23 @@ defmodule Module.Types.Apply do {expected, precise?} = case fun do - :length when :erlang.xor(polarity, literal > 0) -> - {@empty_list, literal == 0} + :length when polarity and literal == 0 -> + {@empty_list, true} + + :length when not polarity and literal == 0 -> + {@non_empty_list, true} :length -> - {@non_empty_list, literal == 0} + {@list, false} + + :map_size when polarity and literal == 0 -> + {@empty_map, true} - :map_size when :erlang.xor(polarity, literal > 0) -> - {@empty_map, literal == 0} + :map_size when not polarity and literal == 0 -> + {@non_empty_map, true} :map_size -> - {@non_empty_map, literal == 0} + {open_map(), false} :tuple_size when polarity -> {tuple(List.duplicate(term(), literal)), true} diff --git a/lib/elixir/test/elixir/module/types/pattern_test.exs b/lib/elixir/test/elixir/module/types/pattern_test.exs index 4584751dcd..5b17f912bb 100644 --- a/lib/elixir/test/elixir/module/types/pattern_test.exs +++ b/lib/elixir/test/elixir/module/types/pattern_test.exs @@ -1159,6 +1159,17 @@ defmodule Module.Types.PatternTest do assert typecheck!([x], 0 != length(x), x) == dynamic(non_empty_list(term())) 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())) + assert typecheck!([x], not (length(x) != 1), x) == dynamic(list(term())) + assert typecheck!([x], not (length(x) == 1), x) == dynamic(list(term())) + + assert typecheck!([x], 1 != length(x), x) == dynamic(list(term())) + assert typecheck!([x], 1 == length(x), x) == dynamic(list(term())) + + assert typecheck!([x], is_list(x) and length(x) != 1 and x != [], hd(x)) == + dynamic(term()) end test "length ordered" do @@ -1206,6 +1217,20 @@ defmodule Module.Types.PatternTest do assert typecheck!([x], 0 != map_size(x), x) == dynamic(@non_empty_map) assert typecheck!([x], not (0 == map_size(x)), x) == dynamic(@non_empty_map) assert typecheck!([x], not (0 != map_size(x)), x) == dynamic(empty_map()) + + assert typecheck!([x], map_size(x) != 1, x) == dynamic(open_map()) + assert typecheck!([x], map_size(x) == 1, x) == dynamic(open_map()) + assert typecheck!([x], not (map_size(x) != 1), x) == dynamic(open_map()) + assert typecheck!([x], not (map_size(x) == 1), x) == dynamic(open_map()) + + assert typecheck!([x], 1 != map_size(x), x) == dynamic(open_map()) + assert typecheck!([x], 1 == map_size(x), x) == dynamic(open_map()) + + assert typecheck!( + [x], + is_map(x) and map_size(x) != 1 and is_map_key(x, :a) and is_map_key(x, :b), + x + ) == dynamic(open_map(a: term(), b: term())) end test "map_size ordered" do @@ -1270,6 +1295,16 @@ defmodule Module.Types.PatternTest do assert typecheck!([x], 2 != tuple_size(x), x) == dynamic(@non_binary_tuple) assert typecheck!([x], not (2 == tuple_size(x)), x) == dynamic(@non_binary_tuple) assert typecheck!([x], not (2 != tuple_size(x)), x) == dynamic(tuple([term(), term()])) + + assert typecheck!([x], tuple_size(x) != 1, x) == + dynamic(difference(open_tuple([]), tuple([term()]))) + + assert typecheck!( + [x], + is_tuple(x) and tuple_size(x) != 1 and tuple_size(x) != 0, + x + ) + |> equal?(dynamic(open_tuple([term(), term()]))) end test "tuple_size ordered" do From 7d3c2b62e5b069f99ba6cccbff1695cc4f12ec3b Mon Sep 17 00:00:00 2001 From: Lukasz Samson Date: Thu, 30 Apr 2026 16:47:02 +0200 Subject: [PATCH 2/2] Address PR comments --- lib/elixir/lib/module/types/apply.ex | 6 ++++++ .../test/elixir/module/types/pattern_test.exs | 16 ++++++++-------- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/lib/elixir/lib/module/types/apply.ex b/lib/elixir/lib/module/types/apply.ex index 43358e0adb..32f788a5f8 100644 --- a/lib/elixir/lib/module/types/apply.ex +++ b/lib/elixir/lib/module/types/apply.ex @@ -596,6 +596,9 @@ defmodule Module.Types.Apply do :length when not polarity and literal == 0 -> {@non_empty_list, true} + :length when polarity -> + {@non_empty_list, false} + :length -> {@list, false} @@ -605,6 +608,9 @@ defmodule Module.Types.Apply do :map_size when not polarity and literal == 0 -> {@non_empty_map, true} + :map_size when polarity -> + {@non_empty_map, false} + :map_size -> {open_map(), false} diff --git a/lib/elixir/test/elixir/module/types/pattern_test.exs b/lib/elixir/test/elixir/module/types/pattern_test.exs index 5b17f912bb..35fdfb6cc2 100644 --- a/lib/elixir/test/elixir/module/types/pattern_test.exs +++ b/lib/elixir/test/elixir/module/types/pattern_test.exs @@ -1160,13 +1160,13 @@ defmodule Module.Types.PatternTest do assert typecheck!([x], 0 != length(x), x) == dynamic(non_empty_list(term())) assert typecheck!([x], not (0 != length(x)), x) == dynamic(empty_list()) + 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], length(x) != 1, x) == dynamic(list(term())) - assert typecheck!([x], length(x) == 1, x) == dynamic(list(term())) - assert typecheck!([x], not (length(x) != 1), x) == dynamic(list(term())) assert typecheck!([x], not (length(x) == 1), x) == dynamic(list(term())) - assert typecheck!([x], 1 != length(x), x) == dynamic(list(term())) - assert typecheck!([x], 1 == length(x), x) == dynamic(list(term())) assert typecheck!([x], is_list(x) and length(x) != 1 and x != [], hd(x)) == dynamic(term()) @@ -1218,13 +1218,13 @@ defmodule Module.Types.PatternTest do assert typecheck!([x], not (0 == map_size(x)), x) == dynamic(@non_empty_map) assert typecheck!([x], not (0 != map_size(x)), x) == dynamic(empty_map()) + assert typecheck!([x], map_size(x) == 1, x) == dynamic(@non_empty_map) + assert typecheck!([x], not (map_size(x) != 1), x) == dynamic(@non_empty_map) + assert typecheck!([x], 1 == map_size(x), x) == dynamic(@non_empty_map) + assert typecheck!([x], map_size(x) != 1, x) == dynamic(open_map()) - assert typecheck!([x], map_size(x) == 1, x) == dynamic(open_map()) - assert typecheck!([x], not (map_size(x) != 1), x) == dynamic(open_map()) assert typecheck!([x], not (map_size(x) == 1), x) == dynamic(open_map()) - assert typecheck!([x], 1 != map_size(x), x) == dynamic(open_map()) - assert typecheck!([x], 1 == map_size(x), x) == dynamic(open_map()) assert typecheck!( [x],