Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package elixir for openSUSE:Factory checked 
in at 2026-08-29 17:43:56
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/elixir (Old)
 and      /work/SRC/openSUSE:Factory/.elixir.new.1265 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "elixir"

Sat Aug 29 17:43:56 2026 rev:51 rq:1374609 version:1.20.4

Changes:
--------
--- /work/SRC/openSUSE:Factory/elixir/elixir.changes    2026-08-05 
17:50:12.621659869 +0200
+++ /work/SRC/openSUSE:Factory/.elixir.new.1265/elixir.changes  2026-08-29 
17:44:57.942571014 +0200
@@ -1,0 +2,24 @@
+Sat Aug 29 14:38:29 UTC 2026 - Alessio Biancalana <[email protected]>
+
+- Add 005-skip-mix-compile-concurrent-listening-test.patch: skip the
+  Mix.Tasks.CompileTest "listening to concurrent compilations" test,
+  which hangs in the OBS build environment
+
+-------------------------------------------------------------------
+Sat Aug 29 08:46:53 UTC 2026 - Alessio Biancalana <[email protected]>
+
+- Upgrade to Elixir 1.20.4:
+  * Changelog available at https://hexdocs.pm/elixir/1.20.4/changelog.html
+
+-------------------------------------------------------------------
+Fri Aug 21 04:55:59 UTC 2026 - Simon Lees <[email protected]>
+
+- Add 004-skip-mix-sync-lock-on-taken-test.patch: skip the
+  Mix.Sync.Lock "calls :on_taken when the lock is held by a
+  different process" test, new in Elixir 1.18. It probes lock
+  holders over a loopback TCP socket, which does not work in the
+  network-isolated OBS build root and hangs until the
+  assert_receive timeout, failing the build. Noticed on AARCH64 in
+  SLES
+
+-------------------------------------------------------------------

Old:
----
  elixir-1.20.3-doc.zip
  elixir-1.20.3.tar.gz

New:
----
  004-skip-mix-sync-lock-on-taken-test.patch
  005-skip-mix-compile-concurrent-listening-test.patch
  elixir-1.20.4-doc.zip
  elixir-1.20.4.tar.gz

----------(New B)----------
  New:
- Add 004-skip-mix-sync-lock-on-taken-test.patch: skip the
  Mix.Sync.Lock "calls :on_taken when the lock is held by a
  New:
- Add 005-skip-mix-compile-concurrent-listening-test.patch: skip the
  Mix.Tasks.CompileTest "listening to concurrent compilations" test,
----------(New E)----------

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ elixir.spec ++++++
--- /var/tmp/diff_new_pack.2m6Twl/_old  2026-08-29 17:44:58.742599313 +0200
+++ /var/tmp/diff_new_pack.2m6Twl/_new  2026-08-29 17:44:58.744599383 +0200
@@ -18,7 +18,7 @@
 
 %define elixirdir %{_prefix}/lib/elixir
 Name:           elixir
-Version:        1.20.3
+Version:        1.20.4
 Release:        0
 Summary:        Functional meta-programming aware language built atop Erlang
 License:        Apache-2.0
@@ -31,6 +31,8 @@
 Patch0:         001-skip-translator-supervisor-test.patch
 Patch1:         002-skip-tests-iex-helpers.patch
 Patch2:         003-skip-tests-daemon-mode-source.patch
+Patch3:         004-skip-mix-sync-lock-on-taken-test.patch
+Patch4:         005-skip-mix-compile-concurrent-listening-test.patch
 BuildRequires:  erlang >= 25
 BuildRequires:  erlang-dialyzer
 BuildRequires:  erlang-src
@@ -102,7 +104,7 @@
 
 %check
 export LANG=en_US.UTF-8
-%make_build test
+/usr/bin/make -O V=1 VERBOSE=1 test
 
 %files
 %doc CHANGELOG.md README.md

++++++ 004-skip-mix-sync-lock-on-taken-test.patch ++++++
Index: elixir-1.18.1/lib/mix/test/mix/sync/lock_test.exs
===================================================================
--- elixir-1.18.1.orig/lib/mix/test/mix/sync/lock_test.exs
+++ elixir-1.18.1/lib/mix/test/mix/sync/lock_test.exs
@@ -153,6 +153,11 @@ defmodule Mix.Sync.LockTest do
            end) == :still_works!
   end

+  # Mix.Sync.Lock probes other lock holders over a loopback TCP socket.
+  # The OBS build root has no usable network access, so the probe
+  # connect always fails and :on_taken is never invoked, hanging
+  # this test until the assert_receive timeout.
+  @tag :skip
   test "calls :on_taken when the lock is held by a different process" do
     parent = self()


++++++ 005-skip-mix-compile-concurrent-listening-test.patch ++++++
diff --git a/lib/mix/test/mix/tasks/compile_test.exs 
b/lib/mix/test/mix/tasks/compile_test.exs
index d202102..743f8d2 100644
--- a/lib/mix/test/mix/tasks/compile_test.exs
+++ b/lib/mix/test/mix/tasks/compile_test.exs
@@ -389,6 +389,7 @@ defmodule Mix.Tasks.CompileTest do
     end)
   end
 
+  @tag :skip
   test "listening to concurrent compilations" do
     timeout = 2_000
 

++++++ _scmsync.obsinfo ++++++
--- /var/tmp/diff_new_pack.2m6Twl/_old  2026-08-29 17:44:58.853603239 +0200
+++ /var/tmp/diff_new_pack.2m6Twl/_new  2026-08-29 17:44:58.856603345 +0200
@@ -1,6 +1,6 @@
-mtime: 1785864304
-commit: c98851904b7aba54535399b4a316f798e58292e7abc79600154d6d4138afac9d
+mtime: 1788014578
+commit: bbf3086d78dde159067fb606ba70d13c01bcd172ee420ae2d7223d1b33d320ff
 url: https://src.opensuse.org/erlang/elixir
-revision: c98851904b7aba54535399b4a316f798e58292e7abc79600154d6d4138afac9d
+revision: bbf3086d78dde159067fb606ba70d13c01bcd172ee420ae2d7223d1b33d320ff
 projectscmsync: https://src.opensuse.org/erlang/_ObsPrj.git
 

++++++ build.specials.obscpio ++++++

++++++ build.specials.obscpio ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/.gitignore new/.gitignore
--- old/.gitignore      1970-01-01 01:00:00.000000000 +0100
+++ new/.gitignore      2026-08-29 16:42:58.000000000 +0200
@@ -0,0 +1 @@
+.osc

++++++ elixir-1.20.3-doc.zip -> elixir-1.20.4-doc.zip ++++++
/work/SRC/openSUSE:Factory/elixir/elixir-1.20.3-doc.zip 
/work/SRC/openSUSE:Factory/.elixir.new.1265/elixir-1.20.4-doc.zip differ: char 
11, line 1

++++++ elixir-1.20.3.tar.gz -> elixir-1.20.4.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/elixir-1.20.3/CHANGELOG.md 
new/elixir-1.20.4/CHANGELOG.md
--- old/elixir-1.20.3/CHANGELOG.md      2026-08-04 16:31:51.000000000 +0200
+++ new/elixir-1.20.4/CHANGELOG.md      2026-08-28 12:02:55.000000000 +0200
@@ -192,6 +192,22 @@
 
 You can enable it by setting `elixirc_options: [module_definition: 
:interpreted]` in your `mix.exs`.
 
+## v1.20.4 (2026-08-28)
+
+### 1. Security
+
+  * [List] Avoid recursion when invalid charlists are given to 
`List.to_string/1` or `List.to_charlist/1` (CVE-2026-75758, GHSA-jf5q-v438-665c)
+
+### 2. Bug fixes
+
+#### Elixir
+
+  * [Base] Fix false positives in Base64 validation
+  * [Kernel] Fix inferred type of `:erlang.split_binary/2` when given a 
bitstring
+  * [Kernel] Fix type checker crash on comprehensions with a non-returning 
`:into` expression
+  * [String] Fix final sigma handling in Greek casing
+  * [Task] Fix `Task.yield_many/2` waiting indefinitely when `:limit` exceeds 
the task count
+
 ## v1.20.3 (2026-08-04)
 
 ### 1. Enhancements
@@ -206,7 +222,7 @@
 
   * [Kernel] Improve type precision of the `++/2` operator
   * [Kernel] Improve type precision of the `send/2` return type
-  * [Kernel] Improve type refinement for `:erlang.andalso/2` and 
`:erlang.orelse/2`
+  * [Kernel] Improve type refinement for `and/2` and `or/2`
   * [Kernel] Fix type checking of `Map.put/3` on empty map types
   * [Kernel] Fix type checking of tuple insertion on equivalent tuple types
   * [Kernel] Fix type checking of tuple insertion on gradual types with empty 
static parts
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/elixir-1.20.3/VERSION new/elixir-1.20.4/VERSION
--- old/elixir-1.20.3/VERSION   2026-08-04 16:31:51.000000000 +0200
+++ new/elixir-1.20.4/VERSION   2026-08-28 12:02:55.000000000 +0200
@@ -1 +1 @@
-1.20.3
+1.20.4
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/elixir-1.20.3/bin/elixir new/elixir-1.20.4/bin/elixir
--- old/elixir-1.20.3/bin/elixir        2026-08-04 16:31:51.000000000 +0200
+++ new/elixir-1.20.4/bin/elixir        2026-08-28 12:02:55.000000000 +0200
@@ -6,7 +6,7 @@
 
 set -e
 
-ELIXIR_VERSION=1.20.3
+ELIXIR_VERSION=1.20.4
 
 if [ $# -eq 0 ] || { [ $# -eq 1 ] && { [ "$1" = "--help" ] || [ "$1" = "-h" ]; 
}; }; then
   cat <<USAGE >&2
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/elixir-1.20.3/bin/elixir.bat 
new/elixir-1.20.4/bin/elixir.bat
--- old/elixir-1.20.3/bin/elixir.bat    2026-08-04 16:31:51.000000000 +0200
+++ new/elixir-1.20.4/bin/elixir.bat    2026-08-28 12:02:55.000000000 +0200
@@ -4,7 +4,7 @@
 :: SPDX-FileCopyrightText: 2021 The Elixir Team
 :: SPDX-FileCopyrightText: 2012 Plataformatec
 
-set ELIXIR_VERSION=1.20.3
+set ELIXIR_VERSION=1.20.4
 
 if    ""%1""==""""                if ""%2""=="""" goto documentation
 if /I ""%1""==""--help""          if ""%2""=="""" goto documentation
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/elixir-1.20.3/lib/elixir/lib/base.ex 
new/elixir-1.20.4/lib/elixir/lib/base.ex
--- old/elixir-1.20.3/lib/elixir/lib/base.ex    2026-08-04 16:31:51.000000000 
+0200
+++ new/elixir-1.20.4/lib/elixir/lib/base.ex    2026-08-28 12:02:55.000000000 
+0200
@@ -187,21 +187,21 @@
 
   # For base64 standard, '/' (0x2F) sits exactly one below '0' (0x30), so we
   # extend the digit range to [0x2F, 0x39], which absorbs '/' into one range
-  # check — saves one Mycroft singleton. Trick lifted from
+  # check — saves one singleton term. Trick lifted from
   # 
https://lemire.me/blog/2025/04/13/detect-control-characters-quotes-and-backslashes-efficiently-using-swar/
   @swar_ge_slash 0x51515151515151
 
-  # Mycroft zero-byte detection for base64 singletons (+, -, _).
-  # Per lane: high bit set iff `bxor(w, K*ones) - 0x01..01` has its high bit
-  # set, i.e. that byte's V value was 0 → original byte was K. Simplified
-  # (no `bnot V` term) — for ASCII-gated `w`, borrow-propagation false
-  # positives only occur for adjacent bytes that happen to equal `K xor 0x01`,
-  # which is outside the base64 alphabet, so it never matters here.
-  # Pattern follows https://github.com/elixir-lang/elixir/pull/15255.
-  @swar_mask01 0x01010101010101
-  @swar_plus_x7 0x2B2B2B2B2B2B2B
-  @swar_dash_x7 0x2D2D2D2D2D2D2D
-  @swar_under_x7 0x5F5F5F5F5F5F5F
+  # base64's '+', '-' and '_' border no other range, so each is checked as the
+  # degenerate range [c, c]. Mycroft zero-byte detection is one operation
+  # cheaper per singleton but not exact per lane: subtracting 0x01..01 makes a
+  # zero lane borrow from the lane above it, and a lane holding 1 turns that
+  # borrow into 0xFF, so bytes equal to `c bxor 0x01` read as matches too.
+  @swar_ge_plus 0x55555555555555
+  @swar_gt_plus 0x54545454545454
+  @swar_ge_dash 0x53535353535353
+  @swar_gt_dash 0x52525252525252
+  @swar_ge_underscore 0x21212121212121
+  @swar_gt_underscore 0x20202020202020
 
   # Per-byte validity checks (used in both the SWAR clauses for the 8th byte
   # of each stride and in the body of the sub-8-byte tail clauses).
@@ -365,8 +365,8 @@
 
   # base64 SWAR word validity: 3 ranges (A-Z, a-z, 0-9) OR'd with singletons.
   # For base, the digit range is extended to [0x2F, 0x39] to absorb '/' as
-  # part of one range (Lemire merge), leaving only '+' as a Mycroft singleton.
-  # For url, the singletons '-' and '_' are detected via two Mycroft terms.
+  # part of one range (Lemire merge), leaving only '+' as a singleton. For url,
+  # '-' and '_' are two more singletons.
   defp valid_word64base?(w) do
     band(w, @swar_mask80) == 0 and
       band(
@@ -378,7 +378,7 @@
             ),
             bxor(w + @swar_ge_slash, w + @swar_gt_9)
           ),
-          bxor(w, @swar_plus_x7) - @swar_mask01
+          bxor(w + @swar_ge_plus, w + @swar_gt_plus)
         ),
         @swar_mask80
       ) == @swar_mask80
@@ -396,8 +396,8 @@
             bxor(w + @swar_ge_0, w + @swar_gt_9)
           ),
           bor(
-            bxor(w, @swar_dash_x7) - @swar_mask01,
-            bxor(w, @swar_under_x7) - @swar_mask01
+            bxor(w + @swar_ge_dash, w + @swar_gt_dash),
+            bxor(w + @swar_ge_underscore, w + @swar_gt_underscore)
           )
         ),
         @swar_mask80
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/elixir-1.20.3/lib/elixir/lib/inspect/algebra.ex 
new/elixir-1.20.4/lib/elixir/lib/inspect/algebra.ex
--- old/elixir-1.20.3/lib/elixir/lib/inspect/algebra.ex 2026-08-04 
16:31:51.000000000 +0200
+++ new/elixir-1.20.4/lib/elixir/lib/inspect/algebra.ex 2026-08-28 
12:02:55.000000000 +0200
@@ -21,8 +21,8 @@
       is `:decimal` and if it is printable, otherwise in bit syntax. See
       `String.printable?/1` to learn when a string is printable.
 
-    * `:charlists` - when `:as_charlists` all lists will be printed as 
charlists,
-      non-printable elements will be escaped.
+    * `:charlists` - when `:as_charlists` all charlists will be printed as 
charlists,
+      non-printable code points will be escaped. Other lists will be printed 
as lists.
 
       When `:as_lists` all lists will be printed as lists.
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/elixir-1.20.3/lib/elixir/lib/inspect.ex 
new/elixir-1.20.4/lib/elixir/lib/inspect.ex
--- old/elixir-1.20.3/lib/elixir/lib/inspect.ex 2026-08-04 16:31:51.000000000 
+0200
+++ new/elixir-1.20.4/lib/elixir/lib/inspect.ex 2026-08-28 12:02:55.000000000 
+0200
@@ -387,13 +387,20 @@
     close = color_doc("]", :list, opts)
 
     cond do
-      lists == :as_charlists or (lists == :infer and 
List.ascii_printable?(term, printable_limit)) ->
-        inspected =
-          case Identifier.escape(IO.chardata_to_string(term), ?", 
printable_limit) do
-            {escaped, ""} -> [?~, ?c, ?", escaped, ?"]
-            {escaped, _} -> [?~, ?c, ?", escaped, ?", " ++ ..."]
+      (lists == :as_charlists and unicode_list?(term, printable_limit)) or
+          (lists == :infer and List.ascii_printable?(term, printable_limit)) ->
+        {split, tail} =
+          if is_integer(printable_limit) do
+            case Enum.split(term, printable_limit) do
+              {split, []} -> {split, []}
+              {split, _} -> {split, " ++ ..."}
+            end
+          else
+            {term, []}
           end
 
+        {escaped, _} = Identifier.escape(IO.chardata_to_string(split), ?")
+        inspected = [?~, ?c, ?", escaped, ?" | tail]
         color_doc(IO.iodata_to_binary(inspected), :charlist, opts)
 
       keyword?(term) ->
@@ -407,6 +414,19 @@
     end
   end
 
+  defp unicode_list?(_, 0), do: true
+
+  defp unicode_list?([char | rest], counter)
+       when char in 0..0xD7FF or char in 0xE000..0x10FFFF,
+       do: unicode_list?(rest, decrement(counter))
+
+  defp unicode_list?([], _counter), do: true
+  defp unicode_list?(_, _counter), do: false
+
+  @compile {:inline, decrement: 1}
+  defp decrement(:infinity), do: :infinity
+  defp decrement(counter), do: counter - 1
+
   @doc false
   def keyword({key, value}, opts) do
     key = color_doc(Macro.inspect_atom(:key, key), :atom, opts)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/elixir-1.20.3/lib/elixir/lib/list.ex 
new/elixir-1.20.4/lib/elixir/lib/list.ex
--- old/elixir-1.20.3/lib/elixir/lib/list.ex    2026-08-04 16:31:51.000000000 
+0200
+++ new/elixir-1.20.4/lib/elixir/lib/list.ex    2026-08-28 12:02:55.000000000 
+0200
@@ -1193,7 +1193,7 @@
 
         Please check the given list or call inspect/1 to get the list 
representation, got:
 
-        #{inspect(list)}
+        #{inspect(list, charlists: :as_lists)}
         """
     else
       result when is_binary(result) ->
@@ -1245,7 +1245,7 @@
 
         Please check the given list or call inspect/1 to get the list 
representation, got:
 
-        #{inspect(list)}
+        #{inspect(list, charlists: :as_lists)}
         """
     else
       result when is_list(result) ->
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/elixir-1.20.3/lib/elixir/lib/module/types/apply.ex 
new/elixir-1.20.4/lib/elixir/lib/module/types/apply.ex
--- old/elixir-1.20.3/lib/elixir/lib/module/types/apply.ex      2026-08-04 
16:31:51.000000000 +0200
+++ new/elixir-1.20.4/lib/elixir/lib/module/types/apply.ex      2026-08-28 
12:02:55.000000000 +0200
@@ -213,7 +213,11 @@
         {:erlang, :spawn_link, [{mfargs, pid()}]},
         {:erlang, :spawn_monitor, [{[fun(0)], tuple([pid(), reference()])}]},
         {:erlang, :spawn_monitor, [{mfargs, tuple([pid(), reference()])}]},
-        {:erlang, :split_binary, [{[binary(), integer()], tuple([binary(), 
binary()])}]},
+        {:erlang, :split_binary,
+         [
+           {[binary(), integer()], tuple([binary(), binary()])},
+           {[bitstring_no_binary(), integer()], tuple([binary(), 
bitstring_no_binary()])}
+         ]},
         {:erlang, :tuple_size, [{[open_tuple([])], integer()}]},
         {:erlang, :trunc, [{[union(integer(), float())], integer()}]},
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/elixir-1.20.3/lib/elixir/lib/module/types/expr.ex 
new/elixir-1.20.4/lib/elixir/lib/module/types/expr.ex
--- old/elixir-1.20.3/lib/elixir/lib/module/types/expr.ex       2026-08-04 
16:31:51.000000000 +0200
+++ new/elixir-1.20.4/lib/elixir/lib/module/types/expr.ex       2026-08-28 
12:02:55.000000000 +0200
@@ -888,6 +888,9 @@
 
         {true, false} ->
           {type, :bitstring, context}
+
+        {false, false} ->
+          {type, :none, context}
       end
     else
       {_type, context} =
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/elixir-1.20.3/lib/elixir/lib/string.ex 
new/elixir-1.20.4/lib/elixir/lib/string.ex
--- old/elixir-1.20.3/lib/elixir/lib/string.ex  2026-08-04 16:31:51.000000000 
+0200
+++ new/elixir-1.20.4/lib/elixir/lib/string.ex  2026-08-28 12:02:55.000000000 
+0200
@@ -1008,6 +1008,10 @@
     IO.iodata_to_binary(downcase_ascii(string))
   end
 
+  def downcase(string, :greek) when is_binary(string) do
+    String.Unicode.downcase_greek(string, [])
+  end
+
   def downcase(string, mode) when is_binary(string) and mode in 
@conditional_mappings do
     String.Unicode.downcase(string, [], mode)
   end
@@ -1058,19 +1062,39 @@
   @letter_I_dot_above <<0x0130::utf8>>
 
   def capitalize(<<@letter_i, right::binary>>, mode) do
-    if(mode == :turkic, do: @letter_I_dot_above, else: @letter_I) <> 
downcase(right, mode)
+    capitalized = if mode == :turkic, do: @letter_I_dot_above, else: @letter_I
+    capitalized <> downcase_after_capitalize(right, mode, ?i)
   end
 
   def capitalize(string, mode) when is_binary(string) do
     case :unicode_util.gc(string) do
-      [gc] -> grapheme_to_binary(:string.titlecase([gc]))
-      [gc, rest] -> grapheme_to_binary(:string.titlecase([gc])) <> 
downcase(rest, mode)
-      [gc | rest] -> grapheme_to_binary(:string.titlecase([gc])) <> 
downcase(rest, mode)
-      [] -> ""
-      {:error, <<byte, rest::bits>>} -> <<byte>> <> downcase(rest, mode)
+      [gc] ->
+        grapheme_to_binary(:string.titlecase([gc]))
+
+      [gc, rest] ->
+        grapheme_to_binary(:string.titlecase([gc])) <>
+          downcase_after_capitalize(rest, mode, gc)
+
+      [gc | rest] ->
+        grapheme_to_binary(:string.titlecase([gc])) <>
+          downcase_after_capitalize(rest, mode, gc)
+
+      [] ->
+        ""
+
+      {:error, <<byte, rest::bits>>} ->
+        <<byte>> <> downcase(rest, mode)
     end
   end
 
+  defp downcase_after_capitalize(rest, :greek, gc) do
+    String.Unicode.downcase_greek(rest, [], grapheme_to_binary(gc))
+  end
+
+  defp downcase_after_capitalize(rest, mode, _gc) do
+    downcase(rest, mode)
+  end
+
   @doc false
   @deprecated "Use String.trim_trailing/1 instead"
   defdelegate rstrip(binary), to: String.Break, as: :trim_trailing
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/elixir-1.20.3/lib/elixir/lib/task.ex 
new/elixir-1.20.4/lib/elixir/lib/task.ex
--- old/elixir-1.20.3/lib/elixir/lib/task.ex    2026-08-04 16:31:51.000000000 
+0200
+++ new/elixir-1.20.4/lib/elixir/lib/task.ex    2026-08-28 12:02:55.000000000 
+0200
@@ -1248,9 +1248,10 @@
         {ref, nil}
       end)
 
+    ref_count = map_size(refs)
     on_timeout = Keyword.get(opts, :on_timeout, :nothing)
     timeout = Keyword.get(opts, :timeout, 5_000)
-    limit = Keyword.get(opts, :limit, map_size(refs))
+    limit = min(Keyword.get(opts, :limit, ref_count), ref_count)
     timeout_ref = make_ref()
 
     timer_ref =
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/elixir-1.20.3/lib/elixir/test/elixir/base_test.exs 
new/elixir-1.20.4/lib/elixir/test/elixir/base_test.exs
--- old/elixir-1.20.3/lib/elixir/test/elixir/base_test.exs      2026-08-04 
16:31:51.000000000 +0200
+++ new/elixir-1.20.4/lib/elixir/test/elixir/base_test.exs      2026-08-28 
12:02:55.000000000 +0200
@@ -263,6 +263,14 @@
     refute valid64?("Zm9)")
   end
 
+  test "valid64?/1 returns false on a non-alphabet character before a ?+" do
+    # ?* is ?+ with its low bit flipped
+    for pos <- 0..14, char <- ~c"*),^ \t" do
+      string = String.duplicate("A", pos) <> <<char, ?+>> <> 
String.duplicate("A", 14 - pos)
+      refute valid64?(string), "expected #{inspect(string)} to be invalid"
+    end
+  end
+
   test "valid64?/1 returns false on whitespace unless there's ignore: 
:whitespace" do
     refute valid64?("\nQWxhZGRp bjpvcGVu\sIHNlc2Ft\t")
 
@@ -446,6 +454,14 @@
     refute url_valid64?("Zm9)")
   end
 
+  test "url_valid64?/1 returns false on a non-alphabet character before a ?- 
or ?_" do
+    # ?, and ?^ are ?- and ?_ with their low bits flipped
+    for pos <- 0..14, char <- ~c",^*) \t", next <- ~c"-_" do
+      string = String.duplicate("A", pos) <> <<char, next>> <> 
String.duplicate("A", 14 - pos)
+      refute url_valid64?(string), "expected #{inspect(string)} to be invalid"
+    end
+  end
+
   test "url_valid64?/1 returns false on whitespace unless there's ignore: 
:whitespace" do
     refute url_valid64?("\nQWxhZGRp bjpvcGVu\sIHNlc2Ft\t")
 
@@ -1054,14 +1070,13 @@
     assert hex_valid32?("cPNg====", case: :mixed, padding: false)
   end
 
-  # TODO: add valid? tests
-  test "encode then decode is identity" do
+  test "encode then decode is identity, and valid? agrees with decode" do
     for {encode, decode, valid?} <- [
-          {&encode16/2, &decode16!/2, &valid16?/2},
-          {&encode32/2, &decode32!/2, &valid32?/2},
-          {&hex_encode32/2, &hex_decode32!/2, &hex_valid32?/2},
-          {&encode64/2, &decode64!/2, &valid64?/2},
-          {&url_encode64/2, &url_decode64!/2, &url_valid64?/2}
+          {&encode16/2, &decode16/2, &valid16?/2},
+          {&encode32/2, &decode32/2, &valid32?/2},
+          {&hex_encode32/2, &hex_decode32/2, &hex_valid32?/2},
+          {&encode64/2, &decode64/2, &valid64?/2},
+          {&url_encode64/2, &url_decode64/2, &url_valid64?/2}
         ],
         encode_case <- [:upper, :lower],
         decode_case <- [:upper, :lower, :mixed],
@@ -1089,10 +1104,26 @@
 
       decode_opts = Keyword.take([case: decode_case, padding: pad?], 
allowed_opts)
       assert valid?.(encoded, decode_opts)
-      expected = decode.(encoded, decode_opts)
 
-      assert data == expected,
+      assert decode.(encoded, decode_opts) == {:ok, data},
              "identity did not match for #{inspect(data)} when 
#{inspect(encode)} (#{encode_case})"
+
+      size = byte_size(encoded)
+
+      if size > 0 do
+        pos = :rand.uniform(size) - 1
+        taken = min(2, size - pos)
+        corruption = for _ <- 1..taken, into: <<>>, do: <<:rand.uniform(256) - 
1>>
+
+        corrupted =
+          binary_part(encoded, 0, pos) <>
+            corruption <>
+            binary_part(encoded, pos + taken, size - pos - taken)
+
+        assert valid?.(corrupted, decode_opts) ==
+                 match?({:ok, _}, decode.(corrupted, decode_opts)),
+               "valid? and decode disagree on #{inspect(corrupted)}"
+      end
     end
   end
 end
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/elixir-1.20.3/lib/elixir/test/elixir/inspect_test.exs 
new/elixir-1.20.4/lib/elixir/test/elixir/inspect_test.exs
--- old/elixir-1.20.3/lib/elixir/test/elixir/inspect_test.exs   2026-08-04 
16:31:51.000000000 +0200
+++ new/elixir-1.20.4/lib/elixir/test/elixir/inspect_test.exs   2026-08-28 
12:02:55.000000000 +0200
@@ -312,10 +312,15 @@
 
   test "printable limit" do
     assert inspect(~c"hello world", printable_limit: 4) == ~s(~c"hell" ++ ...)
+    assert inspect(~c"hello", printable_limit: :infinity) == ~s(~c"hello")
     # Non printable characters after the limit don't matter
     assert inspect(~c"hello world" ++ [0], printable_limit: 4) == ~s(~c"hell" 
++ ...)
     # Non printable strings aren't affected by printable limit
     assert inspect([0, 1, 2, 3, 4], printable_limit: 3) == ~s([0, 1, 2, 3, 4])
+    # Ensure a non-printable value after limit does not crash
+    assert inspect(~c"hello world" ++ [nil], printable_limit: 4) == 
~s(~c"hell" ++ ...)
+    # Ensure a non-printable value after default limit does not hang
+    assert String.ends_with?(inspect(List.duplicate(?a, 5000) ++ [nil]), "\" 
++ ...")
   end
 
   test "keyword" do
@@ -340,10 +345,20 @@
     assert inspect([0], charlists: :infer) == "[0]"
   end
 
-  test "opt as strings" do
+  test "opt as charlists" do
     assert inspect(~c"john" ++ [0] ++ ~c"doe", charlists: :as_charlists) == 
~s(~c"john\\0doe")
     assert inspect(~c"john", charlists: :as_charlists) == ~s(~c"john")
     assert inspect([0], charlists: :as_charlists) == ~s(~c"\\0")
+    assert inspect([nil], charlists: :as_charlists) == "[nil]"
+    assert inspect([0xD800], charlists: :as_charlists) == "[55296]"
+    assert inspect([1_114_112], charlists: :as_charlists) == "[1114112]"
+    assert inspect([[?a]], charlists: :as_charlists) == ~s([~c"a"])
+
+    assert inspect([?a, ?b, ?c | nil], charlists: :as_charlists, 
printable_limit: 4) ==
+             "[97, 98, 99 | nil]"
+
+    assert inspect([?a, ?b, ?c | nil], charlists: :as_charlists, 
printable_limit: 3) ==
+             ~s(~c"abc" ++ ...)
   end
 
   test "opt as lists" do
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/elixir-1.20.3/lib/elixir/test/elixir/list_test.exs 
new/elixir-1.20.4/lib/elixir/test/elixir/list_test.exs
--- old/elixir-1.20.3/lib/elixir/test/elixir/list_test.exs      2026-08-04 
16:31:51.000000000 +0200
+++ new/elixir-1.20.4/lib/elixir/test/elixir/list_test.exs      2026-08-28 
12:02:55.000000000 +0200
@@ -359,6 +359,12 @@
     assert_raise ArgumentError, ~r"cannot convert the given list to a string", 
fn ->
       List.to_string([:a, :b])
     end
+
+    invalid = List.duplicate(?a, 4096) ++ [nil]
+
+    assert_raise ArgumentError, ~r/got:\n\n\[97, 97, .*\.\.\.\]\n\z/s, fn ->
+      List.to_string(invalid)
+    end
   end
 
   test "to_charlist/1" do
@@ -377,6 +383,12 @@
     assert_raise ArgumentError, ~r"cannot convert the given list to a 
charlist", fn ->
       List.to_charlist([:a, :b])
     end
+
+    invalid = List.duplicate(?a, 4096) ++ [nil]
+
+    assert_raise ArgumentError, ~r/got:\n\n\[97, 97, .*\.\.\.\]\n\z/s, fn ->
+      List.to_charlist(invalid)
+    end
   end
 
   describe "myers_difference/2" do
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/elixir-1.20.3/lib/elixir/test/elixir/module/types/expr_test.exs 
new/elixir-1.20.4/lib/elixir/test/elixir/module/types/expr_test.exs
--- old/elixir-1.20.3/lib/elixir/test/elixir/module/types/expr_test.exs 
2026-08-04 16:31:51.000000000 +0200
+++ new/elixir-1.20.4/lib/elixir/test/elixir/module/types/expr_test.exs 
2026-08-28 12:02:55.000000000 +0200
@@ -3223,6 +3223,10 @@
              """
     end
 
+    test ":into with non-returning collectable" do
+      assert typecheck!([list], for(x <- list, into: raise("oops"), do: x)) == 
none()
+    end
+
     test ":reduce checks" do
       assert typecheck!(
                [list],
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/elixir-1.20.3/lib/elixir/test/elixir/string_test.exs 
new/elixir-1.20.4/lib/elixir/test/elixir/string_test.exs
--- old/elixir-1.20.3/lib/elixir/test/elixir/string_test.exs    2026-08-04 
16:31:51.000000000 +0200
+++ new/elixir-1.20.4/lib/elixir/test/elixir/string_test.exs    2026-08-28 
12:02:55.000000000 +0200
@@ -232,6 +232,11 @@
     assert String.downcase("Σ ΣΑΣ Σ", :greek) == "σ σας σ"
     assert String.downcase("ΜΕΣ'ΑΠΟ", :greek) == "μεσ'απο"
     assert String.downcase("ΑΣ'ΤΟΥΣ", :greek) == "ασ'τους"
+    assert String.downcase("AΣ", :greek) == "aς"
+    assert String.downcase("AΣA", :greek) == "aσa"
+    assert String.downcase("A'Σ", :greek) == "a'ς"
+    assert String.downcase("İΣ", :greek) == "i̇ς"
+    assert String.downcase("ⅠΣ", :greek) == "ⅰς"
   end
 
   test "downcase/1 with ascii" do
@@ -274,6 +279,10 @@
     assert String.capitalize("İii", :turkic) == "İii"
     assert String.capitalize("Iıı", :turkic) == "Iıı"
 
+    assert String.capitalize("AΣ", :greek) == "Aς"
+    assert String.capitalize("iΣ", :greek) == "Iς"
+    assert String.capitalize("1Σ", :greek) == "1σ"
+
     assert String.capitalize(<<138, ?B, ?C>>) == <<138, ?b, ?c>>
 
     assert String.capitalize(<<225, 158, 128, 225, 158, 185, 225>>) ==
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/elixir-1.20.3/lib/elixir/test/elixir/task_test.exs 
new/elixir-1.20.4/lib/elixir/test/elixir/task_test.exs
--- old/elixir-1.20.3/lib/elixir/test/elixir/task_test.exs      2026-08-04 
16:31:51.000000000 +0200
+++ new/elixir-1.20.4/lib/elixir/test/elixir/task_test.exs      2026-08-28 
12:02:55.000000000 +0200
@@ -630,6 +630,21 @@
                [{task2, nil}, {task3, {:exit, :normal}}]
     end
 
+    test "returns once all tasks have replied below limit" do
+      parent = self()
+
+      pid =
+        spawn(fn ->
+          task = Task.async(fn -> :done end)
+          result = Task.yield_many([task], limit: 2, timeout: :infinity)
+          send(parent, {self(), result})
+        end)
+
+      on_exit(fn -> Process.exit(pid, :kill) end)
+
+      assert_receive {^pid, [{%Task{}, {:ok, :done}}]}, 500
+    end
+
     test "returns results from multiple tasks with limit and on timeout" do
       Process.flag(:trap_exit, true)
       task1 = Task.async(fn -> Process.sleep(:infinity) end)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/elixir-1.20.3/lib/elixir/unicode/unicode.ex 
new/elixir-1.20.4/lib/elixir/unicode/unicode.ex
--- old/elixir-1.20.3/lib/elixir/unicode/unicode.ex     2026-08-04 
16:31:51.000000000 +0200
+++ new/elixir-1.20.4/lib/elixir/unicode/unicode.ex     2026-08-28 
12:02:55.000000000 +0200
@@ -46,6 +46,35 @@
   [{first, last} | acc]
 end
 
+expand_range = fn range ->
+  [range | _] = :binary.split(range, " ")
+
+  case :binary.split(range, "..") do
+    [first] ->
+      [String.to_integer(first, 16)]
+
+    [first, last] ->
+      Enum.to_list(String.to_integer(first, 16)..String.to_integer(last, 16))
+  end
+end
+
+other_cased_letters =
+  Path.join(__DIR__, "PropList.txt")
+  |> File.read!()
+  |> String.split(["\r\n", "\n"])
+  |> Enum.reduce([], fn line, acc ->
+    case :binary.split(line, ";") do
+      [range, <<" Other_Lowercase", _::binary>>] ->
+        expand_range.(range) ++ acc
+
+      [range, <<" Other_Uppercase", _::binary>>] ->
+        expand_range.(range) ++ acc
+
+      _ ->
+        acc
+    end
+  end)
+
 # A character is case ignorable if:
 #
 #    Word_Break(C) = MidLetter or MidNumLet or Single_Quote, or
@@ -152,6 +181,17 @@
     {cacc, lacc, iacc, wacc, dacc, kacc}
   end)
 
+cased_letters = Enum.sort(Enum.uniq(cased_letters ++ other_cased_letters), 
:desc)
+
+case_ignorable = Enum.sort(Enum.uniq(case_ignorable), :desc)
+
+case_ignorable_map =
+  Enum.reduce(case_ignorable, %{}, fn codepoint, acc ->
+    :maps.put(codepoint, true, acc)
+  end)
+
+cased_non_ignorable = Enum.reject(cased_letters, &:maps.is_key(&1, 
case_ignorable_map))
+
 defmodule String.Unicode do
   @moduledoc false
   def version, do: {17, 0, 0}
@@ -236,6 +276,19 @@
 
   # Downcase
 
+  def downcase_greek(string, acc) do
+    string
+    |> greek_final_sigma([], false)
+    |> downcase(acc, :default)
+  end
+
+  # Used by capitalize/2 to preserve the context supplied by the first 
grapheme.
+  def downcase_greek(string, acc, before) when is_binary(before) do
+    string
+    |> greek_final_sigma([], cased_letter_context(before, false))
+    |> downcase(acc, :default)
+  end
+
   # Turkic İ -> i
   def downcase(<<unquote(@letter_I_dot_above), rest::bits>>, acc, mode) do
     char = if mode == :turkic, do: @letter_i, else: <<@letter_i, 
@combining_dot_above>>
@@ -255,14 +308,7 @@
 
   # Greek sigma
   def downcase(<<@letter_sigma, rest::bits>>, acc, mode) do
-    downcased =
-      if mode == :greek and cased_letter_list?(acc) and not 
cased_letter_binary?(rest) do
-        @letter_small_sigma_final
-      else
-        @letter_small_sigma
-      end
-
-    downcase(rest, [downcased | acc], mode)
+    downcase(rest, [@letter_small_sigma | acc], mode)
   end
 
   conditional_downcase = [@letter_I, @letter_I_dot_above, @letter_sigma]
@@ -300,49 +346,80 @@
 
   # Sigma handling
 
-  defp cased_letter_binary?(<<codepoint::utf8, rest::bits>>) do
-    if case_ignorable?(codepoint) do
-      cased_letter_binary?(rest)
-    else
-      cased_letter?(codepoint)
+  defp greek_final_sigma(string, acc, cased_before) do
+    case :binary.match(string, @letter_sigma) do
+      {index, _length} ->
+        <<before::binary-size(^index), @letter_sigma, rest::bits>> = string
+        cased_before = cased_letter_context(before, cased_before)
+
+        downcased =
+          if cased_before and not cased_letter_binary?(rest) do
+            @letter_small_sigma_final
+          else
+            @letter_small_sigma
+          end
+
+        greek_final_sigma(rest, [downcased, before | acc], true)
+
+      :nomatch ->
+        if acc == [] do
+          string
+        else
+          IO.iodata_to_binary(:lists.reverse([string | acc]))
+        end
     end
   end
 
-  defp cased_letter_binary?(_), do: false
-
-  defp cased_letter_list?([<<codepoint::utf8>> | rest]) do
-    if case_ignorable?(codepoint) do
-      cased_letter_list?(rest)
-    else
-      cased_letter?(codepoint)
+  defp cased_letter_binary?(<<codepoint::utf8, rest::bits>>) do
+    case case_context(codepoint) do
+      :ignorable -> cased_letter_binary?(rest)
+      :cased -> true
+      :other -> false
     end
   end
 
-  defp cased_letter_list?(_), do: false
+  defp cased_letter_binary?(_), do: false
 
-  for {first, last} <- rangify.(cased_letters) do
+  for {first, last} <- rangify.(case_ignorable) do
     if first == last do
-      defp cased_letter?(unquote(first)), do: true
+      defp case_context(unquote(first)), do: :ignorable
     else
-      defp cased_letter?(codepoint)
+      defp case_context(codepoint)
            when codepoint >= unquote(first) and codepoint <= unquote(last),
-           do: true
+           do: :ignorable
     end
   end
 
-  defp cased_letter?(_), do: false
-
-  for {first, last} <- rangify.(case_ignorable) do
+  for {first, last} <- rangify.(cased_non_ignorable) do
     if first == last do
-      defp case_ignorable?(unquote(first)), do: true
+      defp case_context(unquote(first)), do: :cased
     else
-      defp case_ignorable?(codepoint)
+      defp case_context(codepoint)
            when codepoint >= unquote(first) and codepoint <= unquote(last),
-           do: true
+           do: :cased
     end
   end
 
-  defp case_ignorable?(_), do: false
+  defp case_context(_codepoint), do: :other
+
+  defp cased_letter_context(<<codepoint::utf8, rest::bits>>, cased_before) do
+    cased_before = update_cased_context(codepoint, cased_before)
+    cased_letter_context(rest, cased_before)
+  end
+
+  defp cased_letter_context(<<_byte, rest::bits>>, _cased_before) do
+    cased_letter_context(rest, false)
+  end
+
+  defp cased_letter_context("", cased_before), do: cased_before
+
+  defp update_cased_context(codepoint, cased_before) when 
is_integer(codepoint) do
+    case case_context(codepoint) do
+      :ignorable -> cased_before
+      :cased -> true
+      :other -> false
+    end
+  end
 
   # Upcase
 

Reply via email to