From 9ef280820a8d204d5eeb9fa66fb00c6b9f9ed462 Mon Sep 17 00:00:00 2001 From: Lukasz Samson Date: Sat, 29 Aug 2026 10:33:14 +0200 Subject: [PATCH 1/2] Preserve left element types in list subtraction Teach the type system that list subtraction returns a possibly-empty sublist of its left operand while preserving proper-list validation for both operands. Assisted-by: Codex:GPT-5 --- lib/elixir/lib/module/types/apply.ex | 11 +++++++++++ lib/elixir/test/elixir/module/types/expr_test.exs | 8 ++++++++ 2 files changed, 19 insertions(+) diff --git a/lib/elixir/lib/module/types/apply.ex b/lib/elixir/lib/module/types/apply.ex index 0eba019ad87..88f16aff955 100644 --- a/lib/elixir/lib/module/types/apply.ex +++ b/lib/elixir/lib/module/types/apply.ex @@ -1152,6 +1152,17 @@ defmodule Module.Types.Apply do end end + defp remote_apply(:erlang, :--, _info, [left, right], stack) do + case {list_of(left), list_of(right)} do + {{_, list_of}, {_, _}} -> + result = if list_of, do: list(list_of), else: empty_list() + {:ok, return(result, [left, right], stack)} + + _ -> + {:error, badremote(:erlang, :--, [left, right])} + end + end + @struct_key atom([:__struct__]) @nil_atom atom([nil]) diff --git a/lib/elixir/test/elixir/module/types/expr_test.exs b/lib/elixir/test/elixir/module/types/expr_test.exs index 6d26ff8db48..e3d51340938 100644 --- a/lib/elixir/test/elixir/module/types/expr_test.exs +++ b/lib/elixir/test/elixir/module/types/expr_test.exs @@ -173,6 +173,14 @@ defmodule Module.Types.ExprTest do non_empty_list(term()), term() """ end + + test "--" do + assert typecheck!([x], [1, 2, 3] -- x) == list(integer()) + assert typecheck!([x], [] -- x) == empty_list() + + assert typeerror!([x], [1, 2, 3] -- String.to_integer(x)) |> strip_ansi() =~ + "incompatible types given to Kernel.--/2" + end end describe "funs" do From 3a7af7a2f3699836991a487b18507281837168e6 Mon Sep 17 00:00:00 2001 From: Lukasz Samson Date: Sun, 30 Aug 2026 08:46:49 +0200 Subject: [PATCH 2/2] Add :erlang.--/2 parametric TODO and cover static non-empty subtraction --- lib/elixir/lib/module/types/apply.ex | 2 ++ lib/elixir/test/elixir/module/types/expr_test.exs | 1 + 2 files changed, 3 insertions(+) diff --git a/lib/elixir/lib/module/types/apply.ex b/lib/elixir/lib/module/types/apply.ex index 88f16aff955..c0ac9a0f1e9 100644 --- a/lib/elixir/lib/module/types/apply.ex +++ b/lib/elixir/lib/module/types/apply.ex @@ -1153,6 +1153,8 @@ defmodule Module.Types.Apply do end defp remote_apply(:erlang, :--, _info, [left, right], stack) do + # TODO: remove once we add parametric types, this will just be: + # list(a), list(term()) -> list(a) case {list_of(left), list_of(right)} do {{_, list_of}, {_, _}} -> result = if list_of, do: list(list_of), else: empty_list() diff --git a/lib/elixir/test/elixir/module/types/expr_test.exs b/lib/elixir/test/elixir/module/types/expr_test.exs index e3d51340938..6d1275c2f3f 100644 --- a/lib/elixir/test/elixir/module/types/expr_test.exs +++ b/lib/elixir/test/elixir/module/types/expr_test.exs @@ -176,6 +176,7 @@ defmodule Module.Types.ExprTest do test "--" do assert typecheck!([x], [1, 2, 3] -- x) == list(integer()) + assert typecheck!([1] -- [1]) == list(integer()) assert typecheck!([x], [] -- x) == empty_list() assert typeerror!([x], [1, 2, 3] -- String.to_integer(x)) |> strip_ansi() =~