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-01-12 10:22:32 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/elixir (Old) and /work/SRC/openSUSE:Factory/.elixir.new.1928 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "elixir" Mon Jan 12 10:22:32 2026 rev:46 rq:1326507 version:1.19.5 Changes: -------- --- /work/SRC/openSUSE:Factory/elixir/elixir.changes 2025-11-28 16:56:41.391778608 +0100 +++ /work/SRC/openSUSE:Factory/.elixir.new.1928/elixir.changes 2026-01-12 10:31:36.066890244 +0100 @@ -1,0 +2,6 @@ +Sat Jan 10 12:09:22 UTC 2026 - Alessio Biancalana <[email protected]> + +- Upgrade to Elixir 1.19.5: + * Changelog available at https://hexdocs.pm/elixir/1.19.5/changelog.html + +------------------------------------------------------------------- Old: ---- elixir-1.19.4-doc.zip elixir-1.19.4.tar.gz New: ---- elixir-1.19.5-doc.zip elixir-1.19.5.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ elixir.spec ++++++ --- /var/tmp/diff_new_pack.7kcy4C/_old 2026-01-12 10:31:37.542950807 +0100 +++ /var/tmp/diff_new_pack.7kcy4C/_new 2026-01-12 10:31:37.546950971 +0100 @@ -18,7 +18,7 @@ %define elixirdir %{_prefix}/lib/elixir Name: elixir -Version: 1.19.4 +Version: 1.19.5 Release: 0 Summary: Functional meta-programming aware language built atop Erlang License: Apache-2.0 ++++++ _scmsync.obsinfo ++++++ --- /var/tmp/diff_new_pack.7kcy4C/_old 2026-01-12 10:31:37.646955075 +0100 +++ /var/tmp/diff_new_pack.7kcy4C/_new 2026-01-12 10:31:37.670956059 +0100 @@ -1,6 +1,6 @@ -mtime: 1764319463 -commit: a85fc128d402651f852787b6a2f57d17c2aff5586354e645033c644cea34e928 +mtime: 1768047012 +commit: fea2558efa407bd98399d116b8a6e9c095b1680b39f3c99b4111394c886f0241 url: https://src.opensuse.org/erlang/elixir.git -revision: a85fc128d402651f852787b6a2f57d17c2aff5586354e645033c644cea34e928 +revision: fea2558efa407bd98399d116b8a6e9c095b1680b39f3c99b4111394c886f0241 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-01-10 13:12:46.000000000 +0100 @@ -0,0 +1 @@ +.osc ++++++ elixir-1.19.4-doc.zip -> elixir-1.19.5-doc.zip ++++++ ++++ 93186 lines of diff (skipped) ++++++ elixir-1.19.4.tar.gz -> elixir-1.19.5.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/elixir-1.19.4/CHANGELOG.md new/elixir-1.19.5/CHANGELOG.md --- old/elixir-1.19.4/CHANGELOG.md 2025-11-27 16:50:26.000000000 +0100 +++ new/elixir-1.19.5/CHANGELOG.md 2026-01-09 11:57:56.000000000 +0100 @@ -234,6 +234,32 @@ This work was performed by [Jonatan Männchen](https://maennchen.dev) and sponsored by the [Erlang Ecosystem Foundation](https://erlef.org). +## v1.19.5 (2026-01-09) + +### 1. Enhancements + +#### Elixir + + * [Protocol] Optimize protocol consolidation to no longer load structs + +### 2. Bug fixes + +#### Elixir + + * [Kernel] Fix unnecessary recompilation when `dbg_callback` is modified at runtime + * [Kernel] Fix parser crash on missing parentheses on expression following operator `not in` + * [Kernel] Support fetching abstract code for modules compiled with Elixir v1.14 and earlier + * [Protocol] Ensure protocol consolidation no longer stores outdated struct types. As a consequence, protocols types only track struct names at the moment + * [Stream] Revert optimization which caused nested streams in `Stream.flat_map/2` to crash + +#### IEx + + * [IEx] Fix usage of `#iex:break` as part of multi-line prompts + +#### Logger + + * [Logger.Backends] Do not crash on invalid metadata + ## v1.19.4 (2025-11-27) ### 1. Enhancements diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/elixir-1.19.4/VERSION new/elixir-1.19.5/VERSION --- old/elixir-1.19.4/VERSION 2025-11-27 16:50:26.000000000 +0100 +++ new/elixir-1.19.5/VERSION 2026-01-09 11:57:56.000000000 +0100 @@ -1 +1 @@ -1.19.4 +1.19.5 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/elixir-1.19.4/bin/elixir new/elixir-1.19.5/bin/elixir --- old/elixir-1.19.4/bin/elixir 2025-11-27 16:50:26.000000000 +0100 +++ new/elixir-1.19.5/bin/elixir 2026-01-09 11:57:56.000000000 +0100 @@ -6,7 +6,7 @@ set -e -ELIXIR_VERSION=1.19.4 +ELIXIR_VERSION=1.19.5 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.19.4/bin/elixir.bat new/elixir-1.19.5/bin/elixir.bat --- old/elixir-1.19.4/bin/elixir.bat 2025-11-27 16:50:26.000000000 +0100 +++ new/elixir-1.19.5/bin/elixir.bat 2026-01-09 11:57:56.000000000 +0100 @@ -4,7 +4,7 @@ :: SPDX-FileCopyrightText: 2021 The Elixir Team :: SPDX-FileCopyrightText: 2012 Plataformatec -set ELIXIR_VERSION=1.19.4 +set ELIXIR_VERSION=1.19.5 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.19.4/lib/elixir/lib/module/types/apply.ex new/elixir-1.19.5/lib/elixir/lib/module/types/apply.ex --- old/elixir-1.19.4/lib/elixir/lib/module/types/apply.ex 2025-11-27 16:50:26.000000000 +0100 +++ new/elixir-1.19.5/lib/elixir/lib/module/types/apply.ex 2026-01-09 11:57:56.000000000 +0100 @@ -1067,6 +1067,17 @@ """ end + # The protocol has, at the moment, simplified clauses, so we build the complete one + clauses = + case mod.__protocol__(:impls) do + {:consolidated, mods} -> + domain = mods |> Enum.map(&Module.Types.Of.impl/1) |> Enum.reduce(&union/2) + [{[domain], dynamic()}] + + _ -> + clauses + end + {""" but expected a type that implements the #{inspect(mod)} protocol. #{fix}\ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/elixir-1.19.4/lib/elixir/lib/module/types/of.ex new/elixir-1.19.5/lib/elixir/lib/module/types/of.ex --- old/elixir-1.19.4/lib/elixir/lib/module/types/of.ex 2025-11-27 16:50:26.000000000 +0100 +++ new/elixir-1.19.5/lib/elixir/lib/module/types/of.ex 2026-01-09 11:57:56.000000000 +0100 @@ -141,14 +141,25 @@ {Any, term()} ] + @doc """ + Currently, for protocol implementations, we only store + the open struct definition. This is because we don't want + to reconsolidate whenever the struct changes, but at the + moment we can't store references either. Ideally struct + types on protocol dispatches would be lazily resolved. + """ + def impl(for, mode \\ :closed) + for {for, type} <- impls do - def impl(unquote(for)), do: unquote(Macro.escape(type)) + def impl(unquote(for), _mode), do: unquote(Macro.escape(type)) end - def impl(struct) do - # Elixir did not strictly require the implementation to be available, so we need a fallback. + def impl(struct, mode) do + # Elixir did not strictly require the implementation to be available, + # so we need to deal with such cases accordingly. # TODO: Assume implementation is available on Elixir v2.0. - if info = Code.ensure_loaded?(struct) && struct.__info__(:struct) do + # A warning is emitted since v1.19+. + if info = mode == :closed && Code.ensure_loaded?(struct) && struct.__info__(:struct) do struct_type(struct, info) else open_map(__struct__: atom([struct])) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/elixir-1.19.4/lib/elixir/lib/protocol.ex new/elixir-1.19.5/lib/elixir/lib/protocol.ex --- old/elixir-1.19.4/lib/elixir/lib/protocol.ex 2025-11-27 16:50:26.000000000 +0100 +++ new/elixir-1.19.5/lib/elixir/lib/protocol.ex 2026-01-09 11:57:56.000000000 +0100 @@ -453,7 +453,7 @@ true """ - @spec extract_protocols([charlist | String.t()]) :: [atom] + @spec extract_protocols([charlist | String.t() | {charlist, [charlist]}]) :: [atom] def extract_protocols(paths) do extract_matching_by_attribute(paths, [?E, ?l, ?i, ?x, ?i, ?r, ?.], fn module, attributes -> case attributes[:__protocol__] do @@ -482,7 +482,7 @@ true """ - @spec extract_impls(module, [charlist | String.t()]) :: [atom] + @spec extract_impls(module, [charlist | String.t() | {charlist, [charlist]}]) :: [atom] def extract_impls(protocol, paths) when is_atom(protocol) do prefix = Atom.to_charlist(protocol) ++ [?.] @@ -496,17 +496,25 @@ defp extract_matching_by_attribute(paths, prefix, callback) do for path <- paths, - # Do not use protocols as they may be consolidating - path = if(is_list(path), do: path, else: String.to_charlist(path)), - file <- list_dir(path), + {path, files} = list_dir(path), + file <- files, mod = extract_from_file(path, file, prefix, callback), do: mod end + # Do not use protocols as they may be consolidating + defp list_dir({path, files}) when is_list(path) and is_list(files) do + {path, files} + end + + defp list_dir(path) when is_binary(path) do + list_dir(String.to_charlist(path)) + end + defp list_dir(path) when is_list(path) do case :file.list_dir(path) do - {:ok, files} -> files - _ -> [] + {:ok, files} -> {path, files} + _ -> {path, []} end end @@ -633,7 +641,7 @@ checker = if checker do update_in(checker.exports, fn exports -> - signatures = new_signatures(definitions, protocol_funs, protocol, types) + signatures = new_signatures(definitions, protocol_funs, protocol, types, structs) for {fun, info} <- exports do if sig = Map.get(signatures, fun) do @@ -652,14 +660,13 @@ end end - defp new_signatures(definitions, protocol_funs, protocol, types) do + defp new_signatures(definitions, protocol_funs, protocol, types, structs) do alias Module.Types.Descr + types_minus_any = List.delete(types, Any) clauses = - types - |> List.delete(Any) - |> Enum.map(fn impl -> - {[Module.Types.Of.impl(impl)], Descr.atom([__concat__(protocol, impl)])} + Enum.map(types_minus_any, fn impl -> + {[Module.Types.Of.impl(impl, :open)], Descr.atom([__concat__(protocol, impl)])} end) {domain, impl_for, impl_for!} = @@ -674,10 +681,16 @@ end _ -> + structs_domain = + case structs do + [] -> Descr.none() + _ -> Descr.open_map(__struct__: Descr.atom(structs)) + end + domain = - clauses - |> Enum.map(fn {[domain], _} -> domain end) - |> Enum.reduce(&Descr.union/2) + Enum.reduce(types_minus_any -- structs, structs_domain, fn impl, acc -> + Descr.union(Module.Types.Of.impl(impl, :open), acc) + end) not_domain = Descr.negation(domain) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/elixir-1.19.4/lib/elixir/lib/stream.ex new/elixir-1.19.5/lib/elixir/lib/stream.ex --- old/elixir-1.19.4/lib/elixir/lib/stream.ex 2025-11-27 16:50:26.000000000 +0100 +++ new/elixir-1.19.5/lib/elixir/lib/stream.ex 2026-01-09 11:57:56.000000000 +0100 @@ -964,17 +964,13 @@ after_fun.(user_acc) :erlang.raise(kind, reason, __STACKTRACE__) else - {:suspended, [val], next} -> - do_transform_user(val, user_acc, :cont, next, inner_acc, funs) + {:suspended, vals, next} -> + do_transform_user(:lists.reverse(vals), user_acc, :cont, next, inner_acc, funs) - {_, result} -> + {_, vals} -> # Do not attempt to call the resource again, it has either done or halted next = fn _ -> {:done, []} end - - case result do - [val] -> do_transform_user(val, user_acc, :last, next, inner_acc, funs) - [] -> do_transform(user_acc, :last, next, inner_acc, funs) - end + do_transform_user(:lists.reverse(vals), user_acc, :last, next, inner_acc, funs) end end @@ -989,7 +985,7 @@ after_fun.(user_acc) :erlang.raise(kind, reason, __STACKTRACE__) else - result -> do_transform_result(result, :halt, next, inner_acc, funs) + result -> do_transform_result(result, [], :halt, next, inner_acc, funs) end else do_transform(user_acc, :halt, next, inner_acc, funs) @@ -1002,7 +998,11 @@ {:halted, elem(inner_acc, 1)} end - defp do_transform_user(val, user_acc, next_op, next, inner_acc, funs) do + defp do_transform_user([], user_acc, next_op, next, inner_acc, funs) do + do_transform(user_acc, next_op, next, inner_acc, funs) + end + + defp do_transform_user([val | vals], user_acc, next_op, next, inner_acc, funs) do {user, _, _, _, after_fun} = funs try do @@ -1013,20 +1013,20 @@ after_fun.(user_acc) :erlang.raise(kind, reason, __STACKTRACE__) else - result -> do_transform_result(result, next_op, next, inner_acc, funs) + result -> do_transform_result(result, vals, next_op, next, inner_acc, funs) end end - defp do_transform_result(result, next_op, next, inner_acc, funs) do + defp do_transform_result(result, vals, next_op, next, inner_acc, funs) do {_, fun, inner, _, after_fun} = funs case result do {[], user_acc} -> - do_transform(user_acc, next_op, next, inner_acc, funs) + do_transform_user(vals, user_acc, next_op, next, inner_acc, funs) {list, user_acc} when is_list(list) -> reduce = &Enumerable.List.reduce(list, &1, fun) - do_transform_inner_list(user_acc, next_op, next, inner_acc, reduce, funs) + do_transform_inner_list(vals, user_acc, next_op, next, inner_acc, reduce, funs) {:halt, user_acc} -> next.({:halt, []}) @@ -1035,11 +1035,11 @@ {other, user_acc} -> reduce = &Enumerable.reduce(other, &1, inner) - do_transform_inner_enum(user_acc, next_op, next, inner_acc, reduce, funs) + do_transform_inner_enum(vals, user_acc, next_op, next, inner_acc, reduce, funs) end end - defp do_transform_inner_list(user_acc, next_op, next, inner_acc, reduce, funs) do + defp do_transform_inner_list(vals, user_acc, next_op, next, inner_acc, reduce, funs) do {_, _, _, _, after_fun} = funs try do @@ -1051,7 +1051,7 @@ :erlang.raise(kind, reason, __STACKTRACE__) else {:done, acc} -> - do_transform(user_acc, next_op, next, {:cont, acc}, funs) + do_transform_user(vals, user_acc, next_op, next, {:cont, acc}, funs) {:halted, acc} -> next.({:halt, []}) @@ -1059,12 +1059,12 @@ {:halted, acc} {:suspended, acc, continuation} -> - resume = &do_transform_inner_list(user_acc, next_op, next, &1, continuation, funs) + resume = &do_transform_inner_list(vals, user_acc, next_op, next, &1, continuation, funs) {:suspended, acc, resume} end end - defp do_transform_inner_enum(user_acc, next_op, next, {op, inner_acc}, reduce, funs) do + defp do_transform_inner_enum(vals, user_acc, next_op, next, {op, inner_acc}, reduce, funs) do {_, _, _, _, after_fun} = funs try do @@ -1078,7 +1078,7 @@ # The user wanted to cont/suspend but the stream halted, # so we continue with the user intention. {:halted, [inner_op | acc]} when op != :halt and inner_op != :halt -> - do_transform(user_acc, next_op, next, {inner_op, acc}, funs) + do_transform_user(vals, user_acc, next_op, next, {inner_op, acc}, funs) {:halted, [_ | acc]} -> next.({:halt, []}) @@ -1086,10 +1086,10 @@ {:halted, acc} {:done, [_ | acc]} -> - do_transform(user_acc, next_op, next, {:cont, acc}, funs) + do_transform_user(vals, user_acc, next_op, next, {:cont, acc}, funs) {:suspended, [_ | acc], continuation} -> - resume = &do_transform_inner_enum(user_acc, next_op, next, &1, continuation, funs) + resume = &do_transform_inner_enum(vals, user_acc, next_op, next, &1, continuation, funs) {:suspended, acc, resume} end end diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/elixir-1.19.4/lib/elixir/src/elixir.erl new/elixir-1.19.5/lib/elixir/src/elixir.erl --- old/elixir-1.19.4/lib/elixir/src/elixir.erl 2025-11-27 16:50:26.000000000 +0100 +++ new/elixir-1.19.5/lib/elixir/src/elixir.erl 2026-01-09 11:57:56.000000000 +0100 @@ -64,6 +64,11 @@ application:set_env(elixir, ansi_enabled, prim_tty:isatty(stdout) == true) end, + %% Store the initial dbg_callback value before any runtime modifications. + %% This allows Mix compiler to detect config changes vs runtime changes + %% (e.g., Kino wrapping dbg_callback at runtime should not trigger recompilation). + {ok, InitialDbgCallback} = application:get_env(elixir, dbg_callback), + Tokenizer = case code:ensure_loaded('Elixir.String.Tokenizer') of {module, Mod} -> Mod; _ -> elixir_tokenizer @@ -90,6 +95,7 @@ {docs, true}, {ignore_already_consolidated, false}, {ignore_module_conflict, false}, + {initial_dbg_callback, InitialDbgCallback}, {infer_signatures, [elixir]}, {on_undefined_variable, raise}, {parser_options, [{columns, true}]}, diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/elixir-1.19.4/lib/elixir/src/elixir_erl_pass.erl new/elixir-1.19.5/lib/elixir/src/elixir_erl_pass.erl --- old/elixir-1.19.4/lib/elixir/src/elixir_erl_pass.erl 2025-11-27 16:50:26.000000000 +0100 +++ new/elixir-1.19.5/lib/elixir/src/elixir_erl_pass.erl 2026-01-09 11:57:56.000000000 +0100 @@ -566,6 +566,11 @@ extract_bit_type({unit, _, [Arg]}, Acc) -> [{unit, Arg} | Acc]; extract_bit_type({Other, _, nil}, Acc) -> + [Other | Acc]; +%% TODO: Remove me on Elixir v2.0. +%% Elixir v1.14 and earlier emitted an empty list +%% and may still be processed via debug_info. +extract_bit_type({Other, _, []}, Acc) -> [Other | Acc]. %% Optimizations that are specific to Erlang and change diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/elixir-1.19.4/lib/elixir/src/elixir_erl_try.erl new/elixir-1.19.5/lib/elixir/src/elixir_erl_try.erl --- old/elixir-1.19.4/lib/elixir/src/elixir_erl_try.erl 2025-11-27 16:50:26.000000000 +0100 +++ new/elixir-1.19.5/lib/elixir/src/elixir_erl_try.erl 2026-01-09 11:57:56.000000000 +0100 @@ -27,8 +27,9 @@ {Args, Guards} = elixir_utils:extract_splat_guards(Raw), Match = + %% TODO: Remove me on Elixir v2.0. %% Elixir v1.17 and earlier emitted single argument - %% and may still be processed via debug_info + %% and may still be processed via debug_info. case Args of [X] -> [throw, X]; [X, Y] -> [X, Y] diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/elixir-1.19.4/lib/elixir/src/elixir_parser.yrl new/elixir-1.19.5/lib/elixir/src/elixir_parser.yrl --- old/elixir-1.19.4/lib/elixir/src/elixir_parser.yrl 2025-11-27 16:50:26.000000000 +0100 +++ new/elixir-1.19.5/lib/elixir/src/elixir_parser.yrl 2026-01-09 11:57:56.000000000 +0100 @@ -1298,6 +1298,14 @@ ok. %% TODO: Make this an error on v2.0 +warn_no_parens_after_do_op({{in_op, Location, Op, _InLocation}, _}) -> + {Line, _, _} = Location, + + warn( + Line, + "missing parentheses on expression following operator \"" ++ atom_to_list(Op) ++ "\", " + "you must add parentheses to avoid ambiguities" + ); warn_no_parens_after_do_op({{_Type, Location, Op}, _}) -> {Line, _, _} = Location, diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/elixir-1.19.4/lib/elixir/test/elixir/kernel/warning_test.exs new/elixir-1.19.5/lib/elixir/test/elixir/kernel/warning_test.exs --- old/elixir-1.19.4/lib/elixir/test/elixir/kernel/warning_test.exs 2025-11-27 16:50:26.000000000 +0100 +++ new/elixir-1.19.5/lib/elixir/test/elixir/kernel/warning_test.exs 2026-01-09 11:57:56.000000000 +0100 @@ -1953,6 +1953,18 @@ ) end + test "do+end with not in operator without explicit parentheses" do + assert_warn_eval( + ["nofile:3\n", "missing parentheses on expression following operator \"not in\""], + """ + quote do + case do + end not in no_parens 1, 2 + end + """ + ) + end + test "variable is being expanded to function call (on_undefined_variable: warn)" do capture_io(:stderr, fn -> Code.put_compiler_option(:on_undefined_variable, :warn) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/elixir-1.19.4/lib/elixir/test/elixir/protocol/consolidation_test.exs new/elixir-1.19.5/lib/elixir/test/elixir/protocol/consolidation_test.exs --- old/elixir-1.19.4/lib/elixir/test/elixir/protocol/consolidation_test.exs 2025-11-27 16:50:26.000000000 +0100 +++ new/elixir-1.19.5/lib/elixir/test/elixir/protocol/consolidation_test.exs 2026-01-09 11:57:56.000000000 +0100 @@ -187,9 +187,11 @@ assert domain == [term()] assert clauses == [ - {[Of.impl(Map)], atom([WithAny.Map])}, - {[Of.impl(ImplStruct)], atom([WithAny.Protocol.ConsolidationTest.ImplStruct])}, - {[negation(union(Of.impl(ImplStruct), Of.impl(Map)))], atom([WithAny.Any])} + {[Of.impl(Map, :open)], atom([WithAny.Map])}, + {[Of.impl(ImplStruct, :open)], + atom([WithAny.Protocol.ConsolidationTest.ImplStruct])}, + {[negation(union(Of.impl(ImplStruct, :open), Of.impl(Map, :open)))], + atom([WithAny.Any])} ] assert %{{:ok, 2} => %{sig: {:strong, nil, clauses}}} = exports @@ -256,18 +258,34 @@ assert Inspect in protos end + test "protocols with expanded path" do + path = to_charlist(Application.app_dir(:elixir, "ebin")) + {:ok, mods} = :file.list_dir(path) + protos = Protocol.extract_protocols([{path, mods}]) + assert Enumerable in protos + assert Inspect in protos + end + test "implementations with charlist path" do - protos = + impls = Protocol.extract_impls(Enumerable, [to_charlist(Application.app_dir(:elixir, "ebin"))]) - assert List in protos - assert Function in protos + assert List in impls + assert Function in impls end test "implementations with binary path" do - protos = Protocol.extract_impls(Enumerable, [Application.app_dir(:elixir, "ebin")]) - assert List in protos - assert Function in protos + impls = Protocol.extract_impls(Enumerable, [Application.app_dir(:elixir, "ebin")]) + assert List in impls + assert Function in impls + end + + test "implementations with expanded path" do + path = to_charlist(Application.app_dir(:elixir, "ebin")) + {:ok, mods} = :file.list_dir(path) + impls = Protocol.extract_impls(Enumerable, [{path, mods}]) + assert List in impls + assert Function in impls end end end diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/elixir-1.19.4/lib/elixir/test/elixir/regex_test.exs new/elixir-1.19.5/lib/elixir/test/elixir/regex_test.exs --- old/elixir-1.19.4/lib/elixir/test/elixir/regex_test.exs 2025-11-27 16:50:26.000000000 +0100 +++ new/elixir-1.19.5/lib/elixir/test/elixir/regex_test.exs 2026-01-09 11:57:56.000000000 +0100 @@ -127,7 +127,8 @@ test "compile!/1" do assert %Regex{} = Regex.compile!("foo") - assert_raise Regex.CompileError, ~r/position 0$/, fn -> + # The exact position changed between Erlang/OTP 28.1 and 28.3 + assert_raise Regex.CompileError, ~r/at position/, fn -> Regex.compile!("*foo") end end diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/elixir-1.19.4/lib/elixir/test/elixir/stream_test.exs new/elixir-1.19.5/lib/elixir/test/elixir/stream_test.exs --- old/elixir-1.19.4/lib/elixir/test/elixir/stream_test.exs 2025-11-27 16:50:26.000000000 +0100 +++ new/elixir-1.19.5/lib/elixir/test/elixir/stream_test.exs 2026-01-09 11:57:56.000000000 +0100 @@ -183,6 +183,24 @@ assert Stream.chunk_while([1, 2, 3, 4, 5], [], chunk_fun, after_fun) |> Enum.at(0) == [1] end + test "chunk_while/4 regression case with concat" do + result = + ["WrongHeader\nJohn Doe", "skipped"] + |> Stream.take(1) + |> Stream.chunk_while( + "", + fn element, acc -> + {acc, elements} = String.split(acc <> element, "\n") |> List.pop_at(-1) + {:cont, elements, acc} + end, + &{:cont, [&1], []} + ) + |> Stream.concat() + |> Enum.to_list() + + assert result == ["WrongHeader", "John Doe"] + end + test "concat/1" do stream = Stream.concat([1..3, [], [4, 5, 6], [], 7..9]) assert is_function(stream) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/elixir-1.19.4/lib/ex_unit/lib/ex_unit/callbacks.ex new/elixir-1.19.5/lib/ex_unit/lib/ex_unit/callbacks.ex --- old/elixir-1.19.4/lib/ex_unit/lib/ex_unit/callbacks.ex 2025-11-27 16:50:26.000000000 +0100 +++ new/elixir-1.19.5/lib/ex_unit/lib/ex_unit/callbacks.ex 2026-01-09 11:57:56.000000000 +0100 @@ -169,11 +169,7 @@ end """ - @type child_spec_overrides :: [ - restart: :permanent | :transient | :temporary, - shutdown: :brutal_kill | timeout(), - type: :worker | :supervisor - ] + @type child_spec_overrides :: Supervisor.child_spec_overrides() @doc false def __register__(module) do diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/elixir-1.19.4/lib/iex/lib/iex/evaluator.ex new/elixir-1.19.5/lib/iex/lib/iex/evaluator.ex --- old/elixir-1.19.4/lib/iex/lib/iex/evaluator.ex 2025-11-27 16:50:26.000000000 +0100 +++ new/elixir-1.19.5/lib/iex/lib/iex/evaluator.ex 2026-01-09 11:57:56.000000000 +0100 @@ -71,61 +71,56 @@ def parse(input, opts, []), do: parse(input, opts, {[], :other}) - def parse(@break_trigger, opts, _parser_state) do - :elixir_errors.parse_error( - [line: opts[:line]], - opts[:file], - "incomplete expression", - "", - {~c"", Keyword.get(opts, :line, 1), Keyword.get(opts, :column, 1), 0} - ) - end - def parse(input, opts, {buffer, last_op}) do input = buffer ++ input file = Keyword.get(opts, :file, "nofile") line = Keyword.get(opts, :line, 1) column = Keyword.get(opts, :column, 1) - result = - with {:ok, tokens} <- :elixir.string_to_tokens(input, line, column, file, opts), - {:ok, adjusted_tokens, adjusted_op} <- - adjust_operator(tokens, line, column, file, opts, last_op), - {:ok, forms} <- :elixir.tokens_to_quoted(adjusted_tokens, file, opts) do - last_op = - case forms do - {:=, _, [_, _]} -> :match - _ -> :other - end - - forms = - if adjusted_op != nil do - quote do - IEx.Evaluator.assert_no_error!() - unquote(forms) + if List.ends_with?(input, @break_trigger) do + triplet = {~c"", line, column, 0} + :elixir_errors.parse_error([line: line], file, "incomplete expression", "", triplet) + else + result = + with {:ok, tokens} <- :elixir.string_to_tokens(input, line, column, file, opts), + {:ok, adjusted_tokens, adjusted_op} <- + adjust_operator(tokens, line, column, file, opts, last_op), + {:ok, forms} <- :elixir.tokens_to_quoted(adjusted_tokens, file, opts) do + last_op = + case forms do + {:=, _, [_, _]} -> :match + _ -> :other end - else - forms - end - {:ok, forms, last_op} - end + forms = + if adjusted_op != nil do + quote do + IEx.Evaluator.assert_no_error!() + unquote(forms) + end + else + forms + end + + {:ok, forms, last_op} + end - case result do - {:ok, forms, last_op} -> - {:ok, forms, {[], last_op}} - - {:error, {_, _, ""}} -> - {:incomplete, {input, last_op}} - - {:error, {location, error, token}} -> - :elixir_errors.parse_error( - location, - file, - error, - token, - {input, line, column, 0} - ) + case result do + {:ok, forms, last_op} -> + {:ok, forms, {[], last_op}} + + {:error, {_, _, ""}} -> + {:incomplete, {input, last_op}} + + {:error, {location, error, token}} -> + :elixir_errors.parse_error( + location, + file, + error, + token, + {input, line, column, 0} + ) + end end end diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/elixir-1.19.4/lib/logger/lib/logger/backends/handler.ex new/elixir-1.19.5/lib/logger/lib/logger/backends/handler.ex --- old/elixir-1.19.4/lib/logger/lib/logger/backends/handler.ex 2025-11-27 16:50:26.000000000 +0100 +++ new/elixir-1.19.5/lib/logger/lib/logger/backends/handler.ex 2026-01-09 11:57:56.000000000 +0100 @@ -47,7 +47,13 @@ %{truncate: truncate, utc_log: utc_log?} = config level = erlang_level_to_elixir_level(erl_level) message = Logger.Formatter.format_event(event, truncate) - timestamp = Map.get_lazy(metadata, :time, fn -> :os.system_time(:microsecond) end) + + timestamp = + case metadata do + %{time: time} when is_integer(time) and time >= 0 -> time + _ -> :os.system_time(:microsecond) + end + date_time_ms = Logger.Formatter.system_time_to_date_time_ms(timestamp, utc_log?) metadata = [erl_level: erl_level] ++ erlang_metadata_to_elixir_metadata(metadata) event = {level, gl, {Logger, message, date_time_ms, metadata}} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/elixir-1.19.4/lib/logger/test/logger/backends/handler_test.exs new/elixir-1.19.5/lib/logger/test/logger/backends/handler_test.exs --- old/elixir-1.19.4/lib/logger/test/logger/backends/handler_test.exs 2025-11-27 16:50:26.000000000 +0100 +++ new/elixir-1.19.5/lib/logger/test/logger/backends/handler_test.exs 2026-01-09 11:57:56.000000000 +0100 @@ -113,6 +113,12 @@ Logger.configure_backend(Logger.Backends.Console, metadata: []) end + test "ignores invalid Erlang metadata" do + assert capture_log(fn -> :logger.info("ok", %{time: "bad"}) end) =~ "ok" + after + Logger.Backends.Internal.configure(Logger.Backends.Console, metadata: []) + end + test "uses reporting callback with Elixir inspection" do assert capture_log(fn -> callback = fn %{hello: :world} -> {"~p~n", [:formatted]} end diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/elixir-1.19.4/lib/mix/lib/mix/compilers/elixir.ex new/elixir-1.19.5/lib/mix/lib/mix/compilers/elixir.ex --- old/elixir-1.19.4/lib/mix/lib/mix/compilers/elixir.ex 2025-11-27 16:50:26.000000000 +0100 +++ new/elixir-1.19.5/lib/mix/lib/mix/compilers/elixir.ex 2026-01-09 11:57:56.000000000 +0100 @@ -287,7 +287,13 @@ end defp deps_config_compile_env_apps(deps_config) do - if deps_config[:dbg] != Application.fetch_env!(:elixir, :dbg_callback) do + # Use initial_dbg_callback instead of dbg_callback to ignore runtime modifications. + # Tools like Kino modify dbg_callback at runtime to customize dbg/2 behavior, + # but this should not trigger recompilation since the config hasn't actually changed. + # initial_dbg_callback is set when :elixir app starts, before any runtime modifications. + initial_dbg = :elixir_config.get(:initial_dbg_callback) + + if deps_config[:dbg] != initial_dbg do [:elixir] else [] diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/elixir-1.19.4/lib/mix/lib/mix/compilers/protocol.ex new/elixir-1.19.5/lib/mix/lib/mix/compilers/protocol.ex --- old/elixir-1.19.4/lib/mix/lib/mix/compilers/protocol.ex 2025-11-27 16:50:26.000000000 +0100 +++ new/elixir-1.19.5/lib/mix/lib/mix/compilers/protocol.ex 2026-01-09 11:57:56.000000000 +0100 @@ -126,11 +126,16 @@ end defp consolidation_paths do - filter_otp(:code.get_path(), :code.lib_dir()) - end + otp = :code.lib_dir() - defp filter_otp(paths, otp) do - Enum.filter(paths, &(not :lists.prefix(otp, &1))) + # Pre-list the paths in each directory for performance + for path <- :code.get_path(), not :lists.prefix(otp, path) do + {path, + case :file.list_dir(path) do + {:ok, files} -> files + _ -> [] + end} + end end defp consolidate([], _paths, output, _opts) do diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/elixir-1.19.4/lib/mix/test/mix/tasks/compile.elixir_test.exs new/elixir-1.19.5/lib/mix/test/mix/tasks/compile.elixir_test.exs --- old/elixir-1.19.4/lib/mix/test/mix/tasks/compile.elixir_test.exs 2025-11-27 16:50:26.000000000 +0100 +++ new/elixir-1.19.5/lib/mix/test/mix/tasks/compile.elixir_test.exs 2026-01-09 11:57:56.000000000 +0100 @@ -278,9 +278,11 @@ assert_received {:mix_shell, :info, ["Compiled lib/a.ex"]} assert_received {:mix_shell, :info, ["Compiled lib/b.ex"]} - # Change the dbg_callback at runtime + # Simulate a config change by updating both dbg_callback and initial_dbg_callback. + # This represents the case where the user actually changed the config file. File.touch!("_build/dev/lib/sample/.mix/compile.elixir", @old_time) Application.put_env(:elixir, :dbg_callback, {__MODULE__, :dbg, []}) + :elixir_config.put(:initial_dbg_callback, {__MODULE__, :dbg, []}) assert Mix.Tasks.Compile.Elixir.run(["--verbose"]) == {:ok, []} assert_received {:mix_shell, :info, ["Compiled lib/a.ex"]} @@ -289,6 +291,35 @@ end) after Application.put_env(:elixir, :dbg_callback, {Macro, :dbg, []}) + :elixir_config.put(:initial_dbg_callback, {Macro, :dbg, []}) + end + + test "does not recompile when dbg_callback is modified at runtime but initial is unchanged" do + in_fixture("no_mixfile", fn -> + Mix.Project.push(MixTest.Case.Sample) + + File.write!("lib/a.ex", """ + defmodule A do + def a, do: dbg(:ok) + end + """) + + assert Mix.Tasks.Compile.Elixir.run(["--verbose"]) == {:ok, []} + assert_received {:mix_shell, :info, ["Compiled lib/a.ex"]} + + # Simulate a tool like Kino modifying dbg_callback at runtime. + # Since initial_dbg_callback remains unchanged, this should NOT trigger recompilation. + original_dbg = Application.fetch_env!(:elixir, :dbg_callback) + :elixir_config.put(:initial_dbg_callback, original_dbg) + Application.put_env(:elixir, :dbg_callback, {SomeDebugTool, :dbg, [original_dbg]}) + + # Should NOT trigger recompilation since initial_dbg_callback is unchanged + assert Mix.Tasks.Compile.Elixir.run(["--verbose"]) == {:noop, []} + refute_received {:mix_shell, :info, ["Compiled lib/a.ex"]} + end) + after + Application.put_env(:elixir, :dbg_callback, {Macro, :dbg, []}) + :elixir_config.put(:initial_dbg_callback, {Macro, :dbg, []}) end test "recompiles files when config changes export dependencies" do
