From 0e9b5ec0704c21ba63b205c149bbb2373dd0c1de Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jos=C3=A9=20Valim?= <jose.valim@dashbit.co>
Date: Tue, 10 Dec 2024 15:01:39 +0100
Subject: [PATCH 001/139] Branch out v1.18

---
 Makefile                                                     | 2 +-
 SECURITY.md                                                  | 5 ++---
 .../pages/references/compatibility-and-deprecations.md       | 5 ++---
 3 files changed, 5 insertions(+), 7 deletions(-)

diff --git a/Makefile b/Makefile
index b6f51b1ffbc..0a4620bc733 100644
--- a/Makefile
+++ b/Makefile
@@ -2,7 +2,7 @@ PREFIX ?= /usr/local
 TEST_FILES ?= "*_test.exs"
 SHARE_PREFIX ?= $(PREFIX)/share
 MAN_PREFIX ?= $(SHARE_PREFIX)/man
-CANONICAL := main/
+CANONICAL := v1.18/
 ELIXIRC := bin/elixirc --ignore-module-conflict $(ELIXIRC_OPTS)
 ERLC := erlc -I lib/elixir/include
 ERL_MAKE := erl -make
diff --git a/SECURITY.md b/SECURITY.md
index d035b75db8f..f436cf62934 100644
--- a/SECURITY.md
+++ b/SECURITY.md
@@ -6,12 +6,11 @@ Elixir applies bug fixes only to the latest minor branch. Security patches are a
 
 Elixir version | Support
 :------------- | :-----------------------------
-1.18           | Development
-1.17           | Bug fixes and security patches
+1.18           | Bug fixes and security patches
+1.17           | Security patches only
 1.16           | Security patches only
 1.15           | Security patches only
 1.14           | Security patches only
-1.13           | Security patches only
 
 ## Announcements
 
diff --git a/lib/elixir/pages/references/compatibility-and-deprecations.md b/lib/elixir/pages/references/compatibility-and-deprecations.md
index 4b53ae59c01..1519ebe6b96 100644
--- a/lib/elixir/pages/references/compatibility-and-deprecations.md
+++ b/lib/elixir/pages/references/compatibility-and-deprecations.md
@@ -8,12 +8,11 @@ Elixir applies bug fixes only to the latest minor branch. Security patches are a
 
 Elixir version | Support
 :------------- | :-----------------------------
-1.18           | Development
-1.17           | Bug fixes and security patches
+1.18           | Bug fixes and security patches
+1.17           | Security patches only
 1.16           | Security patches only
 1.15           | Security patches only
 1.14           | Security patches only
-1.13           | Security patches only
 
 New releases are announced in the read-only [announcements mailing list](https://groups.google.com/group/elixir-lang-ann). All security releases [will be tagged with `[security]`](https://groups.google.com/forum/#!searchin/elixir-lang-ann/%5Bsecurity%5D%7Csort:date).
 

From 3bdf5a912c3dbbe8ac382172a03b1e9075b0354d Mon Sep 17 00:00:00 2001
From: Daniel Gomez de Souza <eldano.v2@gmail.com>
Date: Tue, 10 Dec 2024 11:34:32 -0300
Subject: [PATCH 002/139] Fix typo (#14046)

---
 CHANGELOG.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 06439826071..2835253afa9 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -10,7 +10,7 @@ The most exciting change in Elixir v1.18 is type checking of function calls, alo
 defmodule User do
   defstruct [:age, :car_choice]
 
-  def drive(%User{age: age, car_choice: car}, cars_choices) when age >= 18 do
+  def drive(%User{age: age, car_choice: car}, car_choices) when age >= 18 do
     if car in car_choices do
       {:ok, car}
     else

From 31d8da6c770d09f2bf070f1436cb2b0a7f7c3653 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jos=C3=A9=20Valim?= <jose.valim@dashbit.co>
Date: Tue, 10 Dec 2024 15:41:05 +0100
Subject: [PATCH 003/139] Avoid mtime races on compile.app_test.exs

---
 lib/mix/test/mix/tasks/compile.app_test.exs | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/lib/mix/test/mix/tasks/compile.app_test.exs b/lib/mix/test/mix/tasks/compile.app_test.exs
index 53909254607..943a1876dd6 100644
--- a/lib/mix/test/mix/tasks/compile.app_test.exs
+++ b/lib/mix/test/mix/tasks/compile.app_test.exs
@@ -256,6 +256,8 @@ defmodule Mix.Tasks.Compile.AppTest do
   test ".app contains description and registered (as required by systools)" do
     in_fixture("no_mixfile", fn ->
       Mix.Project.push(MixTest.Case.Sample)
+      # Pre-create the compilation path to avoid mtime races
+      File.mkdir_p(Mix.Project.compile_path())
 
       Mix.Tasks.Compile.Elixir.run([])
       assert Mix.Tasks.Compile.App.run([]) == {:ok, []}

From 66c5908619f2ee9b4b1113e0302b00b5a59a5abb Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jos=C3=A9=20Valim?= <jose.valim@dashbit.co>
Date: Tue, 10 Dec 2024 18:32:26 +0100
Subject: [PATCH 004/139] Release v1.18.0-rc.0

---
 CHANGELOG.md                                                  | 4 ++--
 VERSION                                                       | 2 +-
 bin/elixir                                                    | 2 +-
 bin/elixir.bat                                                | 2 +-
 bin/elixir.ps1                                                | 2 +-
 lib/elixir/pages/references/compatibility-and-deprecations.md | 1 +
 6 files changed, 7 insertions(+), 6 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 2835253afa9..f5cdd890899 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -182,7 +182,7 @@ If you have a struct, you can derive the implementation of the `JSON.Encoder`
 by specifying which fields should be encoded to JSON:
 
 ```elixir
-  @derive {JSON.Encoder, only: [....]}
+  @derive {JSON.Encoder, only: [...]}
   defstruct ...
 ```
 
@@ -219,7 +219,7 @@ You may also prefer to write using guards:
 
     def foo(x, y, z) when x == y and y == z
 
-## v1.18.0-dev
+## v1.18.0-rc.0 (2024-12-10)
 
 ### 1. Enhancements
 
diff --git a/VERSION b/VERSION
index ee017091ff3..ca117a054fb 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-1.18.0-dev
+1.18.0-rc.0
diff --git a/bin/elixir b/bin/elixir
index 79b74deb9e2..23c07afe136 100755
--- a/bin/elixir
+++ b/bin/elixir
@@ -1,7 +1,7 @@
 #!/bin/sh
 set -e
 
-ELIXIR_VERSION=1.18.0-dev
+ELIXIR_VERSION=1.18.0-rc.0
 
 if [ $# -eq 0 ] || { [ $# -eq 1 ] && { [ "$1" = "--help" ] || [ "$1" = "-h" ]; }; }; then
   cat <<USAGE >&2
diff --git a/bin/elixir.bat b/bin/elixir.bat
index 448e22f4fd3..d4c66c8ab6e 100644
--- a/bin/elixir.bat
+++ b/bin/elixir.bat
@@ -1,6 +1,6 @@
 @echo off
 
-set ELIXIR_VERSION=1.18.0-dev
+set ELIXIR_VERSION=1.18.0-rc.0
 
 if    ""%1""==""""                if ""%2""=="""" goto documentation
 if /I ""%1""==""--help""          if ""%2""=="""" goto documentation
diff --git a/bin/elixir.ps1 b/bin/elixir.ps1
index 90048705549..b9b7e7f5a0d 100755
--- a/bin/elixir.ps1
+++ b/bin/elixir.ps1
@@ -1,6 +1,6 @@
 #!/usr/bin/env pwsh
 
-$ELIXIR_VERSION = "1.18.0-dev"
+$ELIXIR_VERSION = "1.18.0-rc.0"
 
 $scriptPath = Split-Path -Parent $PSCommandPath
 $erlExec = "erl"
diff --git a/lib/elixir/pages/references/compatibility-and-deprecations.md b/lib/elixir/pages/references/compatibility-and-deprecations.md
index 1519ebe6b96..11a37689306 100644
--- a/lib/elixir/pages/references/compatibility-and-deprecations.md
+++ b/lib/elixir/pages/references/compatibility-and-deprecations.md
@@ -42,6 +42,7 @@ Erlang/OTP versioning is independent from the versioning of Elixir. Erlang relea
 
 Elixir version | Supported Erlang/OTP versions
 :------------- | :-------------------------------
+1.18           | 25 - 27
 1.17           | 25 - 27
 1.16           | 24 - 26
 1.15           | 24 - 26

From da7e04a540d4a05616429af4fcb36287fb88fa1f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jos=C3=A9=20Valim?= <jose.valim@dashbit.co>
Date: Tue, 10 Dec 2024 19:16:09 +0100
Subject: [PATCH 005/139] Improve docs around auto_reloading

---
 CHANGELOG.md       | 10 ++++++++++
 lib/iex/lib/iex.ex | 26 +++++++++++++++-----------
 2 files changed, 25 insertions(+), 11 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index f5cdd890899..1ed9f9ae596 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -199,6 +199,16 @@ The default decoding rules are applied as follows:
 | String   | `binary()`             |
 | Object   | `%{binary() => _}`     |
 
+## Language server listeners
+
+4 months ago, we welcomed [the Official Language Server team](https://elixir-lang.org/blog/2024/08/15/welcome-elixir-language-server-team/), with the goal of unifying the efforts behind code intelligence, tools, and editors in Elixir. Elixir v1.18 brings new features on this front by introducing locks and listeners to its compilation. Let's understand what it means.
+
+At the moment, all language server implementations have their own compilation environment. This means that your project and dependencies during development are compiled once, for your own use, and then again for the language server. This duplicate effort could cause the language server experience to lag, when it could be relying on the already compiled artifacts of your project.
+
+This release address by introducing a compiler lock, ensuring that only a single operating system running Elixir compiles your project at a given moment, and by providing the ability for one operating system process to listen to the compilation results of others. In other words, different Elixir instances can now communicate over the same compilation build, instead of racing each other.
+
+These enhancements do not only improve editor tooling, but they also directly benefit projects like IEx and Phoenix. For example, you can invoke `IEx.configure(auto_reload: true)` and IEx will automatically reload modules changed elsewhere, either by a separate terminal or your IDE.
+
 ## Potential incompatibilities
 
 This release no longer supports WERL (a graphical user interface on Windows used by Erlang 25 and earlier). For a better user experience on Windows terminals, use Erlang/OTP 26+ (this is also the last Elixir release to support Erlang/OTP 25).
diff --git a/lib/iex/lib/iex.ex b/lib/iex/lib/iex.ex
index 68a3dd31e72..222679237b1 100644
--- a/lib/iex/lib/iex.ex
+++ b/lib/iex/lib/iex.ex
@@ -391,16 +391,17 @@ defmodule IEx do
 
   The supported options are:
 
+    * `:auto_reload`
+    * `:alive_continuation_prompt`
+    * `:alive_prompt`
     * `:colors`
-    * `:inspect`
-    * `:width`
-    * `:history_size`
-    * `:default_prompt`
     * `:continuation_prompt`
-    * `:alive_prompt`
-    * `:alive_continuation_prompt`
-    * `:parser`
+    * `:default_prompt`
     * `:dot_iex`
+    * `:history_size`
+    * `:inspect`
+    * `:parser`
+    * `:width`
 
   They are discussed individually in the sections below.
 
@@ -488,9 +489,6 @@ defmodule IEx do
     * `:alive_continuation_prompt` - used when `Node.alive?/0` returns
       `true` and more input is expected
 
-    * `:auto_reload` - when set to `true`, automatically purges in-memory
-      modules when they get invalidated by a concurrent compilation
-
   The following values in the prompt string will be replaced appropriately:
 
     * `%counter` - the index of the history
@@ -510,10 +508,16 @@ defmodule IEx do
 
   If the parser raises, the buffer is reset to an empty string.
 
-  ## dot_iex
+  ## `.iex`
 
   Configure the file loaded into your IEx session when it starts.
   See more information [in the `.iex.exs` documentation](`m:IEx#module-the-iex-exs-file`).
+
+  ## Auto reloading
+
+  When set to `true`, the `:auto_reload` option automatically purges
+  in-memory modules when they get invalidated by a concurrent compilation
+  happening in the Operating System.
   """
   @spec configure(keyword()) :: :ok
   def configure(options) do

From b70986597639915a73332ab17fa5bdc3c0f7cc50 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jos=C3=A9=20Valim?= <jose.valim@dashbit.co>
Date: Tue, 10 Dec 2024 19:18:32 +0100
Subject: [PATCH 006/139] Improvements to markdown rendering in CHANGELOG

---
 CHANGELOG.md | 14 ++++----------
 1 file changed, 4 insertions(+), 10 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 1ed9f9ae596..70754374704 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -156,14 +156,11 @@ More migrations may be added in future releases.
 
 This release includes official support for JSON encoding and decoding.
 
-Both encoder and decoder fully conform to [RFC 8259](https://tools.ietf.org/html/rfc8259) and
-[ECMA 404](https://ecma-international.org/publications-and-standards/standards/ecma-404/)
-standards.
+Both encoder and decoder fully conform to [RFC 8259](https://tools.ietf.org/html/rfc8259) and [ECMA 404](https://ecma-international.org/publications-and-standards/standards/ecma-404/) standards.
 
 ### Encoding
 
-Encoding can be done via `JSON.encode!/1` and `JSON.encode_to_iodata!/1` functions.
-The default encoding rules are applied as follows:
+Encoding can be done via `JSON.encode!/1` and `JSON.encode_to_iodata!/1` functions. The default encoding rules are applied as follows:
 
 | **Elixir**             | **JSON** |
 |------------------------|----------|
@@ -177,9 +174,7 @@ The default encoding rules are applied as follows:
 | `%{atom() => _}`       | Object   |
 | `%{integer() => _}`    | Object   |
 
-You may also implement the `JSON.Encoder` protocol for custom data structures.
-If you have a struct, you can derive the implementation of the `JSON.Encoder`
-by specifying which fields should be encoded to JSON:
+You may also implement the `JSON.Encoder` protocol for custom data structures. If you have a struct, you can derive the implementation of the `JSON.Encoder` by specifying which fields should be encoded to JSON:
 
 ```elixir
   @derive {JSON.Encoder, only: [...]}
@@ -188,8 +183,7 @@ by specifying which fields should be encoded to JSON:
 
 ### Decoding
 
-Decoding can be done via `JSON.decode/2` and `JSON.decode!/2` functions.
-The default decoding rules are applied as follows:
+Decoding can be done via `JSON.decode/2` and `JSON.decode!/2` functions. The default decoding rules are applied as follows:
 
 | **JSON** | **Elixir**             |
 |----------|------------------------|

From a78f1715dcae2420a6b377cf17fedf5d66580862 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jos=C3=A9=20Valim?= <jose.valim@dashbit.co>
Date: Tue, 10 Dec 2024 19:29:32 +0100
Subject: [PATCH 007/139] Fix error message around single quote keywords

---
 lib/elixir/src/elixir_tokenizer.erl           | 26 ++++++++++++++-----
 .../test/elixir/kernel/warning_test.exs       |  2 +-
 2 files changed, 20 insertions(+), 8 deletions(-)

diff --git a/lib/elixir/src/elixir_tokenizer.erl b/lib/elixir/src/elixir_tokenizer.erl
index 0290803970f..cfb6e5e14be 100644
--- a/lib/elixir/src/elixir_tokenizer.erl
+++ b/lib/elixir/src/elixir_tokenizer.erl
@@ -266,11 +266,7 @@ tokenize([$" | T], Line, Column, Scope, Tokens) ->
 
 %% TODO: Remove me in Elixir v2.0
 tokenize([$' | T], Line, Column, Scope, Tokens) ->
-  Message = "single-quoted strings represent charlists. "
-    "Use ~c\"\" if you indeed want a charlist or use \"\" instead.\n"
-    "You may run \"mix format --migrate\" to fix this warning automatically.",
-  NewScope = prepend_warning(Line, Column, Message, Scope),
-  handle_strings(T, Line, Column + 1, $', NewScope, Tokens);
+  handle_strings(T, Line, Column + 1, $', Scope, Tokens);
 
 % Operator atoms
 
@@ -786,7 +782,11 @@ handle_strings(T, Line, Column, H, Scope, Tokens) ->
             "number do not require quotes",
             [hd(Parts)]
           ),
-          prepend_warning(Line, Column, WarnMsg, InterScope);
+          prepend_warning(Line, Column-1, WarnMsg, InterScope);
+
+        false when H =:= $' ->
+          WarnMsg = "single quotes around keywords are deprecated. Use double quotes instead",
+          prepend_warning(Line, Column-1, WarnMsg, InterScope);
 
         false ->
           InterScope
@@ -814,7 +814,19 @@ handle_strings(T, Line, Column, H, Scope, Tokens) ->
           error(Reason, Rest, NewScope, Tokens)
       end;
 
-    {NewLine, NewColumn, Parts, Rest, NewScope} ->
+    {NewLine, NewColumn, Parts, Rest, InterScope} ->
+      NewScope =
+        case H of
+          $' ->
+            Message = "single-quoted strings represent charlists. "
+              "Use ~c\"\" if you indeed want a charlist or use \"\" instead.\n"
+              "You may run \"mix format --migrate\" to fix this warning automatically.",
+            prepend_warning(Line, Column-1, Message, InterScope);
+
+          _ ->
+            InterScope
+        end,
+
       case unescape_tokens(Parts, Line, Column, NewScope) of
         {ok, Unescaped} ->
           Token = {string_type(H), {Line, Column - 1, nil}, Unescaped},
diff --git a/lib/elixir/test/elixir/kernel/warning_test.exs b/lib/elixir/test/elixir/kernel/warning_test.exs
index 1f0f975adbc..c318fe6526b 100644
--- a/lib/elixir/test/elixir/kernel/warning_test.exs
+++ b/lib/elixir/test/elixir/kernel/warning_test.exs
@@ -190,7 +190,7 @@ defmodule Kernel.WarningTest do
       )
 
       assert_warn_eval(
-        ["nofile:1:3", "found quoted keyword \"foo\" but the quotes are not required"],
+        ["nofile:1:2", "found quoted keyword \"foo\" but the quotes are not required"],
         ~s/["foo": :bar]/
       )
 

From 2273bafb4701cc94b10ba1de69a47dbda4679e56 Mon Sep 17 00:00:00 2001
From: Eksperimental <eksperimental@autistici.org>
Date: Wed, 11 Dec 2024 02:41:11 -0500
Subject: [PATCH 008/139] Improve grammar in CHANGELOG.md (#14050)

Co-authored-by: Wojtek Mach <wojtekmach@users.noreply.github.com>
---
 CHANGELOG.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 70754374704..d6e079112ef 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -24,7 +24,7 @@ defmodule User do
 end
 ```
 
-Elixir's type system will infer the `drive` function expects a `%User{}` struct as input and returns either `{:ok, dynamic()}`, `{:error, :no_choice}`, or `{:error, :not_allowed}`.
+Elixir's type system will infer that the `drive/2` function expects a `%User{}` struct and returns either `{:ok, dynamic()}`, `{:error, :no_choice}`, or `{:error, :not_allowed}`.
 
 Therefore, the following code should emit a violation, due to an invalid argument:
 

From a4fa71ab23c309d7f471e2a2bb79ad2fbc2938f7 Mon Sep 17 00:00:00 2001
From: Shenghang Tsai <jackalcooper@gmail.com>
Date: Wed, 11 Dec 2024 16:43:42 +0800
Subject: [PATCH 009/139] Update CHANGELOG.md (#14052)

---
 CHANGELOG.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index d6e079112ef..71446201494 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -199,7 +199,7 @@ Decoding can be done via `JSON.decode/2` and `JSON.decode!/2` functions. The def
 
 At the moment, all language server implementations have their own compilation environment. This means that your project and dependencies during development are compiled once, for your own use, and then again for the language server. This duplicate effort could cause the language server experience to lag, when it could be relying on the already compiled artifacts of your project.
 
-This release address by introducing a compiler lock, ensuring that only a single operating system running Elixir compiles your project at a given moment, and by providing the ability for one operating system process to listen to the compilation results of others. In other words, different Elixir instances can now communicate over the same compilation build, instead of racing each other.
+This release address by introducing a compiler lock, ensuring that only a single operating system process running Elixir compiles your project at a given moment, and by providing the ability for one operating system process to listen to the compilation results of others. In other words, different Elixir instances can now communicate over the same compilation build, instead of racing each other.
 
 These enhancements do not only improve editor tooling, but they also directly benefit projects like IEx and Phoenix. For example, you can invoke `IEx.configure(auto_reload: true)` and IEx will automatically reload modules changed elsewhere, either by a separate terminal or your IDE.
 

From dfc659104c0d957e173e34d8c0ee73395b67132e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jos=C3=A9=20Valim?= <jose.valim@dashbit.co>
Date: Wed, 11 Dec 2024 10:00:31 +0100
Subject: [PATCH 010/139] Touch the compile path when writing .app file for the
 first time (#14053)

Closes #14049.
---
 lib/mix/lib/mix/tasks/compile.app.ex        | 7 +++++++
 lib/mix/test/mix/tasks/compile.app_test.exs | 4 ----
 2 files changed, 7 insertions(+), 4 deletions(-)

diff --git a/lib/mix/lib/mix/tasks/compile.app.ex b/lib/mix/lib/mix/tasks/compile.app.ex
index 61fcb907783..78c51706fcf 100644
--- a/lib/mix/lib/mix/tasks/compile.app.ex
+++ b/lib/mix/lib/mix/tasks/compile.app.ex
@@ -189,6 +189,13 @@ defmodule Mix.Tasks.Compile.App do
       Mix.Project.ensure_structure()
       File.write!(target, IO.chardata_to_string(contents))
       File.touch!(target, new_mtime)
+
+      # If we just created the .app file, it will have touched
+      # the directory mtime, so we need to reset it.
+      if current_properties == [] do
+        File.touch!(compile_path, new_mtime)
+      end
+
       Mix.shell().info("Generated #{app} app")
       {:ok, []}
     else
diff --git a/lib/mix/test/mix/tasks/compile.app_test.exs b/lib/mix/test/mix/tasks/compile.app_test.exs
index 943a1876dd6..3674bfcfb25 100644
--- a/lib/mix/test/mix/tasks/compile.app_test.exs
+++ b/lib/mix/test/mix/tasks/compile.app_test.exs
@@ -66,8 +66,6 @@ defmodule Mix.Tasks.Compile.AppTest do
   test "generates .app file when changes happen" do
     in_fixture("no_mixfile", fn ->
       Mix.Project.push(MixTest.Case.Sample)
-      # Pre-create the compilation path to avoid mtime races
-      File.mkdir_p(Mix.Project.compile_path())
 
       Mix.Tasks.Compile.Elixir.run([])
       assert Mix.Tasks.Compile.App.run([]) == {:ok, []}
@@ -256,8 +254,6 @@ defmodule Mix.Tasks.Compile.AppTest do
   test ".app contains description and registered (as required by systools)" do
     in_fixture("no_mixfile", fn ->
       Mix.Project.push(MixTest.Case.Sample)
-      # Pre-create the compilation path to avoid mtime races
-      File.mkdir_p(Mix.Project.compile_path())
 
       Mix.Tasks.Compile.Elixir.run([])
       assert Mix.Tasks.Compile.App.run([]) == {:ok, []}

From e07a91594b0f579ebbdf0194970a0adab0504145 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jos=C3=A9=20Valim?= <jose.valim@dashbit.co>
Date: Wed, 11 Dec 2024 12:02:01 +0100
Subject: [PATCH 011/139] Update deprecations

---
 .../references/compatibility-and-deprecations.md      | 11 ++++++++++-
 1 file changed, 10 insertions(+), 1 deletion(-)

diff --git a/lib/elixir/pages/references/compatibility-and-deprecations.md b/lib/elixir/pages/references/compatibility-and-deprecations.md
index 11a37689306..81a9a57b515 100644
--- a/lib/elixir/pages/references/compatibility-and-deprecations.md
+++ b/lib/elixir/pages/references/compatibility-and-deprecations.md
@@ -82,6 +82,14 @@ The first column is the version the feature was hard deprecated. The second colu
 
 Version | Deprecated feature                                  | Replaced by (available since)
 :-------| :-------------------------------------------------- | :---------------------------------------------------------------
+[v1.18] | `<%#` in EEx                                        | `<%!--` (v1.14) or `<% #` (v1.0)
+[v1.18] | `handle_text/2` callback in EEx                     | `handle_text/3` (v1.14)
+[v1.18] | Returning 2-arity fun from `Enumerable.slice/1`     | Returning 3-arity (v1.14)
+[v1.18] | Ranges with negative steps in `Range.new/2`         | Explicit steps in ranges (v1.11)
+[v1.18] | `Tuple.append/2`                                    | `Tuple.insert_at/3` (v1.0)
+[v1.18] | `mix cmd --app APP`                                 | `mix do --app APP` (v1.14)
+[v1.18] | `List.zip/1`                                        | `Enum.zip/1` (v1.0)
+[v1.18] | `Module.eval_quoted/3`                              | `Code.eval_quoted/3` (v1.0)
 [v1.17] | Single-quoted charlists (`'foo'`)                   | `~c"foo"` (v1.0)
 [v1.17] | `left..right` in patterns and guards                | `left..right//step` (v1.11)
 [v1.17] | `ExUnit.Case.register_test/4`                       | `register_test/6` (v1.10)
@@ -214,4 +222,5 @@ Version | Deprecated feature                                  | Replaced by (ava
 [v1.14]: https://github.com/elixir-lang/elixir/blob/v1.14/CHANGELOG.md#4-hard-deprecations
 [v1.15]: https://github.com/elixir-lang/elixir/blob/v1.15/CHANGELOG.md#4-hard-deprecations
 [v1.16]: https://github.com/elixir-lang/elixir/blob/v1.16/CHANGELOG.md#4-hard-deprecations
-[v1.17]: https://github.com/elixir-lang/elixir/blob/main/CHANGELOG.md#4-hard-deprecations
+[v1.17]: https://github.com/elixir-lang/elixir/blob/v1.17/CHANGELOG.md#4-hard-deprecations
+[v1.18]: https://github.com/elixir-lang/elixir/blob/v1.18/CHANGELOG.md#4-hard-deprecations

From 3ca0bddd9dfcdef6a0c5d7f7641f6166841407a0 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jos=C3=A9=20Valim?= <jose.valim@dashbit.co>
Date: Wed, 11 Dec 2024 12:16:26 +0100
Subject: [PATCH 012/139] Fix encoding specs

---
 lib/elixir/lib/json.ex | 8 +++++---
 1 file changed, 5 insertions(+), 3 deletions(-)

diff --git a/lib/elixir/lib/json.ex b/lib/elixir/lib/json.ex
index d81b4ddcc1c..36a2be3d8a8 100644
--- a/lib/elixir/lib/json.ex
+++ b/lib/elixir/lib/json.ex
@@ -199,6 +199,8 @@ defmodule JSON do
 
   @moduledoc since: "1.18.0"
 
+  @type encoder :: (term(), encoder() -> iodata())
+
   @type decode_error_reason ::
           {:unexpected_end, non_neg_integer()}
           | {:invalid_byte, non_neg_integer(), byte()}
@@ -332,7 +334,7 @@ defmodule JSON do
       "[123,\"string\",{\"key\":\"value\"}]"
 
   """
-  @spec encode!(a, (a -> iodata())) :: binary() when a: var
+  @spec encode!(term(), encoder()) :: binary()
   def encode!(term, encoder \\ &protocol_encode/2) do
     IO.iodata_to_binary(encoder.(term, encoder))
   end
@@ -353,7 +355,7 @@ defmodule JSON do
       "[123,\"string\",{\"key\":\"value\"}]"
 
   """
-  @spec encode_to_iodata!(a, (a -> iodata())) :: iodata() when a: var
+  @spec encode_to_iodata!(term(), encoder()) :: iodata()
   def encode_to_iodata!(term, encoder \\ &protocol_encode/2) do
     encoder.(term, encoder)
   end
@@ -365,7 +367,7 @@ defmodule JSON do
   `encode!/2` and `encode_to_iodata!/2`. The default implementation
   is an optimized dispatch to the `JSON.Encoder` protocol.
   """
-  @spec protocol_encode(a, (a -> iodata())) :: iodata() when a: var
+  @spec protocol_encode(term(), encoder()) :: iodata()
   def protocol_encode(value, encoder) when is_atom(value) do
     case value do
       nil -> "null"

From cda1a09c58de7a8a6b205a8fa91ee4524443b20a Mon Sep 17 00:00:00 2001
From: Andrea Leopardi <an.leopardi@gmail.com>
Date: Wed, 11 Dec 2024 12:42:04 +0100
Subject: [PATCH 013/139] Improve error message on empty "mix do" (#14055)

---
 lib/mix/lib/mix/tasks/do.ex        | 25 +++++++++++++++++++------
 lib/mix/test/mix/tasks/do_test.exs |  8 ++++++++
 2 files changed, 27 insertions(+), 6 deletions(-)

diff --git a/lib/mix/lib/mix/tasks/do.ex b/lib/mix/lib/mix/tasks/do.ex
index c68fcb1c236..c46656f4a75 100644
--- a/lib/mix/lib/mix/tasks/do.ex
+++ b/lib/mix/lib/mix/tasks/do.ex
@@ -58,12 +58,25 @@ defmodule Mix.Tasks.Do do
     {apps, args} = extract_apps_from_args(args)
     show_forgotten_apps_warning(apps)
 
-    Enum.each(gather_commands(args), fn [task | args] ->
-      if apps == [] do
-        Mix.Task.run(task, args)
-      else
-        Mix.Task.run_in_apps(task, apps, args)
-      end
+    Enum.each(gather_commands(args), fn
+      [task | args] ->
+        if apps == [] do
+          Mix.Task.run(task, args)
+        else
+          Mix.Task.run_in_apps(task, apps, args)
+        end
+
+      [] ->
+        Mix.raise("""
+        One of the commands passed to "mix do" is empty. Each command passed to "mix do" must \
+        have at least the task name. These are all invalid:
+
+          mix do
+          mix do my_task +
+          mix do + my_task
+
+        Run "mix help do" for more information.
+        """)
     end)
   end
 
diff --git a/lib/mix/test/mix/tasks/do_test.exs b/lib/mix/test/mix/tasks/do_test.exs
index 23e540f1d15..dab90c4907c 100644
--- a/lib/mix/test/mix/tasks/do_test.exs
+++ b/lib/mix/test/mix/tasks/do_test.exs
@@ -13,6 +13,14 @@ defmodule Mix.Tasks.DoTest do
     end)
   end
 
+  test "raises if a task is empty" do
+    for args <- [~w(), ~w(+), ~w(help +), ~w(+ help)] do
+      assert_raise Mix.Error, ~r"^One of the commands passed to \"mix do\" is empty", fn ->
+        Mix.Tasks.Do.run(args)
+      end
+    end
+  end
+
   test "gather_command returns a list of commands" do
     assert gather_commands(["help", "+", "compile"]) ==
              [["help"], ["compile"]]

From 9c28fb7a39360048106d894054e8dc98a3fb1645 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jos=C3=A9=20Valim?= <jose.valim@dashbit.co>
Date: Wed, 11 Dec 2024 21:08:12 +0100
Subject: [PATCH 014/139] Improve JSON and Float docs

---
 lib/elixir/lib/float.ex | 12 +++---------
 lib/elixir/lib/json.ex  |  6 ++++++
 2 files changed, 9 insertions(+), 9 deletions(-)

diff --git a/lib/elixir/lib/float.ex b/lib/elixir/lib/float.ex
index 5855a975aff..07313e45d31 100644
--- a/lib/elixir/lib/float.ex
+++ b/lib/elixir/lib/float.ex
@@ -577,15 +577,9 @@ defmodule Float do
   Returns a charlist which corresponds to the shortest text representation
   of the given float.
 
-  The underlying algorithm changes depending on the Erlang/OTP version:
-
-    * For OTP >= 24, it uses the algorithm presented in "Ryū: fast
-      float-to-string conversion" in Proceedings of the SIGPLAN '2018
-      Conference on Programming Language Design and Implementation.
-
-    * For OTP < 24, it uses the algorithm presented in "Printing Floating-Point
-      Numbers Quickly and Accurately" in Proceedings of the SIGPLAN '1996
-      Conference on Programming Language Design and Implementation.
+  It uses the algorithm presented in "Ryū: fast float-to-string conversion"
+  in Proceedings of the SIGPLAN '2018 Conference on Programming Language
+  Design and Implementation.
 
   For a configurable representation, use `:erlang.float_to_list/2`.
 
diff --git a/lib/elixir/lib/json.ex b/lib/elixir/lib/json.ex
index 36a2be3d8a8..679f77a8589 100644
--- a/lib/elixir/lib/json.ex
+++ b/lib/elixir/lib/json.ex
@@ -328,6 +328,12 @@ defmodule JSON do
   The second argument is a function that is recursively
   invoked to encode a term.
 
+  > #### IO and performance {: .tip}
+  >
+  > If you need to encode data to be sent over the network
+  > or written to the filesystem, consider using the more
+  > efficient `encode_to_iodata!/2`.
+
   ## Examples
 
       iex> JSON.encode!([123, "string", %{key: "value"}])

From 070a6d51bfdfeb9c833b3854e0e9b2b7cc215448 Mon Sep 17 00:00:00 2001
From: Panagiotis Nezis <pnezis@gmail.com>
Date: Thu, 12 Dec 2024 01:36:49 +0200
Subject: [PATCH 015/139] Improve warning in Range.new/2 with negative step
 (#14062)

---
 lib/elixir/lib/range.ex | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/lib/elixir/lib/range.ex b/lib/elixir/lib/range.ex
index 7265b28966b..c1cdcb03f93 100644
--- a/lib/elixir/lib/range.ex
+++ b/lib/elixir/lib/range.ex
@@ -192,7 +192,7 @@ defmodule Range do
         IO.warn_once(
           {__MODULE__, :new},
           fn ->
-            "Range.new/2 has a default step of -1, please call Range.new/3 explicitly passing the step of -1 instead"
+            "Range.new/2 has a default step of -1 when last < first, please call Range.new/3 explicitly passing the step of -1 instead"
           end,
           3
         )

From 55eca5dff5804390eb3f431494160f48afec38af Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jos=C3=A9=20Valim?= <jose.valim@dashbit.co>
Date: Thu, 12 Dec 2024 00:37:04 +0100
Subject: [PATCH 016/139] Implement JSON.Encoder for Calendar types (#14061)

---
 lib/elixir/lib/json.ex               | 6 ++++++
 lib/elixir/test/elixir/json_test.exs | 7 +++++++
 2 files changed, 13 insertions(+)

diff --git a/lib/elixir/lib/json.ex b/lib/elixir/lib/json.ex
index 679f77a8589..0f39243df2d 100644
--- a/lib/elixir/lib/json.ex
+++ b/lib/elixir/lib/json.ex
@@ -150,6 +150,12 @@ defimpl JSON.Encoder, for: Map do
   end
 end
 
+defimpl JSON.Encoder, for: [Date, Time, NaiveDateTime, DateTime] do
+  def encode(value, _encoder) do
+    [?", @for.to_iso8601(value), ?"]
+  end
+end
+
 defmodule JSON.DecodeError do
   @moduledoc """
   The exception raised by `JSON.decode!/1`.
diff --git a/lib/elixir/test/elixir/json_test.exs b/lib/elixir/test/elixir/json_test.exs
index 8e301944834..99b8d23cd6d 100644
--- a/lib/elixir/test/elixir/json_test.exs
+++ b/lib/elixir/test/elixir/json_test.exs
@@ -46,6 +46,13 @@ defmodule JSONTest do
       assert JSON.encode!(%Token{value: :example}) == "[\"example\"]"
       assert JSON.encode!(%Token{value: "hello\0world"}) == "[\"hello\\u0000world\"]"
     end
+
+    test "calendar" do
+      assert JSON.encode!(~D[2010-04-17]) == "\"2010-04-17\""
+      assert JSON.encode!(~T[14:00:00.123]) == "\"14:00:00.123\""
+      assert JSON.encode!(~N[2010-04-17 14:00:00.123]) == "\"2010-04-17T14:00:00.123\""
+      assert JSON.encode!(~U[2010-04-17 14:00:00.123Z]) == "\"2010-04-17T14:00:00.123Z\""
+    end
   end
 
   describe "JSON.Encoder" do

From 6c866e8ab23f4ccb51ee653b763cf7eefefb52d5 Mon Sep 17 00:00:00 2001
From: Jean Klingler <sabiwara@gmail.com>
Date: Thu, 12 Dec 2024 17:46:24 +0900
Subject: [PATCH 017/139] Fix type warnings in pop_in/1 (#14064)

---
 lib/elixir/lib/kernel.ex               | 4 ++--
 lib/elixir/test/elixir/kernel_test.exs | 2 ++
 2 files changed, 4 insertions(+), 2 deletions(-)

diff --git a/lib/elixir/lib/kernel.ex b/lib/elixir/lib/kernel.ex
index 31a9a4c8c65..0a9d0137652 100644
--- a/lib/elixir/lib/kernel.ex
+++ b/lib/elixir/lib/kernel.ex
@@ -3306,7 +3306,7 @@ defmodule Kernel do
   end
 
   defp nest_pop_in(:map, h, [{:access, key}]) do
-    quote do
+    quote generated: true do
       case unquote(h) do
         nil -> {nil, nil}
         h -> Access.pop(h, unquote(key))
@@ -3327,7 +3327,7 @@ defmodule Kernel do
   end
 
   defp nest_pop_in(_, h, [{:access, key}]) do
-    quote do
+    quote generated: true do
       case unquote(h) do
         nil -> :pop
         h -> Access.pop(h, unquote(key))
diff --git a/lib/elixir/test/elixir/kernel_test.exs b/lib/elixir/test/elixir/kernel_test.exs
index bae0550e3b9..fa4b7364a1a 100644
--- a/lib/elixir/test/elixir/kernel_test.exs
+++ b/lib/elixir/test/elixir/kernel_test.exs
@@ -1136,6 +1136,8 @@ defmodule KernelTest do
     test "pop_in/1" do
       users = %{"john" => %{age: 27}, "meg" => %{age: 23}}
 
+      assert pop_in(users["john"]) == {%{age: 27}, %{"meg" => %{age: 23}}}
+
       assert pop_in(users["john"][:age]) == {27, %{"john" => %{}, "meg" => %{age: 23}}}
       assert pop_in(users["john"][:name]) == {nil, %{"john" => %{age: 27}, "meg" => %{age: 23}}}
       assert pop_in(users["bob"][:age]) == {nil, %{"john" => %{age: 27}, "meg" => %{age: 23}}}

From 85e00cddd534528849ab1b89c46a3d592c695644 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jos=C3=A9=20Valim?= <jose.valim@dashbit.co>
Date: Thu, 12 Dec 2024 09:52:27 +0100
Subject: [PATCH 018/139] Clarify warnings happen on compiled code

---
 CHANGELOG.md | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 71446201494..07705488685 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,7 +4,7 @@ Elixir v1.18 is an impressive release with improvements across the two main effo
 
 ## Type system improvements
 
-The most exciting change in Elixir v1.18 is type checking of function calls, alongside gradual inference of patterns and return types. To understand how this will impact your programs, consider the following code:
+The most exciting change in Elixir v1.18 is type checking of function calls, alongside gradual inference of patterns and return types. To understand how this will impact your programs, consider the following code in "lib/user.ex":
 
 ```elixir
 defmodule User do
@@ -26,7 +26,7 @@ end
 
 Elixir's type system will infer that the `drive/2` function expects a `%User{}` struct and returns either `{:ok, dynamic()}`, `{:error, :no_choice}`, or `{:error, :not_allowed}`.
 
-Therefore, the following code should emit a violation, due to an invalid argument:
+Therefore, the following code in a separate module (either in a separate or the same file), should emit a violation, due to an invalid argument:
 
 ```elixir
 User.drive({:ok, %User{}}, car_choices)

From 048ae58d6de303a2f957d1b74816b5b63d3b897c Mon Sep 17 00:00:00 2001
From: Wojtek Mach <wojtekmach@users.noreply.github.com>
Date: Thu, 12 Dec 2024 11:51:19 +0100
Subject: [PATCH 019/139] Do not set docs source_ref to -latest tags (#14065)

---
 Makefile | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/Makefile b/Makefile
index 0a4620bc733..bfda4058745 100644
--- a/Makefile
+++ b/Makefile
@@ -17,7 +17,7 @@ INSTALL_DIR = $(INSTALL) -m755 -d
 INSTALL_DATA = $(INSTALL) -m644
 INSTALL_PROGRAM = $(INSTALL) -m755
 GIT_REVISION = $(strip $(shell git rev-parse HEAD 2> /dev/null ))
-GIT_TAG = $(strip $(shell head="$(call GIT_REVISION)"; git tag --points-at $$head 2> /dev/null | tail -1) )
+GIT_TAG = $(strip $(shell head="$(call GIT_REVISION)"; git tag --points-at $$head 2> /dev/null | grep -v latest | tail -1))
 SOURCE_DATE_EPOCH_PATH = lib/elixir/tmp/ebin_reproducible
 SOURCE_DATE_EPOCH_FILE = $(SOURCE_DATE_EPOCH_PATH)/SOURCE_DATE_EPOCH
 

From 048cc2ddb178a7c0e6557c96a12316b2d99eceae Mon Sep 17 00:00:00 2001
From: Mathias Polligkeit <13847569+woylie@users.noreply.github.com>
Date: Fri, 13 Dec 2024 17:51:58 +0900
Subject: [PATCH 020/139] Further improve warning message for range with
 negative step (#14070)

---
 lib/elixir/lib/range.ex | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/lib/elixir/lib/range.ex b/lib/elixir/lib/range.ex
index c1cdcb03f93..b876ffb21d2 100644
--- a/lib/elixir/lib/range.ex
+++ b/lib/elixir/lib/range.ex
@@ -192,7 +192,7 @@ defmodule Range do
         IO.warn_once(
           {__MODULE__, :new},
           fn ->
-            "Range.new/2 has a default step of -1 when last < first, please call Range.new/3 explicitly passing the step of -1 instead"
+            "Range.new/2 and first..last default to a step of -1 when last < first. Use Range.new(first, last, -1) or first..last//-1, or pass 1 if that was your intention"
           end,
           3
         )

From 36c06c3a56058369c3c49b83fe61a984a48c239f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Micha=C5=82=20=C5=81=C4=99picki?=
 <michallepicki@users.noreply.github.com>
Date: Fri, 13 Dec 2024 09:52:20 +0100
Subject: [PATCH 021/139] Fix JSON.decode/3 spec (#14068)

The error clause was missing the :error tuple wrapping
---
 lib/elixir/lib/json.ex | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/lib/elixir/lib/json.ex b/lib/elixir/lib/json.ex
index 0f39243df2d..fc7c6477841 100644
--- a/lib/elixir/lib/json.ex
+++ b/lib/elixir/lib/json.ex
@@ -265,7 +265,8 @@ defmodule JSON do
 
   For streaming decoding, see Erlang's `:json` module.
   """
-  @spec decode(binary(), term(), keyword()) :: {term(), term(), binary()} | decode_error_reason()
+  @spec decode(binary(), term(), keyword()) ::
+          {term(), term(), binary()} | {:error, decode_error_reason()}
   def decode(binary, acc, decoders) when is_binary(binary) and is_list(decoders) do
     decoders = Keyword.put_new(decoders, :null, nil)
 

From 8e8c9a733407f9691c4c442f035626d45190ecf6 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jos=C3=A9=20Valim?= <jose.valim@dashbit.co>
Date: Fri, 13 Dec 2024 09:55:39 +0100
Subject: [PATCH 022/139] Wrap Range.new in Function.identity to avoid tail
 call optimization

---
 lib/elixir/lib/kernel.ex | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/lib/elixir/lib/kernel.ex b/lib/elixir/lib/kernel.ex
index 0a9d0137652..3bfc11f0e9d 100644
--- a/lib/elixir/lib/kernel.ex
+++ b/lib/elixir/lib/kernel.ex
@@ -4066,7 +4066,7 @@ defmodule Kernel do
   end
 
   defp stepless_range(nil, first, last, _caller) do
-    quote(do: Elixir.Range.new(unquote(first), unquote(last)))
+    quote(do: Function.identity(Elixir.Range.new(unquote(first), unquote(last))))
   end
 
   defp stepless_range(:guard, first, last, caller) do

From 4329a3c2f21b815e8e6ddd01b83ad18de05e17cd Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jos=C3=A9=20Valim?= <jose.valim@dashbit.co>
Date: Fri, 13 Dec 2024 10:01:18 +0100
Subject: [PATCH 023/139] Address bootstrap

---
 lib/elixir/src/elixir_compiler.erl | 1 +
 1 file changed, 1 insertion(+)

diff --git a/lib/elixir/src/elixir_compiler.erl b/lib/elixir/src/elixir_compiler.erl
index 8ba322b88fd..d9164efcd59 100644
--- a/lib/elixir/src/elixir_compiler.erl
+++ b/lib/elixir/src/elixir_compiler.erl
@@ -203,6 +203,7 @@ bootstrap_files() ->
      <<"path.ex">>,
      <<"file.ex">>,
      <<"map.ex">>,
+     <<"function.ex">>,
      <<"range.ex">>,
      <<"access.ex">>,
      <<"io.ex">>,

From 617dc727594c9ae5b911b9d92484e1ff545b99bb Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jos=C3=A9=20Valim?= <jose.valim@dashbit.co>
Date: Fri, 13 Dec 2024 10:14:47 +0100
Subject: [PATCH 024/139] Fix Range.new deprecation assertion

---
 lib/elixir/test/elixir/range_test.exs | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/lib/elixir/test/elixir/range_test.exs b/lib/elixir/test/elixir/range_test.exs
index 05666d34c4d..a974b3c008e 100644
--- a/lib/elixir/test/elixir/range_test.exs
+++ b/lib/elixir/test/elixir/range_test.exs
@@ -35,7 +35,7 @@ defmodule RangeTest do
 
     assert ExUnit.CaptureIO.capture_io(:stderr, fn ->
              assert Range.new(3, 1) == 3..1//-1
-           end) =~ "has a default step of -1"
+           end) =~ "default to a step of -1"
   end
 
   test "fields" do

From 1c7fc86e9c21ccb5bda6292108a55f4e9969acd1 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jonatan=20K=C5=82osko?= <jonatanklosko@gmail.com>
Date: Fri, 13 Dec 2024 16:57:40 +0100
Subject: [PATCH 025/139] Make :source_path in docs chunk a charlist (#14071)

---
 lib/elixir/src/elixir_erl.erl               | 2 +-
 lib/elixir/test/elixir/kernel/docs_test.exs | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/lib/elixir/src/elixir_erl.erl b/lib/elixir/src/elixir_erl.erl
index 58800a2a966..17ed4d9f0ef 100644
--- a/lib/elixir/src/elixir_erl.erl
+++ b/lib/elixir/src/elixir_erl.erl
@@ -526,7 +526,7 @@ docs_chunk(Map, Set, Module, Anno, Def, Defmacro, Types, Callbacks, ChunkOpts) -
       TypeDocs = get_type_docs(Set, Types),
 
       ModuleMeta = ModuleDocMeta#{
-        source_path => File,
+        source_path => elixir_utils:characters_to_list(File),
         source_annos => [Anno],
         behaviours => [Mod || {behaviour, Mod} <- Attributes]
       },
diff --git a/lib/elixir/test/elixir/kernel/docs_test.exs b/lib/elixir/test/elixir/kernel/docs_test.exs
index c87c9d045c1..ba1cea74f78 100644
--- a/lib/elixir/test/elixir/kernel/docs_test.exs
+++ b/lib/elixir/test/elixir/kernel/docs_test.exs
@@ -255,7 +255,7 @@ defmodule Kernel.DocsTest do
 
       assert module_doc == "Module doc"
 
-      file = __ENV__.file
+      file = String.to_charlist(__ENV__.file)
 
       source_annos = [:erl_anno.new({line + 3, 19})]
 

From 305d8e603ca98a5bcd9ec59f69a68568a322bbe2 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jos=C3=A9=20Valim?= <jose.valim@dashbit.co>
Date: Sun, 15 Dec 2024 15:17:57 +0100
Subject: [PATCH 026/139] Do not convert fun() into (... -> any()) and
 vice-versa in typespecs

---
 lib/elixir/lib/code/typespec.ex          | 9 ---------
 lib/elixir/lib/kernel/typespec.ex        | 9 +--------
 lib/elixir/test/elixir/typespec_test.exs | 6 ++----
 3 files changed, 3 insertions(+), 21 deletions(-)

diff --git a/lib/elixir/lib/code/typespec.ex b/lib/elixir/lib/code/typespec.ex
index 017b2f05dcc..30f0789a479 100644
--- a/lib/elixir/lib/code/typespec.ex
+++ b/lib/elixir/lib/code/typespec.ex
@@ -27,11 +27,6 @@ defmodule Code.Typespec do
     end
   end
 
-  def spec_to_quoted(name, {:type, anno, :fun, []}) when is_atom(name) do
-    meta = meta(anno)
-    {:"::", meta, [{name, meta, []}, quote(do: term)]}
-  end
-
   def spec_to_quoted(name, {:type, anno, :bounded_fun, [type, constrs]}) when is_atom(name) do
     meta = meta(anno)
     {:type, _, :fun, [{:type, _, :product, args}, result]} = type
@@ -317,10 +312,6 @@ defmodule Code.Typespec do
     [{:->, meta(anno), [[typespec_to_quoted(args)], typespec_to_quoted(result)]}]
   end
 
-  defp typespec_to_quoted({:type, anno, :fun, []}) do
-    typespec_to_quoted({:type, anno, :fun, [{:type, anno, :any}, {:type, anno, :any, []}]})
-  end
-
   defp typespec_to_quoted({:type, anno, :range, [left, right]}) do
     {:.., meta(anno), [typespec_to_quoted(left), typespec_to_quoted(right)]}
   end
diff --git a/lib/elixir/lib/kernel/typespec.ex b/lib/elixir/lib/kernel/typespec.ex
index 942cd23455f..fbe1b86b315 100644
--- a/lib/elixir/lib/kernel/typespec.ex
+++ b/lib/elixir/lib/kernel/typespec.ex
@@ -668,14 +668,7 @@ defmodule Kernel.Typespec do
        when is_list(args) do
     {args, state} = fn_args(meta, args, vars, caller, state)
     {spec, state} = typespec(return, vars, caller, state)
-
-    fun_args =
-      case [args, spec] do
-        [{:type, _, :any}, {:type, _, :any, []}] -> []
-        pair -> pair
-      end
-
-    {{:type, location(meta), :fun, fun_args}, state}
+    {{:type, location(meta), :fun, [args, spec]}, state}
   end
 
   # Handle type operator
diff --git a/lib/elixir/test/elixir/typespec_test.exs b/lib/elixir/test/elixir/typespec_test.exs
index 21b38f0d8aa..c81bb4682c7 100644
--- a/lib/elixir/test/elixir/typespec_test.exs
+++ b/lib/elixir/test/elixir/typespec_test.exs
@@ -710,7 +710,8 @@ defmodule TypespecTest do
           @type my_type :: (... -> any)
         end
 
-      assert [type: {:my_type, {:type, _, :fun, []}, []}] = types(bytecode)
+      assert [type: {:my_type, {:type, _, :fun, [{:type, _, :any}, {:type, _, :any, []}]}, []}] =
+               types(bytecode)
     end
 
     test "@type with a fun with multiple arguments and return type" do
@@ -1520,9 +1521,6 @@ defmodule TypespecTest do
             assert ast_string ==
                      "@type literal_struct_all_fields_key_type() :: %TypespecTest.SomeStruct{key: integer()}"
 
-          {:built_in_fun, _, _} ->
-            assert ast_string == "@type built_in_fun() :: (... -> any())"
-
           {:built_in_nonempty_list, _, _} ->
             assert ast_string == "@type built_in_nonempty_list() :: [...]"
 

From 472c4e1a24af9efdbddf3dfd0c3af9b437dbbd37 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jos=C3=A9=20Valim?= <jose.valim@dashbit.co>
Date: Mon, 16 Dec 2024 12:09:02 +0100
Subject: [PATCH 027/139] Avoid crashes on diagnostics with tabs, closes #14073

---
 lib/elixir/src/elixir_errors.erl              |  8 ++---
 .../test/elixir/kernel/diagnostics_test.exs   | 29 +++++++++++++++++++
 2 files changed, 33 insertions(+), 4 deletions(-)

diff --git a/lib/elixir/src/elixir_errors.erl b/lib/elixir/src/elixir_errors.erl
index 6dd3197fc63..0f47189050e 100644
--- a/lib/elixir/src/elixir_errors.erl
+++ b/lib/elixir/src/elixir_errors.erl
@@ -216,12 +216,12 @@ highlight_at_position(Column, Severity, Length) ->
 
 highlight_below_line(Line, Severity) ->
   % Don't highlight leading whitespaces in line
-  {_, SpacesMatched} = trim_line(Line, 0),
+  {Rest, SpacesMatched} = trim_line(Line, 0),
 
-  Length = string:length(Line),
+  Length = string:length(Rest),
   Highlight = case Severity of
-    warning -> highlight(lists:duplicate(Length - SpacesMatched, $~), warning);
-    error -> highlight(lists:duplicate(Length - SpacesMatched, $^), error)
+    warning -> highlight(lists:duplicate(Length, $~), warning);
+    error -> highlight(lists:duplicate(Length, $^), error)
   end,
 
   [n_spaces(SpacesMatched), Highlight].
diff --git a/lib/elixir/test/elixir/kernel/diagnostics_test.exs b/lib/elixir/test/elixir/kernel/diagnostics_test.exs
index f016ac75cc7..0ce2ed7a88b 100644
--- a/lib/elixir/test/elixir/kernel/diagnostics_test.exs
+++ b/lib/elixir/test/elixir/kernel/diagnostics_test.exs
@@ -859,6 +859,35 @@ defmodule Kernel.DiagnosticsTest do
       purge(Sample)
     end
 
+    @tag :tmp_dir
+    test "simple warning with tabs (line + file)", %{tmp_dir: tmp_dir} do
+      path = make_relative_tmp(tmp_dir, "long-warning.ex")
+
+      source = """
+      defmodule Sample do
+      \t@file "#{path}"
+      \tdefp a do
+      \t\tUnknown.b()
+      \tend
+      end
+      """
+
+      File.write!(path, source)
+
+      expected = """
+          warning: Unknown.b/0 is undefined (module Unknown is not available or is yet to be defined). Make sure the module name is correct and has been specified in full (or that an alias has been defined)
+          │
+        4 │ \t\tUnknown.b()
+          │                 ~~~~~~~~~~~
+          │
+          └─ #{path}:4: Sample.a/0
+      """
+
+      assert capture_eval(source, columns: false) =~ expected
+    after
+      purge(Sample)
+    end
+
     test "simple warning (no file)" do
       source = """
       defmodule Sample do

From 8ddef06e81d07a778685a799b1f5e4bfbf1bb749 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jos=C3=A9=20Valim?= <jose.valim@dashbit.co>
Date: Mon, 16 Dec 2024 12:23:40 +0100
Subject: [PATCH 028/139] Also encode Duration to JSON

---
 lib/elixir/lib/json.ex               | 2 +-
 lib/elixir/test/elixir/json_test.exs | 1 +
 2 files changed, 2 insertions(+), 1 deletion(-)

diff --git a/lib/elixir/lib/json.ex b/lib/elixir/lib/json.ex
index fc7c6477841..ec602bf1b7f 100644
--- a/lib/elixir/lib/json.ex
+++ b/lib/elixir/lib/json.ex
@@ -150,7 +150,7 @@ defimpl JSON.Encoder, for: Map do
   end
 end
 
-defimpl JSON.Encoder, for: [Date, Time, NaiveDateTime, DateTime] do
+defimpl JSON.Encoder, for: [Date, Time, NaiveDateTime, DateTime, Duration] do
   def encode(value, _encoder) do
     [?", @for.to_iso8601(value), ?"]
   end
diff --git a/lib/elixir/test/elixir/json_test.exs b/lib/elixir/test/elixir/json_test.exs
index 99b8d23cd6d..5bbc56243f1 100644
--- a/lib/elixir/test/elixir/json_test.exs
+++ b/lib/elixir/test/elixir/json_test.exs
@@ -52,6 +52,7 @@ defmodule JSONTest do
       assert JSON.encode!(~T[14:00:00.123]) == "\"14:00:00.123\""
       assert JSON.encode!(~N[2010-04-17 14:00:00.123]) == "\"2010-04-17T14:00:00.123\""
       assert JSON.encode!(~U[2010-04-17 14:00:00.123Z]) == "\"2010-04-17T14:00:00.123Z\""
+      assert JSON.encode!(Duration.new!(month: 2, hour: 3)) == "\"P2MT3H\""
     end
   end
 

From 6a24bafbdaba3f0745338fdeaa26c34e481cf96c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jos=C3=A9=20Valim?= <jose.valim@dashbit.co>
Date: Mon, 16 Dec 2024 16:12:55 +0100
Subject: [PATCH 029/139] Tag exceptions as dynamic, closes #14074

---
 lib/elixir/lib/module/types/expr.ex           |  3 ++-
 .../test/elixir/module/types/expr_test.exs    | 22 ++++++++++---------
 2 files changed, 14 insertions(+), 11 deletions(-)

diff --git a/lib/elixir/lib/module/types/expr.ex b/lib/elixir/lib/module/types/expr.ex
index 7aab48d6fc3..27b925fe66d 100644
--- a/lib/elixir/lib/module/types/expr.ex
+++ b/lib/elixir/lib/module/types/expr.ex
@@ -444,7 +444,8 @@ defmodule Module.Types.Expr do
         # to avoid export dependencies. So we do it here.
         if Code.ensure_loaded?(exception) and function_exported?(exception, :__struct__, 0) do
           {info, context} = Of.struct_info(exception, meta, stack, context)
-          {Of.struct_type(exception, info, args), context}
+          # TODO: For properly defined structs, this should not be dynamic
+          {dynamic(Of.struct_type(exception, info, args)), context}
         else
           # If the exception cannot be found or is invalid, fetch the signature to emit warnings.
           {_, context} = Apply.signature(exception, :__struct__, 0, meta, stack, context)
diff --git a/lib/elixir/test/elixir/module/types/expr_test.exs b/lib/elixir/test/elixir/module/types/expr_test.exs
index 7e5499cde56..c5c5a6040e0 100644
--- a/lib/elixir/test/elixir/module/types/expr_test.exs
+++ b/lib/elixir/test/elixir/module/types/expr_test.exs
@@ -1224,16 +1224,18 @@ defmodule Module.Types.ExprTest do
                    e
                end
              ) ==
-               union(
-                 closed_map(
-                   __struct__: atom([ArgumentError]),
-                   __exception__: atom([true]),
-                   message: term()
-                 ),
-                 closed_map(
-                   __struct__: atom([RuntimeError]),
-                   __exception__: atom([true]),
-                   message: term()
+               dynamic(
+                 union(
+                   closed_map(
+                     __struct__: atom([ArgumentError]),
+                     __exception__: atom([true]),
+                     message: term()
+                   ),
+                   closed_map(
+                     __struct__: atom([RuntimeError]),
+                     __exception__: atom([true]),
+                     message: term()
+                   )
                  )
                )
     end

From c7f78e8015c5cb73d2302fa7f62ebe3e2131442b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jos=C3=A9=20Valim?= <jose.valim@dashbit.co>
Date: Mon, 16 Dec 2024 19:30:42 +0100
Subject: [PATCH 030/139] Emit warnings for duplicate patterns instead of
 errors

---
 lib/elixir/src/elixir_clauses.erl             | 19 +++++++++++++++----
 .../test/elixir/kernel/expansion_test.exs     | 11 -----------
 .../test/elixir/kernel/warning_test.exs       | 14 ++++++++++++++
 3 files changed, 29 insertions(+), 15 deletions(-)

diff --git a/lib/elixir/src/elixir_clauses.erl b/lib/elixir/src/elixir_clauses.erl
index 6efe5577b02..85198f694ad 100644
--- a/lib/elixir/src/elixir_clauses.erl
+++ b/lib/elixir/src/elixir_clauses.erl
@@ -11,7 +11,7 @@
 
 parallel_match(Meta, Expr, S, #{context := match} = E) ->
   #elixir_ex{vars={_Read, Write}} = S,
-  Matches = unpack_match(Expr, Meta, []),
+  Matches = unpack_match(Expr, Meta, [], E),
 
   {[{_, EHead} | ETail], EWrites, SM, EM} =
     lists:foldl(fun({EMeta, Match}, {AccMatches, AccWrites, SI, EI}) ->
@@ -31,9 +31,13 @@ parallel_match(Meta, Expr, S, #{context := match} = E) ->
   VWrite = (Write /= false) andalso elixir_env:merge_vars(Write, PWrites),
   {EMatch, SM#elixir_ex{vars={VRead, VWrite}, prematch={PRead, PCycles, PInfo}}, EM}.
 
-unpack_match({'=', Meta, [Left, Right]}, _Meta, Acc) ->
-  unpack_match(Left, Meta, unpack_match(Right, Meta, Acc));
-unpack_match(Node, Meta, Acc) ->
+unpack_match({'=', Meta, [{_, VarMeta, _} = Node, Node]}, _Meta, Acc, E) ->
+  %% TODO: remove this clause on Elixir v1.23
+  file_warn(VarMeta, ?key(E, file), ?MODULE, {duplicate_match, Node}),
+  unpack_match(Node, Meta, Acc, E);
+unpack_match({'=', Meta, [Left, Right]}, _Meta, Acc, E) ->
+  unpack_match(Left, Meta, unpack_match(Right, Meta, Acc, E), E);
+unpack_match(Node, Meta, Acc, _E) ->
   [{Meta, Node} | Acc].
 
 store_cycles([Write | Writes], {Cycles, SkipList}, Acc) ->
@@ -532,6 +536,13 @@ origin(Meta, Default) ->
     false -> Default
   end.
 
+format_error({duplicate_match, Expr}) ->
+  String = 'Elixir.Macro':to_string(Expr),
+  io_lib:format(
+    "this pattern is matched against itself inside a match: ~ts = ~ts",
+    [String, String]
+  );
+
 format_error({recursive, Vars, TypeExpr}) ->
   Code =
     case TypeExpr of
diff --git a/lib/elixir/test/elixir/kernel/expansion_test.exs b/lib/elixir/test/elixir/kernel/expansion_test.exs
index 86e500e0fb4..81b0a65a70b 100644
--- a/lib/elixir/test/elixir/kernel/expansion_test.exs
+++ b/lib/elixir/test/elixir/kernel/expansion_test.exs
@@ -190,17 +190,6 @@ defmodule Kernel.ExpansionTest do
     end
 
     test "errors on directly recursive definitions" do
-      assert_compile_error(
-        ~r"""
-        recursive variable definition in patterns:
-
-        x = x
-
-        the variable "x" \(context Kernel.ExpansionTest\) is defined in function of itself
-        """,
-        fn -> expand(quote(do: (x = x) = :ok)) end
-      )
-
       assert_compile_error(
         ~r"""
         recursive variable definition in patterns:
diff --git a/lib/elixir/test/elixir/kernel/warning_test.exs b/lib/elixir/test/elixir/kernel/warning_test.exs
index c318fe6526b..1112b350b20 100644
--- a/lib/elixir/test/elixir/kernel/warning_test.exs
+++ b/lib/elixir/test/elixir/kernel/warning_test.exs
@@ -264,6 +264,20 @@ defmodule Kernel.WarningTest do
     purge(Sample)
   end
 
+  test "duplicate pattern" do
+    output =
+      capture_eval("""
+      defmodule Sample do
+        var = quote(do: x)
+        def hello(unquote(var) = unquote(var)), do: unquote(var)
+      end
+      """)
+
+    assert output =~ "this pattern is matched against itself inside a match: x = x"
+  after
+    purge(Sample)
+  end
+
   test "unused compiler variable" do
     output =
       capture_eval("""

From 4931ab40a504804105011c65f6ea1b7f5b588ff3 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jos=C3=A9=20Valim?= <jose.valim@dashbit.co>
Date: Tue, 17 Dec 2024 10:03:36 +0100
Subject: [PATCH 031/139] Do not warn for CaseTemplate options, closes #14077

---
 lib/ex_unit/lib/ex_unit/case.ex                 | 7 ++++++-
 lib/ex_unit/lib/ex_unit/case_template.ex        | 2 +-
 lib/ex_unit/test/ex_unit/case_template_test.exs | 2 +-
 3 files changed, 8 insertions(+), 3 deletions(-)

diff --git a/lib/ex_unit/lib/ex_unit/case.ex b/lib/ex_unit/lib/ex_unit/case.ex
index 9810df94e9e..81b46908ecd 100644
--- a/lib/ex_unit/lib/ex_unit/case.ex
+++ b/lib/ex_unit/lib/ex_unit/case.ex
@@ -315,6 +315,11 @@ defmodule ExUnit.Case do
     end
   end
 
+  @keys [:async, :group, :parameterize, :register]
+
+  @doc false
+  def __keys__(opts), do: Keyword.take(opts, @keys)
+
   @doc false
   def __register__(module, opts) do
     if not Keyword.keyword?(opts) do
@@ -324,7 +329,7 @@ defmodule ExUnit.Case do
     end
 
     {register?, opts} = Keyword.pop(opts, :register, true)
-    {next_opts, opts} = Keyword.split(opts, [:async, :group, :parameterize])
+    {next_opts, opts} = Keyword.split(opts, @keys)
 
     if opts != [] do
       IO.warn("unknown options given to ExUnit.Case: #{inspect(opts)}")
diff --git a/lib/ex_unit/lib/ex_unit/case_template.ex b/lib/ex_unit/lib/ex_unit/case_template.ex
index 0e4023a8ff0..2449b7c1998 100644
--- a/lib/ex_unit/lib/ex_unit/case_template.ex
+++ b/lib/ex_unit/lib/ex_unit/case_template.ex
@@ -83,7 +83,7 @@ defmodule ExUnit.CaseTemplate do
   # We inject this code in the module that calls "use MyTemplate".
   def __proxy__(module, opts) do
     quote do
-      use ExUnit.Case, unquote(opts)
+      use ExUnit.Case, ExUnit.Case.__keys__(unquote(opts))
 
       setup_all context do
         unquote(module).__ex_unit__(:setup_all, context)
diff --git a/lib/ex_unit/test/ex_unit/case_template_test.exs b/lib/ex_unit/test/ex_unit/case_template_test.exs
index b5265a8ce55..d660a32eb8d 100644
--- a/lib/ex_unit/test/ex_unit/case_template_test.exs
+++ b/lib/ex_unit/test/ex_unit/case_template_test.exs
@@ -36,7 +36,7 @@ defmodule ExUnit.NestedCase do
 end
 
 defmodule ExUnit.CaseTemplateTest do
-  use ExUnit.SampleCase, async: true
+  use ExUnit.SampleCase, async: true, another_option: 123
   use ExUnit.NestedCase
 
   two = 2

From 7c86c4bdb3836b1262fcf56f381d31d544f29dc7 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jos=C3=A9=20Valim?= <jose.valim@dashbit.co>
Date: Tue, 17 Dec 2024 10:11:15 +0100
Subject: [PATCH 032/139] Update CHANGELOG

---
 CHANGELOG.md | 30 +++++++++++++++++++++++++++++-
 1 file changed, 29 insertions(+), 1 deletion(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 07705488685..153cef2eeb9 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -174,7 +174,9 @@ Encoding can be done via `JSON.encode!/1` and `JSON.encode_to_iodata!/1` functio
 | `%{atom() => _}`       | Object   |
 | `%{integer() => _}`    | Object   |
 
-You may also implement the `JSON.Encoder` protocol for custom data structures. If you have a struct, you can derive the implementation of the `JSON.Encoder` by specifying which fields should be encoded to JSON:
+You may also implement the `JSON.Encoder` protocol for custom data structures. Elixir already implements the protocol for all Calendar types.
+
+If you have a struct, you can derive the implementation of the `JSON.Encoder` by specifying which fields should be encoded to JSON:
 
 ```elixir
   @derive {JSON.Encoder, only: [...]}
@@ -223,6 +225,32 @@ You may also prefer to write using guards:
 
     def foo(x, y, z) when x == y and y == z
 
+## v1.18.0-rc.1
+
+### 1. Enhancements
+
+#### Elixir
+
+  * [JSON] Implement `JSON.Encoder` for all Calendar types
+
+### 2. Bug fixes
+
+#### Elixir
+
+  * [Kernel] Avoid crashes when emitting diagnostics on code using \t for indentation
+
+### 3. Regressions
+
+#### Elixir
+
+  * [Kernel] Fix type warnings in `pop_in/1`
+  * [Kernel] Fix false positive warnings when accessing exceptions from `rescue`
+  * [Kernel] Emit warnings for duplicate patterns instead of errors
+
+#### ExUnit
+
+  * [ExUnit] Do not warn on user-supplied CaseTemplate options
+
 ## v1.18.0-rc.0 (2024-12-10)
 
 ### 1. Enhancements

From 1c0f585f40f1102c0f8132b360f4a41d4835433b Mon Sep 17 00:00:00 2001
From: Guillaume Duboc <guilduboc@gmail.com>
Date: Wed, 18 Dec 2024 08:34:17 +0100
Subject: [PATCH 033/139] Fuse maps and tuples for printing (#14079)

---
 lib/elixir/lib/module/types/descr.ex          |  95 ++++++++++++-
 .../test/elixir/module/types/descr_test.exs   | 129 ++++++++++++++++++
 2 files changed, 219 insertions(+), 5 deletions(-)

diff --git a/lib/elixir/lib/module/types/descr.ex b/lib/elixir/lib/module/types/descr.ex
index 5220f93e735..91578f6dd99 100644
--- a/lib/elixir/lib/module/types/descr.ex
+++ b/lib/elixir/lib/module/types/descr.ex
@@ -1704,6 +1704,49 @@ defmodule Module.Types.Descr do
 
       {tag, fields, negs}
     end)
+    |> map_fusion()
+  end
+
+  # Given a dnf, fuse maps when possible
+  # e.g. %{a: integer(), b: atom()} or %{a: float(), b: atom()} into %{a: number(), b: atom()}
+  defp map_fusion(dnf) do
+    # Steps:
+    # 1. Group maps by tags and keys
+    # 2. Try fusions for each group until no fusion is found
+    # 3. Merge the groups back into a dnf
+    dnf
+    |> Enum.group_by(fn {tag, fields, _} -> {tag, Map.keys(fields)} end)
+    |> Enum.flat_map(fn {_, maps} -> fuse_maps(maps) end)
+  end
+
+  defp fuse_maps(maps) do
+    Enum.reduce(maps, [], fn map, acc ->
+      case Enum.split_while(acc, &fusible_maps?(map, &1)) do
+        {_, []} ->
+          [map | acc]
+
+        {others, [match | rest]} ->
+          fused = fuse_map_pair(map, match)
+          others ++ [fused | rest]
+      end
+    end)
+  end
+
+  # Two maps are fusible if they have no negations and differ in at most one element.
+  defp fusible_maps?({_, fields1, negs1}, {_, fields2, negs2}) do
+    negs1 != [] or negs2 != [] or
+      Map.keys(fields1)
+      |> Enum.count(fn key -> Map.get(fields1, key) != Map.get(fields2, key) end) > 1
+  end
+
+  defp fuse_map_pair({tag, fields1, []}, {_, fields2, []}) do
+    fused_fields =
+      Map.new(fields1, fn {key, type1} ->
+        type2 = Map.get(fields2, key)
+        {key, if(type1 != type2, do: union(type1, type2), else: type1)}
+      end)
+
+    {tag, fused_fields, []}
   end
 
   # If all fields are the same except one, we can optimize map difference.
@@ -1925,6 +1968,7 @@ defmodule Module.Types.Descr do
   defp tuple_to_quoted(dnf) do
     dnf
     |> tuple_simplify()
+    |> tuple_fusion()
     |> Enum.map(&tuple_each_to_quoted/1)
     |> case do
       [] -> []
@@ -1932,17 +1976,58 @@ defmodule Module.Types.Descr do
     end
   end
 
-  defp tuple_each_to_quoted({tag, positive_map, negative_maps}) do
-    case negative_maps do
+  # Given a dnf of tuples, fuses the tuple unions when possible,
+  # e.g. {integer(), atom()} or {float(), atom()} into {number(), atom()}
+  # The negations of two fused tuples are just concatenated.
+  defp tuple_fusion(dnf) do
+    # Steps:
+    # 1. Consider tuples without negations apart from those with
+    # 2. Group tuples by size and tag
+    # 3. Try fusions for each group until no fusion is found
+    # 4. Merge the groups back into a dnf
+    dnf
+    |> Enum.group_by(fn {tag, elems, _} -> {tag, length(elems)} end)
+    |> Enum.flat_map(fn {_, tuples} -> fuse_tuples(tuples) end)
+  end
+
+  defp fuse_tuples(tuples) do
+    Enum.reduce(tuples, [], fn tuple, acc ->
+      case Enum.split_while(acc, &fusible_tuples?(tuple, &1)) do
+        {_, []} ->
+          [tuple | acc]
+
+        {others, [match | rest]} ->
+          fused = fuse_tuple_pair(tuple, match)
+          others ++ [fused | rest]
+      end
+    end)
+  end
+
+  # Two tuples are fusible if they have no negations and differ in at most one element.
+  defp fusible_tuples?({_, elems1, negs1}, {_, elems2, negs2}) do
+    negs1 != [] or negs2 != [] or
+      Enum.zip(elems1, elems2) |> Enum.count(fn {a, b} -> a != b end) > 1
+  end
+
+  defp fuse_tuple_pair({tag, elems1, []}, {_, elems2, []}) do
+    fused_elements =
+      Enum.zip(elems1, elems2)
+      |> Enum.map(fn {a, b} -> if a != b, do: union(a, b), else: a end)
+
+    {tag, fused_elements, []}
+  end
+
+  defp tuple_each_to_quoted({tag, positive_tuple, negative_tuples}) do
+    case negative_tuples do
       [] ->
-        tuple_literal_to_quoted({tag, positive_map})
+        tuple_literal_to_quoted({tag, positive_tuple})
 
       _ ->
-        negative_maps
+        negative_tuples
         |> Enum.map(&tuple_literal_to_quoted/1)
         |> Enum.reduce(&{:or, [], [&2, &1]})
         |> Kernel.then(
-          &{:and, [], [tuple_literal_to_quoted({tag, positive_map}), {:not, [], [&1]}]}
+          &{:and, [], [tuple_literal_to_quoted({tag, positive_tuple}), {:not, [], [&1]}]}
         )
     end
   end
diff --git a/lib/elixir/test/elixir/module/types/descr_test.exs b/lib/elixir/test/elixir/module/types/descr_test.exs
index b5100b01b6b..c12d2ae8b60 100644
--- a/lib/elixir/test/elixir/module/types/descr_test.exs
+++ b/lib/elixir/test/elixir/module/types/descr_test.exs
@@ -1268,6 +1268,91 @@ defmodule Module.Types.DescrTest do
       # assert difference(tuple([number(), term()]), tuple([integer(), atom()]))
       #        |> to_quoted_string() ==
       #          "{float(), term()} or {number(), term() and not atom()}"
+
+      assert union(tuple([integer(), atom()]), tuple([integer(), atom()])) |> to_quoted_string() ==
+               "{integer(), atom()}"
+
+      assert union(tuple([integer(), atom()]), tuple([float(), atom()])) |> to_quoted_string() ==
+               "{float() or integer(), atom()}"
+
+      assert union(tuple([integer(), atom()]), tuple([float(), atom()]))
+             |> union(tuple([pid(), pid(), port()]))
+             |> union(tuple([pid(), pid(), atom()]))
+             |> to_quoted_string() ==
+               "{float() or integer(), atom()} or {pid(), pid(), atom() or port()}"
+
+      assert union(open_tuple([integer()]), open_tuple([float()])) |> to_quoted_string() ==
+               "{float() or integer(), ...}"
+
+      # {:ok, {term(), integer()}} or {:ok, {term(), float()}} or {:exit, :kill} or {:exit, :timeout}
+      assert tuple([atom([:ok]), tuple([term(), empty_list()])])
+             |> union(tuple([atom([:ok]), tuple([term(), open_map()])]))
+             |> union(tuple([atom([:exit]), atom([:kill])]))
+             |> union(tuple([atom([:exit]), atom([:timeout])]))
+             |> to_quoted_string() ==
+               "{:exit, :kill or :timeout} or {:ok, {term(), %{...} or empty_list()}}"
+
+      # Detection of duplicates
+      assert tuple([atom([:ok]), term()])
+             |> union(tuple([atom([:ok]), term()]))
+             |> to_quoted_string() == "{:ok, term()}"
+
+      assert tuple([closed_map(a: integer(), b: atom()), open_map()])
+             |> union(tuple([closed_map(a: integer(), b: atom()), open_map()]))
+             |> to_quoted_string() ==
+               "{%{a: integer(), b: atom()}, %{...}}"
+
+      # Nested fusion
+      assert tuple([closed_map(a: integer(), b: atom()), open_map()])
+             |> union(tuple([closed_map(a: float(), b: atom()), open_map()]))
+             |> to_quoted_string() ==
+               "{%{a: float() or integer(), b: atom()}, %{...}}"
+
+      # Complex simplification of map/tuple combinations. Initial type is:
+      # ```
+      #   dynamic(
+      #     :error or
+      #     ({%Decimal{coef: :inf, exp: integer(), sign: integer()}, binary()} or
+      #       {%Decimal{coef: :NaN, exp: integer(), sign: integer()}, binary()} or
+      #       {%Decimal{coef: integer(), exp: integer(), sign: integer()}, term()} or
+      #       {%Decimal{coef: :inf, exp: integer(), sign: integer()} or
+      #           %Decimal{coef: :NaN, exp: integer(), sign: integer()} or
+      #           %Decimal{coef: integer(), exp: integer(), sign: integer()}, term()})
+      #   )
+      # ```
+      decimal_inf =
+        closed_map(
+          __struct__: atom([Decimal]),
+          coef: atom([:inf]),
+          exp: integer(),
+          sign: integer()
+        )
+
+      decimal_nan =
+        closed_map(
+          __struct__: atom([Decimal]),
+          coef: atom([:NaN]),
+          exp: integer(),
+          sign: integer()
+        )
+
+      decimal_int =
+        closed_map(__struct__: atom([Decimal]), coef: integer(), exp: integer(), sign: integer())
+
+      assert atom([:error])
+             |> union(
+               tuple([decimal_inf, binary()])
+               |> union(
+                 tuple([decimal_nan, binary()])
+                 |> union(
+                   tuple([decimal_int, term()])
+                   |> union(tuple([union(decimal_inf, union(decimal_nan, decimal_int)), term()]))
+                 )
+               )
+             )
+             |> dynamic()
+             |> to_quoted_string() ==
+               "dynamic(\n  :error or\n    ({%Decimal{coef: integer() or (:NaN or :inf), exp: integer(), sign: integer()}, term()} or\n       {%Decimal{coef: :NaN or :inf, exp: integer(), sign: integer()}, binary()})\n)"
     end
 
     test "map" do
@@ -1311,6 +1396,50 @@ defmodule Module.Types.DescrTest do
       assert difference(open_map(a: number(), b: atom()), open_map(a: integer()))
              |> to_quoted_string() == "%{..., a: float(), b: atom()}"
 
+      # Basic map fusion
+      assert union(closed_map(a: integer()), closed_map(a: integer())) |> to_quoted_string() ==
+               "%{a: integer()}"
+
+      assert union(closed_map(a: integer()), closed_map(a: float())) |> to_quoted_string() ==
+               "%{a: float() or integer()}"
+
+      # Nested fusion
+      assert union(closed_map(a: integer(), b: atom()), closed_map(a: float(), b: atom()))
+             |> union(closed_map(x: pid(), y: pid(), z: port()))
+             |> union(closed_map(x: pid(), y: pid(), z: atom()))
+             |> to_quoted_string() ==
+               "%{a: float() or integer(), b: atom()} or %{x: pid(), y: pid(), z: atom() or port()}"
+
+      # Open map fusion
+      assert union(open_map(a: integer()), open_map(a: float())) |> to_quoted_string() ==
+               "%{..., a: float() or integer()}"
+
+      # Fusing complex nested maps with unions
+      assert closed_map(status: atom([:ok]), data: closed_map(value: term(), count: empty_list()))
+             |> union(
+               closed_map(status: atom([:ok]), data: closed_map(value: term(), count: open_map()))
+             )
+             |> union(closed_map(status: atom([:error]), reason: atom([:timeout])))
+             |> union(closed_map(status: atom([:error]), reason: atom([:crash])))
+             |> to_quoted_string() ==
+               "%{data: %{count: %{...} or empty_list(), value: term()}, status: :ok} or\n  %{reason: :crash or :timeout, status: :error}"
+
+      # Difference and union tests
+      assert closed_map(status: atom([:ok]), value: term())
+             |> difference(closed_map(status: atom([:ok]), value: float()))
+             |> union(
+               closed_map(status: atom([:ok]), value: term())
+               |> difference(closed_map(status: atom([:ok]), value: integer()))
+             )
+             |> to_quoted_string() ==
+               "%{status: :ok, value: term()}"
+
+      # Nested map fusion
+      assert closed_map(data: closed_map(x: integer(), y: atom()), meta: open_map())
+             |> union(closed_map(data: closed_map(x: float(), y: atom()), meta: open_map()))
+             |> to_quoted_string() ==
+               "%{data: %{x: float() or integer(), y: atom()}, meta: %{...}}"
+
       # Test complex combinations
       assert intersection(open_map(a: number(), b: atom()), open_map(a: integer(), c: boolean()))
              |> union(difference(open_map(x: atom()), open_map(x: boolean())))

From a426520b669efc61bdf4e9807d950e4654feddbc Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jos=C3=A9=20Valim?= <jose.valim@dashbit.co>
Date: Wed, 18 Dec 2024 09:30:51 +0100
Subject: [PATCH 034/139] Improvements to descr pretty printing

---
 lib/elixir/lib/module/types/descr.ex          | 113 +++++++++---------
 .../test/elixir/module/types/descr_test.exs   |  13 +-
 2 files changed, 67 insertions(+), 59 deletions(-)

diff --git a/lib/elixir/lib/module/types/descr.ex b/lib/elixir/lib/module/types/descr.ex
index 91578f6dd99..20892cf2dd8 100644
--- a/lib/elixir/lib/module/types/descr.ex
+++ b/lib/elixir/lib/module/types/descr.ex
@@ -372,11 +372,18 @@ defmodule Module.Types.Descr do
     if term_type?(descr) do
       {:term, [], []}
     else
+      # Dynamic always come first for visibility
+      {dynamic, descr} =
+        case :maps.take(:dynamic, descr) do
+          :error -> {[], descr}
+          {dynamic, descr} -> {to_quoted(:dynamic, dynamic), descr}
+        end
+
+      # Merge empty list and list together if they both exist
       {extra, descr} =
         case descr do
-          # Merge empty list and list together if they both exist
           %{list: list, bitmap: bitmap} when (bitmap &&& @bit_empty_list) != 0 ->
-            descr = descr |> Map.delete(:list) |> Map.update!(:bitmap, &(&1 - @bit_empty_list))
+            descr = descr |> Map.delete(:list) |> Map.replace!(:bitmap, bitmap - @bit_empty_list)
 
             case list_to_quoted(list, :list) do
               [] -> {[{:empty_list, [], []}], descr}
@@ -387,9 +394,13 @@ defmodule Module.Types.Descr do
             {[], descr}
         end
 
-      case extra ++ Enum.flat_map(descr, fn {key, value} -> to_quoted(key, value) end) do
+      unions =
+        dynamic ++
+          Enum.sort(extra ++ Enum.flat_map(descr, fn {key, value} -> to_quoted(key, value) end))
+
+      case unions do
         [] -> {:none, [], []}
-        unions -> unions |> Enum.sort() |> Enum.reduce(&{:or, [], [&2, &1]})
+        unions -> Enum.reduce(unions, &{:or, [], [&2, &1]})
       end
     end
   end
@@ -785,17 +796,16 @@ defmodule Module.Types.Descr do
 
   defp atom_to_quoted({:union, a}) do
     if :sets.is_subset(@boolset, a) do
-      :sets.subtract(a, @boolset)
-      |> :sets.to_list()
-      |> Enum.sort()
-      |> Enum.reduce({:boolean, [], []}, &{:or, [], [&2, literal_to_quoted(&1)]})
+      entries =
+        :sets.subtract(a, @boolset)
+        |> :sets.to_list()
+        |> Enum.map(&literal_to_quoted/1)
+
+      [{:boolean, [], []} | entries]
     else
       :sets.to_list(a)
-      |> Enum.sort()
       |> Enum.map(&literal_to_quoted/1)
-      |> Enum.reduce(&{:or, [], [&2, &1]})
     end
-    |> List.wrap()
   end
 
   defp atom_to_quoted({:negation, a}) do
@@ -1064,11 +1074,7 @@ defmodule Module.Types.Descr do
           |> Enum.reduce(&{:or, [], [&2, &1]})
           |> Kernel.then(
             &[
-              {:and, [],
-               [
-                 {name, [], arguments},
-                 {:not, [], [&1]}
-               ]}
+              {:and, [], [{name, [], arguments}, {:not, [], [&1]}]}
               | acc
             ]
           )
@@ -1691,7 +1697,7 @@ defmodule Module.Types.Descr do
           if map_empty_negation?(tag, acc_fields, neg) do
             {acc_fields, acc_negs}
           else
-            case all_but_one?(tag, acc_fields, neg_tag, neg_fields) do
+            case map_all_but_one?(tag, acc_fields, neg_tag, neg_fields) do
               {:one, diff_key} ->
                 {Map.update!(acc_fields, diff_key, &difference(&1, neg_fields[diff_key])),
                  acc_negs}
@@ -1714,43 +1720,45 @@ defmodule Module.Types.Descr do
     # 1. Group maps by tags and keys
     # 2. Try fusions for each group until no fusion is found
     # 3. Merge the groups back into a dnf
-    dnf
-    |> Enum.group_by(fn {tag, fields, _} -> {tag, Map.keys(fields)} end)
-    |> Enum.flat_map(fn {_, maps} -> fuse_maps(maps) end)
+    {without_negs, with_negs} = Enum.split_with(dnf, fn {_tag, _fields, negs} -> negs == [] end)
+
+    without_negs =
+      without_negs
+      |> Enum.group_by(fn {tag, fields, _} -> {tag, Map.keys(fields)} end)
+      |> Enum.flat_map(fn {_, maps} -> map_non_negated_fuse(maps) end)
+
+    without_negs ++ with_negs
   end
 
-  defp fuse_maps(maps) do
+  defp map_non_negated_fuse(maps) do
     Enum.reduce(maps, [], fn map, acc ->
-      case Enum.split_while(acc, &fusible_maps?(map, &1)) do
+      case Enum.split_while(acc, &non_fusible_maps?(map, &1)) do
         {_, []} ->
           [map | acc]
 
         {others, [match | rest]} ->
-          fused = fuse_map_pair(map, match)
+          fused = map_non_negated_fuse_pair(map, match)
           others ++ [fused | rest]
       end
     end)
   end
 
-  # Two maps are fusible if they have no negations and differ in at most one element.
-  defp fusible_maps?({_, fields1, negs1}, {_, fields2, negs2}) do
-    negs1 != [] or negs2 != [] or
-      Map.keys(fields1)
-      |> Enum.count(fn key -> Map.get(fields1, key) != Map.get(fields2, key) end) > 1
+  # Two maps are fusible if they differ in at most one element.
+  defp non_fusible_maps?({_, fields1, []}, {_, fields2, []}) do
+    Enum.count_until(fields1, fn {key, value} -> Map.fetch!(fields2, key) != value end, 2) > 1
   end
 
-  defp fuse_map_pair({tag, fields1, []}, {_, fields2, []}) do
-    fused_fields =
-      Map.new(fields1, fn {key, type1} ->
-        type2 = Map.get(fields2, key)
-        {key, if(type1 != type2, do: union(type1, type2), else: type1)}
+  defp map_non_negated_fuse_pair({tag, fields1, []}, {_, fields2, []}) do
+    fields =
+      symmetrical_merge(fields1, fields2, fn _k, v1, v2 ->
+        if v1 == v2, do: v1, else: union(v1, v2)
       end)
 
-    {tag, fused_fields, []}
+    {tag, fields, []}
   end
 
   # If all fields are the same except one, we can optimize map difference.
-  defp all_but_one?(tag1, fields1, tag2, fields2) do
+  defp map_all_but_one?(tag1, fields1, tag2, fields2) do
     keys1 = Map.keys(fields1)
     keys2 = Map.keys(fields2)
 
@@ -1782,10 +1790,6 @@ defmodule Module.Types.Descr do
     dnf
     |> map_normalize()
     |> Enum.map(&map_each_to_quoted/1)
-    |> case do
-      [] -> []
-      dnf -> Enum.reduce(dnf, &{:or, [], [&2, &1]}) |> List.wrap()
-    end
   end
 
   defp map_each_to_quoted({tag, positive_map, negative_maps}) do
@@ -1970,10 +1974,6 @@ defmodule Module.Types.Descr do
     |> tuple_simplify()
     |> tuple_fusion()
     |> Enum.map(&tuple_each_to_quoted/1)
-    |> case do
-      [] -> []
-      dnf -> Enum.reduce(dnf, &{:or, [], [&2, &1]}) |> List.wrap()
-    end
   end
 
   # Given a dnf of tuples, fuses the tuple unions when possible,
@@ -1985,34 +1985,37 @@ defmodule Module.Types.Descr do
     # 2. Group tuples by size and tag
     # 3. Try fusions for each group until no fusion is found
     # 4. Merge the groups back into a dnf
-    dnf
-    |> Enum.group_by(fn {tag, elems, _} -> {tag, length(elems)} end)
-    |> Enum.flat_map(fn {_, tuples} -> fuse_tuples(tuples) end)
+    {without_negs, with_negs} = Enum.split_with(dnf, fn {_tag, _elems, negs} -> negs == [] end)
+
+    without_negs =
+      without_negs
+      |> Enum.group_by(fn {tag, elems, _} -> {tag, length(elems)} end)
+      |> Enum.flat_map(fn {_, tuples} -> tuple_non_negated_fuse(tuples) end)
+
+    without_negs ++ with_negs
   end
 
-  defp fuse_tuples(tuples) do
+  defp tuple_non_negated_fuse(tuples) do
     Enum.reduce(tuples, [], fn tuple, acc ->
-      case Enum.split_while(acc, &fusible_tuples?(tuple, &1)) do
+      case Enum.split_while(acc, &non_fusible_tuples?(tuple, &1)) do
         {_, []} ->
           [tuple | acc]
 
         {others, [match | rest]} ->
-          fused = fuse_tuple_pair(tuple, match)
+          fused = tuple_non_negated_fuse_pair(tuple, match)
           others ++ [fused | rest]
       end
     end)
   end
 
   # Two tuples are fusible if they have no negations and differ in at most one element.
-  defp fusible_tuples?({_, elems1, negs1}, {_, elems2, negs2}) do
-    negs1 != [] or negs2 != [] or
-      Enum.zip(elems1, elems2) |> Enum.count(fn {a, b} -> a != b end) > 1
+  defp non_fusible_tuples?({_, elems1, []}, {_, elems2, []}) do
+    Enum.zip(elems1, elems2) |> Enum.count_until(fn {a, b} -> a != b end, 2) > 1
   end
 
-  defp fuse_tuple_pair({tag, elems1, []}, {_, elems2, []}) do
+  defp tuple_non_negated_fuse_pair({tag, elems1, []}, {_, elems2, []}) do
     fused_elements =
-      Enum.zip(elems1, elems2)
-      |> Enum.map(fn {a, b} -> if a != b, do: union(a, b), else: a end)
+      Enum.zip_with(elems1, elems2, fn a, b -> if a == b, do: a, else: union(a, b) end)
 
     {tag, fused_elements, []}
   end
diff --git a/lib/elixir/test/elixir/module/types/descr_test.exs b/lib/elixir/test/elixir/module/types/descr_test.exs
index c12d2ae8b60..13517e87bd0 100644
--- a/lib/elixir/test/elixir/module/types/descr_test.exs
+++ b/lib/elixir/test/elixir/module/types/descr_test.exs
@@ -1184,7 +1184,7 @@ defmodule Module.Types.DescrTest do
 
     test "boolean" do
       assert boolean() |> to_quoted_string() == "boolean()"
-      assert atom([true, false, :a]) |> to_quoted_string() == "boolean() or :a"
+      assert atom([true, false, :a]) |> to_quoted_string() == ":a or boolean()"
       assert atom([true, :a]) |> to_quoted_string() == ":a or true"
       assert difference(atom(), boolean()) |> to_quoted_string() == "atom() and not boolean()"
     end
@@ -1199,7 +1199,7 @@ defmodule Module.Types.DescrTest do
       assert intersection(atom(), dynamic()) |> to_quoted_string() == "dynamic(atom())"
 
       assert union(atom([:foo, :bar]), dynamic()) |> to_quoted_string() ==
-               "dynamic() or (:bar or :foo)"
+               "dynamic() or :bar or :foo"
 
       assert intersection(dynamic(), closed_map(a: integer())) |> to_quoted_string() ==
                "dynamic(%{a: integer()})"
@@ -1256,7 +1256,7 @@ defmodule Module.Types.DescrTest do
       assert open_tuple([integer(), atom()]) |> to_quoted_string() == "{integer(), atom(), ...}"
 
       assert union(tuple([integer(), atom()]), open_tuple([atom()])) |> to_quoted_string() ==
-               "{integer(), atom()} or {atom(), ...}"
+               "{atom(), ...} or {integer(), atom()}"
 
       assert difference(tuple([integer(), atom()]), open_tuple([atom()])) |> to_quoted_string() ==
                "{integer(), atom()}"
@@ -1352,7 +1352,12 @@ defmodule Module.Types.DescrTest do
              )
              |> dynamic()
              |> to_quoted_string() ==
-               "dynamic(\n  :error or\n    ({%Decimal{coef: integer() or (:NaN or :inf), exp: integer(), sign: integer()}, term()} or\n       {%Decimal{coef: :NaN or :inf, exp: integer(), sign: integer()}, binary()})\n)"
+               """
+               dynamic(
+                 :error or {%Decimal{coef: :NaN or :inf, exp: integer(), sign: integer()}, binary()} or
+                   {%Decimal{coef: :NaN or :inf or integer(), exp: integer(), sign: integer()}, term()}
+               )\
+               """
     end
 
     test "map" do

From ed83c407a53e9483b38224538b8736b710cdee04 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jos=C3=A9=20Valim?= <jose.valim@dashbit.co>
Date: Thu, 19 Dec 2024 12:12:49 +0100
Subject: [PATCH 035/139] Simplify and optimize NaiveDateTime.utc_now

---
 lib/elixir/lib/calendar/naive_datetime.ex | 39 +++++++++++------------
 1 file changed, 19 insertions(+), 20 deletions(-)

diff --git a/lib/elixir/lib/calendar/naive_datetime.ex b/lib/elixir/lib/calendar/naive_datetime.ex
index 4fb676c107d..43e71c9eaa3 100644
--- a/lib/elixir/lib/calendar/naive_datetime.ex
+++ b/lib/elixir/lib/calendar/naive_datetime.ex
@@ -110,30 +110,12 @@ defmodule NaiveDateTime do
   @spec utc_now(Calendar.calendar() | :native | :microsecond | :millisecond | :second) :: t
   def utc_now(calendar_or_time_unit \\ Calendar.ISO)
 
-  def utc_now(Calendar.ISO) do
-    {:ok, {year, month, day}, {hour, minute, second}, microsecond} =
-      Calendar.ISO.from_unix(:os.system_time(), :native)
-
-    %NaiveDateTime{
-      year: year,
-      month: month,
-      day: day,
-      hour: hour,
-      minute: minute,
-      second: second,
-      microsecond: microsecond,
-      calendar: Calendar.ISO
-    }
-  end
-
   def utc_now(time_unit) when time_unit in [:microsecond, :millisecond, :second, :native] do
     utc_now(time_unit, Calendar.ISO)
   end
 
   def utc_now(calendar) do
-    calendar
-    |> DateTime.utc_now()
-    |> DateTime.to_naive()
+    utc_now(:native, calendar)
   end
 
   @doc """
@@ -158,7 +140,20 @@ defmodule NaiveDateTime do
   @spec utc_now(:native | :microsecond | :millisecond | :second, Calendar.calendar()) :: t
   def utc_now(time_unit, calendar)
       when time_unit in [:native, :microsecond, :millisecond, :second] do
-    DateTime.utc_now(time_unit, calendar) |> DateTime.to_naive()
+    {:ok, {year, month, day}, {hour, minute, second}, microsecond} =
+      Calendar.ISO.from_unix(System.os_time(time_unit), time_unit)
+
+    %NaiveDateTime{
+      year: year,
+      month: month,
+      day: day,
+      hour: hour,
+      minute: minute,
+      second: second,
+      microsecond: microsecond,
+      calendar: Calendar.ISO
+    }
+    |> convert!(calendar)
   end
 
   @doc """
@@ -1265,6 +1260,10 @@ defmodule NaiveDateTime do
           {:ok, t} | {:error, :incompatible_calendars}
 
   # Keep it multiline for proper function clause errors.
+  def convert(%NaiveDateTime{calendar: calendar} = ndt, calendar) do
+    {:ok, ndt}
+  end
+
   def convert(
         %{
           calendar: calendar,

From ca6edfd389bd9963f0c5906d22bf0f87d6d9c37c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jos=C3=A9=20Valim?= <jose.valim@dashbit.co>
Date: Thu, 19 Dec 2024 12:13:17 +0100
Subject: [PATCH 036/139] Include types in comparison warning

---
 lib/elixir/lib/module/types/apply.ex          | 27 +++++++++++++++----
 .../test/elixir/module/types/expr_test.exs    | 12 +++++++++
 2 files changed, 34 insertions(+), 5 deletions(-)

diff --git a/lib/elixir/lib/module/types/apply.ex b/lib/elixir/lib/module/types/apply.ex
index b8a19022e39..9de891fb5fc 100644
--- a/lib/elixir/lib/module/types/apply.ex
+++ b/lib/elixir/lib/module/types/apply.ex
@@ -363,14 +363,14 @@ defmodule Module.Types.Apply do
 
         match?({false, _}, map_fetch(left, :__struct__)) or
             match?({false, _}, map_fetch(right, :__struct__)) ->
-          warning = {:struct_comparison, expr, context}
+          warning = {:struct_comparison, expr, name, left, right, context}
           warn(__MODULE__, warning, elem(expr, 1), stack, context)
 
         number_type?(left) and number_type?(right) ->
           context
 
         disjoint?(left, right) ->
-          warning = {:mismatched_comparison, expr, context}
+          warning = {:mismatched_comparison, expr, name, left, right, context}
           warn(__MODULE__, warning, elem(expr, 1), stack, context)
 
         true ->
@@ -395,7 +395,7 @@ defmodule Module.Types.Apply do
           context
 
         disjoint?(left, right) ->
-          warning = {:mismatched_comparison, expr, context}
+          warning = {:mismatched_comparison, expr, name, left, right, context}
           warn(__MODULE__, warning, elem(expr, 1), stack, context)
 
         true ->
@@ -810,7 +810,7 @@ defmodule Module.Types.Apply do
     }
   end
 
-  def format_diagnostic({:mismatched_comparison, expr, context}) do
+  def format_diagnostic({:mismatched_comparison, expr, name, left, right, context}) do
     traces = collect_traces(expr, context)
 
     %{
@@ -821,6 +821,10 @@ defmodule Module.Types.Apply do
           comparison between distinct types found:
 
               #{expr_to_string(expr) |> indent(4)}
+
+          given types:
+
+              #{type_comparison_to_string(name, left, right) |> indent(4)}
           """,
           format_traces(traces),
           """
@@ -833,7 +837,7 @@ defmodule Module.Types.Apply do
     }
   end
 
-  def format_diagnostic({:struct_comparison, expr, context}) do
+  def format_diagnostic({:struct_comparison, expr, name, left, right, context}) do
     traces = collect_traces(expr, context)
 
     %{
@@ -844,6 +848,10 @@ defmodule Module.Types.Apply do
           comparison with structs found:
 
               #{expr_to_string(expr) |> indent(4)}
+
+          given types:
+
+              #{type_comparison_to_string(name, left, right) |> indent(4)}
           """,
           format_traces(traces),
           """
@@ -933,6 +941,15 @@ defmodule Module.Types.Apply do
 
   alias Inspect.Algebra, as: IA
 
+  defp type_comparison_to_string(fun, left, right) do
+    {Kernel, fun, [left, right], _} = :elixir_rewrite.erl_to_ex(:erlang, fun, [left, right])
+
+    {fun, [], [to_quoted(left), to_quoted(right)]}
+    |> Code.Formatter.to_algebra()
+    |> Inspect.Algebra.format(98)
+    |> IO.iodata_to_binary()
+  end
+
   defp clauses_args_to_quoted_string([{args, _return}], converter) do
     "\n    " <> (clause_args_to_quoted_string(args, converter) |> indent(4))
   end
diff --git a/lib/elixir/test/elixir/module/types/expr_test.exs b/lib/elixir/test/elixir/module/types/expr_test.exs
index c5c5a6040e0..6c4f4b62365 100644
--- a/lib/elixir/test/elixir/module/types/expr_test.exs
+++ b/lib/elixir/test/elixir/module/types/expr_test.exs
@@ -845,6 +845,10 @@ defmodule Module.Types.ExprTest do
 
                     min(x, y)
 
+                given types:
+
+                    min(dynamic(:foo), integer())
+
                 where "x" was given the type:
 
                     # type: dynamic(:foo)
@@ -868,6 +872,10 @@ defmodule Module.Types.ExprTest do
 
                     x === y
 
+                given types:
+
+                    integer() === float()
+
                 where "x" was given the type:
 
                     # type: integer()
@@ -893,6 +901,10 @@ defmodule Module.Types.ExprTest do
 
                     mod.<=(x, y)
 
+                given types:
+
+                    dynamic(:foo) <= dynamic(%Point{x: term(), y: term(), z: term()})
+
                 where "mod" was given the type:
 
                     # type: dynamic(Kernel)

From c6597bcb20e9926d6c8a437058003c7d4faa13ed Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jos=C3=A9=20Valim?= <jose.valim@dashbit.co>
Date: Thu, 19 Dec 2024 15:12:31 +0100
Subject: [PATCH 037/139] Collapse structs for better pretty printing in
 different scenarios

---
 lib/elixir/lib/module/types/apply.ex          |   2 +-
 lib/elixir/lib/module/types/descr.ex          | 119 +++++++++++-------
 lib/elixir/lib/module/types/helpers.ex        |   2 +-
 lib/elixir/lib/module/types/of.ex             |   4 +-
 .../test/elixir/module/types/expr_test.exs    |  12 +-
 .../test/elixir/module/types/pattern_test.exs |   6 +-
 6 files changed, 92 insertions(+), 53 deletions(-)

diff --git a/lib/elixir/lib/module/types/apply.ex b/lib/elixir/lib/module/types/apply.ex
index 9de891fb5fc..49f98295262 100644
--- a/lib/elixir/lib/module/types/apply.ex
+++ b/lib/elixir/lib/module/types/apply.ex
@@ -944,7 +944,7 @@ defmodule Module.Types.Apply do
   defp type_comparison_to_string(fun, left, right) do
     {Kernel, fun, [left, right], _} = :elixir_rewrite.erl_to_ex(:erlang, fun, [left, right])
 
-    {fun, [], [to_quoted(left), to_quoted(right)]}
+    {fun, [], [to_quoted(left, collapse_structs: true), to_quoted(right, collapse_structs: true)]}
     |> Code.Formatter.to_algebra()
     |> Inspect.Algebra.format(98)
     |> IO.iodata_to_binary()
diff --git a/lib/elixir/lib/module/types/descr.ex b/lib/elixir/lib/module/types/descr.ex
index 20892cf2dd8..c9af3919dde 100644
--- a/lib/elixir/lib/module/types/descr.ex
+++ b/lib/elixir/lib/module/types/descr.ex
@@ -367,8 +367,13 @@ defmodule Module.Types.Descr do
 
   @doc """
   Converts a descr to its quoted representation.
+
+  ## Options
+
+    * `:collapse_structs` - do not show struct fields that match
+      their default type
   """
-  def to_quoted(descr) do
+  def to_quoted(descr, opts \\ []) do
     if term_type?(descr) do
       {:term, [], []}
     else
@@ -376,7 +381,7 @@ defmodule Module.Types.Descr do
       {dynamic, descr} =
         case :maps.take(:dynamic, descr) do
           :error -> {[], descr}
-          {dynamic, descr} -> {to_quoted(:dynamic, dynamic), descr}
+          {dynamic, descr} -> {to_quoted(:dynamic, dynamic, opts), descr}
         end
 
       # Merge empty list and list together if they both exist
@@ -385,7 +390,7 @@ defmodule Module.Types.Descr do
           %{list: list, bitmap: bitmap} when (bitmap &&& @bit_empty_list) != 0 ->
             descr = descr |> Map.delete(:list) |> Map.replace!(:bitmap, bitmap - @bit_empty_list)
 
-            case list_to_quoted(list, :list) do
+            case list_to_quoted(list, :list, opts) do
               [] -> {[{:empty_list, [], []}], descr}
               unions -> {unions, descr}
             end
@@ -396,7 +401,9 @@ defmodule Module.Types.Descr do
 
       unions =
         dynamic ++
-          Enum.sort(extra ++ Enum.flat_map(descr, fn {key, value} -> to_quoted(key, value) end))
+          Enum.sort(
+            extra ++ Enum.flat_map(descr, fn {key, value} -> to_quoted(key, value, opts) end)
+          )
 
       case unions do
         [] -> {:none, [], []}
@@ -405,19 +412,19 @@ defmodule Module.Types.Descr do
     end
   end
 
-  defp to_quoted(:atom, val), do: atom_to_quoted(val)
-  defp to_quoted(:bitmap, val), do: bitmap_to_quoted(val)
-  defp to_quoted(:dynamic, descr), do: dynamic_to_quoted(descr)
-  defp to_quoted(:map, dnf), do: map_to_quoted(dnf)
-  defp to_quoted(:list, dnf), do: list_to_quoted(dnf, :non_empty_list)
-  defp to_quoted(:tuple, dnf), do: tuple_to_quoted(dnf)
+  defp to_quoted(:atom, val, _opts), do: atom_to_quoted(val)
+  defp to_quoted(:bitmap, val, _opts), do: bitmap_to_quoted(val)
+  defp to_quoted(:dynamic, descr, opts), do: dynamic_to_quoted(descr, opts)
+  defp to_quoted(:map, dnf, opts), do: map_to_quoted(dnf, opts)
+  defp to_quoted(:list, dnf, opts), do: list_to_quoted(dnf, :non_empty_list, opts)
+  defp to_quoted(:tuple, dnf, opts), do: tuple_to_quoted(dnf, opts)
 
   @doc """
   Converts a descr to its quoted string representation.
   """
-  def to_quoted_string(descr) do
+  def to_quoted_string(descr, opts \\ []) do
     descr
-    |> to_quoted()
+    |> to_quoted(opts)
     |> Code.Formatter.to_algebra()
     |> Inspect.Algebra.format(98)
     |> IO.iodata_to_binary()
@@ -1045,16 +1052,16 @@ defmodule Module.Types.Descr do
     end
   end
 
-  defp list_to_quoted(dnf, name) do
+  defp list_to_quoted(dnf, name, opts) do
     dnf = list_normalize(dnf)
 
     for {list_type, last_type, negs} <- dnf, reduce: [] do
       acc ->
         arguments =
           if subtype?(last_type, @empty_list) do
-            [to_quoted(list_type)]
+            [to_quoted(list_type, opts)]
           else
-            [to_quoted(list_type), to_quoted(last_type)]
+            [to_quoted(list_type, opts), to_quoted(last_type, opts)]
           end
 
         if negs == [] do
@@ -1064,9 +1071,9 @@ defmodule Module.Types.Descr do
           |> Enum.map(fn {ty, lst} ->
             args =
               if subtype?(lst, @empty_list) do
-                [to_quoted(ty)]
+                [to_quoted(ty, opts)]
               else
-                [to_quoted(ty), to_quoted(lst)]
+                [to_quoted(ty, opts), to_quoted(lst, opts)]
               end
 
             {name, [], args}
@@ -1176,7 +1183,7 @@ defmodule Module.Types.Descr do
     end
   end
 
-  defp dynamic_to_quoted(descr) do
+  defp dynamic_to_quoted(descr, opts) do
     cond do
       term_type?(descr) ->
         [{:dynamic, [], []}]
@@ -1185,7 +1192,7 @@ defmodule Module.Types.Descr do
         [single]
 
       true ->
-        case to_quoted(descr) do
+        case to_quoted(descr, opts) do
           {:none, _meta, []} = none -> [none]
           descr -> [{:dynamic, [], [descr]}]
         end
@@ -1786,51 +1793,77 @@ defmodule Module.Types.Descr do
          end))
   end
 
-  defp map_to_quoted(dnf) do
+  defp map_to_quoted(dnf, opts) do
     dnf
     |> map_normalize()
-    |> Enum.map(&map_each_to_quoted/1)
+    |> Enum.map(&map_each_to_quoted(&1, opts))
   end
 
-  defp map_each_to_quoted({tag, positive_map, negative_maps}) do
+  defp map_each_to_quoted({tag, positive_map, negative_maps}, opts) do
     case negative_maps do
       [] ->
-        map_literal_to_quoted({tag, positive_map})
+        map_literal_to_quoted({tag, positive_map}, opts)
 
       _ ->
         negative_maps
-        |> Enum.map(&map_literal_to_quoted/1)
+        |> Enum.map(&map_literal_to_quoted(&1, opts))
         |> Enum.reduce(&{:or, [], [&2, &1]})
         |> Kernel.then(
-          &{:and, [], [map_literal_to_quoted({tag, positive_map}), {:not, [], [&1]}]}
+          &{:and, [], [map_literal_to_quoted({tag, positive_map}, opts), {:not, [], [&1]}]}
         )
     end
   end
 
-  def map_literal_to_quoted({:closed, fields}) when map_size(fields) == 0 do
+  def map_literal_to_quoted({:closed, fields}, _opts) when map_size(fields) == 0 do
     {:empty_map, [], []}
   end
 
-  def map_literal_to_quoted({tag, fields}) do
+  def map_literal_to_quoted({tag, fields}, opts) do
     case tag do
       :closed ->
         with %{__struct__: struct_descr} <- fields,
              {_, [struct]} <- atom_fetch(struct_descr) do
+          fields = Map.delete(fields, :__struct__)
+
+          fields =
+            with true <- Keyword.get(opts, :collapse_structs, false),
+                 [_ | _] = info <- maybe_struct(struct),
+                 true <- Enum.all?(info, &is_map_key(fields, &1.field)) do
+              Enum.reduce(info, fields, fn %{field: field}, acc ->
+                # TODO: This should consider the struct default value
+                if Map.fetch!(acc, field) == term() do
+                  Map.delete(acc, field)
+                else
+                  acc
+                end
+              end)
+            else
+              _ -> fields
+            end
+
           {:%, [],
            [
              literal_to_quoted(struct),
-             {:%{}, [], map_fields_to_quoted(tag, Map.delete(fields, :__struct__))}
+             {:%{}, [], map_fields_to_quoted(tag, fields, opts)}
            ]}
         else
-          _ -> {:%{}, [], map_fields_to_quoted(tag, fields)}
+          _ -> {:%{}, [], map_fields_to_quoted(tag, fields, opts)}
         end
 
       :open ->
-        {:%{}, [], [{:..., [], nil} | map_fields_to_quoted(tag, fields)]}
+        {:%{}, [], [{:..., [], nil} | map_fields_to_quoted(tag, fields, opts)]}
+    end
+  end
+
+  defp maybe_struct(struct) do
+    try do
+      struct.__info__(:struct)
+    rescue
+      _ -> nil
     end
   end
 
-  defp map_fields_to_quoted(tag, map) do
+  defp map_fields_to_quoted(tag, map, opts) do
     sorted = Enum.sort(Map.to_list(map))
     keyword? = Inspect.List.keyword?(sorted)
 
@@ -1846,9 +1879,9 @@ defmodule Module.Types.Descr do
       {optional?, type} = pop_optional_static(type)
 
       cond do
-        not optional? -> {key, to_quoted(type)}
+        not optional? -> {key, to_quoted(type, opts)}
         empty?(type) -> {key, {:not_set, [], []}}
-        true -> {key, {:if_set, [], [to_quoted(type)]}}
+        true -> {key, {:if_set, [], [to_quoted(type, opts)]}}
       end
     end
   end
@@ -1969,11 +2002,11 @@ defmodule Module.Types.Descr do
   # This is a cheap optimization that relies on structural equality.
   defp tuple_union(left, right), do: left ++ (right -- left)
 
-  defp tuple_to_quoted(dnf) do
+  defp tuple_to_quoted(dnf, opts) do
     dnf
     |> tuple_simplify()
     |> tuple_fusion()
-    |> Enum.map(&tuple_each_to_quoted/1)
+    |> Enum.map(&tuple_each_to_quoted(&1, opts))
   end
 
   # Given a dnf of tuples, fuses the tuple unions when possible,
@@ -2020,27 +2053,27 @@ defmodule Module.Types.Descr do
     {tag, fused_elements, []}
   end
 
-  defp tuple_each_to_quoted({tag, positive_tuple, negative_tuples}) do
+  defp tuple_each_to_quoted({tag, positive_tuple, negative_tuples}, opts) do
     case negative_tuples do
       [] ->
-        tuple_literal_to_quoted({tag, positive_tuple})
+        tuple_literal_to_quoted({tag, positive_tuple}, opts)
 
       _ ->
         negative_tuples
-        |> Enum.map(&tuple_literal_to_quoted/1)
+        |> Enum.map(&tuple_literal_to_quoted(&1, opts))
         |> Enum.reduce(&{:or, [], [&2, &1]})
         |> Kernel.then(
-          &{:and, [], [tuple_literal_to_quoted({tag, positive_tuple}), {:not, [], [&1]}]}
+          &{:and, [], [tuple_literal_to_quoted({tag, positive_tuple}, opts), {:not, [], [&1]}]}
         )
     end
   end
 
-  defp tuple_literal_to_quoted({:closed, []}), do: {:{}, [], []}
+  defp tuple_literal_to_quoted({:closed, []}, _opts), do: {:{}, [], []}
 
-  defp tuple_literal_to_quoted({tag, elements}) do
+  defp tuple_literal_to_quoted({tag, elements}, opts) do
     case tag do
-      :closed -> {:{}, [], Enum.map(elements, &to_quoted/1)}
-      :open -> {:{}, [], Enum.map(elements, &to_quoted/1) ++ [{:..., [], nil}]}
+      :closed -> {:{}, [], Enum.map(elements, &to_quoted(&1, opts))}
+      :open -> {:{}, [], Enum.map(elements, &to_quoted(&1, opts)) ++ [{:..., [], nil}]}
     end
   end
 
diff --git a/lib/elixir/lib/module/types/helpers.ex b/lib/elixir/lib/module/types/helpers.ex
index fa1774adea4..d43b663b7d3 100644
--- a/lib/elixir/lib/module/types/helpers.ex
+++ b/lib/elixir/lib/module/types/helpers.ex
@@ -141,7 +141,7 @@ defmodule Module.Types.Helpers do
         column: meta[:column],
         hints: formatter_hints ++ expr_hints(expr),
         formatted_expr: formatted_expr,
-        formatted_type: Module.Types.Descr.to_quoted_string(type)
+        formatted_type: Module.Types.Descr.to_quoted_string(type, collapse_structs: true)
       }
     end)
     |> Enum.sort_by(&{&1.line, &1.column})
diff --git a/lib/elixir/lib/module/types/of.ex b/lib/elixir/lib/module/types/of.ex
index d04b53151fb..a5bdff26995 100644
--- a/lib/elixir/lib/module/types/of.ex
+++ b/lib/elixir/lib/module/types/of.ex
@@ -495,13 +495,11 @@ defmodule Module.Types.Of do
           unknown key .#{key} in expression:
 
               #{expr_to_string(expr) |> indent(4)}
-          """,
-          empty_if(dot_var?(expr), """
 
           the given type does not have the given key:
 
               #{to_quoted_string(type) |> indent(4)}
-          """),
+          """,
           format_traces(traces)
         ])
     }
diff --git a/lib/elixir/test/elixir/module/types/expr_test.exs b/lib/elixir/test/elixir/module/types/expr_test.exs
index 6c4f4b62365..500393a4d16 100644
--- a/lib/elixir/test/elixir/module/types/expr_test.exs
+++ b/lib/elixir/test/elixir/module/types/expr_test.exs
@@ -798,9 +798,9 @@ defmodule Module.Types.ExprTest do
 
                  x.foo_bar
 
-             where "x" was given the type:
+             the given type does not have the given key:
 
-                 # type: dynamic(%URI{
+                 dynamic(%URI{
                    authority: term(),
                    fragment: term(),
                    host: term(),
@@ -810,6 +810,10 @@ defmodule Module.Types.ExprTest do
                    scheme: term(),
                    userinfo: term()
                  })
+
+             where "x" was given the type:
+
+                 # type: dynamic(%URI{})
                  # from: types_test.ex:LINE-4:43
                  x = %URI{}
              """
@@ -903,7 +907,7 @@ defmodule Module.Types.ExprTest do
 
                 given types:
 
-                    dynamic(:foo) <= dynamic(%Point{x: term(), y: term(), z: term()})
+                    dynamic(:foo) <= dynamic(%Point{})
 
                 where "mod" was given the type:
 
@@ -919,7 +923,7 @@ defmodule Module.Types.ExprTest do
 
                 where "y" was given the type:
 
-                    # type: dynamic(%Point{x: term(), y: term(), z: term()})
+                    # type: dynamic(%Point{})
                     # from: types_test.ex:LINE-2
                     y = %Point{}
 
diff --git a/lib/elixir/test/elixir/module/types/pattern_test.exs b/lib/elixir/test/elixir/module/types/pattern_test.exs
index 490c725a4b8..b6e789e5422 100644
--- a/lib/elixir/test/elixir/module/types/pattern_test.exs
+++ b/lib/elixir/test/elixir/module/types/pattern_test.exs
@@ -166,9 +166,13 @@ defmodule Module.Types.PatternTest do
 
                    x.foo_bar
 
+               the given type does not have the given key:
+
+                   dynamic(%Point{x: term(), y: term(), z: term()})
+
                where "x" was given the type:
 
-                   # type: dynamic(%Point{x: term(), y: term(), z: term()})
+                   # type: dynamic(%Point{})
                    # from: types_test.ex:LINE-1
                    x = %Point{}
                """

From 9df42f5a0a37926a2de98ed79bbcfa6cd81c6fa8 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jos=C3=A9=20Valim?= <jose.valim@dashbit.co>
Date: Thu, 19 Dec 2024 17:26:59 +0100
Subject: [PATCH 038/139] Update CHANGELOG

---
 CHANGELOG.md | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 153cef2eeb9..b469bc2753a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -293,6 +293,7 @@ You may also prefer to write using guards:
 
 #### IEx
 
+  * [IEx] Add `IEx.configure(auto_reload: true)` to automatically pick up modules recompiled from other operating system processes
   * [IEx] Add `:dot_iex` support to `IEx.configure/1`
   * [IEx] Add report for normal/shutdown exits in IEx
 
@@ -366,6 +367,8 @@ You may also prefer to write using guards:
 #### Mix
 
   * [mix cmd] Deprecate `mix cmd --app APP` in favor of `mix do --app APP`
+  * [mix compile] `:warnings_as_errors` configuration in `:elixirc_options` is deprecated. Instead pass the `--warnings-as-errors` flag to `mix compile`. Alternatively, you might alias the task: `aliases: [compile: "compile --warnings-as-errors"]`
+  * [mix test] `:warnings_as_errors` configuration in `:test_elixirc_options` is deprecated. Instead pass the `--warnings-as-errors` flag to `mix test`. Alternatively, you might alias the task: `aliases: [test: "test --warnings-as-errors"]`
   * [Mix.Tasks.Compile] Deprecate `compilers/0` in favor of `Mix.Task.Compiler.compilers/0`
 
 ## v1.17

From 3564c68cdced43fa615acfef9057da447ee15476 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jos=C3=A9=20Valim?= <jose.valim@dashbit.co>
Date: Thu, 19 Dec 2024 18:30:20 +0100
Subject: [PATCH 039/139] Release v1.18.0

---
 CHANGELOG.md   | 31 ++++---------------------------
 VERSION        |  2 +-
 bin/elixir     |  2 +-
 bin/elixir.bat |  2 +-
 bin/elixir.ps1 |  2 +-
 5 files changed, 8 insertions(+), 31 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index b469bc2753a..e8ccdce5a07 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -225,33 +225,7 @@ You may also prefer to write using guards:
 
     def foo(x, y, z) when x == y and y == z
 
-## v1.18.0-rc.1
-
-### 1. Enhancements
-
-#### Elixir
-
-  * [JSON] Implement `JSON.Encoder` for all Calendar types
-
-### 2. Bug fixes
-
-#### Elixir
-
-  * [Kernel] Avoid crashes when emitting diagnostics on code using \t for indentation
-
-### 3. Regressions
-
-#### Elixir
-
-  * [Kernel] Fix type warnings in `pop_in/1`
-  * [Kernel] Fix false positive warnings when accessing exceptions from `rescue`
-  * [Kernel] Emit warnings for duplicate patterns instead of errors
-
-#### ExUnit
-
-  * [ExUnit] Do not warn on user-supplied CaseTemplate options
-
-## v1.18.0-rc.0 (2024-12-10)
+## v1.18.0 (2024-12-19)
 
 ### 1. Enhancements
 
@@ -266,6 +240,8 @@ You may also prefer to write using guards:
   * [Config] Add `Config.read_config/1`
   * [Enumerable] Add `Enum.product_by/2` and `Enum.sum_by/2`
   * [Exception] Add `MissingApplicationsError` exception to denote missing applications
+  * [JSON] Add a new `JSON` module with encoding and decoding functionality
+  * [JSON] Implement `JSON.Encoder` for all Calendar types
   * [Kernel] Update source code parsing to match [UTS #55](https://www.unicode.org/reports/tr55/) latest recommendations. In particular, mixed script is allowed in identifiers as long as they are separate by underscores (`_`), such as `http_сервер`. Previously allowed highly restrictive identifiers, which mixed Latin and other scripts, such as the japanese word for t-shirt, `Tシャツ`, now require the underscore as well
   * [Kernel] Warn on bidirectional confusability in identifiers
   * [Kernel] Verify the type of the binary generators
@@ -317,6 +293,7 @@ You may also prefer to write using guards:
   * [Code.Fragment] Properly handle keyword keys as their own entry
   * [Inspect.Algebra] Ensure `next_break_fits` respects `line_length`
   * [Kernel] Validate AST on `unquote` and `unquote_splicing` to provide better error reports instead of failing too late inside the compiler
+  * [Kernel] Avoid crashes when emitting diagnostics on code using \t for indentation
   * [Module] Include module attribute line and name when tracing its aliases
   * [Stream] Do not halt streams twice in `Stream.transform/5`
   * [URI] Fix a bug when a schemaless URI is given to `URI.merge/2`
diff --git a/VERSION b/VERSION
index ca117a054fb..84cc529467b 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-1.18.0-rc.0
+1.18.0
diff --git a/bin/elixir b/bin/elixir
index 23c07afe136..c937bea182c 100755
--- a/bin/elixir
+++ b/bin/elixir
@@ -1,7 +1,7 @@
 #!/bin/sh
 set -e
 
-ELIXIR_VERSION=1.18.0-rc.0
+ELIXIR_VERSION=1.18.0
 
 if [ $# -eq 0 ] || { [ $# -eq 1 ] && { [ "$1" = "--help" ] || [ "$1" = "-h" ]; }; }; then
   cat <<USAGE >&2
diff --git a/bin/elixir.bat b/bin/elixir.bat
index d4c66c8ab6e..09f3050ff4a 100644
--- a/bin/elixir.bat
+++ b/bin/elixir.bat
@@ -1,6 +1,6 @@
 @echo off
 
-set ELIXIR_VERSION=1.18.0-rc.0
+set ELIXIR_VERSION=1.18.0
 
 if    ""%1""==""""                if ""%2""=="""" goto documentation
 if /I ""%1""==""--help""          if ""%2""=="""" goto documentation
diff --git a/bin/elixir.ps1 b/bin/elixir.ps1
index b9b7e7f5a0d..4ef9cd0e1b7 100755
--- a/bin/elixir.ps1
+++ b/bin/elixir.ps1
@@ -1,6 +1,6 @@
 #!/usr/bin/env pwsh
 
-$ELIXIR_VERSION = "1.18.0-rc.0"
+$ELIXIR_VERSION = "1.18.0"
 
 $scriptPath = Split-Path -Parent $PSCommandPath
 $erlExec = "erl"

From 5d65d60721ea42628c2c0e9fb6fa377dab40e389 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jos=C3=A9=20Valim?= <jose.valim@dashbit.co>
Date: Thu, 19 Dec 2024 20:34:17 +0100
Subject: [PATCH 040/139] Comment out canonical and update release notes

---
 Makefile   | 2 +-
 RELEASE.md | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/Makefile b/Makefile
index bfda4058745..8d0270a5757 100644
--- a/Makefile
+++ b/Makefile
@@ -2,7 +2,7 @@ PREFIX ?= /usr/local
 TEST_FILES ?= "*_test.exs"
 SHARE_PREFIX ?= $(PREFIX)/share
 MAN_PREFIX ?= $(SHARE_PREFIX)/man
-CANONICAL := v1.18/
+# CANONICAL := main/
 ELIXIRC := bin/elixirc --ignore-module-conflict $(ELIXIRC_OPTS)
 ERLC := erlc -I lib/elixir/include
 ERL_MAKE := erl -make
diff --git a/RELEASE.md b/RELEASE.md
index dc769aec9f7..83aa5ffd958 100644
--- a/RELEASE.md
+++ b/RELEASE.md
@@ -30,7 +30,7 @@
 
 ### Back in main
 
-1. Bump /VERSION file, bin/elixir and bin/elixir.bat
+1. Bump /VERSION file, bin/elixir, bin/elixir.bat, and bin/elixir.ps1
 
 2. Start new /CHANGELOG.md
 

From e5033c94ce93649fbdbf0002d5084e317a360772 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jos=C3=A9=20Valim?= <jose.valim@dashbit.co>
Date: Fri, 20 Dec 2024 10:43:14 +0100
Subject: [PATCH 041/139] Keep traces backwards compatible

---
 lib/elixir/lib/module/types/helpers.ex | 16 ++++++++++------
 1 file changed, 10 insertions(+), 6 deletions(-)

diff --git a/lib/elixir/lib/module/types/helpers.ex b/lib/elixir/lib/module/types/helpers.ex
index d43b663b7d3..a76260e97c9 100644
--- a/lib/elixir/lib/module/types/helpers.ex
+++ b/lib/elixir/lib/module/types/helpers.ex
@@ -85,6 +85,9 @@ defmodule Module.Types.Helpers do
 
   @doc """
   Collect traces from variables in expression.
+
+  This information is exposed to language servers and
+  therefore must remain backwards compatible.
   """
   def collect_traces(expr, %{vars: vars}) do
     {_, versions} =
@@ -135,16 +138,17 @@ defmodule Module.Types.Helpers do
           formatter -> formatter.(expr)
         end
 
+      # This information is exposed to language servers and
+      # therefore must remain backwards compatible.
       %{
         file: file,
-        line: meta[:line],
-        column: meta[:column],
-        hints: formatter_hints ++ expr_hints(expr),
+        meta: meta,
         formatted_expr: formatted_expr,
+        formatted_hints: format_hints(formatter_hints ++ expr_hints(expr)),
         formatted_type: Module.Types.Descr.to_quoted_string(type, collapse_structs: true)
       }
     end)
-    |> Enum.sort_by(&{&1.line, &1.column})
+    |> Enum.sort_by(&{&1.meta[:line], &1.meta[:column]})
     |> Enum.dedup()
   end
 
@@ -161,7 +165,7 @@ defmodule Module.Types.Helpers do
         location =
           trace.file
           |> Path.relative_to_cwd()
-          |> Exception.format_file_line(trace.line, trace.column)
+          |> Exception.format_file_line(trace.meta[:line], trace.meta[:column])
           |> String.replace_suffix(":", "")
 
         [
@@ -173,7 +177,7 @@ defmodule Module.Types.Helpers do
           """,
           indent(trace.formatted_expr, 4),
           ?\n,
-          format_hints(trace.hints)
+          trace.formatted_hints
         ]
       end
 

From e4c86d4b5f91fe26f9dc255cb8cc1afcb86493f2 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jonatan=20M=C3=A4nnchen?= <jonatan@maennchen.ch>
Date: Fri, 20 Dec 2024 11:29:29 +0100
Subject: [PATCH 042/139] Hash release files after signing (#14085)

---
 .github/workflows/release.yml                 | 47 ++++++++++++-------
 .../workflows/release_pre_built/action.yml    |  6 ---
 2 files changed, 29 insertions(+), 24 deletions(-)

diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 23557c18b44..3d769bf1699 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -55,7 +55,6 @@ jobs:
           git push origin $ref_name --force
 
   build:
-    needs: create_draft_release
     strategy:
       fail-fast: true
       matrix:
@@ -80,6 +79,22 @@ jobs:
           otp: ${{ matrix.otp }}
           build_docs: ${{ matrix.build_docs }}
 
+      - name: "Sign files with Trusted Signing"
+        if: github.repository == 'elixir-lang/elixir'
+        uses: azure/trusted-signing-action@v0.5.0
+        with:
+          azure-tenant-id: ${{ secrets.AZURE_TENANT_ID }}
+          azure-client-id: ${{ secrets.AZURE_CLIENT_ID }}
+          azure-client-secret: ${{ secrets.AZURE_CLIENT_SECRET }}
+          endpoint: https://eus.codesigning.azure.net/
+          trusted-signing-account-name: trusted-signing-elixir
+          certificate-profile-name: Elixir
+          files-folder: ${{ github.workspace }}
+          files-folder-filter: exe
+          file-digest: SHA256
+          timestamp-rfc3161: http://timestamp.acs.microsoft.com
+          timestamp-digest: SHA256
+
       - name: "Attest release .exe provenance"
         uses: actions/attest-build-provenance@v2
         id: attest-exe-provenance
@@ -112,6 +127,18 @@ jobs:
         env:
           ATTESTATION: "${{ steps.attest-docs-provenance.outputs.bundle-path }}"
 
+      - name: Create Release Hashes
+        run: |
+          shasum -a 1   elixir-otp-${{ matrix.otp }}.zip > elixir-otp-${{ matrix.otp }}.zip.sha1sum
+          shasum -a 256 elixir-otp-${{ matrix.otp }}.zip > elixir-otp-${{ matrix.otp }}.zip.sha256sum
+          shasum -a 1   elixir-otp-${{ matrix.otp }}.exe > elixir-otp-${{ matrix.otp }}.exe.sha1sum
+          shasum -a 256 elixir-otp-${{ matrix.otp }}.exe > elixir-otp-${{ matrix.otp }}.exe.sha256sum
+      - name: Create Docs Hashes
+        if: ${{ matrix.build_docs }}
+        run: |
+          shasum -a 1   Docs.zip > Docs.zip.sha1sum
+          shasum -a 256 Docs.zip > Docs.zip.sha256sum
+
       - name: "Upload release artifacts"
         uses: actions/upload-artifact@v4
         with:
@@ -126,7 +153,7 @@ jobs:
           path: Docs.zip*
 
   upload-release:
-    needs: build
+    needs: [build, create_draft_release]
     runs-on: windows-2022
 
     steps:
@@ -137,22 +164,6 @@ jobs:
           mv Docs/* .
         shell: bash
 
-      - name: "Sign files with Trusted Signing"
-        if: github.repository == 'elixir-lang/elixir'
-        uses: azure/trusted-signing-action@v0.5.0
-        with:
-          azure-tenant-id: ${{ secrets.AZURE_TENANT_ID }}
-          azure-client-id: ${{ secrets.AZURE_CLIENT_ID }}
-          azure-client-secret: ${{ secrets.AZURE_CLIENT_SECRET }}
-          endpoint: https://eus.codesigning.azure.net/
-          trusted-signing-account-name: trusted-signing-elixir
-          certificate-profile-name: Elixir
-          files-folder: ${{ github.workspace }}
-          files-folder-filter: exe
-          file-digest: SHA256
-          timestamp-rfc3161: http://timestamp.acs.microsoft.com
-          timestamp-digest: SHA256
-
       - name: Upload Pre-built
         shell: bash
         env:
diff --git a/.github/workflows/release_pre_built/action.yml b/.github/workflows/release_pre_built/action.yml
index c7a12e0d1bf..3d6efd332af 100644
--- a/.github/workflows/release_pre_built/action.yml
+++ b/.github/workflows/release_pre_built/action.yml
@@ -19,8 +19,6 @@ runs:
       run: |
         make Precompiled.zip
         mv Precompiled.zip elixir-otp-${{ inputs.otp }}.zip
-        shasum -a 1 elixir-otp-${{ inputs.otp }}.zip > elixir-otp-${{ inputs.otp }}.zip.sha1sum
-        shasum -a 256 elixir-otp-${{ inputs.otp }}.zip > elixir-otp-${{ inputs.otp }}.zip.sha256sum
         echo "$PWD/bin" >> $GITHUB_PATH
     - name: Install NSIS
       shell: bash
@@ -34,8 +32,6 @@ runs:
         export ELIXIR_ZIP=$PWD/elixir-otp-${{ inputs.otp }}.zip
         (cd lib/elixir/scripts/windows_installer && ./build.sh)
         mv lib/elixir/scripts/windows_installer/tmp/elixir-otp-${{ inputs.otp }}.exe .
-        shasum -a 1   elixir-otp-${{ inputs.otp }}.exe > elixir-otp-${{ inputs.otp }}.exe.sha1sum
-        shasum -a 256 elixir-otp-${{ inputs.otp }}.exe > elixir-otp-${{ inputs.otp }}.exe.sha256sum
     - name: Get ExDoc ref
       if: ${{ inputs.build_docs }}
       shell: bash
@@ -66,5 +62,3 @@ runs:
       run: |
         git fetch --tags
         make Docs.zip
-        shasum -a 1 Docs.zip > Docs.zip.sha1sum
-        shasum -a 256 Docs.zip > Docs.zip.sha256sum

From 5aa049ccb38e943a63a16c1103d60947325637e3 Mon Sep 17 00:00:00 2001
From: Jean Klingler <sabiwara@gmail.com>
Date: Fri, 20 Dec 2024 22:27:11 +0900
Subject: [PATCH 043/139] Remove no_parens when using capture with arity
 (#14090)

---
 lib/elixir/src/elixir_fn.erl                     | 4 ++++
 lib/elixir/test/elixir/kernel/expansion_test.exs | 5 +++++
 lib/elixir/test/elixir/kernel/fn_test.exs        | 6 ++++++
 3 files changed, 15 insertions(+)

diff --git a/lib/elixir/src/elixir_fn.erl b/lib/elixir/src/elixir_fn.erl
index 668b201909d..fafdbd8207a 100644
--- a/lib/elixir/src/elixir_fn.erl
+++ b/lib/elixir/src/elixir_fn.erl
@@ -112,6 +112,10 @@ capture_expr(Meta, Expr, S, E, Escaped, Sequential) ->
   case escape(Expr, E, Escaped) of
     {_, []} when not Sequential ->
       invalid_capture(Meta, Expr, E);
+    {{{'.', _, [_, _]} = Dot, _, Args}, []} ->
+      Meta2 = lists:keydelete(no_parens, 1, Meta),
+      Fn = {fn, Meta2, [{'->', Meta2, [[], {Dot, Meta2, Args}]}]},
+      {expand, Fn, S, E};
     {EExpr, EDict} ->
       EVars = validate(Meta, EDict, 1, E),
       Fn = {fn, Meta, [{'->', Meta, [EVars, EExpr]}]},
diff --git a/lib/elixir/test/elixir/kernel/expansion_test.exs b/lib/elixir/test/elixir/kernel/expansion_test.exs
index 81b0a65a70b..4e08384a5b0 100644
--- a/lib/elixir/test/elixir/kernel/expansion_test.exs
+++ b/lib/elixir/test/elixir/kernel/expansion_test.exs
@@ -1203,6 +1203,11 @@ defmodule Kernel.ExpansionTest do
                 [{:->, [{:line, 1}], [[{:capture, [line: 1], nil}], {:capture, [line: 1], nil}]}]}
     end
 
+    test "removes no_parens when expanding 0-arity capture to fn" do
+      assert expand(quote(do: &foo().bar/0)) ==
+               quote(do: fn -> foo().bar() end)
+    end
+
     test "expands remotes" do
       assert expand(quote(do: &List.flatten/2)) ==
                quote(do: &:"Elixir.List".flatten/2)
diff --git a/lib/elixir/test/elixir/kernel/fn_test.exs b/lib/elixir/test/elixir/kernel/fn_test.exs
index 1304d753577..0d0e8471994 100644
--- a/lib/elixir/test/elixir/kernel/fn_test.exs
+++ b/lib/elixir/test/elixir/kernel/fn_test.exs
@@ -95,6 +95,12 @@ defmodule Kernel.FnTest do
     assert (&mod.flatten/1) == (&List.flatten/1)
   end
 
+  test "capture with module from local call" do
+    assert (&math_mod().pi/0).() == :math.pi()
+  end
+
+  defp math_mod, do: :math
+
   test "local partial application" do
     assert (&atb(&1, :utf8)).(:a) == "a"
     assert (&atb(List.to_atom(&1), :utf8)).(~c"a") == "a"

From 0fe6f68c3413e7932570fa0e226ae1f0d023c7a5 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jonatan=20K=C5=82osko?= <jonatanklosko@gmail.com>
Date: Sat, 21 Dec 2024 12:22:38 +0100
Subject: [PATCH 044/139] Fix mix escript.build when protocol consolidation is
 disabled (#14098)

---
 lib/mix/lib/mix/tasks/escript.build.ex  |  2 +-
 lib/mix/test/mix/tasks/escript_test.exs | 14 ++++++++++++++
 2 files changed, 15 insertions(+), 1 deletion(-)

diff --git a/lib/mix/lib/mix/tasks/escript.build.ex b/lib/mix/lib/mix/tasks/escript.build.ex
index 36fce7e6759..7a3cebfac0a 100644
--- a/lib/mix/lib/mix/tasks/escript.build.ex
+++ b/lib/mix/lib/mix/tasks/escript.build.ex
@@ -342,7 +342,7 @@ defmodule Mix.Tasks.Escript.Build do
         {zip_path, consolidated[Path.basename(path)] || path}
       end
     else
-      []
+      files
     end
   end
 
diff --git a/lib/mix/test/mix/tasks/escript_test.exs b/lib/mix/test/mix/tasks/escript_test.exs
index 5b284e9f08f..4e25a9b0b49 100644
--- a/lib/mix/test/mix/tasks/escript_test.exs
+++ b/lib/mix/test/mix/tasks/escript_test.exs
@@ -50,6 +50,20 @@ defmodule Mix.Tasks.EscriptTest do
     end)
   end
 
+  test "generate escript without protocol consolidation" do
+    in_fixture("escript_test", fn ->
+      push_project_with_config(Escript, consolidate_protocols: false)
+
+      Mix.Tasks.Escript.Build.run([])
+      assert_received {:mix_shell, :info, ["Generated escript escript_test with MIX_ENV=dev"]}
+      assert System.cmd("escript", ["escript_test"]) == {"TEST\n", 0}
+      assert count_abstract_code("escript_test") == 0
+
+      # Does not consolidate protocols
+      assert System.cmd("escript", ["escript_test", "--protocol", "Enumerable"]) == {"false\n", 0}
+    end)
+  end
+
   test "generate escript with --no-compile option" do
     in_fixture("escript_test", fn ->
       push_project_with_config(Escript)

From 4a5fb1eb6645dc46a95ecda4b0151e75957c78c1 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jos=C3=A9=20Valim?= <jose.valim@dashbit.co>
Date: Sat, 21 Dec 2024 18:24:32 +0100
Subject: [PATCH 045/139] Use binwrite only on Windows, closes #14101

---
 lib/mix/lib/mix/shell/io.ex | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/lib/mix/lib/mix/shell/io.ex b/lib/mix/lib/mix/shell/io.ex
index 44d1e11e312..ca558030a66 100644
--- a/lib/mix/lib/mix/shell/io.ex
+++ b/lib/mix/lib/mix/shell/io.ex
@@ -100,12 +100,12 @@ defmodule Mix.Shell.IO do
   """
   def cmd(command, opts \\ []) do
     print_app? = Keyword.get(opts, :print_app, true)
+    windows? = match?({:win32, _}, :os.type())
 
     Mix.Shell.cmd(command, opts, fn data ->
       if print_app?, do: print_app()
-      # Due to encoding of shell command on Windows,
-      # let's write the data as is
-      IO.binwrite(data)
+      # Due to encoding of shell command on Windows, write the data as is.
+      if windows?, do: IO.binwrite(data), else: IO.write(data)
     end)
   end
 end

From 4e3203f19a2072ad9d3dd48b5abc7d32307695f3 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jonatan=20M=C3=A4nnchen?= <jonatan@maennchen.ch>
Date: Sat, 21 Dec 2024 16:17:38 +0100
Subject: [PATCH 046/139] Fix Release Signing (#14099) (#14100)

---
 .github/workflows/release.yml | 138 +++++++++++++++++++++-------------
 1 file changed, 85 insertions(+), 53 deletions(-)

diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 3d769bf1699..e0f7f1947f4 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -79,42 +79,6 @@ jobs:
           otp: ${{ matrix.otp }}
           build_docs: ${{ matrix.build_docs }}
 
-      - name: "Sign files with Trusted Signing"
-        if: github.repository == 'elixir-lang/elixir'
-        uses: azure/trusted-signing-action@v0.5.0
-        with:
-          azure-tenant-id: ${{ secrets.AZURE_TENANT_ID }}
-          azure-client-id: ${{ secrets.AZURE_CLIENT_ID }}
-          azure-client-secret: ${{ secrets.AZURE_CLIENT_SECRET }}
-          endpoint: https://eus.codesigning.azure.net/
-          trusted-signing-account-name: trusted-signing-elixir
-          certificate-profile-name: Elixir
-          files-folder: ${{ github.workspace }}
-          files-folder-filter: exe
-          file-digest: SHA256
-          timestamp-rfc3161: http://timestamp.acs.microsoft.com
-          timestamp-digest: SHA256
-
-      - name: "Attest release .exe provenance"
-        uses: actions/attest-build-provenance@v2
-        id: attest-exe-provenance
-        with:
-          subject-path: "elixir-otp-${{ matrix.otp }}.exe"
-      - name: "Copy release .exe provenance"
-        run: cp "$ATTESTATION" elixir-otp-${{ matrix.otp }}.exe.sigstore
-        env:
-          ATTESTATION: "${{ steps.attest-exe-provenance.outputs.bundle-path }}"
-
-      - name: "Attest release .zip provenance"
-        uses: actions/attest-build-provenance@v2
-        id: attest-zip-provenance
-        with:
-          subject-path: "elixir-otp-${{ matrix.otp }}.zip"
-      - name: "Copy release .zip provenance"
-        run: cp "$ATTESTATION" elixir-otp-${{ matrix.otp }}.zip.sigstore
-        env:
-          ATTESTATION: "${{ steps.attest-zip-provenance.outputs.bundle-path }}"
-
       - name: "Attest docs provenance"
         uses: actions/attest-build-provenance@v2
         id: attest-docs-provenance
@@ -127,23 +91,23 @@ jobs:
         env:
           ATTESTATION: "${{ steps.attest-docs-provenance.outputs.bundle-path }}"
 
-      - name: Create Release Hashes
-        run: |
-          shasum -a 1   elixir-otp-${{ matrix.otp }}.zip > elixir-otp-${{ matrix.otp }}.zip.sha1sum
-          shasum -a 256 elixir-otp-${{ matrix.otp }}.zip > elixir-otp-${{ matrix.otp }}.zip.sha256sum
-          shasum -a 1   elixir-otp-${{ matrix.otp }}.exe > elixir-otp-${{ matrix.otp }}.exe.sha1sum
-          shasum -a 256 elixir-otp-${{ matrix.otp }}.exe > elixir-otp-${{ matrix.otp }}.exe.sha256sum
       - name: Create Docs Hashes
         if: ${{ matrix.build_docs }}
         run: |
           shasum -a 1   Docs.zip > Docs.zip.sha1sum
           shasum -a 256 Docs.zip > Docs.zip.sha256sum
 
-      - name: "Upload release artifacts"
+      - name: "Upload linux release artifacts"
+        uses: actions/upload-artifact@v4
+        with:
+          name: build-linux-elixir-otp-${{ matrix.otp }}
+          path: elixir-otp-${{ matrix.otp }}.zip
+
+      - name: "Upload windows release artifacts"
         uses: actions/upload-artifact@v4
         with:
-          name: elixir-otp-${{ matrix.otp }}
-          path: elixir-otp-${{ matrix.otp }}*
+          name: build-windows-elixir-otp-${{ matrix.otp }}
+          path: elixir-otp-${{ matrix.otp }}.exe
 
       - name: "Upload doc artifacts"
         uses: actions/upload-artifact@v4
@@ -151,18 +115,84 @@ jobs:
         with:
           name: Docs
           path: Docs.zip*
+  
+  sign:
+    needs: [build]
+    strategy:
+      fail-fast: true
+      matrix:
+        otp: [26, 27]
+        flavor: [windows, linux]
 
-  upload-release:
-    needs: [build, create_draft_release]
-    runs-on: windows-2022
+    env:
+      RELEASE_FILE: elixir-otp-${{ matrix.otp }}.${{ matrix.flavor == 'linux' && 'zip' || 'exe' }}
+
+    runs-on: ${{ matrix.flavor == 'linux' && 'ubuntu-22.04' || 'windows-2022' }}
 
     steps:
       - uses: actions/download-artifact@v4
+        with:
+          name: build-${{ matrix.flavor }}-elixir-otp-${{ matrix.otp }}
 
-      - run: |
-          mv elixir-otp-*/* .
-          mv Docs/* .
+      - name: "Sign files with Trusted Signing"
+        if: github.repository == 'elixir-lang/elixir' && matrix.flavor == 'windows'
+        uses: azure/trusted-signing-action@v0.5.0
+        with:
+          azure-tenant-id: ${{ secrets.AZURE_TENANT_ID }}
+          azure-client-id: ${{ secrets.AZURE_CLIENT_ID }}
+          azure-client-secret: ${{ secrets.AZURE_CLIENT_SECRET }}
+          endpoint: https://eus.codesigning.azure.net/
+          trusted-signing-account-name: trusted-signing-elixir
+          certificate-profile-name: Elixir
+          files-folder: ${{ github.workspace }}
+          files-folder-filter: exe
+          file-digest: SHA256
+          timestamp-rfc3161: http://timestamp.acs.microsoft.com
+          timestamp-digest: SHA256
+
+      - name: "Attest release provenance"
+        uses: actions/attest-build-provenance@v2
+        id: attest-provenance
+        with:
+          subject-path: ${{ env.RELEASE_FILE }}
+      - name: "Copy release .zip provenance"
         shell: bash
+        run: cp "$ATTESTATION" "${RELEASE_FILE}.sigstore"
+        env:
+          ATTESTATION: "${{ steps.attest-provenance.outputs.bundle-path }}"
+      
+      - name: Create Release Hashes
+        if: matrix.flavor == 'windows'
+        shell: pwsh
+        run: |
+          $sha1 = Get-FileHash "$env:RELEASE_FILE" -Algorithm SHA1
+          $sha1.Hash.ToLower() + "  " + $env:RELEASE_FILE | Out-File "$env:RELEASE_FILE.sha1sum"
+          
+          $sha256 = Get-FileHash "$env:RELEASE_FILE" -Algorithm SHA256
+          $sha256.Hash.ToLower() + "  " + $env:RELEASE_FILE | Out-File "$env:RELEASE_FILE.sha256sum"
+
+      - name: Create Release Hashes
+        if: matrix.flavor == 'linux'
+        shell: bash
+        run: |
+          shasum -a 1   "$RELEASE_FILE" > "${RELEASE_FILE}.sha1sum"
+          shasum -a 256 "$RELEASE_FILE" > "${RELEASE_FILE}.sha256sum"
+
+      - name: "Upload linux release artifacts"
+        uses: actions/upload-artifact@v4
+        with:
+          name: sign-${{ matrix.flavor }}-elixir-otp-${{ matrix.otp }}
+          path: ${{ env.RELEASE_FILE }}*
+
+  upload-release:
+    needs: [create_draft_release, build, sign]
+    runs-on: ubuntu-22.04
+
+    steps:
+      - uses: actions/download-artifact@v4
+        with:
+          pattern: '{sign-*-elixir-otp-*,Docs}'
+          merge-multiple: true
 
       - name: Upload Pre-built
         shell: bash
@@ -190,7 +220,7 @@ jobs:
             Docs.zip.sigstore
 
   upload-builds-hex-pm:
-    needs: build
+    needs: [build, sign]
     runs-on: ubuntu-22.04
     concurrency: builds-hex-pm
     env:
@@ -204,6 +234,9 @@ jobs:
       OTP_GENERIC_VERSION: "25"
     steps:
       - uses: actions/download-artifact@v4
+        with:
+          pattern: '{sign-*-elixir-otp-*,Docs}'
+          merge-multiple: true
 
       - name: Init purge keys file
         run: |
@@ -213,7 +246,6 @@ jobs:
         run: |
           ref_name=${{ github.ref_name }}
 
-          mv elixir-otp-*/* .
           for zip in $(find . -type f -name 'elixir-otp-*.zip' | sed 's/^\.\///'); do
             dest=${zip/elixir/${ref_name}}
             surrogate_key=${dest/.zip$/}

From d4e6a558cbc5101bef317cd6bf38a8937b723259 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jonatan=20M=C3=A4nnchen?= <jonatan@maennchen.ch>
Date: Sat, 21 Dec 2024 17:59:53 +0100
Subject: [PATCH 047/139] Fix Release Signing Again (#14099) (#14102)

This time, fix the Docs publishing.
---
 .github/workflows/release.yml | 1 -
 1 file changed, 1 deletion(-)

diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index e0f7f1947f4..9a28d67400d 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -264,7 +264,6 @@ jobs:
           done
 
       - name: Upload Docs to S3
-        working-directory: Docs
         run: |
           version=$(echo ${{ github.ref_name }} | sed -e 's/^v//g')
 

From a1be4fbc8623e56a75b599543dfb0e79e410fb53 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jos=C3=A9=20Valim?= <jose.valim@dashbit.co>
Date: Sat, 21 Dec 2024 21:29:04 +0100
Subject: [PATCH 048/139] Verify no warnings are emitted on generated clauses,
 see #14094

---
 .../elixir/module/types/integration_test.exs  | 25 +++++++++++++++++++
 1 file changed, 25 insertions(+)

diff --git a/lib/elixir/test/elixir/module/types/integration_test.exs b/lib/elixir/test/elixir/module/types/integration_test.exs
index 3208cd2cbd0..c97dc32df6b 100644
--- a/lib/elixir/test/elixir/module/types/integration_test.exs
+++ b/lib/elixir/test/elixir/module/types/integration_test.exs
@@ -195,6 +195,31 @@ defmodule Module.Types.IntegrationTest do
 
       assert_warnings(files, warnings)
     end
+
+    test "unused generated overridable private clauses" do
+      files = %{
+        "a.ex" => """
+        defmodule A do
+          use B
+          def public(x), do: private(x)
+          defp private(x), do: super(List.to_tuple(x))
+        end
+        """,
+        "b.ex" => """
+        defmodule B do
+          defmacro __using__(_) do
+            quote generated: true do
+              defp private({:ok, ok}), do: ok
+              defp private(:error), do: :error
+              defoverridable private: 1
+            end
+          end
+        end
+        """
+      }
+
+      assert_no_warnings(files)
+    end
   end
 
   describe "undefined warnings" do

From 4adaac702e65dd638f7585c30579298270778495 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jos=C3=A9=20Valim?= <jose.valim@dashbit.co>
Date: Sun, 22 Dec 2024 09:54:57 +0100
Subject: [PATCH 049/139] Do not validate clauses of overridable functions,
 closes #14094

---
 lib/elixir/lib/module/types.ex                |  7 +++--
 lib/elixir/src/elixir_overridable.erl         |  3 ++-
 .../elixir/module/types/integration_test.exs  | 27 +++++++++++++++++--
 3 files changed, 32 insertions(+), 5 deletions(-)

diff --git a/lib/elixir/lib/module/types.ex b/lib/elixir/lib/module/types.ex
index efead6b04ba..aa704982096 100644
--- a/lib/elixir/lib/module/types.ex
+++ b/lib/elixir/lib/module/types.ex
@@ -182,9 +182,12 @@ defmodule Module.Types do
   end
 
   defp warn_unused_clauses(defs, stack, context) do
-    for {fun_arity, pending} <- context.local_used, pending != [], reduce: context do
+    for {fun_arity, pending} <- context.local_used,
+        pending != [],
+        {_fun_arity, kind, meta, clauses} = List.keyfind(defs, fun_arity, 0),
+        not Keyword.get(meta, :from_super, false),
+        reduce: context do
       context ->
-        {_fun_arity, kind, _meta, clauses} = List.keyfind(defs, fun_arity, 0)
         {_kind, _inferred, mapping} = Map.fetch!(context.local_sigs, fun_arity)
 
         clauses_indexes =
diff --git a/lib/elixir/src/elixir_overridable.erl b/lib/elixir/src/elixir_overridable.erl
index c08b7ec37a2..6defa05f8b6 100644
--- a/lib/elixir/src/elixir_overridable.erl
+++ b/lib/elixir/src/elixir_overridable.erl
@@ -75,8 +75,9 @@ store_not_overridden(Module) ->
 %% Private
 
 store(Set, Module, Tuple, {_, Count, Def, Overridden}, Hidden) ->
-  {{{def, {Name, Arity}}, Kind, Meta, File, _Check,
+  {{{def, {Name, Arity}}, Kind, BaseMeta, File, _Check,
    {Defaults, _HasBody, _LastDefaults}}, Clauses} = Def,
+  Meta = [{from_super, Hidden} | BaseMeta],
 
   {FinalKind, FinalName, FinalArity, FinalClauses} =
     case Hidden of
diff --git a/lib/elixir/test/elixir/module/types/integration_test.exs b/lib/elixir/test/elixir/module/types/integration_test.exs
index c97dc32df6b..31affb838d4 100644
--- a/lib/elixir/test/elixir/module/types/integration_test.exs
+++ b/lib/elixir/test/elixir/module/types/integration_test.exs
@@ -196,7 +196,30 @@ defmodule Module.Types.IntegrationTest do
       assert_warnings(files, warnings)
     end
 
-    test "unused generated overridable private clauses" do
+    test "unused generated private clauses" do
+      files = %{
+        "a.ex" => """
+        defmodule A do
+          use B
+          def public(x), do: private(List.to_tuple(x))
+        end
+        """,
+        "b.ex" => """
+        defmodule B do
+          defmacro __using__(_) do
+            quote generated: true do
+              defp private({:ok, ok}), do: ok
+              defp private(:error), do: :error
+            end
+          end
+        end
+        """
+      }
+
+      assert_no_warnings(files)
+    end
+
+    test "unused overridable private clauses" do
       files = %{
         "a.ex" => """
         defmodule A do
@@ -208,7 +231,7 @@ defmodule Module.Types.IntegrationTest do
         "b.ex" => """
         defmodule B do
           defmacro __using__(_) do
-            quote generated: true do
+            quote do
               defp private({:ok, ok}), do: ok
               defp private(:error), do: :error
               defoverridable private: 1

From 1a2be16109f856120fe2a4c7c8120d6a01a7447b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jos=C3=A9=20Valim?= <jose.valim@dashbit.co>
Date: Sun, 22 Dec 2024 11:02:14 +0100
Subject: [PATCH 050/139] Track source for warnings, closes #14093

---
 lib/elixir/lib/module/parallel_checker.ex     | 12 ++--
 lib/elixir/src/elixir_errors.erl              |  5 +-
 .../elixir/module/types/integration_test.exs  | 64 ++++++++++++++++---
 .../test/mix/tasks/compile.elixir_test.exs    |  7 +-
 4 files changed, 70 insertions(+), 18 deletions(-)

diff --git a/lib/elixir/lib/module/parallel_checker.ex b/lib/elixir/lib/module/parallel_checker.ex
index 42a0d61fadc..5df93d783bf 100644
--- a/lib/elixir/lib/module/parallel_checker.ex
+++ b/lib/elixir/lib/module/parallel_checker.ex
@@ -239,7 +239,7 @@ defmodule Module.ParallelChecker do
       |> Module.Types.warnings(file, definitions, no_warn_undefined, cache)
       |> Kernel.++(behaviour_warnings)
       |> group_warnings()
-      |> emit_warnings(log?)
+      |> emit_warnings(file, log?)
 
     Enum.each(after_verify, fn {verify_mod, verify_fun} ->
       apply(verify_mod, verify_fun, [module])
@@ -320,9 +320,9 @@ defmodule Module.ParallelChecker do
     Enum.sort(ungrouped ++ grouped)
   end
 
-  defp emit_warnings(warnings, log?) do
+  defp emit_warnings(warnings, file, log?) do
     Enum.flat_map(warnings, fn {locations, diagnostic} ->
-      diagnostics = Enum.map(locations, &to_diagnostic(diagnostic, &1))
+      diagnostics = Enum.map(locations, &to_diagnostic(diagnostic, file, &1))
       log? and print_diagnostics(diagnostics)
       diagnostics
     end)
@@ -336,10 +336,12 @@ defmodule Module.ParallelChecker do
     :elixir_errors.print_diagnostics(diagnostics)
   end
 
-  defp to_diagnostic(diagnostic, {file, position, mfa}) when is_list(position) do
+  defp to_diagnostic(diagnostic, source, {file, position, mfa}) when is_list(position) do
+    file = Path.absname(file)
+
     %{
       severity: :warning,
-      source: file,
+      source: source,
       file: file,
       position: position_to_tuple(position),
       stacktrace: [to_stacktrace(file, position, mfa)],
diff --git a/lib/elixir/src/elixir_errors.erl b/lib/elixir/src/elixir_errors.erl
index 0f47189050e..c8943cbe6af 100644
--- a/lib/elixir/src/elixir_errors.erl
+++ b/lib/elixir/src/elixir_errors.erl
@@ -92,7 +92,10 @@ emit_diagnostic(Severity, Position, File, Message, Stacktrace, Options) ->
 
   Diagnostic = #{
     severity => Severity,
-    source => File,
+    source => case get(elixir_compiler_file) of
+      undefined -> File;
+      CompilerFile -> CompilerFile
+    end,
     file => File,
     position => Position,
     message => unicode:characters_to_binary(Message),
diff --git a/lib/elixir/test/elixir/module/types/integration_test.exs b/lib/elixir/test/elixir/module/types/integration_test.exs
index 31affb838d4..943c18f7d8c 100644
--- a/lib/elixir/test/elixir/module/types/integration_test.exs
+++ b/lib/elixir/test/elixir/module/types/integration_test.exs
@@ -36,7 +36,7 @@ defmodule Module.Types.IntegrationTest do
         """
       }
 
-      modules = compile(files)
+      modules = compile_modules(files)
 
       assert [
                {{:c, 0}, %{}},
@@ -243,6 +243,37 @@ defmodule Module.Types.IntegrationTest do
 
       assert_no_warnings(files)
     end
+
+    test "returns diagnostics with source and file" do
+      files = %{
+        "a.ex" => """
+        defmodule A do
+          @file "generated.ex"
+          def fun(arg) do
+            :ok = List.to_tuple(arg)
+          end
+        end
+        """
+      }
+
+      {_modules, warnings} = with_compile_warnings(files)
+
+      assert [
+               %{
+                 message: "the following pattern will never match" <> _,
+                 file: file,
+                 source: source
+               }
+             ] = warnings.runtime_warnings
+
+      assert String.ends_with?(source, "a.ex")
+      assert Path.type(source) == :absolute
+      assert String.ends_with?(file, "generated.ex")
+      assert Path.type(file) == :absolute
+    after
+      :code.delete(A)
+      :code.purge(A)
+    end
   end
 
   describe "undefined warnings" do
@@ -980,26 +1011,39 @@ defmodule Module.Types.IntegrationTest do
   defp capture_compile_warnings(files) do
     in_tmp(fn ->
       paths = generate_files(files)
-      capture_io(:stderr, fn -> compile_files(paths) end)
+      capture_io(:stderr, fn -> compile_to_path(paths) end)
+    end)
+  end
+
+  defp with_compile_warnings(files) do
+    in_tmp(fn ->
+      paths = generate_files(files)
+      with_io(:stderr, fn -> compile_to_path(paths) end) |> elem(0)
     end)
   end
 
-  defp compile(files) do
+  defp compile_modules(files) do
     in_tmp(fn ->
       paths = generate_files(files)
-      compile_files(paths)
+      {modules, _warnings} = compile_to_path(paths)
+
+      Map.new(modules, fn module ->
+        {^module, binary, _filename} = :code.get_object_code(module)
+        {module, binary}
+      end)
     end)
   end
 
-  defp compile_files(paths) do
-    {:ok, modules, _warnings} = Kernel.ParallelCompiler.compile_to_path(paths, ".")
+  defp compile_to_path(paths) do
+    {:ok, modules, warnings} =
+      Kernel.ParallelCompiler.compile_to_path(paths, ".", return_diagnostics: true)
 
-    Map.new(modules, fn module ->
-      {^module, binary, _filename} = :code.get_object_code(module)
+    for module <- modules do
       :code.delete(module)
       :code.purge(module)
-      {module, binary}
-    end)
+    end
+
+    {modules, warnings}
   end
 
   defp generate_files(files) do
diff --git a/lib/mix/test/mix/tasks/compile.elixir_test.exs b/lib/mix/test/mix/tasks/compile.elixir_test.exs
index 5d31f6e224b..8b130b74f02 100644
--- a/lib/mix/test/mix/tasks/compile.elixir_test.exs
+++ b/lib/mix/test/mix/tasks/compile.elixir_test.exs
@@ -1045,12 +1045,15 @@ defmodule Mix.Tasks.Compile.ElixirTest do
 
       # Compiles with missing external resources
       file = Path.absname("lib/a.eex")
+      source = Path.absname("lib/a.ex")
 
       assert capture_io(:stderr, fn ->
-               assert {:ok, [%Mix.Task.Compiler.Diagnostic{file: ^file, position: 13}]} =
+               assert {:ok,
+                       [%Mix.Task.Compiler.Diagnostic{source: ^source, file: ^file, position: 13}]} =
                         Mix.Tasks.Compile.Elixir.run([])
 
-               assert {:noop, [%Mix.Task.Compiler.Diagnostic{file: ^file, position: 13}]} =
+               assert {:noop,
+                       [%Mix.Task.Compiler.Diagnostic{source: ^source, file: ^file, position: 13}]} =
                         Mix.Tasks.Compile.Elixir.run(["--all-warnings"])
              end) =~ "oops"
 

From 8c9f303e370607950fdee8039acddb024ffdd448 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jos=C3=A9=20Valim?= <jose.valim@dashbit.co>
Date: Sun, 22 Dec 2024 17:56:03 +0100
Subject: [PATCH 051/139] Avoid crash when typing violation is detected on
 dynamic dispatch

Closes #14105.
---
 lib/elixir/lib/module/types/apply.ex          | 106 ++++++++++++------
 .../test/elixir/module/types/expr_test.exs    |  39 +++++++
 2 files changed, 112 insertions(+), 33 deletions(-)

diff --git a/lib/elixir/lib/module/types/apply.ex b/lib/elixir/lib/module/types/apply.ex
index 49f98295262..2c315b88cb9 100644
--- a/lib/elixir/lib/module/types/apply.ex
+++ b/lib/elixir/lib/module/types/apply.ex
@@ -269,7 +269,7 @@ defmodule Module.Types.Apply do
 
     case signature(name, arity) do
       :none -> {dynamic(), context}
-      info -> apply_remote(info, args_types, expr, stack, context)
+      info -> apply_remote(nil, name, info, args_types, expr, stack, context)
     end
   end
 
@@ -287,10 +287,14 @@ defmodule Module.Types.Apply do
         {value_type, context}
 
       :badtuple ->
-        {error_type(), badremote_error(expr, [integer(), tuple], stack, context)}
+        {error_type(),
+         badremote_error(:erlang, :element, expr, [integer(), tuple], stack, context)}
 
-      reason ->
-        {error_type(), error({reason, expr, tuple, index - 1, context}, meta, stack, context)}
+      :badindex ->
+        mfac = mfac(expr, :erlang, :element, 2)
+
+        {error_type(),
+         error({:badindex, mfac, expr, tuple, index - 1, context}, meta, stack, context)}
     end
   end
 
@@ -308,10 +312,16 @@ defmodule Module.Types.Apply do
         {value_type, context}
 
       :badtuple ->
-        {error_type(), badremote_error(expr, [integer(), tuple, value], stack, context)}
+        args_types = [integer(), tuple, value]
+
+        {error_type(),
+         badremote_error(:erlang, :insert_element, expr, args_types, stack, context)}
 
-      reason ->
-        {error_type(), error({reason, expr, tuple, index - 2, context}, meta, stack, context)}
+      :badindex ->
+        mfac = mfac(expr, :erlang, :insert_element, 3)
+
+        {error_type(),
+         error({:badindex, mfac, expr, tuple, index - 2, context}, meta, stack, context)}
     end
   end
 
@@ -322,10 +332,16 @@ defmodule Module.Types.Apply do
         {value_type, context}
 
       :badtuple ->
-        {error_type(), badremote_error(expr, [integer(), tuple], stack, context)}
+        args_types = [integer(), tuple]
+
+        {error_type(),
+         badremote_error(:erlang, :delete_element, expr, args_types, stack, context)}
 
-      reason ->
-        {error_type(), error({reason, expr, tuple, index - 1, context}, meta, stack, context)}
+      :badindex ->
+        mfac = mfac(expr, :erlang, :delete_element, 2)
+
+        {error_type(),
+         error({:badindex, mfac, expr, tuple, index - 1, context}, meta, stack, context)}
     end
   end
 
@@ -340,7 +356,7 @@ defmodule Module.Types.Apply do
         {value_type, context}
 
       :badnonemptylist ->
-        {error_type(), badremote_error(expr, [list], stack, context)}
+        {error_type(), badremote_error(:erlang, :hd, expr, [list], stack, context)}
     end
   end
 
@@ -350,7 +366,7 @@ defmodule Module.Types.Apply do
         {value_type, context}
 
       :badnonemptylist ->
-        {error_type(), badremote_error(expr, [list], stack, context)}
+        {error_type(), badremote_error(:erlang, :tl, expr, [list], stack, context)}
     end
   end
 
@@ -405,30 +421,36 @@ defmodule Module.Types.Apply do
     {return(boolean(), args_types, stack), context}
   end
 
-  def remote(mod, name, args_types, expr, stack, context) do
+  def remote(mod, fun, args_types, expr, stack, context) do
     arity = length(args_types)
 
-    case :elixir_rewrite.inline(mod, name, arity) do
-      {mod, name} ->
-        remote(mod, name, args_types, expr, stack, context)
+    case :elixir_rewrite.inline(mod, fun, arity) do
+      {new_mod, new_fun} ->
+        expr = inline_meta(expr, mod, fun)
+        remote(new_mod, new_fun, args_types, expr, stack, context)
 
       false ->
-        {info, context} = signature(mod, name, arity, elem(expr, 1), stack, context)
-        apply_remote(info, args_types, expr, stack, context)
+        {info, context} = signature(mod, fun, arity, elem(expr, 1), stack, context)
+        apply_remote(mod, fun, info, args_types, expr, stack, context)
     end
   end
 
-  defp apply_remote(info, args_types, expr, stack, context) do
+  defp apply_remote(mod, fun, info, args_types, expr, stack, context) do
     case apply_signature(info, args_types, stack) do
       {:ok, _indexes, type} ->
         {type, context}
 
       {:error, domain, clauses} ->
-        error = {:badremote, expr, args_types, domain, clauses, context}
+        mfac = mfac(expr, mod, fun, length(args_types))
+        error = {:badremote, mfac, expr, args_types, domain, clauses, context}
         {error_type(), error(error, elem(expr, 1), stack, context)}
     end
   end
 
+  defp inline_meta({node, meta, args}, mod, fun) do
+    {node, [inline: {mod, fun}] ++ meta, args}
+  end
+
   @doc """
   Returns the type of a remote capture.
   """
@@ -718,22 +740,28 @@ defmodule Module.Types.Apply do
     error(__MODULE__, warning, meta, stack, context)
   end
 
-  defp badremote_error({{:., _, [mod, fun]}, meta, _} = expr, args_types, stack, context) do
-    {_type, domain, [{args, _} | _] = clauses} = signature(mod, fun, length(args_types))
-    error({:badremote, expr, args_types, domain || args, clauses, context}, meta, stack, context)
+  defp badremote_error(mod, fun, {_, meta, _} = expr, args_types, stack, context) do
+    arity = length(args_types)
+    mfac = mfac(expr, mod, fun, arity)
+    {_type, domain, [{args, _} | _] = clauses} = signature(mod, fun, arity)
+    domain = domain || args
+    tuple = {:badremote, mfac, expr, args_types, domain, clauses, context}
+    error(tuple, meta, stack, context)
   end
 
-  ## Diagnosstics
+  ## Diagnostics
 
-  def format_diagnostic({:badindex, expr, type, index, context}) do
+  def format_diagnostic({:badindex, mfac, expr, type, index, context}) do
     traces = collect_traces(expr, context)
+    {mod, fun, arity, _converter} = mfac
+    mfa = Exception.format_mfa(mod, fun, arity)
 
     %{
       details: %{typing_traces: traces},
       message:
         IO.iodata_to_binary([
           """
-          expected a tuple with at least #{pluralize(index + 1, "element", "elements")} in #{format_mfa(expr)}:
+          expected a tuple with at least #{pluralize(index + 1, "element", "elements")} in #{mfa}:
 
               #{expr_to_string(expr) |> indent(4)}
 
@@ -778,10 +806,9 @@ defmodule Module.Types.Apply do
     }
   end
 
-  def format_diagnostic({:badremote, expr, args_types, domain, clauses, context}) do
+  def format_diagnostic({:badremote, mfac, expr, args_types, domain, clauses, context}) do
     traces = collect_traces(expr, context)
-    {{:., _, [mod, fun]}, _, args} = expr
-    {mod, fun, args, converter} = :elixir_rewrite.erl_to_ex(mod, fun, args)
+    {mod, fun, arity, converter} = mfac
 
     explanation =
       empty_arg_reason(converter.(args_types)) ||
@@ -790,12 +817,15 @@ defmodule Module.Types.Apply do
         #{clauses_args_to_quoted_string(clauses, converter)}
         """
 
+    mfa_or_fa =
+      if mod, do: Exception.format_mfa(mod, fun, arity), else: "#{fun}/#{arity}"
+
     %{
       details: %{typing_traces: traces},
       message:
         IO.iodata_to_binary([
           """
-          incompatible types given to #{Exception.format_mfa(mod, fun, length(args))}:
+          incompatible types given to #{mfa_or_fa}:
 
               #{expr_to_string(expr) |> indent(4)}
 
@@ -932,9 +962,19 @@ defmodule Module.Types.Apply do
   defp pluralize(1, singular, _), do: "1 #{singular}"
   defp pluralize(i, _, plural), do: "#{i} #{plural}"
 
-  defp format_mfa({{:., _, [mod, fun]}, _, args}) do
-    {mod, fun, args, _} = :elixir_rewrite.erl_to_ex(mod, fun, args)
-    Exception.format_mfa(mod, fun, length(args))
+  defp mfac({_, [inline: {mod, fun}] ++ _, _}, _mod, _fun, arity) do
+    {mod, fun, arity, & &1}
+  end
+
+  defp mfac({{:., _, [mod, fun]}, _, args}, _mod, _fun, _arity)
+       when is_atom(mod) and is_atom(fun) do
+    {mod, fun, args, converter} = :elixir_rewrite.erl_to_ex(mod, fun, args)
+    {mod, fun, length(args), converter}
+  end
+
+  defp mfac(_, mod, fun, arity)
+       when is_atom(mod) and is_atom(fun) and is_integer(arity) do
+    {mod, fun, arity, & &1}
   end
 
   ## Algebra helpers
diff --git a/lib/elixir/test/elixir/module/types/expr_test.exs b/lib/elixir/test/elixir/module/types/expr_test.exs
index 500393a4d16..d2a18116ad0 100644
--- a/lib/elixir/test/elixir/module/types/expr_test.exs
+++ b/lib/elixir/test/elixir/module/types/expr_test.exs
@@ -243,6 +243,35 @@ defmodule Module.Types.ExprTest do
                """
     end
 
+    test "calling a function with invalid arguments on variables" do
+      assert typeerror!(
+               (
+                 x = List
+                 x.to_tuple(123)
+               )
+             )
+             |> strip_ansi() ==
+               ~l"""
+               incompatible types given to List.to_tuple/1:
+
+                   x.to_tuple(123)
+
+               given types:
+
+                   integer()
+
+               but expected one of:
+
+                   list(term())
+
+               where "x" was given the type:
+
+                   # type: List
+                   # from: types_test.ex:LINE-5
+                   x = List
+               """
+    end
+
     test "capture a function with non atoms" do
       assert typeerror!([<<x::integer>>], &x.foo_bar/2) ==
                ~l"""
@@ -1400,6 +1429,16 @@ defmodule Module.Types.ExprTest do
 
       assert typewarn!(:string.__info__(:functions)) ==
                {dynamic(), ":string.__info__/1 is undefined or private"}
+
+      assert typeerror!([x], x.__info__(:whatever)) |> strip_ansi() =~ """
+             incompatible types given to __info__/1:
+
+                 x.__info__(:whatever)
+
+             given types:
+
+                 :whatever
+             """
     end
 
     test "behaviour_info/1" do

From 712f24af0a13dd81b07d58ebd0408ce249c33ac7 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jos=C3=A9=20Valim?= <jose.valim@dashbit.co>
Date: Mon, 23 Dec 2024 10:31:43 +0100
Subject: [PATCH 052/139] Do not run async groups on load and support
 --repeat-until-failure (#14107)

---
 lib/ex_unit/lib/ex_unit/runner.ex |   4 +-
 lib/ex_unit/lib/ex_unit/server.ex | 109 ++++++++++++++----------------
 lib/ex_unit/test/ex_unit_test.exs |  18 +++++
 3 files changed, 72 insertions(+), 59 deletions(-)

diff --git a/lib/ex_unit/lib/ex_unit/runner.ex b/lib/ex_unit/lib/ex_unit/runner.ex
index 89996230cca..d94ee86f70e 100644
--- a/lib/ex_unit/lib/ex_unit/runner.ex
+++ b/lib/ex_unit/lib/ex_unit/runner.ex
@@ -125,7 +125,7 @@ defmodule ExUnit.Runner do
 
         # Run all sync modules directly
         for pair <- sync_modules do
-          running = spawn_modules(config, [[pair]], false, %{})
+          running = spawn_modules(config, [{nil, [pair]}], false, %{})
           running != %{} and wait_until_available(config, running)
         end
 
@@ -161,7 +161,7 @@ defmodule ExUnit.Runner do
     running
   end
 
-  defp spawn_modules(config, [[_ | _] = modules | groups], async?, running) do
+  defp spawn_modules(config, [{_group, [_ | _] = modules} | groups], async?, running) do
     if max_failures_reached?(config) do
       running
     else
diff --git a/lib/ex_unit/lib/ex_unit/server.ex b/lib/ex_unit/lib/ex_unit/server.ex
index 820e6c1ca0e..1088dee923b 100644
--- a/lib/ex_unit/lib/ex_unit/server.ex
+++ b/lib/ex_unit/lib/ex_unit/server.ex
@@ -57,9 +57,10 @@ defmodule ExUnit.Server do
     state = %{
       loaded: System.monotonic_time(),
       waiting: nil,
-      async_groups: %{},
+      groups: %{},
+      async_groups: [],
       async_modules: :queue.new(),
-      sync_modules: :queue.new()
+      sync_modules: []
     }
 
     {:ok, state}
@@ -72,31 +73,31 @@ defmodule ExUnit.Server do
 
   # Called once after all async modules have been sent and reverts the state.
   def handle_call(:take_sync_modules, _from, state) do
-    %{waiting: nil, loaded: :done, async_modules: async_modules} = state
-    0 = :queue.len(async_modules)
+    %{waiting: nil, loaded: :done, async_groups: []} = state
+    true = :queue.is_empty(state.async_modules)
 
-    {:reply, :queue.to_list(state.sync_modules),
-     %{state | sync_modules: :queue.new(), loaded: System.monotonic_time()}}
+    {:reply, state.sync_modules, %{state | sync_modules: [], loaded: System.monotonic_time()}}
   end
 
   # Called by the runner when --repeat-until-failure is used.
   def handle_call({:restore_modules, async_modules, sync_modules}, _from, state) do
-    {async_modules, async_groups} =
-      Enum.map_reduce(async_modules, %{}, fn
-        {nil, [module]}, {modules, groups} ->
-          {[{:module, module} | modules], groups}
+    {async_modules, async_groups, groups} =
+      Enum.reduce(async_modules, {[], [], []}, fn
+        {nil, [module]}, {async_modules, async_groups, groups} ->
+          {[module | async_modules], async_groups, groups}
 
-        {group, group_modules}, {modules, groups} ->
-          {[{:group, group} | modules], Map.put(groups, group, group_modules)}
+        {group, group_modules}, {async_modules, async_groups, groups} ->
+          {async_modules, [group | async_groups], [{group, group_modules} | groups]}
       end)
 
     {:reply, :ok,
      %{
        state
        | loaded: :done,
+         groups: Map.new(groups),
          async_groups: async_groups,
          async_modules: :queue.from_list(async_modules),
-         sync_modules: :queue.from_list(sync_modules)
+         sync_modules: sync_modules
      }}
   end
 
@@ -108,22 +109,24 @@ defmodule ExUnit.Server do
       when is_integer(loaded) do
     state =
       if uniq? do
-        async_groups =
-          Map.new(state.async_groups, fn {group, modules} ->
-            {group, Enum.uniq(modules)}
-          end)
-
+        groups = Map.new(state.groups, fn {group, modules} -> {group, Enum.uniq(modules)} end)
+        async_groups = state.async_groups |> Enum.uniq() |> Enum.reverse()
         async_modules = :queue.to_list(state.async_modules) |> Enum.uniq() |> :queue.from_list()
-        sync_modules = :queue.to_list(state.sync_modules) |> Enum.uniq() |> :queue.from_list()
+        sync_modules = state.sync_modules |> Enum.uniq() |> Enum.reverse()
 
         %{
           state
-          | async_groups: async_groups,
+          | groups: groups,
+            async_groups: async_groups,
             async_modules: async_modules,
             sync_modules: sync_modules
         }
       else
-        state
+        %{
+          state
+          | async_groups: Enum.reverse(state.async_groups),
+            sync_modules: Enum.reverse(state.sync_modules)
+        }
       end
 
     diff = System.convert_time_unit(System.monotonic_time() - loaded, :native, :microsecond)
@@ -132,9 +135,7 @@ defmodule ExUnit.Server do
 
   def handle_call({:add, false = _async, _group, names}, _from, %{loaded: loaded} = state)
       when is_integer(loaded) do
-    state =
-      update_in(state.sync_modules, &Enum.reduce(names, &1, fn name, q -> :queue.in(name, q) end))
-
+    state = update_in(state.sync_modules, &Enum.reverse(names, &1))
     {:reply, :ok, state}
   end
 
@@ -143,7 +144,7 @@ defmodule ExUnit.Server do
     state =
       update_in(
         state.async_modules,
-        &Enum.reduce(names, &1, fn name, q -> :queue.in({:module, name}, q) end)
+        &Enum.reduce(names, &1, fn name, q -> :queue.in(name, q) end)
       )
 
     {:reply, :ok, take_modules(state)}
@@ -151,17 +152,16 @@ defmodule ExUnit.Server do
 
   def handle_call({:add, true = _async, group, names}, _from, %{loaded: loaded} = state)
       when is_integer(loaded) do
-    {async_groups, async_modules} =
-      case state.async_groups do
-        %{^group => entries} = async_groups ->
-          {%{async_groups | group => names ++ entries}, state.async_modules}
+    {groups, async_groups} =
+      case state.groups do
+        %{^group => entries} = groups ->
+          {%{groups | group => Enum.reverse(names, entries)}, state.async_groups}
 
-        %{} = async_groups ->
-          {Map.put(async_groups, group, names), :queue.in({:group, group}, state.async_modules)}
+        %{} = groups ->
+          {Map.put(groups, group, names), [group | state.async_groups]}
       end
 
-    {:reply, :ok,
-     take_modules(%{state | async_groups: async_groups, async_modules: async_modules})}
+    {:reply, :ok, take_modules(%{state | groups: groups, async_groups: async_groups})}
   end
 
   def handle_call({:add, _async?, _group, _names}, _from, state) do
@@ -174,49 +174,44 @@ defmodule ExUnit.Server do
 
   defp take_modules(%{waiting: {from, count}} = state) do
     has_async_modules? = not :queue.is_empty(state.async_modules)
+    has_async_groups? = state.async_groups != []
 
     cond do
-      not has_async_modules? and state.loaded == :done ->
+      not has_async_modules? and not has_async_groups? and state.loaded == :done ->
         GenServer.reply(from, nil)
         %{state | waiting: nil}
 
-      not has_async_modules? ->
-        state
-
-      true ->
-        {async_modules, remaining_modules} = take_until(count, state.async_modules)
+      has_async_modules? ->
+        {reply, remaining_modules} = take_until(count, state.async_modules)
+        GenServer.reply(from, reply)
+        %{state | async_modules: remaining_modules, waiting: nil}
 
-        {async_modules, remaining_groups} =
-          Enum.map_reduce(async_modules, state.async_groups, fn
-            {:module, module}, async_groups ->
-              {[module], async_groups}
+      has_async_groups? ->
+        {groups, remaining_groups} = Enum.split(state.async_groups, count)
 
-            {:group, group}, async_groups ->
-              {group_modules, async_groups} = Map.pop!(async_groups, group)
-              {Enum.reverse(group_modules), async_groups}
+        {reply, groups} =
+          Enum.map_reduce(groups, state.groups, fn group, acc ->
+            {entries, acc} = Map.pop!(acc, group)
+            {{group, Enum.reverse(entries)}, acc}
           end)
 
-        GenServer.reply(from, async_modules)
+        GenServer.reply(from, reply)
+        %{state | groups: groups, async_groups: remaining_groups, waiting: nil}
 
-        %{
-          state
-          | async_groups: remaining_groups,
-            async_modules: remaining_modules,
-            waiting: nil
-        }
+      true ->
+        state
     end
   end
 
-  # :queue.split fails if the provided count is larger than the queue size;
-  # as we also want to return the values as a list later, we directly
-  # return {list, queue} instead of {queue, queue}
+  # :queue.split fails if the provided count is larger than the queue size.
+  # We also want to return the values as tuples of shape {group, [modules]}.
   defp take_until(n, queue), do: take_until(n, queue, [])
 
   defp take_until(0, queue, acc), do: {Enum.reverse(acc), queue}
 
   defp take_until(n, queue, acc) do
     case :queue.out(queue) do
-      {{:value, item}, queue} -> take_until(n - 1, queue, [item | acc])
+      {{:value, item}, queue} -> take_until(n - 1, queue, [{nil, [item]} | acc])
       {:empty, queue} -> {Enum.reverse(acc), queue}
     end
   end
diff --git a/lib/ex_unit/test/ex_unit_test.exs b/lib/ex_unit/test/ex_unit_test.exs
index 3db534d401e..152a9122f7b 100644
--- a/lib/ex_unit/test/ex_unit_test.exs
+++ b/lib/ex_unit/test/ex_unit_test.exs
@@ -997,6 +997,24 @@ defmodule ExUnitTest do
       assert length(runs) == 6
     end
 
+    test "repeats tests up to the configured number of times with groups" do
+      defmodule TestGroupedRepeatUntilFailureReached do
+        use ExUnit.Case, async: true, group: :example
+        test __ENV__.line, do: assert(true)
+      end
+
+      configure_and_reload_on_exit(repeat_until_failure: 5)
+
+      output =
+        capture_io(fn ->
+          assert ExUnit.run() == %{total: 1, failures: 0, skipped: 0, excluded: 0}
+        end)
+
+      runs = String.split(output, "Running ExUnit", trim: true)
+      # 6 runs in total, 5 repeats
+      assert length(runs) == 6
+    end
+
     test "stops on failure" do
       {:ok, pid} = Agent.start_link(fn -> 0 end)
       Process.register(pid, :ex_unit_repeat_until_failure_count)

From fae36c5e4928d87bed939dee4cc73e5ec1cb6084 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jos=C3=A9=20Valim?= <jose.valim@dashbit.co>
Date: Mon, 23 Dec 2024 10:49:54 +0100
Subject: [PATCH 053/139] Queue async groups until state is loaded

---
 lib/ex_unit/lib/ex_unit/server.ex | 15 ++++++---------
 1 file changed, 6 insertions(+), 9 deletions(-)

diff --git a/lib/ex_unit/lib/ex_unit/server.ex b/lib/ex_unit/lib/ex_unit/server.ex
index 1088dee923b..b991816c873 100644
--- a/lib/ex_unit/lib/ex_unit/server.ex
+++ b/lib/ex_unit/lib/ex_unit/server.ex
@@ -173,20 +173,13 @@ defmodule ExUnit.Server do
   end
 
   defp take_modules(%{waiting: {from, count}} = state) do
-    has_async_modules? = not :queue.is_empty(state.async_modules)
-    has_async_groups? = state.async_groups != []
-
     cond do
-      not has_async_modules? and not has_async_groups? and state.loaded == :done ->
-        GenServer.reply(from, nil)
-        %{state | waiting: nil}
-
-      has_async_modules? ->
+      not :queue.is_empty(state.async_modules) ->
         {reply, remaining_modules} = take_until(count, state.async_modules)
         GenServer.reply(from, reply)
         %{state | async_modules: remaining_modules, waiting: nil}
 
-      has_async_groups? ->
+      state.async_groups != [] and state.loaded == :done ->
         {groups, remaining_groups} = Enum.split(state.async_groups, count)
 
         {reply, groups} =
@@ -198,6 +191,10 @@ defmodule ExUnit.Server do
         GenServer.reply(from, reply)
         %{state | groups: groups, async_groups: remaining_groups, waiting: nil}
 
+      state.loaded == :done ->
+        GenServer.reply(from, nil)
+        %{state | waiting: nil}
+
       true ->
         state
     end

From 04378bd9a6d85d2699ec7ece051d56a0af66e4ea Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jonatan=20K=C5=82osko?= <jonatanklosko@gmail.com>
Date: Mon, 23 Dec 2024 10:55:52 +0100
Subject: [PATCH 054/139] Add build lock to deps.loadpaths (#14108)

---
 lib/mix/lib/mix/tasks/deps.loadpaths.ex | 51 +++++++++++++++++--------
 1 file changed, 35 insertions(+), 16 deletions(-)

diff --git a/lib/mix/lib/mix/tasks/deps.loadpaths.ex b/lib/mix/lib/mix/tasks/deps.loadpaths.ex
index 1f6566216a9..d7daa070738 100644
--- a/lib/mix/lib/mix/tasks/deps.loadpaths.ex
+++ b/lib/mix/lib/mix/tasks/deps.loadpaths.ex
@@ -48,6 +48,12 @@ defmodule Mix.Tasks.Deps.Loadpaths do
       Mix.Task.run("archive.check", args)
     end
 
+    config = Mix.Project.config()
+
+    if "--no-elixir-version-check" not in args do
+      check_elixir_version(config)
+    end
+
     all = Mix.Dep.load_and_cache()
 
     all =
@@ -57,14 +63,8 @@ defmodule Mix.Tasks.Deps.Loadpaths do
         all
       end
 
-    config = Mix.Project.config()
-
-    if "--no-elixir-version-check" not in args do
-      check_elixir_version(config)
-    end
-
     if "--no-deps-check" not in args do
-      deps_check(all, "--no-compile" in args)
+      deps_check(config, all, "--no-compile" in args)
     end
 
     Code.prepend_paths(Enum.flat_map(all, &Mix.Dep.load_paths/1), cache: true)
@@ -93,25 +93,38 @@ defmodule Mix.Tasks.Deps.Loadpaths do
     end
   end
 
+  defp deps_check(config, all, no_compile?) do
+    with {:compile, _to_compile} <- deps_check(all, no_compile?) do
+      # We need to compile, we first grab the lock, then, we check
+      # again and compile if still applicable
+      Mix.Project.with_build_lock(config, fn ->
+        all = reload_deps(all)
+
+        with {:compile, to_compile} <- deps_check(all, no_compile?) do
+          Mix.Tasks.Deps.Compile.compile(to_compile)
+
+          to_compile
+          |> reload_deps()
+          |> Enum.filter(&(not Mix.Dep.ok?(&1)))
+          |> show_not_ok!()
+        end
+      end)
+    end
+  end
+
   defp deps_check(all, no_compile?) do
     all = Enum.map(all, &check_lock/1)
-    {not_ok, compile} = partition(all, [], [])
+    {not_ok, to_compile} = partition(all, [], [])
 
     cond do
       not_ok != [] ->
         show_not_ok!(not_ok)
 
-      compile == [] or no_compile? ->
+      to_compile == [] or no_compile? ->
         :ok
 
       true ->
-        Mix.Tasks.Deps.Compile.compile(compile)
-
-        compile
-        |> Enum.map(& &1.app)
-        |> Mix.Dep.filter_by_name(Mix.Dep.load_and_cache())
-        |> Enum.filter(&(not Mix.Dep.ok?(&1)))
-        |> show_not_ok!()
+        {:compile, to_compile}
     end
   end
 
@@ -136,6 +149,12 @@ defmodule Mix.Tasks.Deps.Loadpaths do
     {Enum.reverse(not_ok), Enum.reverse(compile)}
   end
 
+  defp reload_deps(deps) do
+    deps
+    |> Enum.map(& &1.app)
+    |> Mix.Dep.filter_by_name(Mix.Dep.load_and_cache())
+  end
+
   # Those are compiled by umbrella.
   defp from_umbrella?(dep) do
     dep.opts[:from_umbrella]

From b7a5fd7b56764774b4a2c7d8a0e96221f3bc6b93 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jos=C3=A9=20Valim?= <jose.valim@dashbit.co>
Date: Mon, 23 Dec 2024 12:23:48 +0100
Subject: [PATCH 055/139] Improve container_cursor_to_quoted with trailing
 fragment, closes #14087

---
 lib/elixir/lib/code/fragment.ex               |  3 ---
 lib/elixir/test/elixir/code_fragment_test.exs | 25 ++++++++++++++++++-
 2 files changed, 24 insertions(+), 4 deletions(-)

diff --git a/lib/elixir/lib/code/fragment.ex b/lib/elixir/lib/code/fragment.ex
index 75a9cf325a3..74ab83f594d 100644
--- a/lib/elixir/lib/code/fragment.ex
+++ b/lib/elixir/lib/code/fragment.ex
@@ -1226,10 +1226,7 @@ defmodule Code.Fragment do
   defp maybe_missing_stab?([{:else, _} | _]), do: true
   defp maybe_missing_stab?([{:catch, _} | _]), do: true
   defp maybe_missing_stab?([{:rescue, _} | _]), do: true
-
   defp maybe_missing_stab?([{:stab_op, _, :->} | _]), do: false
-  defp maybe_missing_stab?([{:eol, _}, next | _]) when elem(next, 0) != :",", do: false
-
   defp maybe_missing_stab?([_ | tail]), do: maybe_missing_stab?(tail)
   defp maybe_missing_stab?([]), do: false
 end
diff --git a/lib/elixir/test/elixir/code_fragment_test.exs b/lib/elixir/test/elixir/code_fragment_test.exs
index a36f498e05f..2736937e810 100644
--- a/lib/elixir/test/elixir/code_fragment_test.exs
+++ b/lib/elixir/test/elixir/code_fragment_test.exs
@@ -1315,9 +1315,15 @@ defmodule CodeFragmentTest do
       assert cc2q!("(fn x ->", trailing_fragment: ":ok end)") ==
                s2q!("(fn x -> __cursor__() end)")
 
-      assert cc2q!("(fn x ->", trailing_fragment: ":ok end)") ==
+      assert cc2q!("(fn x ->", trailing_fragment: "\n:ok end)") ==
                s2q!("(fn x -> __cursor__() end)")
 
+      assert cc2q!("(fn x when ", trailing_fragment: "-> :ok end)") ==
+               s2q!("(fn x when __cursor__() -> :ok end)")
+
+      assert cc2q!("(fn x when ", trailing_fragment: "->\n:ok end)") ==
+               s2q!("(fn x when __cursor__() -> :ok end)")
+
       assert cc2q!("(fn") == s2q!("(__cursor__())")
       assert cc2q!("(fn x") == s2q!("(__cursor__())")
       assert cc2q!("(fn x,") == s2q!("(__cursor__())")
@@ -1327,6 +1333,23 @@ defmodule CodeFragmentTest do
       assert cc2q!("(fn x, y -> x + y end") == s2q!("(__cursor__())")
     end
 
+    test "do -> end" do
+      assert cc2q!("if do\nx ->\n", trailing_fragment: "y\nz ->\nw\nend") ==
+               s2q!("if do\nx ->\n__cursor__()\nend")
+
+      assert cc2q!("if do\nx ->\ny", trailing_fragment: "\nz ->\nw\nend") ==
+               s2q!("if do\nx ->\n__cursor__()\nend")
+
+      assert cc2q!("if do\nx ->\ny\n", trailing_fragment: "\nz ->\nw\nend") ==
+               s2q!("if do\nx ->\ny\n__cursor__()\nend")
+
+      assert cc2q!("for x <- [], reduce: %{} do\ny, ", trailing_fragment: "-> :ok\nend") ==
+               s2q!("for x <- [], reduce: %{} do\ny, __cursor__() -> :ok\nend")
+
+      assert cc2q!("for x <- [], reduce: %{} do\ny, z when ", trailing_fragment: "-> :ok\nend") ==
+               s2q!("for x <- [], reduce: %{} do\ny, z when __cursor__() -> :ok\nend")
+    end
+
     test "removes tokens until opening" do
       assert cc2q!("(123") == s2q!("(__cursor__())")
       assert cc2q!("[foo") == s2q!("[__cursor__()]")

From a3a632efd16ca0596e16c39619112f5f3ec18c38 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jos=C3=A9=20Valim?= <jose.valim@dashbit.co>
Date: Thu, 19 Dec 2024 17:25:14 +0100
Subject: [PATCH 056/139] Improve warning as errors deprecation notice

---
 lib/elixir/lib/code.ex | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/lib/elixir/lib/code.ex b/lib/elixir/lib/code.ex
index 1a4a400f06b..47ee408cd47 100644
--- a/lib/elixir/lib/code.ex
+++ b/lib/elixir/lib/code.ex
@@ -1709,7 +1709,9 @@ defmodule Code do
   def put_compiler_option(:warnings_as_errors, _value) do
     IO.warn(
       ":warnings_as_errors is deprecated as part of Code.put_compiler_option/2, " <>
-        "pass it as option to Kernel.ParallelCompiler instead"
+        "pass it as option to Kernel.ParallelCompiler instead or as a --warnings-as-errors flag. " <>
+        "If you need to set it as a default in a mix task, you can also set it under aliases: " <>
+        "[compile: \"compile --warnings-as-errors\"]"
     )
 
     :ok

From 463f1aa5937437d5ed7130bcfbb1b38449ef76e6 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jos=C3=A9=20Valim?= <jose.valim@dashbit.co>
Date: Mon, 23 Dec 2024 18:25:42 +0100
Subject: [PATCH 057/139] Do not recompile if compilation fails due to
 --warnings-as-errors

---
 lib/elixir/lib/code.ex                        |  2 +-
 lib/elixir/lib/kernel/parallel_compiler.ex    | 21 +------
 .../elixir/kernel/parallel_compiler_test.exs  | 62 -------------------
 lib/mix/lib/mix/compilers/elixir.ex           | 21 ++++---
 4 files changed, 15 insertions(+), 91 deletions(-)

diff --git a/lib/elixir/lib/code.ex b/lib/elixir/lib/code.ex
index 47ee408cd47..be6e4beaaf2 100644
--- a/lib/elixir/lib/code.ex
+++ b/lib/elixir/lib/code.ex
@@ -1709,7 +1709,7 @@ defmodule Code do
   def put_compiler_option(:warnings_as_errors, _value) do
     IO.warn(
       ":warnings_as_errors is deprecated as part of Code.put_compiler_option/2, " <>
-        "pass it as option to Kernel.ParallelCompiler instead or as a --warnings-as-errors flag. " <>
+        "instead you must pass it as a --warnings-as-errors flag. " <>
         "If you need to set it as a default in a mix task, you can also set it under aliases: " <>
         "[compile: \"compile --warnings-as-errors\"]"
     )
diff --git a/lib/elixir/lib/kernel/parallel_compiler.ex b/lib/elixir/lib/kernel/parallel_compiler.ex
index db5a969a7cb..23d79179f3c 100644
--- a/lib/elixir/lib/kernel/parallel_compiler.ex
+++ b/lib/elixir/lib/kernel/parallel_compiler.ex
@@ -257,28 +257,13 @@ defmodule Kernel.ParallelCompiler do
 
     {status, modules_or_errors, info} =
       try do
-        outcome = spawn_workers(schedulers, cache, files, output, options)
-        {outcome, Keyword.get(options, :warnings_as_errors, false)}
+        spawn_workers(schedulers, cache, files, output, options)
       else
-        {{:ok, _, %{runtime_warnings: r_warnings, compile_warnings: c_warnings} = info}, true}
-        when r_warnings != [] or c_warnings != [] ->
-          message =
-            "Compilation failed due to warnings while using the --warnings-as-errors option"
-
-          IO.puts(:stderr, message)
-          errors = Enum.map(r_warnings ++ c_warnings, &Map.replace!(&1, :severity, :error))
-          {:error, errors, %{info | runtime_warnings: [], compile_warnings: []}}
-
-        {{:ok, outcome, info}, _} ->
+        {:ok, outcome, info} ->
           beam_timestamp = Keyword.get(options, :beam_timestamp)
           {:ok, write_module_binaries(outcome, output, beam_timestamp), info}
 
-        {{:error, errors, info}, true} ->
-          %{runtime_warnings: r_warnings, compile_warnings: c_warnings} = info
-          info = %{info | runtime_warnings: [], compile_warnings: []}
-          {:error, c_warnings ++ r_warnings ++ errors, info}
-
-        {{:error, errors, info}, _} ->
+        {:error, errors, info} ->
           {:error, errors, info}
       after
         Module.ParallelChecker.stop(cache)
diff --git a/lib/elixir/test/elixir/kernel/parallel_compiler_test.exs b/lib/elixir/test/elixir/kernel/parallel_compiler_test.exs
index 95b5ae32b9e..6a0dc97a051 100644
--- a/lib/elixir/test/elixir/kernel/parallel_compiler_test.exs
+++ b/lib/elixir/test/elixir/kernel/parallel_compiler_test.exs
@@ -421,40 +421,6 @@ defmodule Kernel.ParallelCompilerTest do
       end)
     end
 
-    test "supports warnings as errors" do
-      [fixture] =
-        write_tmp(
-          "warnings_as_errors",
-          warnings_as_errors: """
-          defmodule WarningsSample do
-            def hello(a), do: a
-            def hello(b), do: b
-          end
-          """
-        )
-
-      output = tmp_path("not_to_be_used")
-
-      try do
-        msg =
-          capture_io(:stderr, fn ->
-            assert {:error, [error], []} =
-                     Kernel.ParallelCompiler.compile_to_path([fixture], output,
-                       warnings_as_errors: true
-                     )
-
-            assert {^fixture, {3, 7}, "this clause " <> _} = error
-          end)
-
-        assert msg =~
-                 "Compilation failed due to warnings while using the --warnings-as-errors option\n"
-      after
-        purge([WarningsSample])
-      end
-
-      refute File.exists?(output)
-    end
-
     test "does not use incorrect line number when error originates in another file" do
       File.mkdir_p!(tmp_path())
 
@@ -587,33 +553,5 @@ defmodule Kernel.ParallelCompilerTest do
                         "cannot compile module WithBehaviourAndStruct (errors have been logged)"
              end) =~ expected_msg
     end
-
-    test "supports warnings as errors" do
-      [fixture] =
-        write_tmp(
-          "warnings_as_errors",
-          warnings_as_errors: """
-          defmodule WarningsSample do
-            def hello(a), do: a
-            def hello(b), do: b
-          end
-          """
-        )
-
-      try do
-        msg =
-          capture_io(:stderr, fn ->
-            assert {:error, [error], []} =
-                     Kernel.ParallelCompiler.require([fixture], warnings_as_errors: true)
-
-            assert {^fixture, {3, 7}, "this clause " <> _} = error
-          end)
-
-        assert msg =~
-                 "Compilation failed due to warnings while using the --warnings-as-errors option\n"
-      after
-        purge([WarningsSample])
-      end
-    end
   end
 end
diff --git a/lib/mix/lib/mix/compilers/elixir.ex b/lib/mix/lib/mix/compilers/elixir.ex
index dcf04e4ed62..629bc73cd04 100644
--- a/lib/mix/lib/mix/compilers/elixir.ex
+++ b/lib/mix/lib/mix/compilers/elixir.ex
@@ -208,14 +208,18 @@ defmodule Mix.Compilers.Elixir do
           end
 
           Mix.Task.Compiler.notify_modules_compiled(lazy_modules_diff)
-
-          unless_previous_warnings_as_errors(previous_warnings, opts, {:ok, all_warnings})
+          unless_warnings_as_errors(opts, {:ok, all_warnings})
 
         {:error, errors, %{runtime_warnings: r_warnings, compile_warnings: c_warnings}, state} ->
+          {errors, warnings} =
+            if opts[:warnings_as_errors],
+              do: {errors ++ r_warnings ++ c_warnings, []},
+              else: {errors, r_warnings ++ c_warnings}
+
           # In case of errors, we show all previous warnings and all new ones.
           {_, _, sources, _, _, _} = state
           errors = Enum.map(errors, &diagnostic/1)
-          warnings = Enum.map(r_warnings ++ c_warnings, &diagnostic/1)
+          warnings = Enum.map(warnings, &diagnostic/1)
           all_warnings = Keyword.get(opts, :all_warnings, errors == [])
           {:error, previous_warnings(sources, all_warnings) ++ warnings ++ errors}
       after
@@ -254,8 +258,7 @@ defmodule Mix.Compilers.Elixir do
 
       all_warnings = Keyword.get(opts, :all_warnings, true)
       previous_warnings = previous_warnings(sources, all_warnings)
-
-      unless_previous_warnings_as_errors(previous_warnings, opts, {status, previous_warnings})
+      unless_warnings_as_errors(opts, {status, previous_warnings})
     end
   end
 
@@ -1012,8 +1015,8 @@ defmodule Mix.Compilers.Elixir do
     File.rm(manifest <> ".checkpoint")
   end
 
-  defp unless_previous_warnings_as_errors(previous_warnings, opts, {status, all_warnings}) do
-    if previous_warnings != [] and opts[:warnings_as_errors] do
+  defp unless_warnings_as_errors(opts, {status, all_warnings}) do
+    if all_warnings != [] and opts[:warnings_as_errors] do
       message = "Compilation failed due to warnings while using the --warnings-as-errors option"
       IO.puts(:stderr, message)
       {:error, all_warnings}
@@ -1049,7 +1052,6 @@ defmodule Mix.Compilers.Elixir do
     threshold = opts[:long_compilation_threshold] || 10
     profile = opts[:profile]
     verbose = opts[:verbose] || false
-    warnings_as_errors = opts[:warnings_as_errors] || false
 
     pid =
       spawn_link(fn ->
@@ -1071,8 +1073,7 @@ defmodule Mix.Compilers.Elixir do
           long_compilation_threshold: threshold,
           profile: profile,
           beam_timestamp: timestamp,
-          return_diagnostics: true,
-          warnings_as_errors: warnings_as_errors
+          return_diagnostics: true
         ]
 
         response = Kernel.ParallelCompiler.compile_to_path(stale, dest, compile_opts)

From 546a0db392a07b9363d440ebab33faf9e7352be8 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jos=C3=A9=20Valim?= <jose.valim@dashbit.co>
Date: Tue, 24 Dec 2024 09:41:34 +0100
Subject: [PATCH 058/139] Use division by zero to show exception

Closes #14111.
---
 lib/ex_unit/lib/ex_unit/assertions.ex | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/lib/ex_unit/lib/ex_unit/assertions.ex b/lib/ex_unit/lib/ex_unit/assertions.ex
index ba119f1057b..22b1a790e57 100644
--- a/lib/ex_unit/lib/ex_unit/assertions.ex
+++ b/lib/ex_unit/lib/ex_unit/assertions.ex
@@ -786,7 +786,7 @@ defmodule ExUnit.Assertions do
   ## Examples
 
       assert_raise ArithmeticError, "bad argument in arithmetic expression", fn ->
-        1 + "test"
+        1 / 0
       end
 
       assert_raise RuntimeError, ~r/^today's lucky number is 0\.\d+!$/, fn ->

From 4911916f626021953b2614bb9699998cffb25200 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jos=C3=A9=20Valim?= <jose.valim@dashbit.co>
Date: Tue, 24 Dec 2024 09:47:23 +0100
Subject: [PATCH 059/139] Do not warn when comparing literals

---
 lib/elixir/lib/kernel.ex                      |  8 +++---
 lib/elixir/lib/module/types/apply.ex          | 12 +++++++--
 lib/elixir/lib/module/types/expr.ex           |  5 ++--
 .../test/elixir/module/types/expr_test.exs    | 27 +++++++++++--------
 4 files changed, 33 insertions(+), 19 deletions(-)

diff --git a/lib/elixir/lib/kernel.ex b/lib/elixir/lib/kernel.ex
index 3bfc11f0e9d..3ceeab3835f 100644
--- a/lib/elixir/lib/kernel.ex
+++ b/lib/elixir/lib/kernel.ex
@@ -1972,7 +1972,7 @@ defmodule Kernel do
 
   defp build_boolean_check(operator, check, true_clause, false_clause) do
     annotate_case(
-      [optimize_boolean: true, type_check: :expr],
+      [optimize_boolean: true],
       quote do
         case unquote(check) do
           false -> unquote(false_clause)
@@ -2006,7 +2006,7 @@ defmodule Kernel do
     assert_no_match_or_guard_scope(__CALLER__.context, "!")
 
     annotate_case(
-      [optimize_boolean: true, type_check: :expr],
+      [optimize_boolean: true],
       quote do
         case unquote(value) do
           x when :"Elixir.Kernel".in(x, [false, nil]) -> false
@@ -2020,7 +2020,7 @@ defmodule Kernel do
     assert_no_match_or_guard_scope(__CALLER__.context, "!")
 
     annotate_case(
-      [optimize_boolean: true, type_check: :expr],
+      [optimize_boolean: true],
       quote do
         case unquote(value) do
           x when :"Elixir.Kernel".in(x, [false, nil]) -> true
@@ -3910,7 +3910,7 @@ defmodule Kernel do
 
   defp build_if(condition, do: do_clause, else: else_clause) do
     annotate_case(
-      [optimize_boolean: true, type_check: :expr],
+      [optimize_boolean: true],
       quote do
         case unquote(condition) do
           x when :"Elixir.Kernel".in(x, [false, nil]) -> unquote(else_clause)
diff --git a/lib/elixir/lib/module/types/apply.ex b/lib/elixir/lib/module/types/apply.ex
index 2c315b88cb9..ed510e5e634 100644
--- a/lib/elixir/lib/module/types/apply.ex
+++ b/lib/elixir/lib/module/types/apply.ex
@@ -400,11 +400,19 @@ defmodule Module.Types.Apply do
     end
   end
 
-  def remote(:erlang, name, [left, right] = args_types, expr, stack, context)
+  def remote(
+        :erlang,
+        name,
+        [left, right] = args_types,
+        {_, _, args} = expr,
+        stack,
+        context
+      )
       when name in [:==, :"/=", :"=:=", :"=/="] do
     context =
       cond do
-        stack.mode == :infer ->
+        # We ignore quoted literals as they most likely come from generated code.
+        stack.mode == :infer or Macro.quoted_literal?(args) ->
           context
 
         name in [:==, :"/="] and number_type?(left) and number_type?(right) ->
diff --git a/lib/elixir/lib/module/types/expr.ex b/lib/elixir/lib/module/types/expr.ex
index 27b925fe66d..22bddbbf5c1 100644
--- a/lib/elixir/lib/module/types/expr.ex
+++ b/lib/elixir/lib/module/types/expr.ex
@@ -263,8 +263,9 @@ defmodule Module.Types.Expr do
     {case_type, context} = of_expr(case_expr, stack, context)
 
     # If we are only type checking the expression and the expression is a literal,
-    # let's mark it as generated, as it is most likely a macro code.
-    if is_atom(case_expr) and {:type_check, :expr} in meta do
+    # let's mark it as generated, as it is most likely a macro code. However, if
+    # no clause is matched, we should still check for that.
+    if Macro.quoted_literal?(case_expr) do
       for {:->, meta, args} <- clauses, do: {:->, [generated: true] ++ meta, args}
     else
       clauses
diff --git a/lib/elixir/test/elixir/module/types/expr_test.exs b/lib/elixir/test/elixir/module/types/expr_test.exs
index d2a18116ad0..27d9e4ed26f 100644
--- a/lib/elixir/test/elixir/module/types/expr_test.exs
+++ b/lib/elixir/test/elixir/module/types/expr_test.exs
@@ -860,7 +860,11 @@ defmodule Module.Types.ExprTest do
     test "in dynamic mode" do
       assert typedyn!([x = 123, y = 456.0], x < y) == dynamic(boolean())
       assert typedyn!([x = 123, y = 456.0], x == y) == dynamic(boolean())
-      assert typedyn!(123 == 456) == boolean()
+      assert typedyn!([x = 123, y = 456], x == y) == dynamic(boolean())
+    end
+
+    test "using literals" do
+      assert typecheck!(:foo == :bar) == boolean()
     end
 
     test "min/max" do
@@ -1060,6 +1064,15 @@ defmodule Module.Types.ExprTest do
   end
 
   describe "case" do
+    test "does not type check literals" do
+      assert typecheck!(
+               case :dev do
+                 :dev -> :ok
+                 :prod -> :error
+               end
+             ) == atom([:ok, :error])
+    end
+
     test "returns unions of all clauses" do
       assert typecheck!(
                [x],
@@ -1115,7 +1128,7 @@ defmodule Module.Types.ExprTest do
   end
 
   describe "conditionals" do
-    test "if does not report on literal atoms" do
+    test "if does not report on literals" do
       assert typecheck!(
                if true do
                  :ok
@@ -1123,17 +1136,9 @@ defmodule Module.Types.ExprTest do
              ) == atom([:ok, nil])
     end
 
-    test "and does not report on literal atoms" do
+    test "and does not report on literals" do
       assert typecheck!(false and true) == boolean()
     end
-
-    test "and reports on non-atom literals" do
-      assert typeerror!(1 and true) == ~l"""
-             the following conditional expression will always evaluate to integer():
-
-                 1
-             """
-    end
   end
 
   describe "receive" do

From 6a3301f237ad6dff6f66c9ded91426a23abfe757 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jos=C3=A9=20Valim?= <jose.valim@dashbit.co>
Date: Tue, 24 Dec 2024 10:17:42 +0100
Subject: [PATCH 060/139] Release v1.18.1

---
 CHANGELOG.md   | 30 ++++++++++++++++++++++++++++++
 VERSION        |  2 +-
 bin/elixir     |  2 +-
 bin/elixir.bat |  2 +-
 bin/elixir.ps1 |  2 +-
 5 files changed, 34 insertions(+), 4 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index e8ccdce5a07..844bc7b8c12 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -225,6 +225,36 @@ You may also prefer to write using guards:
 
     def foo(x, y, z) when x == y and y == z
 
+## v1.18.1 (2024-12-24)
+
+### 1. Enhancements
+
+  * [Kernel] Do not emit type violation warnings when comparing or matching against literals
+  * [Kernel] Do not validate clauses of private overridable functions
+
+### 2. Bug fixes
+
+#### Elixir
+
+  * [Code.Fragment] Ensure `Code.Fragment.container_cursor_to_quoted/2` with `:trailing_fragment` parses expressions that were supported in previous versions
+  * [Kernel] Do not crash when typing violation is detected on dynamic dispatch
+  * [Kernel] Properly annotate the source for warnings emitted by the compiler with the `@file` annotation
+  * [Kernel] Properly annotate the source for warnings emitted by the type system with the `@file` annotation
+  * [Kernel] Remove `:no_parens` metadata when using capture with arity on all cases
+  * [Kernel] Ensure diagnostic traces are kept backwards compatible
+
+#### ExUnit
+
+  * [ExUnit.Case] Ensure async groups do not run concurrenly while the test suite is still loading
+  * [ExUnit.Case] Ensure `--repeat-until-failure` can be combined with groups
+
+#### Mix
+
+  * [mix compile.elixir] Store compilation results if compilation fails due to `--warnings-as-errors`
+  * [mix deps.loadpaths] Add build lock
+  * [mix escript.build] Ensure build succeeds when protocol consolidation is disabled
+  * [Mix.Shell] Ensure encoding is properly respected on Windows and Unix systems
+
 ## v1.18.0 (2024-12-19)
 
 ### 1. Enhancements
diff --git a/VERSION b/VERSION
index 84cc529467b..ec6d649be65 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-1.18.0
+1.18.1
diff --git a/bin/elixir b/bin/elixir
index c937bea182c..69fa4b4695a 100755
--- a/bin/elixir
+++ b/bin/elixir
@@ -1,7 +1,7 @@
 #!/bin/sh
 set -e
 
-ELIXIR_VERSION=1.18.0
+ELIXIR_VERSION=1.18.1
 
 if [ $# -eq 0 ] || { [ $# -eq 1 ] && { [ "$1" = "--help" ] || [ "$1" = "-h" ]; }; }; then
   cat <<USAGE >&2
diff --git a/bin/elixir.bat b/bin/elixir.bat
index 09f3050ff4a..781cd9ad066 100644
--- a/bin/elixir.bat
+++ b/bin/elixir.bat
@@ -1,6 +1,6 @@
 @echo off
 
-set ELIXIR_VERSION=1.18.0
+set ELIXIR_VERSION=1.18.1
 
 if    ""%1""==""""                if ""%2""=="""" goto documentation
 if /I ""%1""==""--help""          if ""%2""=="""" goto documentation
diff --git a/bin/elixir.ps1 b/bin/elixir.ps1
index 4ef9cd0e1b7..c53c82273f3 100755
--- a/bin/elixir.ps1
+++ b/bin/elixir.ps1
@@ -1,6 +1,6 @@
 #!/usr/bin/env pwsh
 
-$ELIXIR_VERSION = "1.18.0"
+$ELIXIR_VERSION = "1.18.1"
 
 $scriptPath = Split-Path -Parent $PSCommandPath
 $erlExec = "erl"

From b9bd0e3dd9180401b6cd8ec3b005fd153f6b19c9 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jos=C3=A9=20Valim?= <jose.valim@dashbit.co>
Date: Thu, 26 Dec 2024 19:37:22 +0100
Subject: [PATCH 061/139] Bring conditional violation reports back

---
 lib/elixir/lib/kernel.ex                          | 8 ++++----
 lib/elixir/test/elixir/module/types/expr_test.exs | 8 ++++++++
 2 files changed, 12 insertions(+), 4 deletions(-)

diff --git a/lib/elixir/lib/kernel.ex b/lib/elixir/lib/kernel.ex
index 3ceeab3835f..3bfc11f0e9d 100644
--- a/lib/elixir/lib/kernel.ex
+++ b/lib/elixir/lib/kernel.ex
@@ -1972,7 +1972,7 @@ defmodule Kernel do
 
   defp build_boolean_check(operator, check, true_clause, false_clause) do
     annotate_case(
-      [optimize_boolean: true],
+      [optimize_boolean: true, type_check: :expr],
       quote do
         case unquote(check) do
           false -> unquote(false_clause)
@@ -2006,7 +2006,7 @@ defmodule Kernel do
     assert_no_match_or_guard_scope(__CALLER__.context, "!")
 
     annotate_case(
-      [optimize_boolean: true],
+      [optimize_boolean: true, type_check: :expr],
       quote do
         case unquote(value) do
           x when :"Elixir.Kernel".in(x, [false, nil]) -> false
@@ -2020,7 +2020,7 @@ defmodule Kernel do
     assert_no_match_or_guard_scope(__CALLER__.context, "!")
 
     annotate_case(
-      [optimize_boolean: true],
+      [optimize_boolean: true, type_check: :expr],
       quote do
         case unquote(value) do
           x when :"Elixir.Kernel".in(x, [false, nil]) -> true
@@ -3910,7 +3910,7 @@ defmodule Kernel do
 
   defp build_if(condition, do: do_clause, else: else_clause) do
     annotate_case(
-      [optimize_boolean: true],
+      [optimize_boolean: true, type_check: :expr],
       quote do
         case unquote(condition) do
           x when :"Elixir.Kernel".in(x, [false, nil]) -> unquote(else_clause)
diff --git a/lib/elixir/test/elixir/module/types/expr_test.exs b/lib/elixir/test/elixir/module/types/expr_test.exs
index 27d9e4ed26f..49c4c8bf932 100644
--- a/lib/elixir/test/elixir/module/types/expr_test.exs
+++ b/lib/elixir/test/elixir/module/types/expr_test.exs
@@ -1139,6 +1139,14 @@ defmodule Module.Types.ExprTest do
     test "and does not report on literals" do
       assert typecheck!(false and true) == boolean()
     end
+
+    test "and reports violations" do
+      assert typeerror!([x = 123], x and true) =~ """
+             the following conditional expression will always evaluate to integer():
+
+                 x
+             """
+    end
   end
 
   describe "receive" do

From 265b9aaeb3d50353aa55e729ee5f388c96319b38 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jos=C3=A9=20Valim?= <jose.valim@dashbit.co>
Date: Fri, 27 Dec 2024 11:37:25 +0100
Subject: [PATCH 062/139] Update AST metadata documentation (#14120)

---
 lib/elixir/lib/macro.ex | 12 +++++++++---
 1 file changed, 9 insertions(+), 3 deletions(-)

diff --git a/lib/elixir/lib/macro.ex b/lib/elixir/lib/macro.ex
index 117cc4bee9a..734c6dad60b 100644
--- a/lib/elixir/lib/macro.ex
+++ b/lib/elixir/lib/macro.ex
@@ -146,8 +146,6 @@ defmodule Macro do
     * `:delimiter` - contains the opening delimiter for sigils, strings,
       and charlists as a string (such as `"{"`, `"/"`, `"'"`, and the like)
 
-    * `:format` - set to `:keyword` when an atom is defined as a keyword
-
     * `:do` - contains metadata about the `do` location in a function call with
       `do`-`end` blocks (when `:token_metadata` is true)
 
@@ -159,10 +157,18 @@ defmodule Macro do
       expressions inside "blocks of code", which are either direct children
       of a `__block__` or the right side of `->`. The last expression of the
       block does not have metadata if it is not followed by an end of line
-      character (either a newline or `;`)
+      character (either a newline or `;`). This entry may appear multiple times
+      in the same metadata if the expression is surround by parens
+
+    * `:format` - set to `:keyword` when an atom is defined as a keyword.
+      It may also be set to `:atom` to distinguish `nil`, `false`, and `true`
 
     * `:indentation` - indentation of a sigil heredoc
 
+    * `:parens` - denotes a node was surrounded by parens for grouping.
+      This entry may appear multiple times in the same metadata if
+      multiple pairs are used for grouping
+
   The following metadata keys are private:
 
     * `:alias` - Used for alias hygiene.

From 0cb79b0ba127062e441ca04cfc8c85680f118899 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jos=C3=A9=20Valim?= <jose.valim@dashbit.co>
Date: Sat, 28 Dec 2024 09:18:59 +0100
Subject: [PATCH 063/139] Add OTP 25 to sign entry

---
 .github/workflows/release.yml | 12 ++++++------
 1 file changed, 6 insertions(+), 6 deletions(-)

diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 9a28d67400d..2efac4bbcd2 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -115,13 +115,13 @@ jobs:
         with:
           name: Docs
           path: Docs.zip*
-  
+
   sign:
     needs: [build]
     strategy:
       fail-fast: true
       matrix:
-        otp: [26, 27]
+        otp: [25, 26, 27]
         flavor: [windows, linux]
 
     env:
@@ -160,14 +160,14 @@ jobs:
         run: cp "$ATTESTATION" "${RELEASE_FILE}.sigstore"
         env:
           ATTESTATION: "${{ steps.attest-provenance.outputs.bundle-path }}"
-      
+
       - name: Create Release Hashes
         if: matrix.flavor == 'windows'
         shell: pwsh
         run: |
           $sha1 = Get-FileHash "$env:RELEASE_FILE" -Algorithm SHA1
           $sha1.Hash.ToLower() + "  " + $env:RELEASE_FILE | Out-File "$env:RELEASE_FILE.sha1sum"
-          
+
           $sha256 = Get-FileHash "$env:RELEASE_FILE" -Algorithm SHA256
           $sha256.Hash.ToLower() + "  " + $env:RELEASE_FILE | Out-File "$env:RELEASE_FILE.sha256sum"
 
@@ -191,7 +191,7 @@ jobs:
     steps:
       - uses: actions/download-artifact@v4
         with:
-          pattern: '{sign-*-elixir-otp-*,Docs}'
+          pattern: "{sign-*-elixir-otp-*,Docs}"
           merge-multiple: true
 
       - name: Upload Pre-built
@@ -235,7 +235,7 @@ jobs:
     steps:
       - uses: actions/download-artifact@v4
         with:
-          pattern: '{sign-*-elixir-otp-*,Docs}'
+          pattern: "{sign-*-elixir-otp-*,Docs}"
           merge-multiple: true
 
       - name: Init purge keys file

From 6dbb932bf31418baf5fe986c8e7cbee0119d2bb9 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jos=C3=A9=20Valim?= <jose.valim@dashbit.co>
Date: Sat, 28 Dec 2024 09:32:43 +0100
Subject: [PATCH 064/139] Properly raise for invalid async value

---
 lib/ex_unit/lib/ex_unit/case.ex | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/lib/ex_unit/lib/ex_unit/case.ex b/lib/ex_unit/lib/ex_unit/case.ex
index 81b46908ecd..519ed08a1ba 100644
--- a/lib/ex_unit/lib/ex_unit/case.ex
+++ b/lib/ex_unit/lib/ex_unit/case.ex
@@ -566,6 +566,10 @@ defmodule ExUnit.Case do
     group = Keyword.get(opts, :group, nil)
     parameterize = Keyword.get(opts, :parameterize, nil)
 
+    if not is_boolean(async?) do
+      raise ArgumentError, ":async must be a boolean, got: #{inspect(async?)}"
+    end
+
     if not (parameterize == nil or (is_list(parameterize) and Enum.all?(parameterize, &is_map/1))) do
       raise ArgumentError, ":parameterize must be a list of maps, got: #{inspect(parameterize)}"
     end

From 7e149619f54c0d57c402a5aa8f60415091971499 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jos=C3=A9=20Valim?= <jose.valim@dashbit.co>
Date: Mon, 30 Dec 2024 11:07:22 +0100
Subject: [PATCH 065/139] Fix expression-tag pairing inside with type checking

---
 lib/elixir/lib/module/types/expr.ex | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/lib/elixir/lib/module/types/expr.ex b/lib/elixir/lib/module/types/expr.ex
index 22bddbbf5c1..ff7f5646509 100644
--- a/lib/elixir/lib/module/types/expr.ex
+++ b/lib/elixir/lib/module/types/expr.ex
@@ -518,7 +518,7 @@ defmodule Module.Types.Expr do
 
   defp with_clause({:<-, _meta, [left, right]} = expr, stack, context) do
     {pattern, guards} = extract_head([left])
-    {_type, context} = Pattern.of_match(pattern, guards, dynamic(), :with, expr, stack, context)
+    {_type, context} = Pattern.of_match(pattern, guards, dynamic(), expr, :with, stack, context)
     {_, context} = of_expr(right, stack, context)
     context
   end

From f3dfb72c69ae4ba9742d8bd4bb97bcdb557b374e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jos=C3=A9=20Valim?= <jose.valim@dashbit.co>
Date: Mon, 30 Dec 2024 11:39:36 +0100
Subject: [PATCH 066/139] Properly handle optional keys in map intersection

---
 lib/elixir/lib/module/types/descr.ex               | 9 ++++-----
 lib/elixir/test/elixir/module/types/descr_test.exs | 3 +++
 2 files changed, 7 insertions(+), 5 deletions(-)

diff --git a/lib/elixir/lib/module/types/descr.ex b/lib/elixir/lib/module/types/descr.ex
index c9af3919dde..175e3e315d2 100644
--- a/lib/elixir/lib/module/types/descr.ex
+++ b/lib/elixir/lib/module/types/descr.ex
@@ -1329,11 +1329,10 @@ defmodule Module.Types.Descr do
         :maps.next(iterator) |> map_literal_intersection_loop(acc)
 
       _ ->
-        # If the key is marked as not_set in the open map, we can ignore it.
-        if type1 == @not_set do
-          :maps.next(iterator) |> map_literal_intersection_loop(acc)
-        else
-          throw(:empty)
+        # If the key is optional in the open map, we can ignore it
+        case type1 do
+          %{optional: 1} -> :maps.next(iterator) |> map_literal_intersection_loop(acc)
+          _ -> throw(:empty)
         end
     end
   end
diff --git a/lib/elixir/test/elixir/module/types/descr_test.exs b/lib/elixir/test/elixir/module/types/descr_test.exs
index 13517e87bd0..3011da51345 100644
--- a/lib/elixir/test/elixir/module/types/descr_test.exs
+++ b/lib/elixir/test/elixir/module/types/descr_test.exs
@@ -163,6 +163,9 @@ defmodule Module.Types.DescrTest do
       assert intersection(closed_map(a: integer()), open_map(b: not_set())) ==
                closed_map(a: integer())
 
+      assert intersection(closed_map(a: integer()), open_map(b: if_set(integer()))) ==
+               closed_map(a: integer())
+
       assert equal?(
                intersection(closed_map(a: integer()), closed_map(a: if_set(integer()))),
                closed_map(a: integer())

From c7a841baae4a47cc93a42743bfa5509e0e8abfff Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jonatan=20K=C5=82osko?= <jonatanklosko@gmail.com>
Date: Mon, 30 Dec 2024 17:00:16 +0100
Subject: [PATCH 067/139] Add an environment variable to optionally disable
 compilation locking (#14129)

---
 lib/mix/lib/mix.ex           |  5 +++++
 lib/mix/lib/mix/sync/lock.ex | 21 ++++++++++++++-------
 2 files changed, 19 insertions(+), 7 deletions(-)

diff --git a/lib/mix/lib/mix.ex b/lib/mix/lib/mix.ex
index 97f018e6fce..dc4181a33ab 100644
--- a/lib/mix/lib/mix.ex
+++ b/lib/mix/lib/mix.ex
@@ -352,6 +352,11 @@ defmodule Mix do
     * `MIX_INSTALL_DIR` *(since v1.12.0)* - specifies directory where `Mix.install/2` keeps
        install cache
 
+    * `MIX_OS_CONCURRENCY_LOCK` - when set to `0` or `false`, disables mix compilation locking.
+      While not recommended, this may be necessary in cases where hard links or TCP sockets are
+      not available. When opting for this behaviour, make sure to not start concurrent compilations
+      of the same project.
+
     * `MIX_PATH` - appends extra code paths
 
     * `MIX_PROFILE` - a list of comma-separated Mix tasks to profile the time spent on
diff --git a/lib/mix/lib/mix/sync/lock.ex b/lib/mix/lib/mix/sync/lock.ex
index 5253d610e09..dc65e57713c 100644
--- a/lib/mix/lib/mix/sync/lock.ex
+++ b/lib/mix/lib/mix/sync/lock.ex
@@ -79,6 +79,10 @@ defmodule Mix.Sync.Lock do
   This function can also be called if this process already has the
   lock. In such case the function is executed immediately.
 
+  When the `MIX_OS_CONCURRENCY_LOCK` environment variable is set to
+  a falsy value, the lock is ignored and the function is executed
+  immediately.
+
   ## Options
 
     * `:on_taken` - a one-arity function called if the lock is held
@@ -96,9 +100,9 @@ defmodule Mix.Sync.Lock do
     path = Path.join([System.tmp_dir!(), "mix_lock", hash])
 
     pdict_key = {__MODULE__, path}
-    has_lock? = Process.get(pdict_key)
+    has_lock? = Process.get(pdict_key, false)
 
-    if has_lock? do
+    if has_lock? or lock_disabled?() do
       fun.()
     else
       lock = lock(path, opts[:on_taken])
@@ -115,6 +119,8 @@ defmodule Mix.Sync.Lock do
     end
   end
 
+  defp lock_disabled?(), do: System.get_env("MIX_OS_CONCURRENCY_LOCK") in ~w(0 false)
+
   defp lock(path, on_taken) do
     File.mkdir_p!(path)
 
@@ -198,11 +204,12 @@ defmodule Mix.Sync.Lock do
         :invalidated
 
       {:error, reason} ->
-        raise File.LinkError,
-          reason: reason,
-          action: "create hard link",
-          existing: port_path,
-          new: lock_path
+        Mix.raise("""
+        could not create hard link from #{port_path} to "#{lock_path}: #{:file.format_error(reason)}.
+
+        Hard link support is required for Mix compilation locking. If your system \
+        does not support hard links, set MIX_OS_CONCURRENCY_LOCK=0\
+        """)
     end
   end
 

From 2da3300bfce855d2565644fb90e493e4f2769017 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jos=C3=A9=20Valim?= <jose.valim@dashbit.co>
Date: Tue, 31 Dec 2024 08:45:30 +0100
Subject: [PATCH 068/139] Do not wrap literals in variable when expanding in/2

---
 lib/elixir/lib/kernel.ex | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/lib/elixir/lib/kernel.ex b/lib/elixir/lib/kernel.ex
index 3bfc11f0e9d..04777ddb320 100644
--- a/lib/elixir/lib/kernel.ex
+++ b/lib/elixir/lib/kernel.ex
@@ -4584,6 +4584,9 @@ defmodule Kernel do
   defp in_var(true, {atom, _, context} = var, fun) when is_atom(atom) and is_atom(context),
     do: fun.(var)
 
+  defp in_var(true, var, fun) when is_atom(var) or is_binary(var) or is_number(var),
+    do: fun.(var)
+
   defp in_var(true, ast, fun) do
     quote do
       var = unquote(ast)

From dddb8f7b1931e0c1de1da2d479bb3bfe889c263c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jos=C3=A9=20Valim?= <jose.valim@dashbit.co>
Date: Tue, 31 Dec 2024 08:53:47 +0100
Subject: [PATCH 069/139] Bring kv-observer image back as it is used in guides

---
 lib/elixir/pages/images/kv-observer.png | Bin 0 -> 34195 bytes
 1 file changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 lib/elixir/pages/images/kv-observer.png

diff --git a/lib/elixir/pages/images/kv-observer.png b/lib/elixir/pages/images/kv-observer.png
new file mode 100644
index 0000000000000000000000000000000000000000..7527d7e5c807127471122a4972adec488a210d01
GIT binary patch

From f088fc9cdb093d3d8db4088d79a84fe79826c92a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jonatan=20K=C5=82osko?= <jonatanklosko@gmail.com>
Date: Thu, 2 Jan 2025 09:40:50 +0100
Subject: [PATCH 070/139] Print compilation lock waiting message to stderr
 (#14138)

---
 lib/mix/lib/mix/project.ex | 10 ++++++++--
 1 file changed, 8 insertions(+), 2 deletions(-)

diff --git a/lib/mix/lib/mix/project.ex b/lib/mix/lib/mix/project.ex
index 6f946b7e27d..157badc96b2 100644
--- a/lib/mix/lib/mix/project.ex
+++ b/lib/mix/lib/mix/project.ex
@@ -917,7 +917,10 @@ defmodule Mix.Project do
     build_path = build_path(config)
 
     on_taken = fn os_pid ->
-      Mix.shell().info("Waiting for lock on the build directory (held by process #{os_pid})")
+      Mix.shell().error([
+        IO.ANSI.reset(),
+        "Waiting for lock on the build directory (held by process #{os_pid})"
+      ])
     end
 
     Mix.Sync.Lock.with_lock(build_path, fun, on_taken: on_taken)
@@ -931,7 +934,10 @@ defmodule Mix.Project do
     deps_path = deps_path(config)
 
     on_taken = fn os_pid ->
-      Mix.shell().info("Waiting for lock on the deps directory (held by process #{os_pid})")
+      Mix.shell().error([
+        IO.ANSI.reset(),
+        "Waiting for lock on the deps directory (held by process #{os_pid})"
+      ])
     end
 
     Mix.Sync.Lock.with_lock(deps_path, fun, on_taken: on_taken)

From 24714c68a795156b603b39a423f3c91dcdde92ca Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jos=C3=A9=20Valim?= <jose.valim@dashbit.co>
Date: Thu, 2 Jan 2025 18:35:46 +0100
Subject: [PATCH 071/139] Improve error message for invalid default arguments

---
 lib/elixir/lib/module/types/apply.ex          | 23 ++++++++++---
 lib/elixir/src/elixir_def.erl                 |  2 +-
 .../elixir/module/types/integration_test.exs  | 33 +++++++++++++++++++
 3 files changed, 52 insertions(+), 6 deletions(-)

diff --git a/lib/elixir/lib/module/types/apply.ex b/lib/elixir/lib/module/types/apply.ex
index ed510e5e634..c01a9d12da1 100644
--- a/lib/elixir/lib/module/types/apply.ex
+++ b/lib/elixir/lib/module/types/apply.ex
@@ -785,7 +785,7 @@ defmodule Module.Types.Apply do
   def format_diagnostic({:badlocal, expr, args_types, domain, clauses, context}) do
     traces = collect_traces(expr, context)
     converter = &Function.identity/1
-    {fun, _, _} = expr
+    {fun, meta, _} = expr
 
     explanation =
       empty_arg_reason(args_types) ||
@@ -794,16 +794,29 @@ defmodule Module.Types.Apply do
         #{clauses_args_to_quoted_string(clauses, converter)}
         """
 
-    %{
-      details: %{typing_traces: traces},
-      message:
-        IO.iodata_to_binary([
+    banner =
+      case fun == :super && meta[:default] && meta[:super] do
+        {_kind, fun} ->
+          """
+          incompatible types given as default arguments to #{fun}/#{length(args_types)}:
+          """
+
+        _ ->
           """
           incompatible types given to #{fun}/#{length(args_types)}:
 
               #{expr_to_string(expr) |> indent(4)}
 
           given types:
+          """
+      end
+
+    %{
+      details: %{typing_traces: traces},
+      message:
+        IO.iodata_to_binary([
+          banner,
+          """
 
               #{args_to_quoted_string(args_types, domain, converter) |> indent(4)}
 
diff --git a/lib/elixir/src/elixir_def.erl b/lib/elixir/src/elixir_def.erl
index af9874b2985..da348e7eeb3 100644
--- a/lib/elixir/src/elixir_def.erl
+++ b/lib/elixir/src/elixir_def.erl
@@ -314,7 +314,7 @@ unpack_defaults(Kind, Meta, Name, Args, S, E) ->
 unpack_expanded(Kind, Meta, Name, [{'\\\\', DefaultMeta, [Expr, _]} | T] = List, VersionOffset, Acc, Clauses) ->
   Base = match_defaults(Acc, length(Acc) + VersionOffset, []),
   {Args, Invoke} = extract_defaults(List, length(Base) + VersionOffset, [], []),
-  Clause = {Meta, Base ++ Args, [], {super, [{super, {Kind, Name}} | DefaultMeta], Base ++ Invoke}},
+  Clause = {Meta, Base ++ Args, [], {super, [{super, {Kind, Name}}, {default, true} | DefaultMeta], Base ++ Invoke}},
   unpack_expanded(Kind, Meta, Name, T, VersionOffset, [Expr | Acc], [Clause | Clauses]);
 unpack_expanded(Kind, Meta, Name, [H | T], VersionOffset, Acc, Clauses) ->
   unpack_expanded(Kind, Meta, Name, T, VersionOffset, [H | Acc], Clauses);
diff --git a/lib/elixir/test/elixir/module/types/integration_test.exs b/lib/elixir/test/elixir/module/types/integration_test.exs
index 943c18f7d8c..5ca6f17bb34 100644
--- a/lib/elixir/test/elixir/module/types/integration_test.exs
+++ b/lib/elixir/test/elixir/module/types/integration_test.exs
@@ -244,6 +244,39 @@ defmodule Module.Types.IntegrationTest do
       assert_no_warnings(files)
     end
 
+    test "incompatible default argument" do
+      files = %{
+        "a.ex" => """
+        defmodule A do
+          def ok(x = :ok \\\\ nil) do
+            x
+          end
+        end
+        """
+      }
+
+      warnings = [
+        ~S"""
+            warning: incompatible types given as default arguments to ok/1:
+
+                -nil-
+
+            but expected one of:
+
+                dynamic(:ok)
+
+            typing violation found at:
+            │
+          2 │   def ok(x = :ok \\ nil) do
+            │                  ~
+            │
+            └─ a.ex:2:18: A.ok/0
+        """
+      ]
+
+      assert_warnings(files, warnings)
+    end
+
     test "returns diagnostics with source and file" do
       files = %{
         "a.ex" => """

From 432d6fa1c28a70452589edb99a279241f6bfbf5f Mon Sep 17 00:00:00 2001
From: Guillaume Milan <guillaumilan@gmail.com>
Date: Tue, 7 Jan 2025 11:39:41 +0100
Subject: [PATCH 072/139] Fix crashing on autocompleting structs with runtime
 values (#14150)

---
 lib/iex/lib/iex/autocomplete.ex        |  2 +-
 lib/iex/test/iex/autocomplete_test.exs | 13 +++++++++++++
 2 files changed, 14 insertions(+), 1 deletion(-)

diff --git a/lib/iex/lib/iex/autocomplete.ex b/lib/iex/lib/iex/autocomplete.ex
index ae245e2a6fb..531b129e661 100644
--- a/lib/iex/lib/iex/autocomplete.ex
+++ b/lib/iex/lib/iex/autocomplete.ex
@@ -363,7 +363,7 @@ defmodule IEx.Autocomplete do
       end)
 
     entries =
-      for {key, _value} <- pairs,
+      for key when key != :__struct__ <- Map.keys(pairs),
           name = Atom.to_string(key),
           if(hint == "",
             do: not String.starts_with?(name, "_"),
diff --git a/lib/iex/test/iex/autocomplete_test.exs b/lib/iex/test/iex/autocomplete_test.exs
index 0827daf82e9..c9d7747091e 100644
--- a/lib/iex/test/iex/autocomplete_test.exs
+++ b/lib/iex/test/iex/autocomplete_test.exs
@@ -430,6 +430,19 @@ defmodule IEx.AutocompleteTest do
     assert {:yes, ~c"ry: ", []} = expand(~c"%URI{var | path: \"foo\", que")
     assert {:no, [], []} = expand(~c"%URI{var | path: \"foo\", unkno")
     assert {:no, [], []} = expand(~c"%Unknown{var | path: \"foo\", unkno")
+
+    eval("var = %URI{}")
+
+    assert {:yes, ~c"", entries} = expand(~c"%{var | ")
+    assert ~c"path:" in entries
+    assert ~c"query:" in entries
+
+    assert {:yes, ~c"", entries} = expand(~c"%{var | path: \"foo\",")
+    assert ~c"path:" not in entries
+    assert ~c"query:" in entries
+
+    assert {:yes, ~c"ry: ", []} = expand(~c"%{var | path: \"foo\", que")
+    assert {:no, [], []} = expand(~c"%URI{var | path: \"foo\", unkno")
   end
 
   test "completion for map keys in update syntax" do

From defef37856b2d1befa07119db8bc54f35bada6f2 Mon Sep 17 00:00:00 2001
From: Daniel Drexler <aeturnum@gmail.com>
Date: Thu, 9 Jan 2025 02:22:02 -0600
Subject: [PATCH 073/139] Improve warning for charlists (#14160)

The current warning for single-quoted character lists is confusing
because it appears to be a stylistic note instead of a deprecation
warning. This commit updates the language to make the reasoning clearer
and to more clearly describe what `mix format --migrate` will change
about the users' code.
---
 lib/elixir/src/elixir_tokenizer.erl | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

diff --git a/lib/elixir/src/elixir_tokenizer.erl b/lib/elixir/src/elixir_tokenizer.erl
index cfb6e5e14be..5823299a39b 100644
--- a/lib/elixir/src/elixir_tokenizer.erl
+++ b/lib/elixir/src/elixir_tokenizer.erl
@@ -818,9 +818,10 @@ handle_strings(T, Line, Column, H, Scope, Tokens) ->
       NewScope =
         case H of
           $' ->
-            Message = "single-quoted strings represent charlists. "
+            Message = "using single-quoted strings to represent charlists is deprecated.\n"
               "Use ~c\"\" if you indeed want a charlist or use \"\" instead.\n"
-              "You may run \"mix format --migrate\" to fix this warning automatically.",
+              "You may run \"mix format --migrate\" to change all single-quoted\n"
+              "strings to use the ~c sigil and fix this warning.",
             prepend_warning(Line, Column-1, Message, InterScope);
 
           _ ->

From 5c340e488ea91d07953151189accb88d8997e546 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jos=C3=A9=20Valim?= <jose.valim@dashbit.co>
Date: Thu, 9 Jan 2025 10:03:13 +0100
Subject: [PATCH 074/139] Migrate the git repo against the origin branch,
 closes #14163

---
 lib/mix/lib/mix/scm/git.ex | 14 +++++++++++---
 1 file changed, 11 insertions(+), 3 deletions(-)

diff --git a/lib/mix/lib/mix/scm/git.ex b/lib/mix/lib/mix/scm/git.ex
index 4ff914dc7ee..95fcff44caa 100644
--- a/lib/mix/lib/mix/scm/git.ex
+++ b/lib/mix/lib/mix/scm/git.ex
@@ -126,17 +126,17 @@ defmodule Mix.SCM.Git do
     update_origin(opts[:git])
 
     # Fetch external data
-    rev = get_lock_rev(opts[:lock], opts) || get_opts_rev(opts)
+    lock_rev = get_lock_rev(opts[:lock], opts)
 
     ["--git-dir=.git", "fetch", "--force", "--quiet"]
     |> Kernel.++(progress_switch(git_version()))
     |> Kernel.++(tags_switch(opts[:tag]))
     |> Kernel.++(depth_switch(opts[:depth]))
-    |> Kernel.++(refspec_switch(opts, rev))
+    |> Kernel.++(refspec_switch(opts, lock_rev || get_opts_rev(opts)))
     |> git!()
 
     # Migrate the Git repo
-    rev = rev || default_branch()
+    rev = lock_rev || get_origin_opts_rev(opts) || default_branch()
     git!(["--git-dir=.git", "checkout", "--quiet", rev])
 
     if opts[:submodules] do
@@ -314,6 +314,14 @@ defmodule Mix.SCM.Git do
     opts[:branch] || opts[:ref] || opts[:tag]
   end
 
+  defp get_origin_opts_rev(opts) do
+    if branch = opts[:branch] do
+      "origin/#{branch}"
+    else
+      opts[:ref] || opts[:tag]
+    end
+  end
+
   defp redact_uri(git) do
     case URI.parse(git) do
       %{userinfo: nil} -> git

From ac910c1cc232adf1dfb3f5f7fca9a5bbb21a2ec2 Mon Sep 17 00:00:00 2001
From: Jean Klingler <sabiwara@gmail.com>
Date: Thu, 9 Jan 2025 18:25:51 +0900
Subject: [PATCH 075/139] Prevent infinite loop in compiler for some invalid
 type specs (#14155)

---
 lib/elixir/lib/kernel/typespec.ex        | 11 ++++++----
 lib/elixir/test/elixir/typespec_test.exs | 27 +++++++++++++++++++++---
 2 files changed, 31 insertions(+), 7 deletions(-)

diff --git a/lib/elixir/lib/kernel/typespec.ex b/lib/elixir/lib/kernel/typespec.ex
index fbe1b86b315..023c86f9fac 100644
--- a/lib/elixir/lib/kernel/typespec.ex
+++ b/lib/elixir/lib/kernel/typespec.ex
@@ -938,7 +938,7 @@ defmodule Kernel.Typespec do
 
   ## Helpers
 
-  # This is a backport of Macro.expand/2 because we want to expand
+  # This is a modified backport of Macro.expand/2 because we want to expand
   # aliases but we don't them to become compile-time references.
   defp expand_remote({:__aliases__, meta, list} = alias, env) do
     case :elixir_aliases.expand_or_concat(meta, list, env, true) do
@@ -946,9 +946,12 @@ defmodule Kernel.Typespec do
         receiver
 
       [head | tail] ->
-        case Macro.expand_once(head, env) do
-          head when is_atom(head) -> :elixir_aliases.concat([head | tail])
-          _ -> alias
+        case Macro.expand(head, env) do
+          head when is_atom(head) ->
+            :elixir_aliases.concat([head | tail])
+
+          _ ->
+            compile_error(env, "unexpected expression in typespec: #{Macro.to_string(alias)}")
         end
     end
   end
diff --git a/lib/elixir/test/elixir/typespec_test.exs b/lib/elixir/test/elixir/typespec_test.exs
index c81bb4682c7..7cf5e714dc3 100644
--- a/lib/elixir/test/elixir/typespec_test.exs
+++ b/lib/elixir/test/elixir/typespec_test.exs
@@ -75,6 +75,14 @@ defmodule TypespecTest do
                        @type my_type :: %URI.t(){}
                      end
                    end
+
+      assert_raise Kernel.TypespecError,
+                   ~r"unexpected expression in typespec: t\.Foo",
+                   fn ->
+                     test_module do
+                       @type my_type :: t.Foo
+                     end
+                   end
     end
 
     test "invalid function specification" do
@@ -120,7 +128,7 @@ defmodule TypespecTest do
 
     test "redefined type" do
       assert_raise Kernel.TypespecError,
-                   ~r"type foo/0 is already defined in .*test/elixir/typespec_test.exs:126",
+                   ~r"type foo/0 is already defined in .*test/elixir/typespec_test.exs:134",
                    fn ->
                      test_module do
                        @type foo :: atom
@@ -129,7 +137,7 @@ defmodule TypespecTest do
                    end
 
       assert_raise Kernel.TypespecError,
-                   ~r"type foo/2 is already defined in .*test/elixir/typespec_test.exs:136",
+                   ~r"type foo/2 is already defined in .*test/elixir/typespec_test.exs:144",
                    fn ->
                      test_module do
                        @type foo :: atom
@@ -139,7 +147,7 @@ defmodule TypespecTest do
                    end
 
       assert_raise Kernel.TypespecError,
-                   ~r"type foo/0 is already defined in .*test/elixir/typespec_test.exs:145",
+                   ~r"type foo/0 is already defined in .*test/elixir/typespec_test.exs:153",
                    fn ->
                      test_module do
                        @type foo :: atom
@@ -841,6 +849,19 @@ defmodule TypespecTest do
       assert [{:atom, _, Keyword}, {:atom, _, :t}, [{:var, _, :value}]] = kw_with_value_args
     end
 
+    test "@type with macro in alias" do
+      bytecode =
+        test_module do
+          defmacro module() do
+            quote do: __MODULE__
+          end
+
+          @type my_type :: module().Foo
+        end
+
+      assert [type: {:my_type, {:atom, _, TypespecTest.TypespecSample.Foo}, []}] = types(bytecode)
+    end
+
     test "@type with a reserved signature" do
       assert_raise Kernel.TypespecError,
                    ~r"type required\/1 is a reserved type and it cannot be defined",

From 40da7e8857a3cefaff91a13d6ebc956c9a8d5299 Mon Sep 17 00:00:00 2001
From: Jean Klingler <sabiwara@gmail.com>
Date: Thu, 9 Jan 2025 18:26:50 +0900
Subject: [PATCH 076/139] Fix ExUnit crash when diffing bitstring specifiers
 (#14161)

---
 lib/ex_unit/lib/ex_unit/diff.ex        | 3 ++-
 lib/ex_unit/test/ex_unit/diff_test.exs | 6 ++++++
 2 files changed, 8 insertions(+), 1 deletion(-)

diff --git a/lib/ex_unit/lib/ex_unit/diff.ex b/lib/ex_unit/lib/ex_unit/diff.ex
index 03c75c96e45..170e988c35a 100644
--- a/lib/ex_unit/lib/ex_unit/diff.ex
+++ b/lib/ex_unit/lib/ex_unit/diff.ex
@@ -878,7 +878,8 @@ defmodule ExUnit.Diff do
   end
 
   defp rebuild_split_strings(%{contents: contents, delimiter: delimiter}, right) do
-    %{contents: contents ++ [{false, right}], delimiter: delimiter}
+    {new_right, diff} = extract_diff_meta(right)
+    %{contents: contents ++ [{diff, new_right}], delimiter: delimiter}
   end
 
   defp rebuild_concat_string(literal, nil, []) do
diff --git a/lib/ex_unit/test/ex_unit/diff_test.exs b/lib/ex_unit/test/ex_unit/diff_test.exs
index 019f6c754c7..a2317cf73e2 100644
--- a/lib/ex_unit/test/ex_unit/diff_test.exs
+++ b/lib/ex_unit/test/ex_unit/diff_test.exs
@@ -1133,6 +1133,12 @@ defmodule ExUnit.DiffTest do
       "-<<trap::binary-size(3)>> <> \"baz\"-",
       "+\"foobar\"+"
     )
+
+    refute_diff(
+      "hello " <> <<_::binary-size(6)>> = "hello world",
+      "\"hello \" <> -<<_::binary-size(6)>>-",
+      "\"hello +world+\""
+    )
   end
 
   test "underscore" do

From b0dae83353faa7a81043c2bf0be9d602d0b5325a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jos=C3=A9=20Valim?= <jose.valim@dashbit.co>
Date: Thu, 9 Jan 2025 16:36:59 +0100
Subject: [PATCH 077/139] Clarify the need for better tools in the TCP guide

---
 lib/elixir/pages/mix-and-otp/task-and-gen-tcp.md | 6 +++++-
 1 file changed, 5 insertions(+), 1 deletion(-)

diff --git a/lib/elixir/pages/mix-and-otp/task-and-gen-tcp.md b/lib/elixir/pages/mix-and-otp/task-and-gen-tcp.md
index abb883f3a87..30826a5e0e6 100644
--- a/lib/elixir/pages/mix-and-otp/task-and-gen-tcp.md
+++ b/lib/elixir/pages/mix-and-otp/task-and-gen-tcp.md
@@ -157,7 +157,7 @@ say me
 say me
 ```
 
-Yes, it works! However, does it *scale*?
+Yes, it works! However, can it handle more than one client?
 
 Try to connect two telnet clients at the same time. When you do so, you will notice that the second client doesn't echo:
 
@@ -303,4 +303,8 @@ Luckily, this can be done by using `Supervisor.child_spec/2`, which allows us to
 
 Now we have an always running acceptor that starts temporary task processes under an always running task supervisor.
 
+## Wrapping up
+
+In this chapter we have implemented a straigh-forward TCP acceptor, which allowed is to explore tools for concurrency and fault-tolerance. While our acceptor can manage concurrent connections, it is still not ready for production. In practice, TCP servers run a pool of acceptors, instead of a single one, each of them with their own supervisor. While Elixir has tools to make it easier to partition and scale the accept, such as the `PartitionSupervisor`, they are out of scope for this guide. In any case, a better path forward may be to use existing packages tailored for this use case, such as [Ranch](https://github.com/ninenines/ranch) (in Erlang) or [Thousand Island](https://github.com/mtrudel/thousand_island) (in Elixir).
+
 In the next chapter, we will start parsing the client requests and sending responses, finishing our server.

From f5e42ba11aab10ff4553e125362ce7f60e41e8b5 Mon Sep 17 00:00:00 2001
From: Zach Allaun <zach.allaun@gmail.com>
Date: Thu, 9 Jan 2025 12:10:23 -0500
Subject: [PATCH 078/139] Update task-and-gen-tcp.md (#14166)

---
 lib/elixir/pages/mix-and-otp/task-and-gen-tcp.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/lib/elixir/pages/mix-and-otp/task-and-gen-tcp.md b/lib/elixir/pages/mix-and-otp/task-and-gen-tcp.md
index 30826a5e0e6..79d3a2972ec 100644
--- a/lib/elixir/pages/mix-and-otp/task-and-gen-tcp.md
+++ b/lib/elixir/pages/mix-and-otp/task-and-gen-tcp.md
@@ -305,6 +305,6 @@ Now we have an always running acceptor that starts temporary task processes unde
 
 ## Wrapping up
 
-In this chapter we have implemented a straigh-forward TCP acceptor, which allowed is to explore tools for concurrency and fault-tolerance. While our acceptor can manage concurrent connections, it is still not ready for production. In practice, TCP servers run a pool of acceptors, instead of a single one, each of them with their own supervisor. While Elixir has tools to make it easier to partition and scale the accept, such as the `PartitionSupervisor`, they are out of scope for this guide. In any case, a better path forward may be to use existing packages tailored for this use case, such as [Ranch](https://github.com/ninenines/ranch) (in Erlang) or [Thousand Island](https://github.com/mtrudel/thousand_island) (in Elixir).
+In this chapter, we implemented a basic TCP acceptor while exploring concurrency and fault-tolerance. Our acceptor can manage concurrent connections, but it is still not ready for production. Production-ready TCP servers run a pool of acceptors, each with their own supervisor. Elixir's `PartitionSupervisor` might be used to partition and scale the acceptor, but it is out of scope for this guide. In practice, you will use existing packages tailored for this use-case, such as [Ranch](https://github.com/ninenines/ranch) (in Erlang) or [Thousand Island](https://github.com/mtrudel/thousand_island) (in Elixir).
 
 In the next chapter, we will start parsing the client requests and sending responses, finishing our server.

From ff29628602c656dc6ebb0b3b72f1be9c79aaefc1 Mon Sep 17 00:00:00 2001
From: Steffen Deusch <steffen@deusch.me>
Date: Fri, 10 Jan 2025 20:02:36 +0100
Subject: [PATCH 079/139] Track mix_pubsub and mix_lock folders per user
 (#14171)

---
 lib/mix/lib/mix/sync/lock.ex   | 9 ++++++++-
 lib/mix/lib/mix/sync/pubsub.ex | 9 ++++++++-
 2 files changed, 16 insertions(+), 2 deletions(-)

diff --git a/lib/mix/lib/mix/sync/lock.ex b/lib/mix/lib/mix/sync/lock.ex
index dc65e57713c..62369a6e0fe 100644
--- a/lib/mix/lib/mix/sync/lock.ex
+++ b/lib/mix/lib/mix/sync/lock.ex
@@ -97,7 +97,7 @@ defmodule Mix.Sync.Lock do
     opts = Keyword.validate!(opts, [:on_taken])
 
     hash = key |> :erlang.md5() |> Base.url_encode64(padding: false)
-    path = Path.join([System.tmp_dir!(), "mix_lock", hash])
+    path = Path.join(base_path(), hash)
 
     pdict_key = {__MODULE__, path}
     has_lock? = Process.get(pdict_key, false)
@@ -119,6 +119,13 @@ defmodule Mix.Sync.Lock do
     end
   end
 
+  defp base_path do
+    user = System.get_env("USER", "default")
+    path = Path.join([System.tmp_dir!(), "mix_lock_#{Base.url_encode64(user, padding: false)}"])
+    File.mkdir_p!(path)
+    path
+  end
+
   defp lock_disabled?(), do: System.get_env("MIX_OS_CONCURRENCY_LOCK") in ~w(0 false)
 
   defp lock(path, on_taken) do
diff --git a/lib/mix/lib/mix/sync/pubsub.ex b/lib/mix/lib/mix/sync/pubsub.ex
index 8c72406e944..b048f69f2a5 100644
--- a/lib/mix/lib/mix/sync/pubsub.ex
+++ b/lib/mix/lib/mix/sync/pubsub.ex
@@ -272,9 +272,16 @@ defmodule Mix.Sync.PubSub do
 
   defp hash(key), do: :erlang.md5(key)
 
+  defp base_path do
+    user = System.get_env("USER", "default")
+    path = Path.join([System.tmp_dir!(), "mix_pubsub_#{Base.url_encode64(user, padding: false)}"])
+    File.mkdir_p!(path)
+    path
+  end
+
   defp path(hash) do
     hash = Base.url_encode64(hash, padding: false)
-    Path.join([System.tmp_dir!(), "mix_pubsub", hash])
+    Path.join(base_path(), hash)
   end
 
   defp recv(socket, size, timeout \\ :infinity) do

From c78f426bf547cc905a647763ae0a58fe8a1528bc Mon Sep 17 00:00:00 2001
From: Claudio Ortolina <cloud8421@gmail.com>
Date: Thu, 9 Jan 2025 21:49:21 +0000
Subject: [PATCH 080/139] Improve clarity of Path.safe_relative/2 argument
 names (#14167)

Switches from `cwd` to `relative_to`, which is more generic and more consistent with possible use cases of the function.
---
 lib/elixir/lib/path.ex | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/lib/elixir/lib/path.ex b/lib/elixir/lib/path.ex
index 7dca45f018c..535cac5ea6c 100644
--- a/lib/elixir/lib/path.ex
+++ b/lib/elixir/lib/path.ex
@@ -884,7 +884,7 @@ defmodule Path do
     * A `..` component would make it so that the path would traverse up above
       the root of `relative_to`.
 
-    * A symbolic link in the path points to something above the root of `cwd`.
+    * A symbolic link in the path points to something above the root of `relative_to`.
 
   ## Examples
 
@@ -906,10 +906,10 @@ defmodule Path do
   """
   @doc since: "1.14.0"
   @spec safe_relative(t, t) :: {:ok, binary} | :error
-  def safe_relative(path, cwd \\ File.cwd!()) do
+  def safe_relative(path, relative_to \\ File.cwd!()) do
     path = IO.chardata_to_string(path)
 
-    case :filelib.safe_relative_path(path, cwd) do
+    case :filelib.safe_relative_path(path, relative_to) do
       :unsafe -> :error
       relative_path -> {:ok, IO.chardata_to_string(relative_path)}
     end

From 9593cef2f5146486afcacb205ceae9674851e5e4 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jonatan=20K=C5=82osko?= <jonatanklosko@gmail.com>
Date: Sat, 11 Jan 2025 10:53:25 +0100
Subject: [PATCH 081/139] Remove duplicate call to mkdir in concurrency lock
 (#14175)

---
 lib/mix/lib/mix/sync/lock.ex   |  5 ++---
 lib/mix/lib/mix/sync/pubsub.ex | 13 ++++++-------
 2 files changed, 8 insertions(+), 10 deletions(-)

diff --git a/lib/mix/lib/mix/sync/lock.ex b/lib/mix/lib/mix/sync/lock.ex
index 62369a6e0fe..89bc5826646 100644
--- a/lib/mix/lib/mix/sync/lock.ex
+++ b/lib/mix/lib/mix/sync/lock.ex
@@ -120,10 +120,9 @@ defmodule Mix.Sync.Lock do
   end
 
   defp base_path do
+    # We include user in the dir to avoid permission conflicts across users
     user = System.get_env("USER", "default")
-    path = Path.join([System.tmp_dir!(), "mix_lock_#{Base.url_encode64(user, padding: false)}"])
-    File.mkdir_p!(path)
-    path
+    Path.join(System.tmp_dir!(), "mix_lock_#{Base.url_encode64(user, padding: false)}")
   end
 
   defp lock_disabled?(), do: System.get_env("MIX_OS_CONCURRENCY_LOCK") in ~w(0 false)
diff --git a/lib/mix/lib/mix/sync/pubsub.ex b/lib/mix/lib/mix/sync/pubsub.ex
index b048f69f2a5..bf58a098af2 100644
--- a/lib/mix/lib/mix/sync/pubsub.ex
+++ b/lib/mix/lib/mix/sync/pubsub.ex
@@ -272,18 +272,17 @@ defmodule Mix.Sync.PubSub do
 
   defp hash(key), do: :erlang.md5(key)
 
-  defp base_path do
-    user = System.get_env("USER", "default")
-    path = Path.join([System.tmp_dir!(), "mix_pubsub_#{Base.url_encode64(user, padding: false)}"])
-    File.mkdir_p!(path)
-    path
-  end
-
   defp path(hash) do
     hash = Base.url_encode64(hash, padding: false)
     Path.join(base_path(), hash)
   end
 
+  defp base_path do
+    # We include user in the dir to avoid permission conflicts across users
+    user = System.get_env("USER", "default")
+    Path.join(System.tmp_dir!(), "mix_pubsub_#{Base.url_encode64(user, padding: false)}")
+  end
+
   defp recv(socket, size, timeout \\ :infinity) do
     # eintr is "Interrupted system call".
     with {:error, :eintr} <- :gen_tcp.recv(socket, size, timeout) do

From 6f7eaf1122951d1f7e504dbf0be3d4e8ab8e6a48 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jos=C3=A9=20Valim?= <jose.valim@dashbit.co>
Date: Sun, 12 Jan 2025 20:08:30 +0100
Subject: [PATCH 082/139] Keep line information in defstruct

---
 lib/elixir/lib/kernel.ex               | 34 +++++++++-----------------
 lib/elixir/lib/kernel/utils.ex         |  6 ++---
 lib/elixir/test/elixir/kernel_test.exs |  4 +++
 3 files changed, 18 insertions(+), 26 deletions(-)

diff --git a/lib/elixir/lib/kernel.ex b/lib/elixir/lib/kernel.ex
index 04777ddb320..5a454e3281c 100644
--- a/lib/elixir/lib/kernel.ex
+++ b/lib/elixir/lib/kernel.ex
@@ -5463,33 +5463,21 @@ defmodule Kernel do
   use `@type`.
   """
   defmacro defstruct(fields) do
-    header =
-      quote bind_quoted: [fields: fields, bootstrapped?: bootstrapped?(Enum)] do
-        {struct, derive, escaped_struct, kv, body} =
-          Kernel.Utils.defstruct(__MODULE__, fields, bootstrapped?, __ENV__)
-
-        case derive do
-          [] -> :ok
-          _ -> Protocol.__derive__(derive, __MODULE__, __ENV__)
-        end
-      end
+    quote bind_quoted: [fields: fields, bootstrapped?: bootstrapped?(Enum)] do
+      {struct, derive, escaped_struct, kv, body} =
+        Kernel.Utils.defstruct(__MODULE__, fields, bootstrapped?, __ENV__)
 
-    # We attach the line: 0 to struct functions because we don't want
-    # the generated callbacks to count towards code coverage and metrics,
-    # especially since they are often expanded at compile-time.
-    functions =
-      quote line: 0, unquote: false do
-        def __struct__(), do: unquote(escaped_struct)
-        def __struct__(unquote(kv)), do: unquote(body)
+      case derive do
+        [] -> :ok
+        _ -> Protocol.__derive__(derive, __MODULE__, __ENV__)
       end
 
-    footer =
-      quote do
-        Kernel.Utils.announce_struct(__MODULE__)
-        struct
-      end
+      def __struct__(), do: unquote(escaped_struct)
+      def __struct__(unquote(kv)), do: unquote(body)
 
-    {:__block__, [], [header, functions, footer]}
+      Kernel.Utils.announce_struct(__MODULE__)
+      struct
+    end
   end
 
   @doc ~S"""
diff --git a/lib/elixir/lib/kernel/utils.ex b/lib/elixir/lib/kernel/utils.ex
index 251be8c19de..b2c2ffc6aed 100644
--- a/lib/elixir/lib/kernel/utils.ex
+++ b/lib/elixir/lib/kernel/utils.ex
@@ -174,14 +174,14 @@ defmodule Kernel.Utils do
         true ->
           case enforce_keys do
             [] ->
-              quote do
+              quote line: 0, generated: true do
                 Enum.reduce(kv, unquote(escaped_struct), fn {key, val}, map ->
                   %{map | key => val}
                 end)
               end
 
             _ ->
-              quote do
+              quote line: 0, generated: true do
                 {map, keys} =
                   Enum.reduce(kv, {unquote(escaped_struct), unquote(enforce_keys)}, fn
                     {key, val}, {map, keys} ->
@@ -201,7 +201,7 @@ defmodule Kernel.Utils do
           end
 
         false ->
-          quote do
+          quote line: 0, generated: true do
             :lists.foldl(
               fn {key, val}, acc -> %{acc | key => val} end,
               unquote(escaped_struct),
diff --git a/lib/elixir/test/elixir/kernel_test.exs b/lib/elixir/test/elixir/kernel_test.exs
index fa4b7364a1a..4a458e9fd17 100644
--- a/lib/elixir/test/elixir/kernel_test.exs
+++ b/lib/elixir/test/elixir/kernel_test.exs
@@ -205,6 +205,10 @@ defmodule KernelTest do
 
   defmodule User do
     assert is_map(defstruct name: "john")
+    # Ensure we keep the line information around.
+    # It is important for debugging tools, ExDoc, etc.
+    {:v1, :def, anno, _clauses} = Module.get_definition(__MODULE__, {:__struct__, 1})
+    anno[:line] == __ENV__.line - 4
   end
 
   test "struct/1 and struct/2" do

From 98aeee34b651827887f7054411f0720e0d7ba58b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jos=C3=A9=20Valim?= <jose.valim@dashbit.co>
Date: Mon, 13 Jan 2025 07:33:06 +0100
Subject: [PATCH 083/139] Simplify bat and ps1 files (#14180)

---
 bin/elixir.bat |  4 ++--
 bin/elixir.ps1 | 50 ++++++++------------------------------------------
 2 files changed, 10 insertions(+), 44 deletions(-)

diff --git a/bin/elixir.bat b/bin/elixir.bat
index 781cd9ad066..251a6aaec24 100644
--- a/bin/elixir.bat
+++ b/bin/elixir.bat
@@ -133,9 +133,9 @@ if not defined useIEx (
 set beforeExtra=-noshell -elixir_root "%SCRIPT_PATH%..\lib" -pa "%SCRIPT_PATH%..\lib\elixir\ebin" %beforeExtra%
 
 if defined ELIXIR_CLI_DRY_RUN (
-  echo "%ERTS_BIN%erl.exe" %ext_libs% %ELIXIR_ERL_OPTIONS% %parsErlang% %beforeExtra% -extra %*
+  echo "%ERTS_BIN%erl.exe" %ELIXIR_ERL_OPTIONS% %parsErlang% %beforeExtra% -extra %*
 ) else (
-  "%ERTS_BIN%erl.exe" %ext_libs% %ELIXIR_ERL_OPTIONS% %parsErlang% %beforeExtra% -extra %*
+  "%ERTS_BIN%erl.exe" %ELIXIR_ERL_OPTIONS% %parsErlang% %beforeExtra% -extra %*
 )
 exit /B %ERRORLEVEL%
 :end
diff --git a/bin/elixir.ps1 b/bin/elixir.ps1
index c53c82273f3..b7b4d8e263d 100755
--- a/bin/elixir.ps1
+++ b/bin/elixir.ps1
@@ -76,14 +76,6 @@ if (($allArgs.Count -eq 0) -or (($allArgs.Count -eq 1) -and ($allArgs[0] -in @("
   exit 1
 }
 
-function NormalizeArg {
-  param(
-    [Parameter(Mandatory = $true, ValueFromPipeline = $true)]
-    [string[]] $Items
-  )
-  $Items -join ","
-}
-
 function QuoteString {
   param(
     [Parameter(ValueFromPipeline = $true)]
@@ -103,7 +95,6 @@ function QuoteString {
   }
 }
 
-$elixirParams = @()
 $erlangParams = @()
 $beforeExtras = @()
 $allOtherParams = @()
@@ -111,32 +102,16 @@ $allOtherParams = @()
 $runErlPipe = $null
 $runErlLog = $null
 
-for ($i = 0; $i -lt $allArgs.Count; $i++) {
+:loop for ($i = 0; $i -lt $allArgs.Count; $i++) {
   $private:arg = $allArgs[$i]
 
   switch -exact ($arg) {
     { $_ -in @("-e", "-r", "-pr", "-pa", "-pz", "--eval", "--remsh", "--dot-iex", "--dbg") } {
-      $private:nextArg = NormalizeArg($allArgs[++$i])
-
-      $elixirParams += $arg
-      $elixirParams += $nextArg
-
+      ++$i
       break
     }
 
-    { $_ -in @("-v", "--version") } {
-      # Standalone options goes only once in the Elixir params, when they are empty.
-      if (($elixirParams.Count -eq 0) -and ($allOtherParams.Count -eq 0)) {
-        $elixirParams += $arg
-      }
-      else {
-        $allOtherParams += $arg
-      }
-      break
-    }
-
-    "--no-halt" {
-      $elixirParams += $arg
+    { $_ -in @("-v", "--version", "--no-halt") } {
       break
     }
 
@@ -204,14 +179,11 @@ for ($i = 0; $i -lt $allArgs.Count; $i++) {
     }
 
     "+iex" {
-      $elixirParams += "+iex"
       $useIex = $true
-
       break
     }
 
     "+elixirc" {
-      $elixirParams += "+elixirc"
       break
     }
 
@@ -229,9 +201,6 @@ for ($i = 0; $i -lt $allArgs.Count; $i++) {
         exit 1
       }
 
-      $elixirParams += "--rpc-eval"
-      $elixirParams += $key
-      $elixirParams += $value
       break
     }
 
@@ -249,16 +218,14 @@ for ($i = 0; $i -lt $allArgs.Count; $i++) {
         exit 1
       }
 
-      $elixirParams += "-boot_var"
-      $elixirParams += $key
-      $elixirParams += $value
+      $erlangParams += "-boot_var"
+      $erlangParams += $key
+      $erlangParams += $value
       break
     }
 
     Default {
-      $private:normalized = NormalizeArg $arg
-      $allOtherParams += $normalized
-      break
+      break :loop
     }
   }
 }
@@ -280,8 +247,7 @@ if ($null -ne $env:ELIXIR_ERL_OPTIONS) {
 $allParams += $erlangParams
 $allParams += $beforeExtras
 $allParams += "-extra"
-$allParams += $elixirParams
-$allParams += $allOtherParams
+$allParams += $allArgs
 
 $binSuffix = ""
 

From 786f3ce79721779efdd4985989c72264b98b2e7c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jos=C3=A9=20Valim?= <jose.valim@dashbit.co>
Date: Mon, 13 Jan 2025 08:54:07 +0100
Subject: [PATCH 084/139] Add --color for easy enabling and disabling of
 coloring (#14183)

---
 bin/elixir                   |  3 ++-
 bin/elixir.bat               |  2 ++
 bin/elixir.ps1               |  3 ++-
 lib/elixir/lib/kernel/cli.ex | 18 ++++++++++++++++++
 4 files changed, 24 insertions(+), 2 deletions(-)

diff --git a/bin/elixir b/bin/elixir
index 69fa4b4695a..676a0d21e8f 100755
--- a/bin/elixir
+++ b/bin/elixir
@@ -18,6 +18,7 @@ Usage: $(basename "$0") [options] [.exs file] [data]
   -pz "PATH"                   Appends the given path to Erlang code path (*)
   -v, --version                Prints Erlang/OTP and Elixir versions (standalone)
 
+  --color BOOL                 Enables or disables ANSI coloring
   --erl "SWITCHES"             Switches to be passed down to Erlang (*)
   --eval "COMMAND"             Evaluates the given command, same as -e (*)
   --logger-otp-reports BOOL    Enables or disables OTP reporting
@@ -114,7 +115,7 @@ while [ $I -le $LENGTH ]; do
     -v|--no-halt)
         C=1
         ;;
-    -e|-r|-pr|-pa|-pz|--eval|--remsh|--dot-iex|--dbg)
+    -e|-r|-pr|-pa|-pz|--eval|--remsh|--dot-iex|--dbg|--color)
         C=2
         ;;
     --rpc-eval)
diff --git a/bin/elixir.bat b/bin/elixir.bat
index 251a6aaec24..ddf06ee1f41 100644
--- a/bin/elixir.bat
+++ b/bin/elixir.bat
@@ -24,6 +24,7 @@ echo   -pa "PATH"                   Prepends the given path to Erlang code path
 echo   -pz "PATH"                   Appends the given path to Erlang code path (*)
 echo   -v, --version                Prints Erlang/OTP and Elixir versions (standalone)
 echo.
+echo   --color BOOL                 Enables or disables ANSI coloring
 echo   --erl "SWITCHES"             Switches to be passed down to Erlang (*)
 echo   --eval "COMMAND"             Evaluates the given command, same as -e (*)
 echo   --logger-otp-reports BOOL    Enables or disables OTP reporting
@@ -110,6 +111,7 @@ if ""==!par:--no-halt=!   (goto startloop)
 if ""==!par:--remsh=!     (shift && goto startloop)
 if ""==!par:--dot-iex=!   (shift && goto startloop)
 if ""==!par:--dbg=!       (shift && goto startloop)
+if ""==!par:--color=!     (shift && goto startloop)
 rem ******* ERLANG PARAMETERS **********************
 if ""==!par:--boot=!                (set "parsErlang=!parsErlang! -boot "%~1"" && shift && goto startloop)
 if ""==!par:--boot-var=!            (set "parsErlang=!parsErlang! -boot_var "%~1" "%~2"" && shift && shift && goto startloop)
diff --git a/bin/elixir.ps1 b/bin/elixir.ps1
index b7b4d8e263d..007f3aa05ca 100755
--- a/bin/elixir.ps1
+++ b/bin/elixir.ps1
@@ -26,6 +26,7 @@ Usage: $scriptName [options] [.exs file] [data]
   -pz "PATH"                   Appends the given path to Erlang code path (*)
   -v, --version                Prints Erlang/OTP and Elixir versions (standalone)
 
+  --color                      Enables or disables ANSI coloring
   --erl "SWITCHES"             Switches to be passed down to Erlang (*)
   --eval "COMMAND"             Evaluates the given command, same as -e (*)
   --logger-otp-reports BOOL    Enables or disables OTP reporting
@@ -106,7 +107,7 @@ $runErlLog = $null
   $private:arg = $allArgs[$i]
 
   switch -exact ($arg) {
-    { $_ -in @("-e", "-r", "-pr", "-pa", "-pz", "--eval", "--remsh", "--dot-iex", "--dbg") } {
+    { $_ -in @("-e", "-r", "-pr", "-pa", "-pz", "--eval", "--remsh", "--dot-iex", "--dbg", "--color") } {
       ++$i
       break
     }
diff --git a/lib/elixir/lib/kernel/cli.ex b/lib/elixir/lib/kernel/cli.ex
index c4b302b236b..517a47ce277 100644
--- a/lib/elixir/lib/kernel/cli.ex
+++ b/lib/elixir/lib/kernel/cli.ex
@@ -295,6 +295,24 @@ defmodule Kernel.CLI do
     parse_argv(t, %{config | commands: [{:parallel_require, h} | config.commands]})
   end
 
+  defp parse_argv([~c"--color", value | t], config) do
+    config =
+      case value do
+        ~c"true" ->
+          Application.put_env(:elixir, :ansi_enabled, true)
+          config
+
+        ~c"false" ->
+          Application.put_env(:elixir, :ansi_enabled, false)
+          config
+
+        _ ->
+          %{config | errors: ["--color : must be a boolean" | config.errors]}
+      end
+
+    parse_argv(t, config)
+  end
+
   ## Compiler
 
   defp parse_argv([~c"-o", h | t], %{mode: :elixirc} = config) do

From 88c75bcbf5c52b7d1c5f976298afc18f33b1f8a4 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jos=C3=A9=20Valim?= <jose.valim@dashbit.co>
Date: Mon, 13 Jan 2025 11:29:21 +0100
Subject: [PATCH 085/139] Remove powershell scripts

It current leaves the shell broken after quitting Erlang.

Issues have been reported upstream, so we may be able
to bring it back in future Erlang/OTP versions.
---
 bin/elixir.ps1                             | 271 ---------------------
 bin/elixirc.ps1                            |  35 ---
 bin/iex.ps1                                |  30 ---
 bin/mix.ps1                                |  32 ++-
 lib/elixir/test/elixir/kernel/cli_test.exs |   4 +-
 5 files changed, 22 insertions(+), 350 deletions(-)
 delete mode 100755 bin/elixir.ps1
 delete mode 100755 bin/elixirc.ps1
 delete mode 100755 bin/iex.ps1

diff --git a/bin/elixir.ps1 b/bin/elixir.ps1
deleted file mode 100755
index 007f3aa05ca..00000000000
--- a/bin/elixir.ps1
+++ /dev/null
@@ -1,271 +0,0 @@
-#!/usr/bin/env pwsh
-
-$ELIXIR_VERSION = "1.18.1"
-
-$scriptPath = Split-Path -Parent $PSCommandPath
-$erlExec = "erl"
-
-# The iex.ps1, elixirc.ps1 and mix.ps1 scripts may populate this var.
-if ($null -eq $allArgs) {
-  $allArgs = $args
-}
-
-function PrintElixirHelp {
-  $scriptName = Split-Path -Leaf $PSCommandPath
-  $help = @"
-Usage: $scriptName [options] [.exs file] [data]
-
-## General options
-
-  -e "COMMAND"                 Evaluates the given command (*)
-  -h, --help                   Prints this message (standalone)
-  -r "FILE"                    Requires the given files/patterns (*)
-  -S SCRIPT                    Finds and executes the given script in `$PATH
-  -pr "FILE"                   Requires the given files/patterns in parallel (*)
-  -pa "PATH"                   Prepends the given path to Erlang code path (*)
-  -pz "PATH"                   Appends the given path to Erlang code path (*)
-  -v, --version                Prints Erlang/OTP and Elixir versions (standalone)
-
-  --color                      Enables or disables ANSI coloring
-  --erl "SWITCHES"             Switches to be passed down to Erlang (*)
-  --eval "COMMAND"             Evaluates the given command, same as -e (*)
-  --logger-otp-reports BOOL    Enables or disables OTP reporting
-  --logger-sasl-reports BOOL   Enables or disables SASL reporting
-  --no-halt                    Does not halt the Erlang VM after execution
-  --short-version              Prints Elixir version (standalone)
-
-Options given after the .exs file or -- are passed down to the executed code.
-Options can be passed to the Erlang runtime using `$ELIXIR_ERL_OPTIONS or --erl.
-
-## Distribution options
-
-The following options are related to node distribution.
-
-  --cookie COOKIE              Sets a cookie for this distributed node
-  --hidden                     Makes a hidden node
-  --name NAME                  Makes and assigns a name to the distributed node
-  --rpc-eval NODE "COMMAND"    Evaluates the given command on the given remote node (*)
-  --sname NAME                 Makes and assigns a short name to the distributed node
-
---name and --sname may be set to undefined so one is automatically generated.
-
-## Release options
-
-The following options are generally used under releases.
-
-  --boot "FILE"                Uses the given FILE.boot to start the system
-  --boot-var VAR "VALUE"       Makes `$VAR available as VALUE to FILE.boot (*)
-  --erl-config "FILE"          Loads configuration in FILE.config written in Erlang (*)
-  --vm-args "FILE"             Passes the contents in file as arguments to the VM
-
---pipe-to is not supported via PowerShell.
-
-** Options marked with (*) can be given more than once.
-** Standalone options can't be combined with other options.
-"@
-
-  Write-Host $help
-}
-
-if (($allArgs.Count -eq 1) -and ($allArgs[0] -eq "--short-version")) {
-  Write-Host "$ELIXIR_VERSION"
-  exit
-}
-
-if (($allArgs.Count -eq 0) -or (($allArgs.Count -eq 1) -and ($allArgs[0] -in @("-h", "--help")))) {
-  PrintElixirHelp
-  exit 1
-}
-
-function QuoteString {
-  param(
-    [Parameter(ValueFromPipeline = $true)]
-    [string] $Item
-  )
-
-  # We surround the string with double quotes, in order to preserve its contents as
-  # only one command arg.
-  # This is needed because PowerShell consider spaces as separator of arguments.
-  # The double quotes around will be removed when PowerShell process the argument.
-  # See: https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_quoting_rules?view=powershell-7.4#passing-quoted-strings-to-external-commands
-  if ($Item.Contains(" ")) {
-    '"' + $Item + '"'
-  }
-  else {
-    $Item
-  }
-}
-
-$erlangParams = @()
-$beforeExtras = @()
-$allOtherParams = @()
-
-$runErlPipe = $null
-$runErlLog = $null
-
-:loop for ($i = 0; $i -lt $allArgs.Count; $i++) {
-  $private:arg = $allArgs[$i]
-
-  switch -exact ($arg) {
-    { $_ -in @("-e", "-r", "-pr", "-pa", "-pz", "--eval", "--remsh", "--dot-iex", "--dbg", "--color") } {
-      ++$i
-      break
-    }
-
-    { $_ -in @("-v", "--version", "--no-halt") } {
-      break
-    }
-
-    "--cookie" {
-      $erlangParams += "-setcookie"
-      $erlangParams += $allArgs[++$i]
-      break
-    }
-
-    "--hidden" {
-      $erlangParams += "-hidden"
-      break
-    }
-
-    "--name" {
-      $erlangParams += "-name"
-      $erlangParams += $allArgs[++$i]
-      break
-    }
-
-    "--sname" {
-      $erlangParams += "-sname"
-      $erlangParams += $allArgs[++$i]
-      break
-    }
-
-    "--boot" {
-      $erlangParams += "-boot"
-      $erlangParams += $allArgs[++$i]
-      break
-    }
-
-    "--erl-config" {
-      $erlangParams += "-config"
-      $erlangParams += $allArgs[++$i]
-      break
-    }
-
-    "--vm-args" {
-      $erlangParams += "-args_file"
-      $erlangParams += $allArgs[++$i]
-      break
-    }
-
-    "--logger-otp-reports" {
-      $private:tempVal = $allArgs[$i + 1]
-      if ($tempVal -in @("true", "false")) {
-        $erlangParams += @("-logger", "handle_otp_reports", $allArgs[++$i])
-      }
-      break
-    }
-
-    "--logger-sasl-reports" {
-      $private:tempVal = $allArgs[$i + 1]
-      if ($tempVal -in @("true", "false")) {
-        $erlangParams += @("-logger", "handle_sasl_reports", $allArgs[++$i])
-      }
-      break
-    }
-
-    "--erl" {
-      $private:erlFlags = $allArgs[++$i] -split " "
-      $beforeExtras += $erlFlags
-      break
-    }
-
-    "+iex" {
-      $useIex = $true
-      break
-    }
-
-    "+elixirc" {
-      break
-    }
-
-    "--rpc-eval" {
-      $private:key = $allArgs[++$i]
-      $private:value = $allArgs[++$i]
-
-      if ($null -eq $key) {
-        Write-Error "--rpc-eval: NODE must be present"
-        exit 1
-      }
-
-      if ($null -eq $value) {
-        Write-Error "--rpc-eval: COMMAND for the '$key' node must be present"
-        exit 1
-      }
-
-      break
-    }
-
-    "--boot-var" {
-      $private:key = $allArgs[++$i]
-      $private:value = $allArgs[++$i]
-
-      if ($null -eq $key) {
-        Write-Error "--boot-var: VAR must be present"
-        exit 1
-      }
-
-      if ($null -eq $value) {
-        Write-Error "--boot-var: Value for the '$key' var must be present"
-        exit 1
-      }
-
-      $erlangParams += "-boot_var"
-      $erlangParams += $key
-      $erlangParams += $value
-      break
-    }
-
-    Default {
-      break :loop
-    }
-  }
-}
-
-if ($null -eq $useIEx) {
-  $beforeExtras = @("-s", "elixir", "start_cli") + $beforeExtras
-}
-
-$beforeExtras = @("-pa", "$(Join-Path $scriptPath -ChildPath "../lib/elixir/ebin")") + $beforeExtras
-$beforeExtras = @("-noshell", "-elixir_root", "$(Join-Path $scriptPath -ChildPath "../lib")") + $beforeExtras
-
-$allParams = @()
-
-if ($null -ne $env:ELIXIR_ERL_OPTIONS) {
-  $private:erlFlags = $env:ELIXIR_ERL_OPTIONS -split " "
-  $allParams += $erlFlags
-}
-
-$allParams += $erlangParams
-$allParams += $beforeExtras
-$allParams += "-extra"
-$allParams += $allArgs
-
-$binSuffix = ""
-
-# The variable is available after PowerShell 7.2. Previous to that, PS only worked on Windows.
-if ($isWindows -or ($null -eq $isWindows)) {
-  $binSuffix = ".exe"
-}
-
-$binPath = "$erlExec$binSuffix"
-
-# We double the double-quotes because they are going to be escaped by arguments parsing.
-$paramsPart = $allParams | ForEach-Object -Process { QuoteString($_ -replace "`"", "`"`"") }
-
-if ($env:ELIXIR_CLI_DRY_RUN) {
-  Write-Host "$binPath $paramsPart"
-}
-else {
-  $output = Start-Process -FilePath $binPath -ArgumentList $paramsPart -NoNewWindow -Wait -PassThru
-  exit $output.ExitCode
-}
diff --git a/bin/elixirc.ps1 b/bin/elixirc.ps1
deleted file mode 100755
index 29742f010d6..00000000000
--- a/bin/elixirc.ps1
+++ /dev/null
@@ -1,35 +0,0 @@
-#!/usr/bin/env pwsh
-
-$scriptName = Split-Path -Leaf $PSCommandPath
-
-if (($args.Count -eq 0) -or ($args[0] -in @("-h", "--help"))) {
-  Write-Host @"
-Usage: $scriptName [elixir switches] [compiler switches] [.ex files]
-
-  -h, --help                Prints this message and exits
-  -o                        The directory to output compiled files
-  -v, --version             Prints Elixir version and exits (standalone)
-
-  --ignore-module-conflict  Does not emit warnings if a module was previously defined
-  --no-debug-info           Does not attach debug info to compiled modules
-  --no-docs                 Does not attach documentation to compiled modules
-  --profile time            Profile the time to compile modules
-  --verbose                 Prints compilation status
-  --warnings-as-errors      Treats warnings as errors and returns non-zero exit status
-
-** Options given after -- are passed down to the executed code
-** Options can be passed to the Erlang runtime using ELIXIR_ERL_OPTIONS
-** Options can be passed to the Erlang compiler using ERL_COMPILER_OPTIONS
-"@
-  exit
-}
-
-$scriptPath = Split-Path -Parent $PSCommandPath
-$elixirMainScript = Join-Path -Path $scriptPath -ChildPath "elixir.ps1"
-
-$prependedArgs = @("+elixirc")
-
-$allArgs = $prependedArgs + $args
-
-# The dot is going to evaluate the script with the vars defined here.
-. $elixirMainScript
diff --git a/bin/iex.ps1 b/bin/iex.ps1
deleted file mode 100755
index 870909377fb..00000000000
--- a/bin/iex.ps1
+++ /dev/null
@@ -1,30 +0,0 @@
-#!/usr/bin/env pwsh
-
-$scriptName = Split-Path -Leaf $PSCommandPath
-
-if ($args[0] -in @("-h", "--help")) {
-  Write-Host @"
-Usage: $scriptName [options] [.exs file] [data]
-
-The following options are exclusive to IEx:
-
-  --dbg pry           Sets the backend for Kernel.dbg/2 to IEx.pry/0
-  --dot-iex "FILE"    Evaluates FILE, line by line, to set up IEx' environment.
-                      Defaults to evaluating .iex.exs or ~/.iex.exs, if any exists.
-                      If FILE is empty, then no file will be loaded.
-  --remsh NAME        Connects to a node using a remote shell.
-
-It accepts all other options listed by "elixir --help".
-"@
-  exit
-}
-
-$scriptPath = Split-Path -Parent $PSCommandPath
-$elixirMainScript = Join-Path -Path $scriptPath -ChildPath "elixir.ps1"
-
-$prependedArgs = @("--no-halt", "--erl", "-user elixir", "+iex") 
-
-$allArgs = $prependedArgs + $args
-
-# The dot is going to evaluate the script with the vars defined here.
-. $elixirMainScript
diff --git a/bin/mix.ps1 b/bin/mix.ps1
index a1d3bd29458..05b19a04746 100755
--- a/bin/mix.ps1
+++ b/bin/mix.ps1
@@ -1,13 +1,23 @@
-#!/usr/bin/env pwsh
+# Store path to mix.bat as a FileInfo object
+$mixBatPath = (Get-ChildItem (((Get-ChildItem $MyInvocation.MyCommand.Path).Directory.FullName) + '\mix.bat'))
+$newArgs = @()
 
-$scriptPath = Split-Path -Parent $PSCommandPath
-$elixirMainScript = Join-Path -Path $scriptPath -ChildPath "elixir.ps1"
+for ($i = 0; $i -lt $args.length; $i++)
+{
+  if ($args[$i] -is [array])
+  {
+    # Commas created the array so we need to reintroduce those commas
+    for ($j = 0; $j -lt $args[$i].length - 1; $j++)
+    {
+      $newArgs += ($args[$i][$j] + ',')
+    }
+    $newArgs += $args[$i][-1]
+  }
+  else
+  {
+    $newArgs += $args[$i]
+  }
+}
 
-$mixFile = Join-Path -Path $scriptPath -ChildPath "mix"
-
-$prependedArgs = @($mixFile)
-
-$allArgs = $prependedArgs + $args
-
-# The dot is going to evaluate the script with the vars defined here.
-. $elixirMainScript
+# Corrected arguments are ready to pass to batch file
+& $mixBatPath $newArgs
\ No newline at end of file
diff --git a/lib/elixir/test/elixir/kernel/cli_test.exs b/lib/elixir/test/elixir/kernel/cli_test.exs
index 79602df7b72..6779161fad9 100644
--- a/lib/elixir/test/elixir/kernel/cli_test.exs
+++ b/lib/elixir/test/elixir/kernel/cli_test.exs
@@ -64,9 +64,7 @@ end
 test_parameters =
   if(PathHelpers.windows?(),
     do: [%{cli_extension: ".bat"}],
-    else:
-      [%{cli_extension: ""}] ++
-        if(System.find_executable("pwsh"), do: [%{cli_extension: ".ps1"}], else: [])
+    else: [%{cli_extension: ""}]
   )
 
 defmodule Kernel.CLI.ExecutableTest do

From 4def31f8abea5fba44d2d92b11a72f3f9c855efd Mon Sep 17 00:00:00 2001
From: Jean Klingler <sabiwara@gmail.com>
Date: Wed, 15 Jan 2025 20:41:33 +0900
Subject: [PATCH 086/139] Mention the new type system in typespecs doc (#14188)

---
 lib/elixir/pages/references/typespecs.md | 16 ++++++++++++++--
 1 file changed, 14 insertions(+), 2 deletions(-)

diff --git a/lib/elixir/pages/references/typespecs.md b/lib/elixir/pages/references/typespecs.md
index 95eb40253f2..49d39f30e24 100644
--- a/lib/elixir/pages/references/typespecs.md
+++ b/lib/elixir/pages/references/typespecs.md
@@ -1,7 +1,12 @@
 # Typespecs reference
 
-Elixir comes with a notation for declaring types and specifications. This document is a
-reference into their uses and syntax.
+> #### Typespecs are not set-theoretic types {: .warning}
+>
+> Elixir is in the process of implementing its
+> [own type system](./gradual-set-theoretic-types.md) based on set-theoretic types.
+> Typespecs, which are described in the following document, are a distinct notation
+> for declaring types and specifications based on Erlang.
+> Typespecs may be phased out as the set-theoretic type effort moves forward.
 
 Elixir is a dynamically typed language, and as such, type specifications are never used by the compiler to optimize or modify code. Still, using type specifications is useful because:
 
@@ -48,6 +53,13 @@ The syntax Elixir provides for type specifications is similar to [the one in Erl
 
 The notation to represent the union of types is the pipe `|`. For example, the typespec `type :: atom() | pid() | tuple()` creates a type `type` that can be either an `atom`, a `pid`, or a `tuple`. This is usually called a [sum type](https://en.wikipedia.org/wiki/Tagged_union) in other languages
 
+> #### Differences with set-theoretic types {: .warning}
+>
+> While they do share some similarities, the types below do not map one-to-one
+> to the new types from the set theoretic type system.
+> For example, there is no plan to support subsets of the `integer()` type such
+> as positive, ranges or literals.
+
 ### Basic types
 
     type ::

From 01a88e7137aa601dcce8e49952f9f7990a357eba Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jos=C3=A9=20Valim?= <jose.valim@dashbit.co>
Date: Mon, 20 Jan 2025 18:51:29 +0100
Subject: [PATCH 087/139] Simplify token pruning, closes #14139

---
 lib/elixir/src/elixir_tokenizer.erl | 119 ++++++++++++++--------------
 1 file changed, 58 insertions(+), 61 deletions(-)

diff --git a/lib/elixir/src/elixir_tokenizer.erl b/lib/elixir/src/elixir_tokenizer.erl
index 5823299a39b..33f08c0ff28 100644
--- a/lib/elixir/src/elixir_tokenizer.erl
+++ b/lib/elixir/src/elixir_tokenizer.erl
@@ -1763,84 +1763,81 @@ add_cursor(_Line, Column, noprune, Terminators, Tokens) ->
   {Column, Terminators, Tokens};
 add_cursor(Line, Column, prune_and_cursor, Terminators, Tokens) ->
   PrePrunedTokens = prune_identifier(Tokens),
-  {PrunedTokens, PrunedTerminators} = prune_tokens(PrePrunedTokens, [], Terminators),
+  PrunedTokens = prune_tokens(PrePrunedTokens, []),
   CursorTokens = [
     {')', {Line, Column + 11, nil}},
     {'(', {Line, Column + 10, nil}},
     {paren_identifier, {Line, Column, nil}, '__cursor__'}
     | PrunedTokens
   ],
-  {Column + 12, PrunedTerminators, CursorTokens}.
+  {Column + 12, Terminators, CursorTokens}.
 
 prune_identifier([{identifier, _, _} | Tokens]) -> Tokens;
 prune_identifier(Tokens) -> Tokens.
 
 %%% Any terminator needs to be closed
-prune_tokens([{'end', _} | Tokens], Opener, Terminators) ->
-  prune_tokens(Tokens, ['end' | Opener], Terminators);
-prune_tokens([{')', _} | Tokens], Opener, Terminators) ->
-  prune_tokens(Tokens, [')' | Opener], Terminators);
-prune_tokens([{']', _} | Tokens], Opener, Terminators) ->
-  prune_tokens(Tokens, [']' | Opener], Terminators);
-prune_tokens([{'}', _} | Tokens], Opener, Terminators) ->
-  prune_tokens(Tokens, ['}' | Opener], Terminators);
-prune_tokens([{'>>', _} | Tokens], Opener, Terminators) ->
-  prune_tokens(Tokens, ['>>' | Opener], Terminators);
+prune_tokens([{'end', _} | Tokens], Opener) ->
+  prune_tokens(Tokens, ['end' | Opener]);
+prune_tokens([{')', _} | Tokens], Opener) ->
+  prune_tokens(Tokens, [')' | Opener]);
+prune_tokens([{']', _} | Tokens], Opener) ->
+  prune_tokens(Tokens, [']' | Opener]);
+prune_tokens([{'}', _} | Tokens], Opener) ->
+  prune_tokens(Tokens, ['}' | Opener]);
+prune_tokens([{'>>', _} | Tokens], Opener) ->
+  prune_tokens(Tokens, ['>>' | Opener]);
 %%% Close opened terminators
-prune_tokens([{'fn', _} | Tokens], ['end' | Opener], Terminators) ->
-  prune_tokens(Tokens, Opener, Terminators);
-prune_tokens([{'do', _} | Tokens], ['end' | Opener], Terminators) ->
-  prune_tokens(Tokens, Opener, Terminators);
-prune_tokens([{'(', _} | Tokens], [')' | Opener], Terminators) ->
-  prune_tokens(Tokens, Opener, Terminators);
-prune_tokens([{'[', _} | Tokens], [']' | Opener], Terminators) ->
-  prune_tokens(Tokens, Opener, Terminators);
-prune_tokens([{'{', _} | Tokens], ['}' | Opener], Terminators) ->
-  prune_tokens(Tokens, Opener, Terminators);
-prune_tokens([{'<<', _} | Tokens], ['>>' | Opener], Terminators) ->
-  prune_tokens(Tokens, Opener, Terminators);
-%%% Handle anonymous functions
-prune_tokens([{'(', _}, {capture_op, _, _} | Tokens], [], [{'(', _, _} | Terminators]) ->
-  prune_tokens(Tokens, [], Terminators);
+prune_tokens([{'fn', _} | Tokens], ['end' | Opener]) ->
+  prune_tokens(Tokens, Opener);
+prune_tokens([{'do', _} | Tokens], ['end' | Opener]) ->
+  prune_tokens(Tokens, Opener);
+prune_tokens([{'(', _} | Tokens], [')' | Opener]) ->
+  prune_tokens(Tokens, Opener);
+prune_tokens([{'[', _} | Tokens], [']' | Opener]) ->
+  prune_tokens(Tokens, Opener);
+prune_tokens([{'{', _} | Tokens], ['}' | Opener]) ->
+  prune_tokens(Tokens, Opener);
+prune_tokens([{'<<', _} | Tokens], ['>>' | Opener]) ->
+  prune_tokens(Tokens, Opener);
 %%% or it is time to stop...
-prune_tokens([{';', _} | _] = Tokens, [], Terminators) ->
-  {Tokens, Terminators};
-prune_tokens([{'eol', _} | _] = Tokens, [], Terminators) ->
-  {Tokens, Terminators};
-prune_tokens([{',', _} | _] = Tokens, [], Terminators) ->
-  {Tokens, Terminators};
-prune_tokens([{'fn', _} | _] = Tokens, [], Terminators) ->
-  {Tokens, Terminators};
-prune_tokens([{'do', _} | _] = Tokens, [], Terminators) ->
-  {Tokens, Terminators};
-prune_tokens([{'(', _} | _] = Tokens, [], Terminators) ->
-  {Tokens, Terminators};
-prune_tokens([{'[', _} | _] = Tokens, [], Terminators) ->
-  {Tokens, Terminators};
-prune_tokens([{'{', _} | _] = Tokens, [], Terminators) ->
-  {Tokens, Terminators};
-prune_tokens([{'<<', _} | _] = Tokens, [], Terminators) ->
-  {Tokens, Terminators};
-prune_tokens([{identifier, _, _} | _] = Tokens, [], Terminators) ->
-  {Tokens, Terminators};
-prune_tokens([{block_identifier, _, _} | _] = Tokens, [], Terminators) ->
-  {Tokens, Terminators};
-prune_tokens([{kw_identifier, _, _} | _] = Tokens, [], Terminators) ->
-  {Tokens, Terminators};
-prune_tokens([{kw_identifier_safe, _, _} | _] = Tokens, [], Terminators) ->
-  {Tokens, Terminators};
-prune_tokens([{kw_identifier_unsafe, _, _} | _] = Tokens, [], Terminators) ->
-  {Tokens, Terminators};
-prune_tokens([{OpType, _, _} | _] = Tokens, [], Terminators)
+prune_tokens([{';', _} | _] = Tokens, []) ->
+  Tokens;
+prune_tokens([{'eol', _} | _] = Tokens, []) ->
+  Tokens;
+prune_tokens([{',', _} | _] = Tokens, []) ->
+  Tokens;
+prune_tokens([{'fn', _} | _] = Tokens, []) ->
+  Tokens;
+prune_tokens([{'do', _} | _] = Tokens, []) ->
+  Tokens;
+prune_tokens([{'(', _} | _] = Tokens, []) ->
+  Tokens;
+prune_tokens([{'[', _} | _] = Tokens, []) ->
+  Tokens;
+prune_tokens([{'{', _} | _] = Tokens, []) ->
+  Tokens;
+prune_tokens([{'<<', _} | _] = Tokens, []) ->
+  Tokens;
+prune_tokens([{identifier, _, _} | _] = Tokens, []) ->
+  Tokens;
+prune_tokens([{block_identifier, _, _} | _] = Tokens, []) ->
+  Tokens;
+prune_tokens([{kw_identifier, _, _} | _] = Tokens, []) ->
+  Tokens;
+prune_tokens([{kw_identifier_safe, _, _} | _] = Tokens, []) ->
+  Tokens;
+prune_tokens([{kw_identifier_unsafe, _, _} | _] = Tokens, []) ->
+  Tokens;
+prune_tokens([{OpType, _, _} | _] = Tokens, [])
   when OpType =:= comp_op; OpType =:= at_op; OpType =:= unary_op; OpType =:= and_op;
        OpType =:= or_op; OpType =:= arrow_op; OpType =:= match_op; OpType =:= in_op;
        OpType =:= in_match_op; OpType =:= type_op; OpType =:= dual_op; OpType =:= mult_op;
        OpType =:= power_op; OpType =:= concat_op; OpType =:= range_op; OpType =:= xor_op;
        OpType =:= pipe_op; OpType =:= stab_op; OpType =:= when_op; OpType =:= assoc_op;
        OpType =:= rel_op; OpType =:= ternary_op; OpType =:= capture_op; OpType =:= ellipsis_op ->
-  {Tokens, Terminators};
+  Tokens;
 %%% or we traverse until the end.
-prune_tokens([_ | Tokens], Opener, Terminators) ->
-  prune_tokens(Tokens, Opener, Terminators);
-prune_tokens([], [], Terminators) ->
-  {[], Terminators}.
+prune_tokens([_ | Tokens], Opener) ->
+  prune_tokens(Tokens, Opener);
+prune_tokens([], _Opener) ->
+  [].

From 329442c48173c6b4a29e95374ba13c920850e493 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jos=C3=A9=20Valim?= <jose.valim@dashbit.co>
Date: Tue, 21 Jan 2025 10:36:00 +0100
Subject: [PATCH 088/139] Provide more AST around cursor fragments, closes
 #14118

---
 lib/elixir/lib/code/fragment.ex               | 34 +++++++++++++------
 lib/elixir/test/elixir/code_fragment_test.exs |  9 +++--
 2 files changed, 29 insertions(+), 14 deletions(-)

diff --git a/lib/elixir/lib/code/fragment.ex b/lib/elixir/lib/code/fragment.ex
index 74ab83f594d..567f31348cc 100644
--- a/lib/elixir/lib/code/fragment.ex
+++ b/lib/elixir/lib/code/fragment.ex
@@ -1151,7 +1151,7 @@ defmodule Code.Fragment do
         {rev_tokens, rev_terminators} =
           with [close, open, {_, _, :__cursor__} = cursor | rev_tokens] <- rev_tokens,
                {_, [_ | after_fn]} <- Enum.split_while(rev_terminators, &(elem(&1, 0) != :fn)),
-               true <- maybe_missing_stab?(rev_tokens),
+               true <- maybe_missing_stab?(rev_tokens, false),
                [_ | rev_tokens] <- Enum.drop_while(rev_tokens, &(elem(&1, 0) != :fn)) do
             {[close, open, cursor | rev_tokens], after_fn}
           else
@@ -1165,7 +1165,7 @@ defmodule Code.Fragment do
         tokens =
           with {before_start, [_ | _] = after_start} <-
                  Enum.split_while(rev_terminators, &(elem(&1, 0) not in [:do, :fn])),
-               true <- maybe_missing_stab?(rev_tokens),
+               true <- maybe_missing_stab?(rev_tokens, true),
                opts =
                  Keyword.put(opts, :check_terminators, {:cursor, before_start}),
                {:error, {meta, _, ~c"end"}, _rest, _warnings, trailing_rev_tokens} <-
@@ -1173,6 +1173,14 @@ defmodule Code.Fragment do
             trailing_tokens =
               reverse_tokens(meta[:line], meta[:column], trailing_rev_tokens, after_start)
 
+            # If the cursor has its own line, then we do not trim new lines trailing tokens.
+            # Otherwise we want to drop any newline so we drop the next tokens after eol.
+            trailing_tokens =
+              case rev_tokens do
+                [_close, _open, {_, _, :__cursor__}, {:eol, _} | _] -> trailing_tokens
+                _ -> Enum.drop_while(trailing_tokens, &match?({:eol, _}, &1))
+              end
+
             Enum.reverse(rev_tokens, drop_tokens(trailing_tokens, 0))
           else
             _ -> reverse_tokens(line, column, rev_tokens, rev_terminators)
@@ -1196,12 +1204,16 @@ defmodule Code.Fragment do
     Enum.reverse(tokens, terminators)
   end
 
+  # Otherwise we drop all tokens, trying to build a minimal AST
+  # for cursor completion.
   defp drop_tokens([{:"}", _} | _] = tokens, 0), do: tokens
   defp drop_tokens([{:"]", _} | _] = tokens, 0), do: tokens
   defp drop_tokens([{:")", _} | _] = tokens, 0), do: tokens
   defp drop_tokens([{:">>", _} | _] = tokens, 0), do: tokens
   defp drop_tokens([{:end, _} | _] = tokens, 0), do: tokens
   defp drop_tokens([{:",", _} | _] = tokens, 0), do: tokens
+  defp drop_tokens([{:";", _} | _] = tokens, 0), do: tokens
+  defp drop_tokens([{:eol, _} | _] = tokens, 0), do: tokens
   defp drop_tokens([{:stab_op, _, :->} | _] = tokens, 0), do: tokens
 
   defp drop_tokens([{:"}", _} | tokens], counter), do: drop_tokens(tokens, counter - 1)
@@ -1220,13 +1232,13 @@ defmodule Code.Fragment do
   defp drop_tokens([_ | tokens], counter), do: drop_tokens(tokens, counter)
   defp drop_tokens([], 0), do: []
 
-  defp maybe_missing_stab?([{:after, _} | _]), do: true
-  defp maybe_missing_stab?([{:do, _} | _]), do: true
-  defp maybe_missing_stab?([{:fn, _} | _]), do: true
-  defp maybe_missing_stab?([{:else, _} | _]), do: true
-  defp maybe_missing_stab?([{:catch, _} | _]), do: true
-  defp maybe_missing_stab?([{:rescue, _} | _]), do: true
-  defp maybe_missing_stab?([{:stab_op, _, :->} | _]), do: false
-  defp maybe_missing_stab?([_ | tail]), do: maybe_missing_stab?(tail)
-  defp maybe_missing_stab?([]), do: false
+  defp maybe_missing_stab?([{:after, _} | _], _stab_choice?), do: true
+  defp maybe_missing_stab?([{:do, _} | _], _stab_choice?), do: true
+  defp maybe_missing_stab?([{:fn, _} | _], _stab_choice?), do: true
+  defp maybe_missing_stab?([{:else, _} | _], _stab_choice?), do: true
+  defp maybe_missing_stab?([{:catch, _} | _], _stab_choice?), do: true
+  defp maybe_missing_stab?([{:rescue, _} | _], _stab_choice?), do: true
+  defp maybe_missing_stab?([{:stab_op, _, :->} | _], stab_choice?), do: stab_choice?
+  defp maybe_missing_stab?([_ | tail], stab_choice?), do: maybe_missing_stab?(tail, stab_choice?)
+  defp maybe_missing_stab?([], _stab_choice?), do: false
 end
diff --git a/lib/elixir/test/elixir/code_fragment_test.exs b/lib/elixir/test/elixir/code_fragment_test.exs
index 2736937e810..1efa1974724 100644
--- a/lib/elixir/test/elixir/code_fragment_test.exs
+++ b/lib/elixir/test/elixir/code_fragment_test.exs
@@ -1335,19 +1335,22 @@ defmodule CodeFragmentTest do
 
     test "do -> end" do
       assert cc2q!("if do\nx ->\n", trailing_fragment: "y\nz ->\nw\nend") ==
-               s2q!("if do\nx ->\n__cursor__()\nend")
+               s2q!("if do\nx ->\n__cursor__()\nz -> \nw\nend")
 
       assert cc2q!("if do\nx ->\ny", trailing_fragment: "\nz ->\nw\nend") ==
-               s2q!("if do\nx ->\n__cursor__()\nend")
+               s2q!("if do\nx ->\n__cursor__()\nz -> \nw\nend")
 
       assert cc2q!("if do\nx ->\ny\n", trailing_fragment: "\nz ->\nw\nend") ==
-               s2q!("if do\nx ->\ny\n__cursor__()\nend")
+               s2q!("if do\nx ->\ny\n__cursor__()\nz -> \nw\nend")
 
       assert cc2q!("for x <- [], reduce: %{} do\ny, ", trailing_fragment: "-> :ok\nend") ==
                s2q!("for x <- [], reduce: %{} do\ny, __cursor__() -> :ok\nend")
 
       assert cc2q!("for x <- [], reduce: %{} do\ny, z when ", trailing_fragment: "-> :ok\nend") ==
                s2q!("for x <- [], reduce: %{} do\ny, z when __cursor__() -> :ok\nend")
+
+      assert cc2q!("case do\na -> a\nb = ", trailing_fragment: "c -> c\nend") ==
+               s2q!("case do\na -> a\nb = __cursor__() -> c\nend")
     end
 
     test "removes tokens until opening" do

From f29e18bca1819e2245fd4660035ff48d2bf5a935 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jos=C3=A9=20Valim?= <jose.valim@dashbit.co>
Date: Tue, 21 Jan 2025 18:51:54 +0100
Subject: [PATCH 089/139] Do not precompile regexes on Erlang/OTP 28

---
 lib/elixir/lib/kernel.ex            | 26 +++++++++++++++++---------
 lib/ex_unit/lib/ex_unit/doc_test.ex |  4 +---
 2 files changed, 18 insertions(+), 12 deletions(-)

diff --git a/lib/elixir/lib/kernel.ex b/lib/elixir/lib/kernel.ex
index 5a454e3281c..ed1ec8a9fc4 100644
--- a/lib/elixir/lib/kernel.ex
+++ b/lib/elixir/lib/kernel.ex
@@ -6437,29 +6437,37 @@ defmodule Kernel do
   """
   defmacro sigil_r(term, modifiers)
 
-  defmacro sigil_r({:<<>>, _meta, [string]}, options) when is_binary(string) do
-    binary = :elixir_interpolation.unescape_string(string, &regex_unescape_map/1)
-    regex = Regex.compile!(binary, :binary.list_to_bin(options))
-    Macro.escape(regex)
+  defmacro sigil_r({:<<>>, _meta, [binary]}, options) when is_binary(binary) do
+    binary = :elixir_interpolation.unescape_string(binary, &regex_unescape_map/1)
+    compile_regex(binary, options)
   end
 
   defmacro sigil_r({:<<>>, meta, pieces}, options) do
-    binary = {:<<>>, meta, unescape_tokens(pieces, &regex_unescape_map/1)}
-    quote(do: Regex.compile!(unquote(binary), unquote(:binary.list_to_bin(options))))
+    tuple = {:<<>>, meta, unescape_tokens(pieces, &regex_unescape_map/1)}
+    compile_regex(tuple, options)
   end
 
   defp regex_unescape_map(:newline), do: true
   defp regex_unescape_map(_), do: false
 
   @doc false
-  defmacro sigil_R({:<<>>, _meta, [string]}, options) when is_binary(string) do
+  defmacro sigil_R({:<<>>, _meta, [binary]}, options) when is_binary(binary) do
     IO.warn(
       "~R/.../ is deprecated, use ~r/.../ instead",
       Macro.Env.stacktrace(__CALLER__)
     )
 
-    regex = Regex.compile!(string, :binary.list_to_bin(options))
-    Macro.escape(regex)
+    compile_regex(binary, options)
+  end
+
+  defp compile_regex(binary_or_tuple, options) do
+    case is_binary(binary_or_tuple) and :erlang.system_info(:otp_release) < [?2, ?8] do
+      true ->
+        Macro.escape(Regex.compile!(binary_or_tuple, :binary.list_to_bin(options)))
+
+      false ->
+        quote(do: Regex.compile!(unquote(binary_or_tuple), unquote(:binary.list_to_bin(options))))
+    end
   end
 
   @doc ~S"""
diff --git a/lib/ex_unit/lib/ex_unit/doc_test.ex b/lib/ex_unit/lib/ex_unit/doc_test.ex
index dd354b2e4f8..0b1a04a7bfb 100644
--- a/lib/ex_unit/lib/ex_unit/doc_test.ex
+++ b/lib/ex_unit/lib/ex_unit/doc_test.ex
@@ -149,8 +149,6 @@ defmodule ExUnit.DocTest do
   suite run.
   """
 
-  @opaque_type_regex ~r/#[\w\.]+</
-
   defmodule Error do
     @moduledoc """
     Exception raised when there's an error with the syntax or semantics of a doctest.
@@ -554,7 +552,7 @@ defmodule ExUnit.DocTest do
         message = "Doctest did not compile, got: #{ex_message}"
 
         message =
-          if e.__struct__ == TokenMissingError and expr =~ @opaque_type_regex do
+          if e.__struct__ == TokenMissingError and expr =~ ~r/#[\w\.]+</ do
             message <>
               """
               \nIf you are planning to assert on the result of an iex> expression \

From 2c1a836db32898d2b7a17b0dd28e07425bf46213 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jos=C3=A9=20Valim?= <jose.valim@dashbit.co>
Date: Wed, 22 Jan 2025 17:33:00 +0100
Subject: [PATCH 090/139] Make --color/--no-color consistent with mix test

---
 bin/elixir                    |  6 +++---
 bin/elixir.bat                |  5 +++--
 lib/elixir/lib/kernel/cli.ex  | 20 ++++++--------------
 lib/mix/lib/mix/tasks/test.ex |  2 +-
 4 files changed, 13 insertions(+), 20 deletions(-)

diff --git a/bin/elixir b/bin/elixir
index 676a0d21e8f..9c0af9f1b43 100755
--- a/bin/elixir
+++ b/bin/elixir
@@ -18,7 +18,7 @@ Usage: $(basename "$0") [options] [.exs file] [data]
   -pz "PATH"                   Appends the given path to Erlang code path (*)
   -v, --version                Prints Erlang/OTP and Elixir versions (standalone)
 
-  --color BOOL                 Enables or disables ANSI coloring
+  --color, --no-color          Enables or disables ANSI coloring
   --erl "SWITCHES"             Switches to be passed down to Erlang (*)
   --eval "COMMAND"             Evaluates the given command, same as -e (*)
   --logger-otp-reports BOOL    Enables or disables OTP reporting
@@ -112,10 +112,10 @@ while [ $I -le $LENGTH ]; do
         C=1
         MODE="iex"
         ;;
-    -v|--no-halt)
+    -v|--no-halt|--color|--no-color)
         C=1
         ;;
-    -e|-r|-pr|-pa|-pz|--eval|--remsh|--dot-iex|--dbg|--color)
+    -e|-r|-pr|-pa|-pz|--eval|--remsh|--dot-iex|--dbg)
         C=2
         ;;
     --rpc-eval)
diff --git a/bin/elixir.bat b/bin/elixir.bat
index ddf06ee1f41..fc9392d8ca0 100644
--- a/bin/elixir.bat
+++ b/bin/elixir.bat
@@ -24,7 +24,7 @@ echo   -pa "PATH"                   Prepends the given path to Erlang code path
 echo   -pz "PATH"                   Appends the given path to Erlang code path (*)
 echo   -v, --version                Prints Erlang/OTP and Elixir versions (standalone)
 echo.
-echo   --color BOOL                 Enables or disables ANSI coloring
+echo   --color, --no-color          Enables or disables ANSI coloring
 echo   --erl "SWITCHES"             Switches to be passed down to Erlang (*)
 echo   --eval "COMMAND"             Evaluates the given command, same as -e (*)
 echo   --logger-otp-reports BOOL    Enables or disables OTP reporting
@@ -108,10 +108,11 @@ if ""==!par:-pz=!         (shift && goto startloop)
 if ""==!par:-v=!          (goto startloop)
 if ""==!par:--version=!   (goto startloop)
 if ""==!par:--no-halt=!   (goto startloop)
+if ""==!par:--color=!     (goto startloop)
+if ""==!par:--no-color=!  (goto startloop)
 if ""==!par:--remsh=!     (shift && goto startloop)
 if ""==!par:--dot-iex=!   (shift && goto startloop)
 if ""==!par:--dbg=!       (shift && goto startloop)
-if ""==!par:--color=!     (shift && goto startloop)
 rem ******* ERLANG PARAMETERS **********************
 if ""==!par:--boot=!                (set "parsErlang=!parsErlang! -boot "%~1"" && shift && goto startloop)
 if ""==!par:--boot-var=!            (set "parsErlang=!parsErlang! -boot_var "%~1" "%~2"" && shift && shift && goto startloop)
diff --git a/lib/elixir/lib/kernel/cli.ex b/lib/elixir/lib/kernel/cli.ex
index 517a47ce277..c20c5c2d766 100644
--- a/lib/elixir/lib/kernel/cli.ex
+++ b/lib/elixir/lib/kernel/cli.ex
@@ -295,21 +295,13 @@ defmodule Kernel.CLI do
     parse_argv(t, %{config | commands: [{:parallel_require, h} | config.commands]})
   end
 
-  defp parse_argv([~c"--color", value | t], config) do
-    config =
-      case value do
-        ~c"true" ->
-          Application.put_env(:elixir, :ansi_enabled, true)
-          config
-
-        ~c"false" ->
-          Application.put_env(:elixir, :ansi_enabled, false)
-          config
-
-        _ ->
-          %{config | errors: ["--color : must be a boolean" | config.errors]}
-      end
+  defp parse_argv([~c"--color" | t], config) do
+    Application.put_env(:elixir, :ansi_enabled, true)
+    parse_argv(t, config)
+  end
 
+  defp parse_argv([~c"--no-color" | t], config) do
+    Application.put_env(:elixir, :ansi_enabled, true)
     parse_argv(t, config)
   end
 
diff --git a/lib/mix/lib/mix/tasks/test.ex b/lib/mix/lib/mix/tasks/test.ex
index ec3a0b60c5f..e41bac222ed 100644
--- a/lib/mix/lib/mix/tasks/test.ex
+++ b/lib/mix/lib/mix/tasks/test.ex
@@ -113,7 +113,7 @@ defmodule Mix.Tasks.Test do
       and imports (but not local functions). You can press `n` for the next line
       and `c` for the next test. This automatically sets `--trace`
 
-    * `--color` - enables color in the output
+    * `--color` - enables color in ExUnit formatting results
 
     * `--cover` - runs coverage tool. See "Coverage" section below
 

From 175c8243b23c4cfcaaa99e60b030085bfef8e9a0 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jos=C3=A9=20Valim?= <jose.valim@dashbit.co>
Date: Wed, 22 Jan 2025 17:38:30 +0100
Subject: [PATCH 091/139] Release v1.18.2

---
 CHANGELOG.md   | 34 ++++++++++++++++++++++++++++++++++
 VERSION        |  2 +-
 bin/elixir     |  2 +-
 bin/elixir.bat |  2 +-
 4 files changed, 37 insertions(+), 3 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 844bc7b8c12..a2190326ee0 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -225,6 +225,40 @@ You may also prefer to write using guards:
 
     def foo(x, y, z) when x == y and y == z
 
+## v1.18.2 (2024-01-22)
+
+### 1. Enhancements
+
+#### Elixir
+
+  * [CLI] Add `--color`/`--no-color` for enabling and disabling of ANSI colors
+  * [Code.Fragment] Provide more AST context when invoking `container_cursor_to_quoted` with trailing fragments
+  * [Regex] Ensure compatibility with Erlang/OTP 28+ new Regex engine
+
+#### Mix
+
+  * [mix] Print compilation lock waiting message to stderr
+  * [mix] Add an environment variable to optionally disable compilation locking
+
+### 2. Bug fixes
+
+#### Elixir
+
+  * [CLI] Temporarily remove PowerShell scripts for `elixir`, `elixirc`, and `mix` on Windows, as they leave the shell broken after quitting Erlang
+
+#### ExUnit
+
+  * [ExUnit] Fix crash when diffing bitstring specifiers
+
+#### IEx
+
+  * [IEx.Autocomplete] Fix crashing whhen autocompleting structs with runtime values
+
+#### Mix
+
+  * [mix] Track compilation locks per user to avoid permission errors
+  * [mix deps.update] Ensure Git dependencies can be upgraded by doing so against the origin
+
 ## v1.18.1 (2024-12-24)
 
 ### 1. Enhancements
diff --git a/VERSION b/VERSION
index ec6d649be65..b57fc7228b6 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-1.18.1
+1.18.2
diff --git a/bin/elixir b/bin/elixir
index 9c0af9f1b43..226936a875b 100755
--- a/bin/elixir
+++ b/bin/elixir
@@ -1,7 +1,7 @@
 #!/bin/sh
 set -e
 
-ELIXIR_VERSION=1.18.1
+ELIXIR_VERSION=1.18.2
 
 if [ $# -eq 0 ] || { [ $# -eq 1 ] && { [ "$1" = "--help" ] || [ "$1" = "-h" ]; }; }; then
   cat <<USAGE >&2
diff --git a/bin/elixir.bat b/bin/elixir.bat
index fc9392d8ca0..d9857edc4cd 100644
--- a/bin/elixir.bat
+++ b/bin/elixir.bat
@@ -1,6 +1,6 @@
 @echo off
 
-set ELIXIR_VERSION=1.18.1
+set ELIXIR_VERSION=1.18.2
 
 if    ""%1""==""""                if ""%2""=="""" goto documentation
 if /I ""%1""==""--help""          if ""%2""=="""" goto documentation

From 7426acb1b9d7f7583e2c37d0b582e7a921bed5c6 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jos=C3=A9=20Valim?= <jose.valim@dashbit.co>
Date: Fri, 24 Jan 2025 19:58:24 +0100
Subject: [PATCH 092/139] Use exdoc:loaded to reload mermaid graphs

---
 lib/elixir/scripts/elixir_docs.exs | 20 +++++++++++++-------
 1 file changed, 13 insertions(+), 7 deletions(-)

diff --git a/lib/elixir/scripts/elixir_docs.exs b/lib/elixir/scripts/elixir_docs.exs
index 11086e20a38..0fe4c3f3f8e 100644
--- a/lib/elixir/scripts/elixir_docs.exs
+++ b/lib/elixir/scripts/elixir_docs.exs
@@ -191,12 +191,19 @@ canonical = System.fetch_env!("CANONICAL")
   before_closing_body_tag: fn
     :html ->
       """
+      <script defer src="https://cdn.jsdelivr.net/npm/mermaid@10.2.3/dist/mermaid.min.js"></script>
       <script>
-        function mermaidLoaded() {
-          mermaid.initialize({
-            startOnLoad: false,
-            theme: document.body.className.includes("dark") ? "dark" : "default"
-          });
+        let initialized = false;
+
+        window.addEventListener("exdoc:loaded", () => {
+          if (!initialized) {
+            mermaid.initialize({
+              startOnLoad: false,
+              theme: document.body.className.includes("dark") ? "dark" : "default"
+            });
+            initialized = true;
+          }
+
           let id = 0;
           for (const codeEl of document.querySelectorAll("pre code.mermaid")) {
             const preEl = codeEl.parentElement;
@@ -210,9 +217,8 @@ canonical = System.fetch_env!("CANONICAL")
               preEl.remove();
             });
           }
-        }
+        });
       </script>
-      <script async src="https://cdn.jsdelivr.net/npm/mermaid@10.2.3/dist/mermaid.min.js" onload="mermaidLoaded();"></script>
       """
 
     _ ->

From 8e61baacabb0293fb5bdbd496a203f4415cc5db1 Mon Sep 17 00:00:00 2001
From: Jean Klingler <sabiwara@gmail.com>
Date: Sat, 25 Jan 2025 07:11:26 +0900
Subject: [PATCH 093/139] Fix String.split/3 example in guide and add note in
 docs (#14223)

---
 lib/elixir/lib/string.ex                      |  9 +++++
 .../getting-started/keywords-and-maps.md      | 34 +++++++++++--------
 2 files changed, 28 insertions(+), 15 deletions(-)

diff --git a/lib/elixir/lib/string.ex b/lib/elixir/lib/string.ex
index e8b3a61c8ca..12007e58fb3 100644
--- a/lib/elixir/lib/string.ex
+++ b/lib/elixir/lib/string.ex
@@ -488,6 +488,15 @@ defmodule String do
       iex> String.split(String.normalize("é", :nfc), "e")
       ["é"]
 
+  When using both the `:trim` and the `:parts` option, the empty values
+  are removed as the parts are computed (if any). No trimming happens
+  after all parts are computed:
+
+      iex> String.split(" a  b  c  ", " ", trim: true, parts: 2)
+      ["a", " b  c  "]
+      iex> String.split(" a  b  c  ", " ", trim: true, parts: 3)
+      ["a", "b", " c  "]
+
   """
   @spec split(t, pattern | Regex.t(), keyword) :: [t]
   def split(string, pattern, options \\ [])
diff --git a/lib/elixir/pages/getting-started/keywords-and-maps.md b/lib/elixir/pages/getting-started/keywords-and-maps.md
index 734597eac5f..51cb2ad7120 100644
--- a/lib/elixir/pages/getting-started/keywords-and-maps.md
+++ b/lib/elixir/pages/getting-started/keywords-and-maps.md
@@ -11,36 +11,40 @@ Keyword lists are a data-structure used to pass options to functions. Let's see
 Imagine you want to split a string of numbers. Initially, we can invoke `String.split/2` passing two strings as arguments:
 
 ```elixir
-iex> String.split("1 2 3", " ")
-["1", "2", "3"]
+iex> String.split("1 2 3 4", " ")
+["1", "2", "3", "4"]
 ```
 
-However, what happens if there is an additional space between the numbers:
+What if you only want to split at most 2 times? The `String.split/3` function allows the `parts` option to be set to the maximum number of entries in the result:
 
 ```elixir
-iex> String.split("1  2  3", " ")
-["1", "", "2", "", "3"]
+iex> String.split("1 2 3 4", " ", [parts: 3])
+["1", "2", "3 4"]
 ```
 
-As you can see, there are now empty strings in our results. Luckily, the `String.split/3` function allows the `trim` option to be set to true:
+As you can see, we got 3 parts, the last one containing the remaining of the input without splitting it.
+
+Now imagine that some of the inputs you must split on contains additional spaces between the numbers:
 
 ```elixir
-iex> String.split("1  2  3", " ", [trim: true])
-["1", "2", "3"]
+iex> String.split("1  2  3  4", " ", [parts: 3])
+["1", "", "2  3  4"]
 ```
 
-We can also use options to limit the splitting algorithm to a maximum number of parts, as shown next:
+As you can see, the additional spaces lead to empty entries in the output. Luckily, we can also set the `trim` option to `true` to remove them:
 
 ```elixir
-iex> String.split("1  2  3", " ", [trim: true, parts: 2])
-["1", "2  3"]
+iex> String.split("1  2  3  4", " ", [parts: 3, trim: true])
+["1", "2", " 3  4"]
 ```
 
-`[trim: true]` and `[trim: true, parts: 2]` are keyword lists. When a keyword list is the last argument of a function, we can skip the brackets and write:
+Once again we got 3 parts, with the last one containing the leftovers.
+
+`[parts: 3]` and `[parts: 3, trim: true]` are keyword lists. When a keyword list is the last argument of a function, we can skip the brackets and write:
 
 ```elixir
-iex> String.split("1  2  3", " ", trim: true, parts: 2)
-["1", "2  3"]
+iex> String.split("1  2  3  4", " ", parts: 3, trim: true)
+["1", "2", " 3  4"]
 ```
 
 As shown in the example above, keyword lists are mostly used as optional arguments to functions.
@@ -48,7 +52,7 @@ As shown in the example above, keyword lists are mostly used as optional argumen
 As the name implies, keyword lists are simply lists. In particular, they are lists consisting of 2-item tuples where the first element (the key) is an atom and the second element can be any value. Both representations are the same:
 
 ```elixir
-iex> [{:trim, true}, {:parts, 2}] == [trim: true, parts: 2]
+iex> [{:parts, 3}, {:trim, true}] == [parts: 3, trim: true]
 true
 ```
 

From 55c05d943e9eee57356aaf5217f1133cbacd2237 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?tea=E2=98=86?= <tea@teatwig.net>
Date: Sun, 26 Jan 2025 14:10:03 +0100
Subject: [PATCH 094/139] Fix `--no-color` not setting `:ansi_enabled` to false
 (#14229)

---
 lib/elixir/lib/kernel/cli.ex | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/lib/elixir/lib/kernel/cli.ex b/lib/elixir/lib/kernel/cli.ex
index c20c5c2d766..59d91e1d0bf 100644
--- a/lib/elixir/lib/kernel/cli.ex
+++ b/lib/elixir/lib/kernel/cli.ex
@@ -301,7 +301,7 @@ defmodule Kernel.CLI do
   end
 
   defp parse_argv([~c"--no-color" | t], config) do
-    Application.put_env(:elixir, :ansi_enabled, true)
+    Application.put_env(:elixir, :ansi_enabled, false)
     parse_argv(t, config)
   end
 

From 4b09a836e011cc4e553cd6dd9a043565b8fcb152 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jos=C3=A9=20Valim?= <jose.valim@dashbit.co>
Date: Tue, 28 Jan 2025 10:32:10 +0100
Subject: [PATCH 095/139] Validate signature for forward compatibility (#14235)

---
 lib/elixir/lib/module/parallel_checker.ex | 10 ++++++++--
 1 file changed, 8 insertions(+), 2 deletions(-)

diff --git a/lib/elixir/lib/module/parallel_checker.ex b/lib/elixir/lib/module/parallel_checker.ex
index 5df93d783bf..a22076997cb 100644
--- a/lib/elixir/lib/module/parallel_checker.ex
+++ b/lib/elixir/lib/module/parallel_checker.ex
@@ -439,10 +439,16 @@ defmodule Module.ParallelChecker do
 
   defp cache_chunk(table, module, exports) do
     Enum.each(exports, fn {{fun, arity}, info} ->
-      # TODO: Match on signature directly in Elixir v1.22+
+      sig =
+        case info do
+          %{sig: {:strong, _, _} = sig} -> sig
+          %{sig: {:infer, _} = sig} -> sig
+          _ -> :none
+        end
+
       :ets.insert(
         table,
-        {{module, {fun, arity}}, Map.get(info, :deprecated), Map.get(info, :sig, :none)}
+        {{module, {fun, arity}}, Map.get(info, :deprecated), sig}
       )
     end)
 

From 120467f83c020af650f3648fc91689be649b3157 Mon Sep 17 00:00:00 2001
From: cevado <fcevado@users.noreply.github.com>
Date: Wed, 29 Jan 2025 04:56:49 -0300
Subject: [PATCH 096/139] Add section to Process.exit/2 with differences to
 Kernel.exit/1 (#14238)

---
 lib/elixir/lib/process.ex | 10 ++++++++++
 1 file changed, 10 insertions(+)

diff --git a/lib/elixir/lib/process.ex b/lib/elixir/lib/process.ex
index 43299a1b680..ef1c8c46a7f 100644
--- a/lib/elixir/lib/process.ex
+++ b/lib/elixir/lib/process.ex
@@ -219,6 +219,16 @@ defmodule Process do
 
   Inlined by the compiler.
 
+  > #### Differences to `Kernel.exit/1` {: .info }
+  >
+  > The functions `Kernel.exit/1` and `Process.exit/2` are
+  > named similarly but provide very different functionalities. The
+  > `Kernel:exit/1` function should be used when the intent is to stop the current
+  > process while `Process.exit/2` should be used when the intent is to send an
+  > exit signal to another process. Note also that `Kernel.exit/1` can be caught
+  > with `try/1` while `Process.exit/2` can only be handled by trapping exits and
+  > when the signal is different than `:kill`.
+
   ## Examples
 
       Process.exit(pid, :kill)

From b615c8435abf4a2af71c9de93222ce47a5d82bfa Mon Sep 17 00:00:00 2001
From: Ben Murden <got.ben@gmail.com>
Date: Wed, 29 Jan 2025 16:59:17 +0900
Subject: [PATCH 097/139] Improved help on deprecation warning (#14239)

---
 lib/mix/lib/mix/tasks/cmd.ex | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/lib/mix/lib/mix/tasks/cmd.ex b/lib/mix/lib/mix/tasks/cmd.ex
index cfe43466db2..b4e3001e527 100644
--- a/lib/mix/lib/mix/tasks/cmd.ex
+++ b/lib/mix/lib/mix/tasks/cmd.ex
@@ -63,7 +63,7 @@ defmodule Mix.Tasks.Cmd do
       |> Enum.map(&String.to_atom/1)
 
     if apps != [] do
-      IO.warn("the --app in mix cmd is deprecated")
+      IO.warn("the --app in mix cmd is deprecated. Use mix do --app instead.")
     end
 
     if apps == [] or Mix.Project.config()[:app] in apps do

From 6ecb43061476c0870e24899a23ce8921835920d5 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jos=C3=A9=20Valim?= <jose.valim@dashbit.co>
Date: Wed, 12 Feb 2025 10:02:54 +0100
Subject: [PATCH 098/139] Do not purge on recompile if IEx is not running,
 closes #14260

---
 lib/iex/lib/iex/mix_listener.ex | 10 +++++++++-
 1 file changed, 9 insertions(+), 1 deletion(-)

diff --git a/lib/iex/lib/iex/mix_listener.ex b/lib/iex/lib/iex/mix_listener.ex
index 57f2e0f7f17..ccb194149d3 100644
--- a/lib/iex/lib/iex/mix_listener.ex
+++ b/lib/iex/lib/iex/mix_listener.ex
@@ -12,10 +12,18 @@ defmodule IEx.MixListener do
 
   @doc """
   Unloads all modules invalidated by external compilations.
+
+  Returns `:noop` if there is no module to purge or if
+  the listener is not running (it may happen when connecting
+  via --remsh to a node that was started without IEx).
   """
   @spec purge :: :ok | :noop
   def purge do
-    GenServer.call(@name, :purge, :infinity)
+    if Process.whereis(@name) do
+      GenServer.call(@name, :purge, :infinity)
+    else
+      :noop
+    end
   end
 
   @impl true

From b823f9efdfd5ee950264b93257a7517dfb7b2b81 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jos=C3=A9=20Valim?= <jose.valim@dashbit.co>
Date: Thu, 30 Jan 2025 10:21:32 +0100
Subject: [PATCH 099/139] Allow <<_::3*8>> in typespecs

---
 lib/elixir/lib/code/typespec.ex          |  7 +++++--
 lib/elixir/lib/kernel/typespec.ex        | 18 ++++++++++++++++++
 lib/elixir/test/elixir/typespec_test.exs | 12 ++++++------
 3 files changed, 29 insertions(+), 8 deletions(-)

diff --git a/lib/elixir/lib/code/typespec.ex b/lib/elixir/lib/code/typespec.ex
index 30f0789a479..3efa7be86e0 100644
--- a/lib/elixir/lib/code/typespec.ex
+++ b/lib/elixir/lib/code/typespec.ex
@@ -283,7 +283,6 @@ defmodule Code.Typespec do
   end
 
   defp typespec_to_quoted({:type, anno, :binary, [arg1, arg2]}) do
-    [arg1, arg2] = for arg <- [arg1, arg2], do: typespec_to_quoted(arg)
     line = meta(anno)[:line]
 
     case {typespec_to_quoted(arg1), typespec_to_quoted(arg2)} do
@@ -329,10 +328,14 @@ defmodule Code.Typespec do
     {erl_to_ex_var(var), meta(anno), nil}
   end
 
-  defp typespec_to_quoted({:op, anno, op, arg}) do
+  defp typespec_to_quoted({:op, anno, op, arg}) when op in [:+, :-] do
     {op, meta(anno), [typespec_to_quoted(arg)]}
   end
 
+  defp typespec_to_quoted({:op, anno, :*, arg1, arg2}) do
+    {:*, meta(anno), [typespec_to_quoted(arg1), typespec_to_quoted(arg2)]}
+  end
+
   defp typespec_to_quoted({:remote_type, anno, [mod, name, args]}) do
     remote_type(anno, mod, name, args)
   end
diff --git a/lib/elixir/lib/kernel/typespec.ex b/lib/elixir/lib/kernel/typespec.ex
index 023c86f9fac..149a302e95f 100644
--- a/lib/elixir/lib/kernel/typespec.ex
+++ b/lib/elixir/lib/kernel/typespec.ex
@@ -493,6 +493,24 @@ defmodule Kernel.Typespec do
      state}
   end
 
+  defp typespec(
+         {:<<>>, meta, [{:"::", _, [{:_, _, ctx1}, {:*, prod_meta, [size, unit]}]}]},
+         _,
+         _,
+         state
+       )
+       when is_atom(ctx1) and is_integer(size) and size >= 0 and unit in 1..256 do
+    location = location(meta)
+    prod_location = location(prod_meta)
+
+    {{:type, location, :binary,
+      [
+        {:op, prod_location, :*, {:integer, prod_location, size},
+         {:integer, prod_location, unit}},
+        {:integer, location, 0}
+      ]}, state}
+  end
+
   defp typespec({:<<>>, meta, [{:"::", size_meta, [{:_, _, ctx}, size]}]}, _, _, state)
        when is_atom(ctx) and is_integer(size) and size >= 0 do
     location = location(meta)
diff --git a/lib/elixir/test/elixir/typespec_test.exs b/lib/elixir/test/elixir/typespec_test.exs
index 7cf5e714dc3..0658f2e0f28 100644
--- a/lib/elixir/test/elixir/typespec_test.exs
+++ b/lib/elixir/test/elixir/typespec_test.exs
@@ -393,6 +393,7 @@ defmodule TypespecTest do
           @type size :: <<_::3>>
           @type unit :: <<_::_*8>>
           @type size_and_unit :: <<_::3, _::_*8>>
+          @type size_prod_unit :: <<_::3*8>>
         end
 
       assert [
@@ -401,17 +402,15 @@ defmodule TypespecTest do
                type: {:size, {:type, _, :binary, [{:integer, _, 3}, {:integer, _, 0}]}, []},
                type:
                  {:size_and_unit, {:type, _, :binary, [{:integer, _, 3}, {:integer, _, 8}]}, []},
+               type:
+                 {:size_prod_unit,
+                  {:type, _, :binary,
+                   [{:op, _, :*, {:integer, _, 3}, {:integer, _, 8}}, {:integer, _, 0}]}, []},
                type: {:unit, {:type, _, :binary, [{:integer, _, 0}, {:integer, _, 8}]}, []}
              ] = types(bytecode)
     end
 
     test "@type with invalid binary spec" do
-      assert_raise Kernel.TypespecError, ~r"invalid binary specification", fn ->
-        test_module do
-          @type my_type :: <<_::3*8>>
-        end
-      end
-
       assert_raise Kernel.TypespecError, ~r"invalid binary specification", fn ->
         test_module do
           @type my_type :: <<_::atom()>>
@@ -1217,6 +1216,7 @@ defmodule TypespecTest do
           quote(do: @type(binary_type1() :: <<_::_*8>>)),
           quote(do: @type(binary_type2() :: <<_::3>>)),
           quote(do: @type(binary_type3() :: <<_::3, _::_*8>>)),
+          quote(do: @type(binary_type4() :: <<_::3*8>>)),
           quote(do: @type(tuple_type() :: {integer()})),
           quote(do: @type(ftype() :: (-> any()) | (-> integer()) | (integer() -> integer()))),
           quote(do: @type(cl() :: charlist())),

From d7478095e0524bffb3f058c67fe66aaedbe00245 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jos=C3=A9=20Valim?= <jose.valim@dashbit.co>
Date: Thu, 20 Feb 2025 18:10:23 +0100
Subject: [PATCH 100/139] Support Erlang/OTP 28

---
 lib/elixir/lib/regex.ex                       | 15 ++--
 lib/elixir/src/elixir_erl.erl                 |  7 +-
 .../fixtures/dialyzer/protocol_opaque.ex      | 29 --------
 .../test/elixir/kernel/dialyzer_test.exs      | 17 -----
 lib/elixir/test/elixir/option_parser_test.exs |  5 +-
 lib/elixir/test/elixir/regex_test.exs         | 70 -------------------
 lib/ex_unit/test/ex_unit/assertions_test.exs  |  4 +-
 lib/iex/test/iex/helpers_test.exs             |  2 +-
 8 files changed, 17 insertions(+), 132 deletions(-)
 delete mode 100644 lib/elixir/test/elixir/fixtures/dialyzer/protocol_opaque.ex

diff --git a/lib/elixir/lib/regex.ex b/lib/elixir/lib/regex.ex
index b6cfbc0e23c..6e363325c27 100644
--- a/lib/elixir/lib/regex.ex
+++ b/lib/elixir/lib/regex.ex
@@ -209,17 +209,14 @@ defmodule Regex do
 
   ## Examples
 
-      iex> Regex.compile("foo")
-      {:ok, ~r/foo/}
+      Regex.compile("foo")
+      #=> {:ok, ~r/foo/}
 
-      iex> Regex.compile("*foo")
-      {:error, {~c"nothing to repeat", 0}}
+      Regex.compile("foo", "i")
+      #=>{:ok, ~r/foo/i}
 
-      iex> Regex.compile("foo", "i")
-      {:ok, ~r/foo/i}
-
-      iex> Regex.compile("foo", [:caseless])
-      {:ok, Regex.compile!("foo", [:caseless])}
+      Regex.compile("*foo")
+      #=> {:error, {~c"nothing to repeat", 0}}
 
   """
   @spec compile(binary, binary | [term]) :: {:ok, t} | {:error, term}
diff --git a/lib/elixir/src/elixir_erl.erl b/lib/elixir/src/elixir_erl.erl
index 17ed4d9f0ef..be7339147d0 100644
--- a/lib/elixir/src/elixir_erl.erl
+++ b/lib/elixir/src/elixir_erl.erl
@@ -165,7 +165,12 @@ dynamic_form(#{module := Module, relative_file := RelativeFile,
   {Def, Defmacro, Macros, Exports, Functions} =
     split_definition(Definitions, Unreachable, Line, [], [], [], [], {[], []}),
 
-  FilteredOpts = lists:filter(fun({no_warn_undefined, _}) -> false; (_) -> true end, Opts),
+  FilteredOpts = lists:filter(fun(
+    {no_warn_undefined, _}) -> false;
+    (debug_info) -> false;
+    (_) -> true
+  end, Opts),
+
   Location = {elixir_utils:characters_to_list(RelativeFile), Line},
 
   Prefix = [{attribute, Line, file, Location},
diff --git a/lib/elixir/test/elixir/fixtures/dialyzer/protocol_opaque.ex b/lib/elixir/test/elixir/fixtures/dialyzer/protocol_opaque.ex
deleted file mode 100644
index d6a00f31229..00000000000
--- a/lib/elixir/test/elixir/fixtures/dialyzer/protocol_opaque.ex
+++ /dev/null
@@ -1,29 +0,0 @@
-defmodule Dialyzer.ProtocolOpaque do
-  def circus() do
-    duck = Dialyzer.ProtocolOpaque.Duck.new()
-    Dialyzer.ProtocolOpaque.Entity.speak(duck)
-  end
-end
-
-defprotocol Dialyzer.ProtocolOpaque.Entity do
-  @fallback_to_any true
-  def speak(entity)
-end
-
-defmodule Dialyzer.ProtocolOpaque.Duck do
-  @opaque t :: %__MODULE__{feathers: :white_and_grey}
-  defstruct feathers: :white_and_grey
-
-  @spec new :: t
-  def new(), do: %__MODULE__{}
-
-  defimpl Dialyzer.ProtocolOpaque.Entity do
-    def speak(%Dialyzer.ProtocolOpaque.Duck{}), do: "Quack!"
-  end
-end
-
-defimpl Dialyzer.ProtocolOpaque.Entity, for: Any do
-  def speak(_any) do
-    "I can be anything"
-  end
-end
diff --git a/lib/elixir/test/elixir/kernel/dialyzer_test.exs b/lib/elixir/test/elixir/kernel/dialyzer_test.exs
index d055e71c618..868127c264f 100644
--- a/lib/elixir/test/elixir/kernel/dialyzer_test.exs
+++ b/lib/elixir/test/elixir/kernel/dialyzer_test.exs
@@ -119,23 +119,6 @@ defmodule Kernel.DialyzerTest do
     assert_dialyze_no_warnings!(context)
   end
 
-  @tag warnings: [:specdiffs]
-  test "no warnings on protocol calls with opaque types", context do
-    alias Dialyzer.ProtocolOpaque
-
-    copy_beam!(context, ProtocolOpaque)
-    copy_beam!(context, ProtocolOpaque.Entity)
-    copy_beam!(context, ProtocolOpaque.Entity.Any)
-    copy_beam!(context, ProtocolOpaque.Duck)
-    assert_dialyze_no_warnings!(context)
-
-    # Also ensure no warnings after consolidation.
-    Code.prepend_path(context.base_dir)
-    {:ok, binary} = Protocol.consolidate(ProtocolOpaque.Entity, [ProtocolOpaque.Duck, Any])
-    File.write!(Path.join(context.outdir, "#{ProtocolOpaque.Entity}.beam"), binary)
-    assert_dialyze_no_warnings!(context)
-  end
-
   test "no warnings on and/2 and or/2", context do
     copy_beam!(context, Dialyzer.BooleanCheck)
     assert_dialyze_no_warnings!(context)
diff --git a/lib/elixir/test/elixir/option_parser_test.exs b/lib/elixir/test/elixir/option_parser_test.exs
index 39df43bdcb0..f2cab66a0d1 100644
--- a/lib/elixir/test/elixir/option_parser_test.exs
+++ b/lib/elixir/test/elixir/option_parser_test.exs
@@ -428,10 +428,9 @@ end
 defmodule OptionsParserDeprecationsTest do
   use ExUnit.Case, async: true
 
-  @warning ~r[not passing the :switches or :strict option to OptionParser is deprecated]
-
   def assert_deprecated(fun) do
-    assert ExUnit.CaptureIO.capture_io(:stderr, fun) =~ @warning
+    assert ExUnit.CaptureIO.capture_io(:stderr, fun) =~
+             "not passing the :switches or :strict option to OptionParser is deprecated"
   end
 
   test "parses boolean option" do
diff --git a/lib/elixir/test/elixir/regex_test.exs b/lib/elixir/test/elixir/regex_test.exs
index 9b72e967dbe..917df6d2603 100644
--- a/lib/elixir/test/elixir/regex_test.exs
+++ b/lib/elixir/test/elixir/regex_test.exs
@@ -3,39 +3,6 @@ Code.require_file("test_helper.exs", __DIR__)
 defmodule RegexTest do
   use ExUnit.Case, async: true
 
-  @re_21_3_little %Regex{
-    re_pattern:
-      {:re_pattern, 1, 0, 0,
-       <<69, 82, 67, 80, 94, 0, 0, 0, 0, 0, 0, 0, 17, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255,
-         255, 99, 0, 0, 0, 0, 0, 1, 0, 0, 0, 64, 0, 6, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
-         0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 102, 111, 111, 0, 131, 0, 20, 29, 99, 133,
-         0, 7, 0, 1, 29, 100, 119, 0, 5, 29, 101, 120, 0, 12, 120, 0, 20, 0>>},
-    re_version: {"8.42 2018-03-20", :little},
-    source: "c(?<foo>d|e)"
-  }
-
-  @re_21_3_big %Regex{
-    re_pattern:
-      {:re_pattern, 1, 0, 0,
-       <<80, 67, 82, 69, 0, 0, 0, 86, 0, 0, 0, 0, 0, 0, 0, 17, 255, 255, 255, 255, 255, 255, 255,
-         255, 0, 99, 0, 0, 0, 0, 0, 1, 0, 0, 0, 56, 0, 6, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
-         0, 0, 0, 0, 0, 0, 1, 102, 111, 111, 0, 131, 0, 20, 29, 99, 133, 0, 7, 0, 1, 29, 100, 119,
-         0, 5, 29, 101, 120, 0, 12, 120, 0, 20, 0>>},
-    re_version: {"8.42 2018-03-20", :big},
-    source: "c(?<foo>d|e)"
-  }
-
-  @re_19_3_little %Regex{
-    re_pattern:
-      {:re_pattern, 1, 0, 0,
-       <<69, 82, 67, 80, 94, 0, 0, 0, 0, 0, 0, 0, 17, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255,
-         255, 99, 0, 0, 0, 0, 0, 1, 0, 0, 0, 64, 0, 6, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
-         0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 102, 111, 111, 0, 125, 0, 20, 29, 99, 127,
-         0, 7, 0, 1, 29, 100, 113, 0, 5, 29, 101, 114, 0, 12, 114, 0, 20, 0>>},
-    re_version: {"8.33 2013-05-29", :little},
-    source: "c(?<foo>d|e)"
-  }
-
   doctest Regex
 
   test "multiline" do
@@ -68,16 +35,9 @@ defmodule RegexTest do
   test "literal source" do
     assert Regex.source(Regex.compile!("foo")) == "foo"
     assert Regex.source(~r"foo") == "foo"
-    assert Regex.re_pattern(Regex.compile!("foo")) == Regex.re_pattern(~r"foo")
 
     assert Regex.source(Regex.compile!("\a\b\d\e\f\n\r\s\t\v")) == "\a\b\d\e\f\n\r\s\t\v"
     assert Regex.source(~r<\a\b\d\e\f\n\r\s\t\v>) == "\\a\\b\\d\\e\\f\\n\\r\\s\\t\\v"
-
-    assert Regex.re_pattern(Regex.compile!("\a\b\d\e\f\n\r\s\t\v")) ==
-             Regex.re_pattern(~r"\x07\x08\x7F\x1B\x0C\x0A\x0D\x20\x09\x0B")
-
-    assert Regex.re_pattern(Regex.compile!("\\a\\b\\d\e\f\\n\\r\\s\\t\\v")) ==
-             Regex.re_pattern(~r"\a\b\d\e\f\n\r\s\t\v")
   end
 
   test "Unicode" do
@@ -116,16 +76,6 @@ defmodule RegexTest do
     end
   end
 
-  test "recompile/1" do
-    new_regex = ~r/foo/
-    {:ok, %Regex{}} = Regex.recompile(new_regex)
-    assert %Regex{} = Regex.recompile!(new_regex)
-
-    old_regex = Map.delete(~r/foo/, :re_version)
-    {:ok, %Regex{}} = Regex.recompile(old_regex)
-    assert %Regex{} = Regex.recompile!(old_regex)
-  end
-
   test "opts/1" do
     assert Regex.opts(Regex.compile!("foo", "i")) == [:caseless]
     assert Regex.opts(Regex.compile!("foo", [:ucp])) == [:ucp]
@@ -179,16 +129,6 @@ defmodule RegexTest do
     assert Regex.run(~r"bar", "foobar", offset: 2, return: :index) == [{3, 3}]
   end
 
-  test "run/3 with regexes compiled in different systems" do
-    assert Regex.run(@re_21_3_little, "abcd abce", capture: :all_names) == ["d"]
-    assert Regex.run(@re_21_3_big, "abcd abce", capture: :all_names) == ["d"]
-    assert Regex.run(@re_19_3_little, "abcd abce", capture: :all_names) == ["d"]
-  end
-
-  test "run/3 with regexes with options compiled in different systems" do
-    assert Regex.run(%{~r/foo/i | re_version: "bad version"}, "FOO") == ["FOO"]
-  end
-
   test "scan/2" do
     assert Regex.scan(~r"c(d|e)", "abcd abce") == [["cd", "d"], ["ce", "e"]]
     assert Regex.scan(~r"c(?:d|e)", "abcd abce") == [["cd"], ["ce"]]
@@ -207,16 +147,6 @@ defmodule RegexTest do
     assert Regex.scan(~r"^foo", "foobar", offset: 1) == []
   end
 
-  test "scan/2 with regexes compiled in different systems" do
-    assert Regex.scan(@re_21_3_little, "abcd abce", capture: :all_names) == [["d"], ["e"]]
-    assert Regex.scan(@re_21_3_big, "abcd abce", capture: :all_names) == [["d"], ["e"]]
-    assert Regex.scan(@re_19_3_little, "abcd abce", capture: :all_names) == [["d"], ["e"]]
-  end
-
-  test "scan/2 with regexes with options compiled in different systems" do
-    assert Regex.scan(%{~r/foo/i | re_version: "bad version"}, "FOO") == [["FOO"]]
-  end
-
   test "split/2,3" do
     assert Regex.split(~r",", "") == [""]
     assert Regex.split(~r",", "", trim: true) == []
diff --git a/lib/ex_unit/test/ex_unit/assertions_test.exs b/lib/ex_unit/test/ex_unit/assertions_test.exs
index e2094f44535..8a0d7107993 100644
--- a/lib/ex_unit/test/ex_unit/assertions_test.exs
+++ b/lib/ex_unit/test/ex_unit/assertions_test.exs
@@ -707,7 +707,7 @@ defmodule ExUnit.AssertionsTest do
     rescue
       error in [ExUnit.AssertionError] ->
         "foo" = error.left
-        ~r{a} = error.right
+        %Regex{} = error.right
     end
   end
 
@@ -722,7 +722,7 @@ defmodule ExUnit.AssertionsTest do
     rescue
       error in [ExUnit.AssertionError] ->
         "foo" = error.left
-        ~r"o" = error.right
+        %Regex{} = error.right
     end
   end
 
diff --git a/lib/iex/test/iex/helpers_test.exs b/lib/iex/test/iex/helpers_test.exs
index 1af56114bae..490c97cb5bd 100644
--- a/lib/iex/test/iex/helpers_test.exs
+++ b/lib/iex/test/iex/helpers_test.exs
@@ -349,7 +349,7 @@ defmodule IEx.HelpersTest do
         assert captured =~ "-spec sleep(Time) -> ok when Time :: timeout()."
       else
         assert captured =~ "sleep(Time)"
-        assert captured =~ "@spec sleep(time) :: :ok when time: timeout()"
+        assert captured =~ "@spec sleep(time) :: :ok when time: "
       end
     end
 

From 930ec697398517f2b255c5660adf30ac2689ca33 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jos=C3=A9=20Valim?= <jose.valim@dashbit.co>
Date: Fri, 21 Feb 2025 11:01:36 +0100
Subject: [PATCH 101/139] Support --no-listeners in loadpaths

---
 lib/mix/lib/mix/tasks/deps.loadpaths.ex | 5 ++++-
 lib/mix/lib/mix/tasks/loadpaths.ex      | 1 +
 lib/mix/test/mix/tasks/compile_test.exs | 4 ++++
 3 files changed, 9 insertions(+), 1 deletion(-)

diff --git a/lib/mix/lib/mix/tasks/deps.loadpaths.ex b/lib/mix/lib/mix/tasks/deps.loadpaths.ex
index d7daa070738..b8bba13e019 100644
--- a/lib/mix/lib/mix/tasks/deps.loadpaths.ex
+++ b/lib/mix/lib/mix/tasks/deps.loadpaths.ex
@@ -23,6 +23,7 @@ defmodule Mix.Tasks.Deps.Loadpaths do
     * `--no-compile` - does not compile even if files require compilation
     * `--no-deps-check` - does not check or compile deps, only load available ones
     * `--no-elixir-version-check` - does not check Elixir version
+    * `--no-listeners` - does not start Mix listeners
     * `--no-optional-deps` - does not compile or load optional deps
 
   """
@@ -71,7 +72,9 @@ defmodule Mix.Tasks.Deps.Loadpaths do
 
     # For now we only allow listeners defined in dependencies, so
     # we start them right after adding adding deps to the path
-    Mix.PubSub.start_listeners()
+    if "--no-listeners" not in args do
+      Mix.PubSub.start_listeners()
+    end
 
     :ok
   end
diff --git a/lib/mix/lib/mix/tasks/loadpaths.ex b/lib/mix/lib/mix/tasks/loadpaths.ex
index fffe930f5fc..5d0cc2bc4d8 100644
--- a/lib/mix/lib/mix/tasks/loadpaths.ex
+++ b/lib/mix/lib/mix/tasks/loadpaths.ex
@@ -21,6 +21,7 @@ defmodule Mix.Tasks.Loadpaths do
     * `--no-compile` - does not compile dependencies, only check and load them
     * `--no-deps-check` - does not check dependencies, only load available ones
     * `--no-elixir-version-check` - does not check Elixir version
+    * `--no-listeners` - does not start Mix listeners
     * `--no-optional-deps` - does not compile or load optional deps
 
   """
diff --git a/lib/mix/test/mix/tasks/compile_test.exs b/lib/mix/test/mix/tasks/compile_test.exs
index 2ecf29cbeb7..088887403d1 100644
--- a/lib/mix/test/mix/tasks/compile_test.exs
+++ b/lib/mix/test/mix/tasks/compile_test.exs
@@ -389,6 +389,10 @@ defmodule Mix.Tasks.CompileTest do
       File.write!("src/b.erl", "-module(b).")
       File.write!("src/c.erl", "-module(c).")
 
+      # Ensure we can boot with compilation and listeners if desired
+      assert mix(["loadpaths", "--no-compile", "--no-listeners"]) == ""
+
+      # Now setup dependencies
       mix(["deps.compile"])
 
       parent = self()

From 69990a5d1d3cd00ed0422a87b49841c27e16ff15 Mon Sep 17 00:00:00 2001
From: Jean Klingler <sabiwara@gmail.com>
Date: Fri, 21 Feb 2025 23:08:36 +0900
Subject: [PATCH 102/139] Fix regression when diffing nested improper lists
 (#14292)

Close https://github.com/elixir-lang/elixir/issues/14291
---
 lib/ex_unit/lib/ex_unit/diff.ex             |  1 +
 lib/ex_unit/test/ex_unit/diff_test.exs      |  2 ++
 lib/ex_unit/test/ex_unit/formatter_test.exs | 11 +++++++++++
 3 files changed, 14 insertions(+)

diff --git a/lib/ex_unit/lib/ex_unit/diff.ex b/lib/ex_unit/lib/ex_unit/diff.ex
index 170e988c35a..93fe0113727 100644
--- a/lib/ex_unit/lib/ex_unit/diff.ex
+++ b/lib/ex_unit/lib/ex_unit/diff.ex
@@ -1156,6 +1156,7 @@ defmodule ExUnit.Diff do
     else
       other
       |> Map.to_list()
+      |> Enum.map(&escape_pair/1)
       |> build_map_or_struct(struct)
     end
   end
diff --git a/lib/ex_unit/test/ex_unit/diff_test.exs b/lib/ex_unit/test/ex_unit/diff_test.exs
index a2317cf73e2..87474a2324c 100644
--- a/lib/ex_unit/test/ex_unit/diff_test.exs
+++ b/lib/ex_unit/test/ex_unit/diff_test.exs
@@ -330,6 +330,8 @@ defmodule ExUnit.DiffTest do
       "[[[[], \"Hello-,- \"] | \"world\"] | \"!\"]",
       "[[[[], \"Hello \"] | \"world\"] | \"!\"]"
     )
+
+    refute_diff(:foo = %{bar: [:a | :b]}, "", "")
   end
 
   test "proper lists" do
diff --git a/lib/ex_unit/test/ex_unit/formatter_test.exs b/lib/ex_unit/test/ex_unit/formatter_test.exs
index a1e5506c1f6..6d115da9bc0 100644
--- a/lib/ex_unit/test/ex_unit/formatter_test.exs
+++ b/lib/ex_unit/test/ex_unit/formatter_test.exs
@@ -503,6 +503,17 @@ defmodule ExUnit.FormatterTest do
            """
   end
 
+  test "formats assertions with nested improper list diffing" do
+    failure = [{:error, catch_assertion(assert :foo = %{bar: [1 | 2]}), []}]
+
+    assert format_test_all_failure(test_module(), failure, 1, :infinity, &diff_formatter/2) =~ """
+           match (=) failed
+                code:  assert :foo = %{bar: [1 | 2]}
+                left:  :foo
+                right: %{bar: [1 | 2]}
+           """
+  end
+
   defmodule BadInspect do
     defstruct key: 0
 

From d51153b56e506d319ff804210bf2e0f1db053080 Mon Sep 17 00:00:00 2001
From: Graham Preston <graham.preston@gmail.com>
Date: Wed, 26 Feb 2025 17:57:17 +0100
Subject: [PATCH 103/139] Fix typo (#14302)

---
 CHANGELOG.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index a2190326ee0..f72b17c8bca 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -252,7 +252,7 @@ You may also prefer to write using guards:
 
 #### IEx
 
-  * [IEx.Autocomplete] Fix crashing whhen autocompleting structs with runtime values
+  * [IEx.Autocomplete] Fix crashing when autocompleting structs with runtime values
 
 #### Mix
 

From d89fabe5e4466dde69182c8000d9250af765983b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jos=C3=A9=20Valim?= <jose.valim@dashbit.co>
Date: Thu, 27 Feb 2025 07:59:57 +0100
Subject: [PATCH 104/139] Fix date in CHANGELOG, closes #14303

---
 CHANGELOG.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index f72b17c8bca..ab679fd0c90 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -225,7 +225,7 @@ You may also prefer to write using guards:
 
     def foo(x, y, z) when x == y and y == z
 
-## v1.18.2 (2024-01-22)
+## v1.18.2 (2025-01-22)
 
 ### 1. Enhancements
 

From c63aeb9f77339f8c181d810984b8d64a5f017bd7 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jos=C3=A9=20Valim?= <jose.valim@dashbit.co>
Date: Sat, 1 Mar 2025 09:53:15 +0100
Subject: [PATCH 105/139] Clarify order on uniq/uniq_by, closes #14304

---
 lib/elixir/lib/enum.ex | 6 +++++-
 1 file changed, 5 insertions(+), 1 deletion(-)

diff --git a/lib/elixir/lib/enum.ex b/lib/elixir/lib/enum.ex
index d2181d42e7f..575c1b16c6c 100644
--- a/lib/elixir/lib/enum.ex
+++ b/lib/elixir/lib/enum.ex
@@ -3837,6 +3837,9 @@ defmodule Enum do
   @doc """
   Enumerates the `enumerable`, removing all duplicate elements.
 
+  The first occurrence of each element is kept and all following
+  duplicates are removed. The overall order is preserved.
+
   ## Examples
 
       iex> Enum.uniq([1, 2, 3, 3, 2, 1])
@@ -3862,7 +3865,8 @@ defmodule Enum do
   considered duplicates if the return value of `fun` is equal for
   both of them.
 
-  The first occurrence of each element is kept.
+  The first occurrence of each element is kept and all following
+  duplicates are removed. The overall order is preserved.
 
   ## Example
 

From 8493f1934a820d9ccbe9c6fc4ad74a6d8c3c2ec8 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jos=C3=A9=20Valim?= <jose.valim@dashbit.co>
Date: Mon, 3 Mar 2025 12:54:49 +0100
Subject: [PATCH 106/139] Do not raise when Stream.cycle is explicitly halted,
 closes #14307

---
 lib/elixir/lib/stream.ex             |  2 +-
 lib/elixir/test/elixir/enum_test.exs | 12 ++++++++++++
 2 files changed, 13 insertions(+), 1 deletion(-)

diff --git a/lib/elixir/lib/stream.ex b/lib/elixir/lib/stream.ex
index 495edcf12bd..a69e7f5d3b9 100644
--- a/lib/elixir/lib/stream.ex
+++ b/lib/elixir/lib/stream.ex
@@ -1444,7 +1444,7 @@ defmodule Stream do
   defp check_cycle_first_element(reduce) do
     fn acc ->
       case reduce.(acc) do
-        {state, []} when state in [:done, :halted] ->
+        {state, []} when state in [:done, :halted] and elem(acc, 0) != :halt ->
           raise ArgumentError, "cannot cycle over an empty enumerable"
 
         other ->
diff --git a/lib/elixir/test/elixir/enum_test.exs b/lib/elixir/test/elixir/enum_test.exs
index 483849c4f22..e19c21381bf 100644
--- a/lib/elixir/test/elixir/enum_test.exs
+++ b/lib/elixir/test/elixir/enum_test.exs
@@ -1517,6 +1517,18 @@ defmodule EnumTest do
     assert Enum.zip([], []) == []
   end
 
+  test "zip/2 with infinite streams" do
+    assert Enum.zip([], Stream.cycle([1, 2])) == []
+    assert Enum.zip([], Stream.cycle(1..2)) == []
+    assert Enum.zip(.., Stream.cycle([1, 2])) == []
+    assert Enum.zip(.., Stream.cycle(1..2)) == []
+
+    assert Enum.zip(Stream.cycle([1, 2]), ..) == []
+    assert Enum.zip(Stream.cycle(1..2), ..) == []
+    assert Enum.zip(Stream.cycle([1, 2]), ..) == []
+    assert Enum.zip(Stream.cycle(1..2), ..) == []
+  end
+
   test "zip/1" do
     assert Enum.zip([[:a, :b], [1, 2], ["foo", "bar"]]) == [{:a, 1, "foo"}, {:b, 2, "bar"}]
 

From 25ab64850224229a497da8b331948f2eec1ca1db Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=C5=81ukasz=20Samson?= <lukaszsamson@gmail.com>
Date: Wed, 5 Mar 2025 09:44:11 +0100
Subject: [PATCH 107/139] Fix autocomplete crash when expanding struct with
 `__MODULE__` (#14308)

---
 lib/iex/lib/iex/autocomplete.ex        | 8 +++++---
 lib/iex/test/iex/autocomplete_test.exs | 3 +++
 2 files changed, 8 insertions(+), 3 deletions(-)

diff --git a/lib/iex/lib/iex/autocomplete.ex b/lib/iex/lib/iex/autocomplete.ex
index 531b129e661..5dbd9992690 100644
--- a/lib/iex/lib/iex/autocomplete.ex
+++ b/lib/iex/lib/iex/autocomplete.ex
@@ -378,7 +378,8 @@ defmodule IEx.Autocomplete do
     case Code.Fragment.container_cursor_to_quoted(code) do
       {:ok, quoted} ->
         case Macro.path(quoted, &match?({:__cursor__, _, []}, &1)) do
-          [cursor, {:%{}, _, pairs}, {:%, _, [{:__aliases__, _, aliases}, _map]} | _] ->
+          [cursor, {:%{}, _, pairs}, {:%, _, [{:__aliases__, _, aliases = [h | _]}, _map]} | _]
+          when is_atom(h) ->
             container_context_struct(cursor, pairs, aliases, shell)
 
           [
@@ -386,8 +387,9 @@ defmodule IEx.Autocomplete do
             pairs,
             {:|, _, _},
             {:%{}, _, _},
-            {:%, _, [{:__aliases__, _, aliases}, _map]} | _
-          ] ->
+            {:%, _, [{:__aliases__, _, aliases = [h | _]}, _map]} | _
+          ]
+          when is_atom(h) ->
             container_context_struct(cursor, pairs, aliases, shell)
 
           [cursor, pairs, {:|, _, [{variable, _, nil} | _]}, {:%{}, _, _} | _] ->
diff --git a/lib/iex/test/iex/autocomplete_test.exs b/lib/iex/test/iex/autocomplete_test.exs
index c9d7747091e..e4d14196306 100644
--- a/lib/iex/test/iex/autocomplete_test.exs
+++ b/lib/iex/test/iex/autocomplete_test.exs
@@ -416,6 +416,9 @@ defmodule IEx.AutocompleteTest do
     assert {:yes, ~c"ry: ", []} = expand(~c"%URI{path: \"foo\", que")
     assert {:no, [], []} = expand(~c"%URI{path: \"foo\", unkno")
     assert {:no, [], []} = expand(~c"%Unknown{path: \"foo\", unkno")
+
+    assert {:yes, [], _} = expand(~c"%__MODULE__{")
+    assert {:yes, [], _} = expand(~c"%__MODULE__.Some{")
   end
 
   test "completion for struct keys in update syntax" do

From 75677b9dabf4e9f1f45aa6fe328e5ca1328dd35b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jos=C3=A9=20Valim?= <jose.valim@dashbit.co>
Date: Wed, 5 Mar 2025 09:47:33 +0100
Subject: [PATCH 108/139] Encode any JSON key to string, closes #14305 (#14309)

---
 lib/elixir/lib/json.ex               | 46 ++++++++++++++++++++--------
 lib/elixir/test/elixir/json_test.exs |  8 ++---
 2 files changed, 37 insertions(+), 17 deletions(-)

diff --git a/lib/elixir/lib/json.ex b/lib/elixir/lib/json.ex
index ec602bf1b7f..0cd9285c44f 100644
--- a/lib/elixir/lib/json.ex
+++ b/lib/elixir/lib/json.ex
@@ -146,8 +146,30 @@ end
 
 defimpl JSON.Encoder, for: Map do
   def encode(value, encoder) do
-    :elixir_json.encode_map(value, encoder)
+    case :maps.next(:maps.iterator(value)) do
+      :none ->
+        "{}"
+
+      {key, value, iterator} ->
+        [?{, key(key, encoder), ?:, encoder.(value, encoder) | next(iterator, encoder)]
+    end
   end
+
+  defp next(iterator, encoder) do
+    case :maps.next(iterator) do
+      :none ->
+        "}"
+
+      {key, value, iterator} ->
+        [?,, key(key, encoder), ?:, encoder.(value, encoder) | next(iterator, encoder)]
+    end
+  end
+
+  # Erlang supports only numbers, binaries, and atoms as keys,
+  # we support anything that implements the String.Chars protocol.
+  defp key(key, encoder) when is_atom(key), do: encoder.(Atom.to_string(key), encoder)
+  defp key(key, encoder) when is_binary(key), do: encoder.(key, encoder)
+  defp key(key, encoder), do: encoder.(String.Chars.to_string(key), encoder)
 end
 
 defimpl JSON.Encoder, for: [Date, Time, NaiveDateTime, DateTime, Duration] do
@@ -175,17 +197,15 @@ defmodule JSON do
 
   Elixir built-in data structures are encoded to JSON as follows:
 
-  | **Elixir**             | **JSON** |
-  |------------------------|----------|
-  | `integer() \| float()` | Number   |
-  | `true \| false `       | Boolean  |
-  | `nil`                  | Null     |
-  | `binary()`             | String   |
-  | `atom()`               | String   |
-  | `list()`               | Array    |
-  | `%{binary() => _}`     | Object   |
-  | `%{atom() => _}`       | Object   |
-  | `%{integer() => _}`    | Object   |
+  | **Elixir**                 | **JSON** |
+  |----------------------------|----------|
+  | `integer() \| float()`     | Number   |
+  | `true \| false `           | Boolean  |
+  | `nil`                      | Null     |
+  | `binary()`                 | String   |
+  | `atom()`                   | String   |
+  | `list()`                   | Array    |
+  | `%{String.Chars.t() => _}` | Object   |
 
   You may also implement the `JSON.Encoder` protocol for custom data structures.
 
@@ -403,7 +423,7 @@ defmodule JSON do
     do: :elixir_json.encode_list(value, encoder)
 
   def protocol_encode(%{} = value, encoder) when not is_map_key(value, :__struct__),
-    do: :elixir_json.encode_map(value, encoder)
+    do: JSON.Encoder.Map.encode(value, encoder)
 
   def protocol_encode(value, encoder),
     do: JSON.Encoder.encode(value, encoder)
diff --git a/lib/elixir/test/elixir/json_test.exs b/lib/elixir/test/elixir/json_test.exs
index 5bbc56243f1..6411db2fbe0 100644
--- a/lib/elixir/test/elixir/json_test.exs
+++ b/lib/elixir/test/elixir/json_test.exs
@@ -33,8 +33,8 @@ defmodule JSONTest do
     end
 
     test "maps" do
-      assert JSON.encode!(%{1 => 2, 3.0 => 4.0, key: :bar}) ==
-               "{\"1\":2,\"3.0\":4.0,\"key\":\"bar\"}"
+      assert JSON.encode!(%{1 => 2, 3.0 => 4.0, ~c"list" => ~c"list", key: :bar}) ==
+               "{\"1\":2,\"3.0\":4.0,\"key\":\"bar\",\"list\":[108,105,115,116]}"
     end
 
     test "lists" do
@@ -80,8 +80,8 @@ defmodule JSONTest do
     end
 
     test "maps" do
-      assert protocol_encode(%{1 => 2, 3.0 => 4.0, key: :bar}) ==
-               "{\"1\":2,\"3.0\":4.0,\"key\":\"bar\"}"
+      assert protocol_encode(%{1 => 2, 3.0 => 4.0, ~c"list" => ~c"list", key: :bar}) ==
+               "{\"1\":2,\"3.0\":4.0,\"key\":\"bar\",\"list\":[108,105,115,116]}"
     end
 
     test "lists" do

From 178643f9eb3c684543a2fe139071e038ef264fa5 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jos=C3=A9=20Valim?= <jose.valim@dashbit.co>
Date: Thu, 6 Mar 2025 10:39:22 +0100
Subject: [PATCH 109/139] Do not discard nil on protocol concat, closes #14311
 (#14314)

---
 lib/elixir/lib/module.ex                 |  6 ++++-
 lib/elixir/lib/protocol.ex               | 33 ++++++++++++++++++------
 lib/elixir/test/elixir/protocol_test.exs |  7 +++--
 3 files changed, 35 insertions(+), 11 deletions(-)

diff --git a/lib/elixir/lib/module.ex b/lib/elixir/lib/module.ex
index b31eeb3203f..8c388d9b487 100644
--- a/lib/elixir/lib/module.ex
+++ b/lib/elixir/lib/module.ex
@@ -946,7 +946,8 @@ defmodule Module do
   @doc """
   Concatenates two aliases and returns a new alias.
 
-  It handles binaries and atoms.
+  It handles binaries and atoms. If one of the aliases
+  is nil, it is discarded.
 
   ## Examples
 
@@ -956,6 +957,9 @@ defmodule Module do
       iex> Module.concat(Foo, "Bar")
       Foo.Bar
 
+      iex> Module.concat(Foo, nil)
+      Foo
+
   """
   @spec concat(binary | atom, binary | atom) :: atom
   def concat(left, right)
diff --git a/lib/elixir/lib/protocol.ex b/lib/elixir/lib/protocol.ex
index 247b2b59e27..526131f5d9b 100644
--- a/lib/elixir/lib/protocol.ex
+++ b/lib/elixir/lib/protocol.ex
@@ -347,7 +347,7 @@ defmodule Protocol do
   end
 
   defp assert_impl!(protocol, base, extra) do
-    impl = Module.concat(protocol, base)
+    impl = Protocol.__concat__(protocol, base)
 
     try do
       Code.ensure_compiled!(impl)
@@ -678,7 +678,7 @@ defmodule Protocol do
   end
 
   defp load_impl(protocol, for) do
-    Module.concat(protocol, for)
+    Protocol.__concat__(protocol, for)
   end
 
   # Finally compile the module and emit its bytecode.
@@ -831,7 +831,7 @@ defmodule Protocol do
       # Define the implementation for built-ins
       :lists.foreach(
         fn {guard, mod} ->
-          target = Module.concat(__MODULE__, mod)
+          target = Protocol.__concat__(__MODULE__, mod)
 
           Kernel.def impl_for(data) when :erlang.unquote(guard)(data) do
             case Code.ensure_compiled(unquote(target)) do
@@ -875,7 +875,7 @@ defmodule Protocol do
 
       # Internal handler for Structs
       Kernel.defp struct_impl_for(struct) do
-        case Code.ensure_compiled(Module.concat(__MODULE__, struct)) do
+        case Code.ensure_compiled(Protocol.__concat__(__MODULE__, struct)) do
           {:module, module} -> module
           {:error, _} -> unquote(any_impl_for)
         end
@@ -948,7 +948,7 @@ defmodule Protocol do
     quote do
       protocol = unquote(protocol)
       for = unquote(for)
-      name = Module.concat(protocol, for)
+      name = Protocol.__concat__(protocol, for)
 
       Protocol.assert_protocol!(protocol)
       Protocol.__ensure_defimpl__(protocol, for, __ENV__)
@@ -994,7 +994,7 @@ defmodule Protocol do
       else
         # TODO: Deprecate this on Elixir v1.22+
         assert_impl!(protocol, Any, extra)
-        {Module.concat(protocol, Any), [for, Macro.struct!(for, env), opts]}
+        {Protocol.__concat__(protocol, Any), [for, Macro.struct!(for, env), opts]}
       end
 
     # Clean up variables from eval context
@@ -1006,7 +1006,7 @@ defmodule Protocol do
       else
         __ensure_defimpl__(protocol, for, env)
         assert_impl!(protocol, Any, extra)
-        impl = Module.concat(protocol, Any)
+        impl = Protocol.__concat__(protocol, Any)
 
         funs =
           for {fun, arity} <- protocol.__protocol__(:functions) do
@@ -1031,7 +1031,11 @@ defmodule Protocol do
             def __impl__(:for), do: unquote(for)
           end
 
-        Module.create(Module.concat(protocol, for), [quoted | funs], Macro.Env.location(env))
+        Module.create(
+          Protocol.__concat__(protocol, for),
+          [quoted | funs],
+          Macro.Env.location(env)
+        )
       end
     end)
   end
@@ -1070,4 +1074,17 @@ defmodule Protocol do
       is_reference: Reference
     ]
   end
+
+  @doc false
+  def __concat__(left, right) do
+    String.to_atom(
+      ensure_prefix(Atom.to_string(left)) <> "." <> remove_prefix(Atom.to_string(right))
+    )
+  end
+
+  defp ensure_prefix("Elixir." <> _ = left), do: left
+  defp ensure_prefix(left), do: "Elixir." <> left
+
+  defp remove_prefix("Elixir." <> right), do: right
+  defp remove_prefix(right), do: right
 end
diff --git a/lib/elixir/test/elixir/protocol_test.exs b/lib/elixir/test/elixir/protocol_test.exs
index 210f41b7088..91d20a76b98 100644
--- a/lib/elixir/test/elixir/protocol_test.exs
+++ b/lib/elixir/test/elixir/protocol_test.exs
@@ -106,15 +106,18 @@ defmodule ProtocolTest do
     assert Sample.impl_for(%ImplStruct{}) == Sample.ProtocolTest.ImplStruct
     assert Sample.impl_for(%ImplStructExplicitFor{}) == Sample.ProtocolTest.ImplStructExplicitFor
     assert Sample.impl_for(%NoImplStruct{}) == nil
+    assert is_nil(Sample.impl_for(%{__struct__: nil}))
   end
 
   test "protocol implementation with Any and struct fallbacks" do
     assert WithAny.impl_for(%NoImplStruct{}) == WithAny.Any
-    # Derived
-    assert WithAny.impl_for(%ImplStruct{}) == ProtocolTest.WithAny.ProtocolTest.ImplStruct
+    assert WithAny.impl_for(%{__struct__: nil}) == WithAny.Any
     assert WithAny.impl_for(%{__struct__: "foo"}) == WithAny.Map
     assert WithAny.impl_for(%{}) == WithAny.Map
     assert WithAny.impl_for(self()) == WithAny.Any
+
+    # Derived
+    assert WithAny.impl_for(%ImplStruct{}) == ProtocolTest.WithAny.ProtocolTest.ImplStruct
   end
 
   test "protocol not implemented" do

From e35ffc5a903bff3b595e323eb1ac12c4ecd515ad Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jos=C3=A9=20Valim?= <jose.valim@dashbit.co>
Date: Thu, 6 Mar 2025 10:55:08 +0100
Subject: [PATCH 110/139] Release v1.18.3

---
 CHANGELOG.md   | 50 +++++++++++++++++++++++++++++++++++++++-----------
 RELEASE.md     |  2 +-
 VERSION        |  2 +-
 bin/elixir     |  2 +-
 bin/elixir.bat |  2 +-
 5 files changed, 43 insertions(+), 15 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index ab679fd0c90..a3c83b55b39 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -162,17 +162,15 @@ Both encoder and decoder fully conform to [RFC 8259](https://tools.ietf.org/html
 
 Encoding can be done via `JSON.encode!/1` and `JSON.encode_to_iodata!/1` functions. The default encoding rules are applied as follows:
 
-| **Elixir**             | **JSON** |
-|------------------------|----------|
-| `integer() \| float()` | Number   |
-| `true \| false `       | Boolean  |
-| `nil`                  | Null     |
-| `binary()`             | String   |
-| `atom()`               | String   |
-| `list()`               | Array    |
-| `%{binary() => _}`     | Object   |
-| `%{atom() => _}`       | Object   |
-| `%{integer() => _}`    | Object   |
+| **Elixir**                  | **JSON** |
+|-----------------------------|----------|
+| `integer() \| float()`      | Number   |
+| `true \| false `            | Boolean  |
+| `nil`                       | Null     |
+| `binary()`                  | String   |
+| `atom()`                    | String   |
+| `list()`                    | Array    |
+| `%{String.Chars.t() => _}`  | Object   |
 
 You may also implement the `JSON.Encoder` protocol for custom data structures. Elixir already implements the protocol for all Calendar types.
 
@@ -225,6 +223,36 @@ You may also prefer to write using guards:
 
     def foo(x, y, z) when x == y and y == z
 
+## v1.18.3 (2025-03-06)
+
+### 1. Enhancements
+
+#### Elixir
+
+  * [JSON] Encode any JSON key to string
+  * [Kernel] Allow `<<_::3*8>>` in typespecs
+
+#### Mix
+
+  * [mix loadpaths] Support `--no-listeners` option
+
+### 2. Bug fixes
+
+#### Elixir
+
+  * [CLI] Fix `--no-color` not setting `:ansi_enabled` to false
+  * [Protocol] Return correct implementation for an invalid struct pointing to `nil`
+  * [Stream] Do not raise when `Stream.cycle/1` is explicitly halted
+
+#### ExUnit
+
+  * [ExUnit.Diff] Fix regression when diffing nested improper lists
+
+#### IEx
+
+  * [IEx.Autocomplete] Fix autocomplete crash when expanding struct with `__MODULE__`
+  * [IEx.Helpers] Do not purge on `recompile` if IEx is not running
+
 ## v1.18.2 (2025-01-22)
 
 ### 1. Enhancements
diff --git a/RELEASE.md b/RELEASE.md
index 83aa5ffd958..79ba58241ce 100644
--- a/RELEASE.md
+++ b/RELEASE.md
@@ -2,7 +2,7 @@
 
 ## Shipping a new version
 
-1. Update version in /VERSION, bin/elixir, bin/elixir.bat, and bin/elixir.ps1
+1. Update version in /VERSION, bin/elixir, and bin/elixir.bat
 
 2. Ensure /CHANGELOG.md is updated, versioned and add the current date
 
diff --git a/VERSION b/VERSION
index b57fc7228b6..72582753e34 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-1.18.2
+1.18.3
\ No newline at end of file
diff --git a/bin/elixir b/bin/elixir
index 226936a875b..79cb49484fe 100755
--- a/bin/elixir
+++ b/bin/elixir
@@ -1,7 +1,7 @@
 #!/bin/sh
 set -e
 
-ELIXIR_VERSION=1.18.2
+ELIXIR_VERSION=1.18.3
 
 if [ $# -eq 0 ] || { [ $# -eq 1 ] && { [ "$1" = "--help" ] || [ "$1" = "-h" ]; }; }; then
   cat <<USAGE >&2
diff --git a/bin/elixir.bat b/bin/elixir.bat
index d9857edc4cd..eeec4db500a 100644
--- a/bin/elixir.bat
+++ b/bin/elixir.bat
@@ -1,6 +1,6 @@
 @echo off
 
-set ELIXIR_VERSION=1.18.2
+set ELIXIR_VERSION=1.18.3
 
 if    ""%1""==""""                if ""%2""=="""" goto documentation
 if /I ""%1""==""--help""          if ""%2""=="""" goto documentation

From 8115bd38a131e987b30eb5af8e7043a0ea40b24b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jos=C3=A9=20Valim?= <jose.valim@dashbit.co>
Date: Fri, 14 Mar 2025 20:13:07 +0100
Subject: [PATCH 111/139] Preserve backwards compatibility in elixir_erl,
 closes #14323

---
 lib/elixir/src/elixir_erl_try.erl | 10 +++++++++-
 1 file changed, 9 insertions(+), 1 deletion(-)

diff --git a/lib/elixir/src/elixir_erl_try.erl b/lib/elixir/src/elixir_erl_try.erl
index 3543203462f..2a3d32aaf0e 100644
--- a/lib/elixir/src/elixir_erl_try.erl
+++ b/lib/elixir/src/elixir_erl_try.erl
@@ -20,7 +20,15 @@ reduce_clauses([], Acc, OldStack, SAcc, _S) ->
   {lists:reverse(Acc), SAcc#elixir_erl{stacktrace=OldStack}}.
 
 each_clause({'catch', Meta, Raw, Expr}, S) ->
-  {Match, Guards} = elixir_utils:extract_splat_guards(Raw),
+  {Args, Guards} = elixir_utils:extract_splat_guards(Raw),
+
+  Match =
+    %% Elixir v1.17 and earlier emitted single argument
+    %% and may still be processed via debug_info
+    case Args of
+      [X] -> [throw, X];
+      [X, Y] -> [X, Y]
+    end,
 
   {{clause, Line, [TKind, TMatches], TGuards, TBody}, TS} =
     elixir_erl_clauses:clause(?ann(Meta), fun elixir_erl_pass:translate_args/3, Match, Expr, Guards, S),

From c32f72581a0a24f46bf22bd28fa325ba84c5dd56 Mon Sep 17 00:00:00 2001
From: Jean Klingler <sabiwara@gmail.com>
Date: Sat, 15 Mar 2025 19:18:18 +0900
Subject: [PATCH 112/139] Fix handling of ErlangError when :general key is
 chardata (#14329)

---
 lib/elixir/lib/exception.ex               |  2 +-
 lib/elixir/test/elixir/exception_test.exs | 10 ++++++++++
 2 files changed, 11 insertions(+), 1 deletion(-)

diff --git a/lib/elixir/lib/exception.ex b/lib/elixir/lib/exception.ex
index 63ec7a2407d..094e5f6a735 100644
--- a/lib/elixir/lib/exception.ex
+++ b/lib/elixir/lib/exception.ex
@@ -2530,7 +2530,7 @@ defmodule ErlangError do
           {:ok, reason, IO.iodata_to_binary([":\n\n" | Enum.map(args_errors, &arg_error/1)])}
 
         general = extra[:general] ->
-          {:ok, reason, ": " <> general}
+          {:ok, reason, ": " <> IO.chardata_to_string(general)}
 
         true ->
           :error
diff --git a/lib/elixir/test/elixir/exception_test.exs b/lib/elixir/test/elixir/exception_test.exs
index a0032a85f45..4059cf37054 100644
--- a/lib/elixir/test/elixir/exception_test.exs
+++ b/lib/elixir/test/elixir/exception_test.exs
@@ -46,6 +46,12 @@ defmodule ExceptionTest do
                {:io, :put_chars, [self(), <<222>>],
                 [error_info: %{module: __MODULE__, function: :dummy_error_extras}]}
              ])
+
+    assert %ErlangError{original: {:failed_load_cacerts, :enoent}, reason: ": this is chardata"} =
+             Exception.normalize(:error, {:failed_load_cacerts, :enoent}, [
+               {:pubkey_os_cacerts, :get, 0,
+                [error_info: %{module: __MODULE__, function: :dummy_error_chardata}]}
+             ])
   end
 
   test "format/2 without stacktrace" do
@@ -1026,4 +1032,8 @@ defmodule ExceptionTest do
   end
 
   def dummy_error_extras(_exception, _stacktrace), do: %{general: "foo"}
+
+  def dummy_error_chardata(_exception, _stacktrace) do
+    %{general: ~c"this is " ++ [~c"chardata"], reason: ~c"this " ++ [~c"too"]}
+  end
 end

From ca57cfe6f3ebaee3c33b2a520745ddb1157b73fe Mon Sep 17 00:00:00 2001
From: Jean Klingler <sabiwara@gmail.com>
Date: Tue, 25 Mar 2025 18:07:07 +0900
Subject: [PATCH 113/139] Handle non-binary bitstring in struct default values
 (#14363)

---
 lib/elixir/src/elixir_erl.erl       | 10 ++++++++++
 lib/elixir/test/elixir/map_test.exs |  9 +++++++++
 2 files changed, 19 insertions(+)

diff --git a/lib/elixir/src/elixir_erl.erl b/lib/elixir/src/elixir_erl.erl
index be7339147d0..342835dd360 100644
--- a/lib/elixir/src/elixir_erl.erl
+++ b/lib/elixir/src/elixir_erl.erl
@@ -92,6 +92,9 @@ elixir_to_erl(Tree, Ann) when is_binary(Tree) ->
   %% considers a string in a binary to be encoded in latin1, so the bytes
   %% are not changed in any fashion.
   {bin, Ann, [{bin_element, Ann, {string, Ann, binary_to_list(Tree)}, default, default}]};
+elixir_to_erl(Tree, Ann) when is_bitstring(Tree) ->
+  Segments = [elixir_to_erl_bitstring_segment(X, Ann) || X <- bitstring_to_list(Tree)],
+  {bin, Ann, Segments};
 elixir_to_erl(Tree, Ann) when is_function(Tree) ->
   case (erlang:fun_info(Tree, type) == {type, external}) andalso
        (erlang:fun_info(Tree, env) == {env, []}) of
@@ -111,6 +114,13 @@ elixir_to_erl(Tree, Ann) ->
 elixir_to_erl_cons([H | T], Ann) -> {cons, Ann, elixir_to_erl(H, Ann), elixir_to_erl_cons(T, Ann)};
 elixir_to_erl_cons(T, Ann) -> elixir_to_erl(T, Ann).
 
+elixir_to_erl_bitstring_segment(Int, Ann) when is_integer(Int) ->
+  {bin_element, Ann, {integer, Ann, Int}, default, [integer]};
+elixir_to_erl_bitstring_segment(Rest, Ann) when is_bitstring(Rest) ->
+  Size = bit_size(Rest),
+  <<Int:Size>> = Rest,
+  {bin_element, Ann, {integer, Ann, Int}, {integer, Ann, Size}, [integer]}.
+
 %% Returns a scope for translation.
 
 scope(_Meta, ExpandCaptures) ->
diff --git a/lib/elixir/test/elixir/map_test.exs b/lib/elixir/test/elixir/map_test.exs
index 553d1956f48..a2fdcbb01d8 100644
--- a/lib/elixir/test/elixir/map_test.exs
+++ b/lib/elixir/test/elixir/map_test.exs
@@ -395,6 +395,15 @@ defmodule MapTest do
     assert quoted == {:%, [], [User, {:%{}, [], [{:foo, 1}]}]}
   end
 
+  test "structs with bitstring defaults" do
+    defmodule WithBitstring do
+      defstruct bitstring: <<255, 127::7>>
+    end
+
+    info = Macro.struct_info!(WithBitstring, __ENV__)
+    assert info == [%{default: <<255, 127::7>>, field: :bitstring}]
+  end
+
   test "defstruct can only be used once in a module" do
     message =
       "defstruct has already been called for TestMod, " <>

From 6ac1d10f777a91d43372b26dca51c4ff90285677 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jos=C3=A9=20Valim?= <jose.valim@dashbit.co>
Date: Wed, 26 Mar 2025 16:56:38 +0100
Subject: [PATCH 114/139] Do not run listeners when not checking the deps

---
 lib/mix/lib/mix/tasks/clean.ex   | 8 +++++++-
 lib/mix/lib/mix/tasks/compile.ex | 8 +++++++-
 lib/mix/lib/mix/tasks/help.ex    | 8 +++++++-
 3 files changed, 21 insertions(+), 3 deletions(-)

diff --git a/lib/mix/lib/mix/tasks/clean.ex b/lib/mix/lib/mix/tasks/clean.ex
index 7c0964f97d7..2f73b08c26c 100644
--- a/lib/mix/lib/mix/tasks/clean.ex
+++ b/lib/mix/lib/mix/tasks/clean.ex
@@ -65,7 +65,13 @@ defmodule Mix.Tasks.Clean do
 
   # Loadpaths without checks because compilers may be defined in deps.
   defp loadpaths! do
-    options = ["--no-elixir-version-check", "--no-deps-check", "--no-archives-check"]
+    options = [
+      "--no-elixir-version-check",
+      "--no-deps-check",
+      "--no-archives-check",
+      "--no-listeners"
+    ]
+
     Mix.Task.run("loadpaths", options)
     Mix.Task.reenable("loadpaths")
     Mix.Task.reenable("deps.loadpaths")
diff --git a/lib/mix/lib/mix/tasks/compile.ex b/lib/mix/lib/mix/tasks/compile.ex
index b007653b4d1..148a9ef5268 100644
--- a/lib/mix/lib/mix/tasks/compile.ex
+++ b/lib/mix/lib/mix/tasks/compile.ex
@@ -86,7 +86,13 @@ defmodule Mix.Tasks.Compile do
   @impl true
   def run(["--list"]) do
     # Loadpaths without checks because compilers may be defined in deps.
-    args = ["--no-elixir-version-check", "--no-deps-check", "--no-archives-check"]
+    args = [
+      "--no-elixir-version-check",
+      "--no-deps-check",
+      "--no-archives-check",
+      "--no-listeners"
+    ]
+
     Mix.Task.run("loadpaths", args)
     Mix.Task.reenable("loadpaths")
     Mix.Task.reenable("deps.loadpaths")
diff --git a/lib/mix/lib/mix/tasks/help.ex b/lib/mix/lib/mix/tasks/help.ex
index f52df49b33e..0b1913ad46a 100644
--- a/lib/mix/lib/mix/tasks/help.ex
+++ b/lib/mix/lib/mix/tasks/help.ex
@@ -119,7 +119,13 @@ defmodule Mix.Tasks.Help do
 
   # Loadpaths without checks because tasks may be defined in deps.
   defp loadpaths! do
-    args = ["--no-elixir-version-check", "--no-deps-check", "--no-archives-check"]
+    args = [
+      "--no-elixir-version-check",
+      "--no-deps-check",
+      "--no-archives-check",
+      "--no-listeners"
+    ]
+
     Mix.Task.run("loadpaths", args)
     Mix.Task.reenable("loadpaths")
     Mix.Task.reenable("deps.loadpaths")

From 76c64a0f32a5993286e5bce9b00149f0a85693ce Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jos=C3=A9=20Valim?= <jose.valim@dashbit.co>
Date: Sun, 30 Mar 2025 16:13:02 +0200
Subject: [PATCH 115/139] Recompile regexes when escaped from module attributes
 (#14381)

---
 lib/elixir/lib/kernel.ex              | 37 +++++++++++++++++----------
 lib/elixir/test/elixir/regex_test.exs | 19 ++++++++++++++
 2 files changed, 43 insertions(+), 13 deletions(-)

diff --git a/lib/elixir/lib/kernel.ex b/lib/elixir/lib/kernel.ex
index ed1ec8a9fc4..f8f6c9e6fac 100644
--- a/lib/elixir/lib/kernel.ex
+++ b/lib/elixir/lib/kernel.ex
@@ -3716,26 +3716,25 @@ defmodule Kernel do
 
     case function? do
       true ->
-        value =
-          case Module.__get_attribute__(env.module, name, line, false) do
-            {_, doc} when doc_attr? -> doc
-            other -> other
-          end
+        case Module.__get_attribute__(env.module, name, line, false) do
+          {_, doc} when doc_attr? ->
+            do_at_escape(name, doc)
+
+          %{__struct__: Regex, source: source, opts: opts} = regex ->
+            case :erlang.system_info(:otp_release) < [?2, ?8] do
+              true -> do_at_escape(name, regex)
+              false -> quote(do: Regex.compile!(unquote(source), unquote(opts)))
+            end
 
-        try do
-          :elixir_quote.escape(value, :none, false)
-        rescue
-          ex in [ArgumentError] ->
-            raise ArgumentError,
-                  "cannot inject attribute @#{name} into function/macro because " <>
-                    Exception.message(ex)
+          value ->
+            do_at_escape(name, value)
         end
 
       false when doc_attr? ->
         quote do
           case Module.__get_attribute__(__MODULE__, unquote(name), unquote(line), false) do
             {_, doc} -> doc
-            other -> other
+            value -> value
           end
         end
 
@@ -3770,6 +3769,17 @@ defmodule Kernel do
     raise ArgumentError, "expected 0 or 1 argument for @#{name}, got: #{length(args)}"
   end
 
+  defp do_at_escape(name, value) do
+    try do
+      :elixir_quote.escape(value, :none, false)
+    rescue
+      ex in [ArgumentError] ->
+        raise ArgumentError,
+              "cannot inject attribute @#{name} into function/macro because " <>
+                Exception.message(ex)
+    end
+  end
+
   # Those are always compile-time dependencies, so we can skip the trace.
   defp collect_traces(:before_compile, arg, _env), do: {arg, []}
   defp collect_traces(:after_compile, arg, _env), do: {arg, []}
@@ -6461,6 +6471,7 @@ defmodule Kernel do
   end
 
   defp compile_regex(binary_or_tuple, options) do
+    # TODO: Remove this when we require Erlang/OTP 28+
     case is_binary(binary_or_tuple) and :erlang.system_info(:otp_release) < [?2, ?8] do
       true ->
         Macro.escape(Regex.compile!(binary_or_tuple, :binary.list_to_bin(options)))
diff --git a/lib/elixir/test/elixir/regex_test.exs b/lib/elixir/test/elixir/regex_test.exs
index 917df6d2603..3b7d8f781d5 100644
--- a/lib/elixir/test/elixir/regex_test.exs
+++ b/lib/elixir/test/elixir/regex_test.exs
@@ -5,6 +5,25 @@ defmodule RegexTest do
 
   doctest Regex
 
+  if System.otp_release() >= "28" do
+    test "module attribute" do
+      assert ExUnit.CaptureIO.capture_io(:stderr, fn ->
+               defmodule ModAttr do
+                 @regex ~r/example/
+                 def regex, do: @regex
+
+                 @bare_regex :erlang.term_to_binary(@regex)
+                 def bare_regex, do: :erlang.binary_to_term(@bare_regex)
+
+                 # We don't rewrite outside of functions
+                 assert @regex.re_pattern == :erlang.binary_to_term(@bare_regex).re_pattern
+               end
+
+               assert ModAttr.regex().re_pattern != ModAttr.bare_regex().re_pattern
+             end) =~ "storing and reading regexes from module attributes is deprecated"
+    end
+  end
+
   test "multiline" do
     refute Regex.match?(~r/^b$/, "a\nb\nc")
     assert Regex.match?(~r/^b$/m, "a\nb\nc")

From 65baed8681f7fd7cbc501b8134b172a0f45897b9 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jos=C3=A9=20Valim?= <jose.valim@dashbit.co>
Date: Sun, 30 Mar 2025 16:25:38 +0200
Subject: [PATCH 116/139] Update Windows CI

---
 .github/workflows/ci.yml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 978f36a2f9e..5b44545fe87 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -81,7 +81,7 @@ jobs:
     name: Windows Server 2019, Erlang/OTP ${{ matrix.otp_version }}
     strategy:
       matrix:
-        otp_version: ["25.3", "26.2", "27.1"]
+        otp_version: ["25.3", "26.2", "27.3"]
     runs-on: windows-2022
     steps:
       - name: Configure Git

From 18d367c01ba4447d78633fab251f741ce939c5e5 Mon Sep 17 00:00:00 2001
From: Fabian Becker <fbecker@adobe.com>
Date: Mon, 31 Mar 2025 17:02:48 +0200
Subject: [PATCH 117/139] Remove deprecation warning check for 1.18 (#14382)

---
 lib/elixir/test/elixir/regex_test.exs | 20 +++++++++-----------
 1 file changed, 9 insertions(+), 11 deletions(-)

diff --git a/lib/elixir/test/elixir/regex_test.exs b/lib/elixir/test/elixir/regex_test.exs
index 3b7d8f781d5..64f9c91d6d8 100644
--- a/lib/elixir/test/elixir/regex_test.exs
+++ b/lib/elixir/test/elixir/regex_test.exs
@@ -7,20 +7,18 @@ defmodule RegexTest do
 
   if System.otp_release() >= "28" do
     test "module attribute" do
-      assert ExUnit.CaptureIO.capture_io(:stderr, fn ->
-               defmodule ModAttr do
-                 @regex ~r/example/
-                 def regex, do: @regex
+      defmodule ModAttr do
+        @regex ~r/example/
+        def regex, do: @regex
 
-                 @bare_regex :erlang.term_to_binary(@regex)
-                 def bare_regex, do: :erlang.binary_to_term(@bare_regex)
+        @bare_regex :erlang.term_to_binary(@regex)
+        def bare_regex, do: :erlang.binary_to_term(@bare_regex)
 
-                 # We don't rewrite outside of functions
-                 assert @regex.re_pattern == :erlang.binary_to_term(@bare_regex).re_pattern
-               end
+        # We don't rewrite outside of functions
+        assert @regex.re_pattern == :erlang.binary_to_term(@bare_regex).re_pattern
+      end
 
-               assert ModAttr.regex().re_pattern != ModAttr.bare_regex().re_pattern
-             end) =~ "storing and reading regexes from module attributes is deprecated"
+      assert ModAttr.regex().re_pattern != ModAttr.bare_regex().re_pattern
     end
   end
 

From f89c5076f96b2032e02d1fcc54d0ad574e4421e4 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jos=C3=A9=20Valim?= <jose.valim@dashbit.co>
Date: Thu, 3 Apr 2025 11:50:05 +0200
Subject: [PATCH 118/139] Do not crash on nested bitstrings

Closes #14391.
---
 lib/elixir/lib/module/types/pattern.ex               | 4 ++++
 lib/elixir/test/elixir/module/types/pattern_test.exs | 4 ++++
 2 files changed, 8 insertions(+)

diff --git a/lib/elixir/lib/module/types/pattern.ex b/lib/elixir/lib/module/types/pattern.ex
index 77f51a16d7c..9f4a76f678a 100644
--- a/lib/elixir/lib/module/types/pattern.ex
+++ b/lib/elixir/lib/module/types/pattern.ex
@@ -335,6 +335,10 @@ defmodule Module.Types.Pattern do
     {type, context}
   end
 
+  def of_match_var({:<<>>, _meta, args}, _expected, _expr, stack, context) do
+    {binary(), Of.binary(args, :match, stack, context)}
+  end
+
   def of_match_var(ast, expected, expr, stack, context) do
     of_match(ast, expected, expr, :default, stack, context)
   end
diff --git a/lib/elixir/test/elixir/module/types/pattern_test.exs b/lib/elixir/test/elixir/module/types/pattern_test.exs
index b6e789e5422..8e940d2ea06 100644
--- a/lib/elixir/test/elixir/module/types/pattern_test.exs
+++ b/lib/elixir/test/elixir/module/types/pattern_test.exs
@@ -251,6 +251,10 @@ defmodule Module.Types.PatternTest do
       assert typecheck!([<<x::utf8>>], x) == integer()
     end
 
+    test "nested" do
+      assert typecheck!([<<0, <<x::bitstring>>::binary>>], x) == binary()
+    end
+
     test "error" do
       assert typeerror!([<<x::binary-size(2), x::float>>], x) == ~l"""
              incompatible types assigned to "x":

From ef002e2b15b92246b349a833d50862141c0106c1 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jos=C3=A9=20Valim?= <jose.valim@dashbit.co>
Date: Tue, 8 Apr 2025 09:25:20 +0200
Subject: [PATCH 119/139] Properly track imported function calls in tracer

Closes #13878.
---
 lib/elixir/src/elixir_dispatch.erl            |  4 +-
 .../elixir/kernel/lexical_tracker_test.exs    | 50 +++++++++++++++++++
 2 files changed, 53 insertions(+), 1 deletion(-)

diff --git a/lib/elixir/src/elixir_dispatch.erl b/lib/elixir/src/elixir_dispatch.erl
index 121b1683642..779880a41e8 100644
--- a/lib/elixir/src/elixir_dispatch.erl
+++ b/lib/elixir/src/elixir_dispatch.erl
@@ -204,7 +204,9 @@ do_expand_import(Result, Meta, Name, Arity, Module, E, Trace) ->
     {import, Receiver} ->
       case expand_require(true, Meta, Receiver, Name, Arity, E, Trace) of
         {macro, _, _} = Response -> Response;
-        error -> {function, Receiver, Name}
+        error ->
+          Trace andalso elixir_env:trace({remote_function, Meta, Receiver, Name, Arity}, E),
+          {function, Receiver, Name}
       end;
     false when Module == ?kernel ->
       case elixir_rewrite:inline(Module, Name, Arity) of
diff --git a/lib/elixir/test/elixir/kernel/lexical_tracker_test.exs b/lib/elixir/test/elixir/kernel/lexical_tracker_test.exs
index c4e10309073..c464899b042 100644
--- a/lib/elixir/test/elixir/kernel/lexical_tracker_test.exs
+++ b/lib/elixir/test/elixir/kernel/lexical_tracker_test.exs
@@ -517,5 +517,55 @@ defmodule Kernel.LexicalTrackerTest do
       refute URI in exports
       refute URI in runtime
     end
+
+    test "imported functions from quote adds dependencies" do
+      {{compile, exports, runtime, _}, _binding} =
+        Code.eval_string("""
+        defmodule Kernel.LexicalTrackerTest.QuotedFun do
+          import URI
+
+          defmacro parse_root() do
+            quote do
+              parse("/")
+            end
+          end
+        end
+
+        defmodule Kernel.LexicalTrackerTest.UsingQuotedFun do
+          require Kernel.LexicalTrackerTest.QuotedFun, as: QF
+          QF.parse_root()
+          Kernel.LexicalTracker.references(__ENV__.lexical_tracker)
+        end |> elem(3)
+        """)
+
+      assert URI in compile
+      refute URI in exports
+      refute URI in runtime
+    end
+
+    test "imported macro from quote adds dependencies" do
+      {{compile, exports, runtime, _}, _binding} =
+        Code.eval_string("""
+        defmodule Kernel.LexicalTrackerTest.QuotedMacro do
+          import Config
+
+          defmacro config_env() do
+            quote do
+              config_env()
+            end
+          end
+        end
+
+        defmodule Kernel.LexicalTrackerTest.UsingQuotedMacro do
+          require Kernel.LexicalTrackerTest.QuotedMacro, as: QM
+          def fun(), do: QM.config_env()
+          Kernel.LexicalTracker.references(__ENV__.lexical_tracker)
+        end |> elem(3)
+        """)
+
+      assert Config in compile
+      refute Config in exports
+      refute Config in runtime
+    end
   end
 end

From 64c4ecf9c77f46da3eb7efb4c7aa43d0847a9bc9 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jos=C3=A9=20Valim?= <jose.valim@dashbit.co>
Date: Fri, 17 Jan 2025 15:42:50 +0100
Subject: [PATCH 120/139] Do not add duplicates on maps and tuples
 intersections

---
 lib/elixir/lib/module/types/descr.ex | 23 ++++++++++++++++++++---
 1 file changed, 20 insertions(+), 3 deletions(-)

diff --git a/lib/elixir/lib/module/types/descr.ex b/lib/elixir/lib/module/types/descr.ex
index 175e3e315d2..4f836b9e4d2 100644
--- a/lib/elixir/lib/module/types/descr.ex
+++ b/lib/elixir/lib/module/types/descr.ex
@@ -1275,7 +1275,16 @@ defmodule Module.Types.Descr do
       acc ->
         try do
           {tag, fields} = map_literal_intersection(tag1, pos1, tag2, pos2)
-          [{tag, fields, negs1 ++ negs2} | acc]
+          entry = {tag, fields, negs1 ++ negs2}
+
+          # Imagine a, b, c, where a is closed and b and c are open with
+          # no keys in common. The result in both cases will be a and we
+          # want to avoid adding duplicates, especially as intersection
+          # is a cartesian product.
+          case :lists.member(entry, acc) do
+            true -> acc
+            false -> [entry | acc]
+          end
         catch
           :empty -> acc
         end
@@ -1931,8 +1940,16 @@ defmodule Module.Types.Descr do
         reduce: [] do
       acc ->
         case tuple_literal_intersection(tag1, elements1, tag2, elements2) do
-          {tag, fields} -> [{tag, fields, negs1 ++ negs2} | acc]
-          :empty -> acc
+          {tag, elements} ->
+            entry = {tag, elements, negs1 ++ negs2}
+
+            case :lists.member(entry, acc) do
+              true -> acc
+              false -> [entry | acc]
+            end
+
+          :empty ->
+            acc
         end
     end
     |> case do

From e34495e304de1b1758b71d20c6859584b365fee4 Mon Sep 17 00:00:00 2001
From: Jean Klingler <sabiwara@gmail.com>
Date: Fri, 24 Jan 2025 17:12:13 +0900
Subject: [PATCH 121/139] Optimize map unions to avoid building long lists
 (#14215)

---
 lib/elixir/lib/module/types/descr.ex          | 139 +++++++++++++++---
 .../test/elixir/module/types/descr_test.exs   |  40 +++++
 2 files changed, 158 insertions(+), 21 deletions(-)

diff --git a/lib/elixir/lib/module/types/descr.ex b/lib/elixir/lib/module/types/descr.ex
index 4f836b9e4d2..3cc5b20c551 100644
--- a/lib/elixir/lib/module/types/descr.ex
+++ b/lib/elixir/lib/module/types/descr.ex
@@ -1264,8 +1264,115 @@ defmodule Module.Types.Descr do
 
   defp map_only?(descr), do: empty?(Map.delete(descr, :map))
 
-  # Union is list concatenation
-  defp map_union(dnf1, dnf2), do: dnf1 ++ (dnf2 -- dnf1)
+  defp map_union(dnf1, dnf2) do
+    # Union is just concatenation, but we rely on some optimization strategies to
+    # avoid the list to grow when possible
+
+    # first pass trying to identify patterns where two maps can be fused as one
+    with [map1] <- dnf1,
+         [map2] <- dnf2,
+         optimized when optimized != nil <- maybe_optimize_map_union(map1, map2) do
+      [optimized]
+    else
+      # otherwise we just concatenate and remove structural duplicates
+      _ -> dnf1 ++ (dnf2 -- dnf1)
+    end
+  end
+
+  defp maybe_optimize_map_union({tag1, pos1, []} = map1, {tag2, pos2, []} = map2) do
+    case map_union_optimization_strategy(tag1, pos1, tag2, pos2) do
+      :all_equal ->
+        map1
+
+      :any_map ->
+        {:open, %{}, []}
+
+      {:one_key_difference, key, v1, v2} ->
+        new_pos = Map.put(pos1, key, union(v1, v2))
+        {tag1, new_pos, []}
+
+      :left_subtype_of_right ->
+        map2
+
+      :right_subtype_of_left ->
+        map1
+
+      nil ->
+        nil
+    end
+  end
+
+  defp maybe_optimize_map_union(_, _), do: nil
+
+  defp map_union_optimization_strategy(tag1, pos1, tag2, pos2)
+  defp map_union_optimization_strategy(tag, pos, tag, pos), do: :all_equal
+  defp map_union_optimization_strategy(:open, empty, _, _) when empty == %{}, do: :any_map
+  defp map_union_optimization_strategy(_, _, :open, empty) when empty == %{}, do: :any_map
+
+  defp map_union_optimization_strategy(tag, pos1, tag, pos2)
+       when map_size(pos1) == map_size(pos2) do
+    :maps.iterator(pos1)
+    |> :maps.next()
+    |> do_map_union_optimization_strategy(pos2, :all_equal)
+  end
+
+  defp map_union_optimization_strategy(:open, pos1, _, pos2)
+       when map_size(pos1) <= map_size(pos2) do
+    :maps.iterator(pos1)
+    |> :maps.next()
+    |> do_map_union_optimization_strategy(pos2, :right_subtype_of_left)
+  end
+
+  defp map_union_optimization_strategy(_, pos1, :open, pos2)
+       when map_size(pos1) >= map_size(pos2) do
+    :maps.iterator(pos2)
+    |> :maps.next()
+    |> do_map_union_optimization_strategy(pos1, :right_subtype_of_left)
+    |> case do
+      :right_subtype_of_left -> :left_subtype_of_right
+      nil -> nil
+    end
+  end
+
+  defp map_union_optimization_strategy(_, _, _, _), do: nil
+
+  defp do_map_union_optimization_strategy(:none, _, status), do: status
+
+  defp do_map_union_optimization_strategy({key, v1, iterator}, pos2, status) do
+    with %{^key => v2} <- pos2,
+         next_status when next_status != nil <- map_union_next_strategy(key, v1, v2, status) do
+      do_map_union_optimization_strategy(:maps.next(iterator), pos2, next_status)
+    else
+      _ -> nil
+    end
+  end
+
+  defp map_union_next_strategy(key, v1, v2, status)
+
+  # structurally equal values do not impact the ongoing strategy
+  defp map_union_next_strategy(_key, same, same, status), do: status
+
+  defp map_union_next_strategy(key, v1, v2, :all_equal) do
+    if key != :__struct__, do: {:one_key_difference, key, v1, v2}
+  end
+
+  defp map_union_next_strategy(_key, v1, v2, {:one_key_difference, _, d1, d2}) do
+    # we have at least two key differences now, we switch strategy
+    # if both are subtypes in one direction, keep checking
+    cond do
+      subtype?(d1, d2) and subtype?(v1, v2) -> :left_subtype_of_right
+      subtype?(d2, d1) and subtype?(v2, v1) -> :right_subtype_of_left
+      true -> nil
+    end
+  end
+
+  defp map_union_next_strategy(_key, v1, v2, :left_subtype_of_right) do
+    if subtype?(v1, v2), do: :left_subtype_of_right
+  end
+
+  defp map_union_next_strategy(_key, v1, v2, :right_subtype_of_left) do
+    if subtype?(v2, v1), do: :right_subtype_of_left
+  end
 
   # Given two unions of maps, intersects each pair of maps.
   defp map_intersection(dnf1, dnf2) do
@@ -1747,29 +1854,19 @@ defmodule Module.Types.Descr do
 
   defp map_non_negated_fuse(maps) do
     Enum.reduce(maps, [], fn map, acc ->
-      case Enum.split_while(acc, &non_fusible_maps?(map, &1)) do
-        {_, []} ->
-          [map | acc]
-
-        {others, [match | rest]} ->
-          fused = map_non_negated_fuse_pair(map, match)
-          others ++ [fused | rest]
-      end
+      fuse_with_first_fusible(map, acc)
     end)
   end
 
-  # Two maps are fusible if they differ in at most one element.
-  defp non_fusible_maps?({_, fields1, []}, {_, fields2, []}) do
-    Enum.count_until(fields1, fn {key, value} -> Map.fetch!(fields2, key) != value end, 2) > 1
-  end
-
-  defp map_non_negated_fuse_pair({tag, fields1, []}, {_, fields2, []}) do
-    fields =
-      symmetrical_merge(fields1, fields2, fn _k, v1, v2 ->
-        if v1 == v2, do: v1, else: union(v1, v2)
-      end)
+  defp fuse_with_first_fusible(map, []), do: [map]
 
-    {tag, fields, []}
+  defp fuse_with_first_fusible(map, [candidate | rest]) do
+    if fused = maybe_optimize_map_union(map, candidate) do
+      # we found a fusible candidate, we're done
+      [fused | rest]
+    else
+      [candidate | fuse_with_first_fusible(map, rest)]
+    end
   end
 
   # If all fields are the same except one, we can optimize map difference.
diff --git a/lib/elixir/test/elixir/module/types/descr_test.exs b/lib/elixir/test/elixir/module/types/descr_test.exs
index 3011da51345..e63ed883735 100644
--- a/lib/elixir/test/elixir/module/types/descr_test.exs
+++ b/lib/elixir/test/elixir/module/types/descr_test.exs
@@ -101,6 +101,46 @@ defmodule Module.Types.DescrTest do
       assert union(difference(list(term()), list(integer())), list(integer()))
              |> equal?(list(term()))
     end
+
+    test "optimizations" do
+      # The tests are checking the actual implementation, not the semantics.
+      # This is why we are using structural comparisons.
+      # It's fine to remove these if the implementation changes, but breaking
+      # these might have an important impact on compile times.
+
+      # Optimization one: same tags, all but one key are structurally equal
+      assert union(
+               open_map(a: float(), b: atom()),
+               open_map(a: integer(), b: atom())
+             ) == open_map(a: union(float(), integer()), b: atom())
+
+      assert union(
+               closed_map(a: float(), b: atom()),
+               closed_map(a: integer(), b: atom())
+             ) == closed_map(a: union(float(), integer()), b: atom())
+
+      # Optimization two: we can tell that one map is a trivial subtype of the other:
+
+      assert union(
+               closed_map(a: term(), b: term()),
+               closed_map(a: float(), b: binary())
+             ) == closed_map(a: term(), b: term())
+
+      assert union(
+               open_map(a: term()),
+               closed_map(a: float(), b: binary())
+             ) == open_map(a: term())
+
+      assert union(
+               closed_map(a: float(), b: binary()),
+               open_map(a: term())
+             ) == open_map(a: term())
+
+      assert union(
+               closed_map(a: term(), b: tuple([term(), term()])),
+               closed_map(a: float(), b: tuple([atom(), binary()]))
+             ) == closed_map(a: term(), b: tuple([term(), term()]))
+    end
   end
 
   describe "intersection" do

From 6432e8655c995075af96e70bdc20dd61cefdcd33 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jos=C3=A9=20Valim?= <jose.valim@dashbit.co>
Date: Fri, 25 Apr 2025 20:14:01 +0200
Subject: [PATCH 122/139] Write modules before verification so modules can be
 found during verification

---
 lib/elixir/lib/kernel/parallel_compiler.ex | 11 +++--------
 1 file changed, 3 insertions(+), 8 deletions(-)

diff --git a/lib/elixir/lib/kernel/parallel_compiler.ex b/lib/elixir/lib/kernel/parallel_compiler.ex
index 23d79179f3c..ec64c107d50 100644
--- a/lib/elixir/lib/kernel/parallel_compiler.ex
+++ b/lib/elixir/lib/kernel/parallel_compiler.ex
@@ -258,13 +258,6 @@ defmodule Kernel.ParallelCompiler do
     {status, modules_or_errors, info} =
       try do
         spawn_workers(schedulers, cache, files, output, options)
-      else
-        {:ok, outcome, info} ->
-          beam_timestamp = Keyword.get(options, :beam_timestamp)
-          {:ok, write_module_binaries(outcome, output, beam_timestamp), info}
-
-        {:error, errors, info} ->
-          {:error, errors, info}
       after
         Module.ParallelChecker.stop(cache)
       end
@@ -288,6 +281,7 @@ defmodule Kernel.ParallelCompiler do
 
     {outcome, state} =
       spawn_workers(files, %{}, %{}, [], %{}, [], [], %{
+        beam_timestamp: Keyword.get(options, :beam_timestamp),
         dest: Keyword.get(options, :dest),
         each_cycle: Keyword.get(options, :each_cycle, fn -> {:runtime, [], []} end),
         each_file: Keyword.get(options, :each_file, fn _, _ -> :ok end) |> each_file(),
@@ -345,9 +339,10 @@ defmodule Kernel.ParallelCompiler do
   ## Verification
 
   defp verify_modules(result, compile_warnings, dependent_modules, state) do
+    modules = write_module_binaries(result, state.output, state.beam_timestamp)
     runtime_warnings = maybe_check_modules(result, dependent_modules, state)
     info = %{compile_warnings: Enum.reverse(compile_warnings), runtime_warnings: runtime_warnings}
-    {{:ok, result, info}, state}
+    {{:ok, modules, info}, state}
   end
 
   defp maybe_check_modules(result, runtime_modules, state) do

From 7e01628b5d771ca7cb69058b09c13c383e3eb9ae Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jos=C3=A9=20Valim?= <jose.valim@dashbit.co>
Date: Fri, 25 Apr 2025 19:57:25 +0200
Subject: [PATCH 123/139] Compute beam location early

---
 lib/elixir/src/elixir_module.erl | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/lib/elixir/src/elixir_module.erl b/lib/elixir/src/elixir_module.erl
index 4725cfd2a72..66455a23cb4 100644
--- a/lib/elixir/src/elixir_module.erl
+++ b/lib/elixir/src/elixir_module.erl
@@ -155,6 +155,7 @@ compile(Meta, Module, ModuleAsCharlist, Block, Vars, Prune, E) ->
     put_compiler_modules([Module | CompilerModules]),
     {Result, ModuleE, CallbackE} = eval_form(Line, Module, DataBag, Block, Vars, Prune, E),
     CheckerInfo = checker_info(),
+    BeamLocation = beam_location(ModuleAsCharlist),
 
     {Binary, PersistedAttributes, Autoload} =
       elixir_erl_compiler:spawn(fun() ->
@@ -219,7 +220,7 @@ compile(Meta, Module, ModuleAsCharlist, Block, Vars, Prune, E) ->
         {Binary, PersistedAttributes, Autoload}
       end),
 
-    Autoload andalso code:load_binary(Module, beam_location(ModuleAsCharlist), Binary),
+    Autoload andalso code:load_binary(Module, BeamLocation, Binary),
     put_compiler_modules(CompilerModules),
     eval_callbacks(Line, DataBag, after_compile, [CallbackE, Binary], CallbackE),
     elixir_env:trace({on_module, Binary, none}, ModuleE),

From 0eb5289cd0c959ce23f6b7efa6b4b2bf33ae7537 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jos=C3=A9=20Valim?= <jose.valim@dashbit.co>
Date: Thu, 8 May 2025 10:18:30 +0200
Subject: [PATCH 124/139] Add --no-listeners to mix compile and mix run

---
 lib/mix/lib/mix/tasks/compile.ex | 1 +
 lib/mix/lib/mix/tasks/run.ex     | 2 ++
 2 files changed, 3 insertions(+)

diff --git a/lib/mix/lib/mix/tasks/compile.ex b/lib/mix/lib/mix/tasks/compile.ex
index 148a9ef5268..c80e2e81d3f 100644
--- a/lib/mix/lib/mix/tasks/compile.ex
+++ b/lib/mix/lib/mix/tasks/compile.ex
@@ -67,6 +67,7 @@ defmodule Mix.Tasks.Compile do
     * `--no-compile` - does not actually compile, only loads code and perform checks
     * `--no-deps-check` - skips checking of dependencies
     * `--no-elixir-version-check` - does not check Elixir version
+    * `--no-listeners` - does not start Mix listeners
     * `--no-optional-deps` - does not compile or load optional deps. Useful for testing
       if a library still successfully compiles without optional dependencies (which is the
       default case with dependencies)
diff --git a/lib/mix/lib/mix/tasks/run.ex b/lib/mix/lib/mix/tasks/run.ex
index d32131f6a9e..aef97f1d645 100644
--- a/lib/mix/lib/mix/tasks/run.ex
+++ b/lib/mix/lib/mix/tasks/run.ex
@@ -53,6 +53,7 @@ defmodule Mix.Tasks.Run do
     * `--no-deps-check` - does not check dependencies
     * `--no-elixir-version-check` - does not check the Elixir version from mix.exs
     * `--no-halt` - does not halt the system after running the command
+    * `--no-listeners` - does not start Mix listeners
     * `--no-mix-exs` - allows the command to run even if there is no mix.exs
     * `--no-start` - does not start applications after compilation
 
@@ -73,6 +74,7 @@ defmodule Mix.Tasks.Run do
           halt: :boolean,
           compile: :boolean,
           deps_check: :boolean,
+          listeners: :boolean,
           start: :boolean,
           archives_check: :boolean,
           elixir_version_check: :boolean,

From d00f172c59c405dfa3481636ea83304c31fec42f Mon Sep 17 00:00:00 2001
From: ruslandoga <ruslandoga+gh@icloud.com>
Date: Tue, 25 Mar 2025 00:07:02 +0300
Subject: [PATCH 125/139] Retry mix httpc with disabled middlebox_comp_mode
 (#14357)

Closes #14356
---
 lib/mix/lib/mix/utils.ex | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/lib/mix/lib/mix/utils.ex b/lib/mix/lib/mix/utils.ex
index 733591d4548..90582ac7681 100644
--- a/lib/mix/lib/mix/utils.ex
+++ b/lib/mix/lib/mix/utils.ex
@@ -692,6 +692,11 @@ defmodule Mix.Utils do
           :httpc.set_options([ipfamily: fallback(inet)], :mix)
           request |> httpc_request(http_options) |> httpc_response()
 
+        {:error, {:failed_connect, [{:to_address, _}, {inet, _, reason}]}}
+        when inet in [:inet, :inet6] and elem(reason, 0) == :tls_alert ->
+          http_options = put_in(http_options, [:ssl, :middlebox_comp_mode], false)
+          request |> httpc_request(http_options) |> httpc_response()
+
         response ->
           httpc_response(response)
       end

From 2522bba1bf8809f6a3944141dbbb85aff1bd7d68 Mon Sep 17 00:00:00 2001
From: Steffen Deusch <steffen@deusch.me>
Date: Thu, 10 Apr 2025 19:49:05 +0200
Subject: [PATCH 126/139] IEx.Helpers.process_info/1 (#14418)

---
 lib/iex/lib/iex/helpers.ex        | 163 ++++++++++++++++++++++++++++++
 lib/iex/test/iex/helpers_test.exs |  31 ++++++
 2 files changed, 194 insertions(+)

diff --git a/lib/iex/lib/iex/helpers.ex b/lib/iex/lib/iex/helpers.ex
index b14fe542a9a..7e81b02804e 100644
--- a/lib/iex/lib/iex/helpers.ex
+++ b/lib/iex/lib/iex/helpers.ex
@@ -40,6 +40,7 @@ defmodule IEx.Helpers do
     * `pid/3`           - creates a PID with the 3 integer arguments passed
     * `port/1`          - creates a port from a string
     * `port/2`          - creates a port with the 2 non-negative integers passed
+    * `process_info/1`  - returns information about the given process
     * `pwd/0`           - prints the current working directory
     * `r/1`             - recompiles the given module's source file
     * `recompile/0`     - recompiles the current project
@@ -817,6 +818,168 @@ defmodule IEx.Helpers do
 
   defp pad_key(key), do: String.pad_trailing(key, 21, " ")
 
+  @process_info_keys_and_labels [
+    {:initial_call, "Initial call"},
+    {:dictionary, "Dictionary"},
+    {:registered_name, "Registered name"},
+    {:current_function, "Current function"},
+    {:status, "Status"},
+    {:message_queue_len, "Message queue length"},
+    {:trap_exit, "Trap exit"},
+    {:priority, "Priority"},
+    {:group_leader, "Group leader"},
+    {:reductions, "Reductions"},
+    {:links, "Links"},
+    {:monitors, "Monitors"},
+    {:memory, "Memory"},
+    {:total_heap_size, "Total heap size"},
+    {:heap_size, "Heap size"},
+    {:stack_size, "Stack size"},
+    {:current_stacktrace, "Current stacktrace"}
+  ]
+  @process_info_keys Enum.map(@process_info_keys_and_labels, fn {key, _} -> key end)
+  @process_info_label_mapping Map.new(@process_info_keys_and_labels)
+
+  @doc """
+  Prints information about the given process.
+
+  Includes a generic overview and details such as the linked and monitored processes,
+  the memory usage and the current stacktrace.
+
+  ## Examples
+
+      iex> process_info(self())
+      ...
+      iex> process_info({:via, Registry, {MyApp.Registry, :name}})
+      ...
+
+  """
+  @doc since: "1.19.0"
+  def process_info(pid) do
+    with pid when is_pid(pid) <- GenServer.whereis(pid),
+         info when is_list(info) <-
+           :erpc.call(node(pid), :erlang, :process_info, [pid, @process_info_keys]) do
+      info = Map.new(info)
+
+      IO.puts(IEx.color(:eval_result, ["\n# Process ", inspect(pid)]))
+
+      print_process_overview(info)
+      print_process_links(info[:links])
+      print_process_monitors(info[:monitors])
+      print_process_memory(info)
+      print_process_stacktrace(info[:current_stacktrace])
+    else
+      _ ->
+        IO.puts(
+          IEx.color(
+            :eval_error,
+            "Failed to get process info. Either the process was not found or is not alive."
+          )
+        )
+    end
+
+    dont_display_result()
+  end
+
+  defp print_process_overview(info) do
+    print_pane("Overview")
+
+    for key <- [
+          :initial_call,
+          :current_function,
+          :registered_name,
+          :status,
+          :message_queue_len,
+          :group_leader,
+          :priority,
+          :trap_exit,
+          :reductions
+        ] do
+      print_entry(
+        @process_info_label_mapping[key],
+        inspect(info[key], printable_limit: 256, limit: 5)
+      )
+    end
+  end
+
+  defp print_process_links([]), do: :ok
+
+  defp print_process_links(ports_and_pids) do
+    print_pane("Links")
+
+    for link <- ports_and_pids do
+      print_entry(inspect(link), pid_or_port_details(link))
+    end
+  end
+
+  defp print_process_monitors([]), do: :ok
+
+  defp print_process_monitors(monitors) do
+    print_pane("Monitors")
+
+    for {_, pid_or_port} <- monitors do
+      print_entry(inspect(pid_or_port), pid_or_port_details(pid_or_port))
+    end
+  end
+
+  defp print_process_memory(info) do
+    print_pane("Memory")
+
+    for key <- [:memory, :total_heap_size, :heap_size, :stack_size] do
+      print_entry(@process_info_label_mapping[key], format_bytes(info[key]))
+    end
+  end
+
+  defp print_process_stacktrace([]), do: :ok
+
+  defp print_process_stacktrace(stacktrace) do
+    print_pane("Current stacktrace")
+
+    IO.puts(IEx.color(:eval_info, Exception.format_stacktrace(stacktrace)))
+  end
+
+  defp pid_or_port_details(pid) when is_pid(pid), do: to_process_details(pid)
+  defp pid_or_port_details(name) when is_atom(name), do: to_process_details(name)
+  defp pid_or_port_details(port) when is_port(port), do: to_port_details(port)
+  defp pid_or_port_details(reference) when is_reference(reference), do: reference
+
+  defp to_process_details(pid) when is_pid(pid) do
+    case Process.info(pid, [:initial_call, :dictionary, :registered_name]) do
+      [{:initial_call, initial_call}, {:dictionary, dictionary}, {:registered_name, name}] ->
+        initial_call = Keyword.get(dictionary, :"$initial_call", initial_call)
+
+        format_registered_name(name) ||
+          format_process_label(Keyword.get(dictionary, :"$process_label")) ||
+          format_initial_call(initial_call)
+
+      _ ->
+        "-"
+    end
+  end
+
+  defp to_process_details(name) when is_atom(name) do
+    Process.whereis(name)
+    |> to_process_details()
+  end
+
+  defp format_process_label(nil), do: nil
+  defp format_process_label(label) when is_binary(label), do: label
+  defp format_process_label(label), do: inspect(label)
+
+  defp format_registered_name([]), do: nil
+  defp format_registered_name(name), do: inspect(name)
+
+  defp format_initial_call({:supervisor, mod, arity}), do: Exception.format_mfa(mod, :init, arity)
+  defp format_initial_call({m, f, a}), do: Exception.format_mfa(m, f, a)
+  defp format_initial_call(nil), do: nil
+
+  defp to_port_details(port) when is_port(port) do
+    case Port.info(port, :name) do
+      {:name, name} -> name
+      _ -> "-"
+    end
+  end
+
   @doc """
   Clears out all messages sent to the shell's inbox and prints them out.
   """
diff --git a/lib/iex/test/iex/helpers_test.exs b/lib/iex/test/iex/helpers_test.exs
index 490c97cb5bd..14f2db8c95c 100644
--- a/lib/iex/test/iex/helpers_test.exs
+++ b/lib/iex/test/iex/helpers_test.exs
@@ -1625,6 +1625,37 @@ defmodule IEx.HelpersTest do
     end
   end
 
+  describe "process_info/1" do
+    test "returns information about a process" do
+      result = capture_io(fn -> process_info(self()) end)
+
+      assert result =~ "Process #{inspect(self())}"
+      assert result =~ "## Overview"
+      assert result =~ "Initial call"
+      assert result =~ "## Memory"
+      assert result =~ "## Current stacktrace"
+      assert result =~ "IEx.Helpers.process_info/1"
+
+      refute result =~ "## Links"
+      refute result =~ "## Monitors"
+    end
+
+    test "includes process links and monitors" do
+      pid =
+        spawn_link(fn ->
+          Process.register(self(), :iex_process_info_link_test)
+          Process.sleep(:infinity)
+        end)
+
+      Process.monitor(pid)
+
+      result = capture_io(fn -> process_info(self()) end)
+      assert result =~ "## Links"
+      assert result =~ "## Monitors"
+      assert result =~ ":iex_process_info_link_test"
+    end
+  end
+
   defp test_module_code do
     """
     defmodule Sample do

From ab17a3c0504e5d339173d1d94cd4e001602fdd84 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jos=C3=A9=20Valim?= <jose.valim@dashbit.co>
Date: Tue, 8 Apr 2025 09:45:11 +0200
Subject: [PATCH 127/139] Emit trace events for super calls, closes #14395

---
 lib/elixir/src/elixir_expand.erl              |  9 +++---
 .../test/elixir/kernel/tracers_test.exs       | 28 +++++++++++++++++++
 2 files changed, 33 insertions(+), 4 deletions(-)

diff --git a/lib/elixir/src/elixir_expand.erl b/lib/elixir/src/elixir_expand.erl
index 07155a334cc..e5b360782e0 100644
--- a/lib/elixir/src/elixir_expand.erl
+++ b/lib/elixir/src/elixir_expand.erl
@@ -293,7 +293,7 @@ expand({'&', Meta, [{'/', ArityMeta, [{super, SuperMeta, Context}, Arity]} = Exp
 
   case resolve_super(Meta, Arity, E) of
     {Kind, Name, _} when Kind == def; Kind == defp ->
-      {{'&', Meta, [{'/', ArityMeta, [{Name, SuperMeta, Context}, Arity]}]}, S, E};
+      expand({'&', Meta, [{'/', ArityMeta, [{Name, SuperMeta, Context}, Arity]}]}, S, E);
     _ ->
       expand_fn_capture(Meta, Expr, S, E)
   end;
@@ -343,7 +343,9 @@ expand({with, Meta, [_ | _] = Args}, S, E) ->
 
 expand({super, Meta, Args}, S, E) when is_list(Args) ->
   assert_no_match_or_guard_scope(Meta, "super", S, E),
-  {Kind, Name, _} = resolve_super(Meta, length(Args), E),
+  Arity = length(Args),
+  {Kind, Name, _} = resolve_super(Meta, Arity, E),
+  elixir_env:trace({local_function, Meta, Name, Arity}, E),
   {EArgs, SA, EA} = expand_args(Args, S, E),
   {{super, [{super, {Kind, Name}} | Meta], EArgs}, SA, EA};
 
@@ -899,8 +901,7 @@ expand_local(Meta, Name, Args, S, #{function := Function, context := Context} =
       module_error(Meta, E, ?MODULE, {invalid_local_invocation, elixir_utils:guard_info(S), {Name, Meta, Args}});
 
     nil ->
-      Arity = length(Args),
-      elixir_env:trace({local_function, Meta, Name, Arity}, E)
+      elixir_env:trace({local_function, Meta, Name, length(Args)}, E)
   end,
 
   {EArgs, SA, EA} = expand_args(Args, S, E),
diff --git a/lib/elixir/test/elixir/kernel/tracers_test.exs b/lib/elixir/test/elixir/kernel/tracers_test.exs
index 74bf02d8c71..dcc2f778a28 100644
--- a/lib/elixir/test/elixir/kernel/tracers_test.exs
+++ b/lib/elixir/test/elixir/kernel/tracers_test.exs
@@ -274,6 +274,34 @@ defmodule Kernel.TracersTest do
     assert meta[:from_brackets]
   end
 
+  test "traces super" do
+    compile_string("""
+    defmodule TracerOverridable do
+      def local(x), do: x
+      defoverridable [local: 1]
+      def local(x), do: super(x)
+
+      defmacro macro(x), do: x
+      defoverridable [macro: 1]
+      defmacro macro(x), do: super(x)
+
+      def capture(x), do: x
+      defoverridable [capture: 1]
+      def capture(x), do: tap(x, &super/1)
+
+      def capture_arg(x), do: x
+      defoverridable [capture_arg: 1]
+      def capture_arg(x), do: tap(x, &super(&1))
+    end
+    """)
+
+    assert_received {{:local_function, _, :"local (overridable 1)", 1}, _}
+    assert_received {{:local_function, _, :"macro (overridable 1)", 1}, _}
+    assert_received {{:local_function, _, :"capture (overridable 1)", 1}, _}
+    assert_received {{:local_function, _, :"capture_arg (overridable 1)", 1}, _}
+    refute_received {{:local_function, _, _, _}, _}
+  end
+
   test "does not trace bind quoted twice" do
     compile_string("""
     quote bind_quoted: [foo: List.flatten([])] do

From 332ff949a07dcf445e47603ea775116ed0a88bd0 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jos=C3=A9=20Valim?= <jose.valim@dashbit.co>
Date: Fri, 25 Apr 2025 14:45:55 +0200
Subject: [PATCH 128/139] Ensure git checkout works with untracked files
 (#14428)

Closes #14425.
---
 lib/mix/lib/mix/scm/git.ex               |  2 +-
 lib/mix/test/mix/tasks/deps.git_test.exs | 10 ++++++++++
 2 files changed, 11 insertions(+), 1 deletion(-)

diff --git a/lib/mix/lib/mix/scm/git.ex b/lib/mix/lib/mix/scm/git.ex
index 95fcff44caa..20ad6afe0cc 100644
--- a/lib/mix/lib/mix/scm/git.ex
+++ b/lib/mix/lib/mix/scm/git.ex
@@ -137,7 +137,7 @@ defmodule Mix.SCM.Git do
 
     # Migrate the Git repo
     rev = lock_rev || get_origin_opts_rev(opts) || default_branch()
-    git!(["--git-dir=.git", "checkout", "--quiet", rev])
+    git!(["--git-dir=.git", "checkout", "--force", "--quiet", rev])
 
     if opts[:submodules] do
       git!(~w[-c core.hooksPath='' --git-dir=.git submodule update --init --recursive])
diff --git a/lib/mix/test/mix/tasks/deps.git_test.exs b/lib/mix/test/mix/tasks/deps.git_test.exs
index 4508f89df87..da654a624c5 100644
--- a/lib/mix/test/mix/tasks/deps.git_test.exs
+++ b/lib/mix/test/mix/tasks/deps.git_test.exs
@@ -396,9 +396,14 @@ defmodule Mix.Tasks.DepsGitTest do
       Mix.State.clear_cache()
       purge([DepsOnGitRepo.MixProject])
 
+      # Write to the checkout location to ensure it is replaced
+      File.mkdir_p!("deps/deps_on_git_repo/lib")
+      File.write!("deps/deps_on_git_repo/lib/deps_on_git_repo.ex", "# WILL BE OVERRIDDEN")
+
       Mix.Tasks.Deps.Update.run(["deps_on_git_repo"])
       assert File.exists?("deps/git_repo/lib/git_repo.ex")
       assert File.read!("mix.lock") =~ last
+      assert File.read!("deps/deps_on_git_repo/lib/deps_on_git_repo.ex") =~ "GitRepo.hello()"
       refute File.exists?("_build/dev/lib/git_repo/.mix/compile.fetch")
     end)
   after
@@ -423,10 +428,15 @@ defmodule Mix.Tasks.DepsGitTest do
       Mix.State.clear_cache()
       purge([DepsOnGitRepo.MixProject])
 
+      # Write to the checkout location to ensure it is replaced
+      File.mkdir_p!("deps/deps_on_git_repo/lib")
+      File.write!("deps/deps_on_git_repo/lib/deps_on_git_repo.ex", "# WILL BE OVERRIDDEN")
+
       Mix.Dep.Lock.write(%{deps_on_git_repo: {:git, fixture_path("deps_on_git_repo"), last, []}})
       Mix.Tasks.Deps.Get.run([])
       assert File.exists?("deps/git_repo/lib/git_repo.ex")
       assert File.read!("mix.lock") =~ last
+      assert File.read!("deps/deps_on_git_repo/lib/deps_on_git_repo.ex") =~ "GitRepo.hello()"
       refute File.exists?("_build/dev/lib/git_repo/.mix/compile.fetch")
     end)
   after

From c51acfcbc530e7dd6e997f6f869e1ba74e8fb486 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jos=C3=A9=20Valim?= <jose.valim@dashbit.co>
Date: Fri, 25 Apr 2025 16:34:53 +0200
Subject: [PATCH 129/139] Trace on load functions, see #14427

---
 lib/elixir/src/elixir_module.erl              |  5 ++--
 .../test/elixir/kernel/tracers_test.exs       | 25 +++++++++++++++++--
 2 files changed, 26 insertions(+), 4 deletions(-)

diff --git a/lib/elixir/src/elixir_module.erl b/lib/elixir/src/elixir_module.erl
index 66455a23cb4..a5915b0f966 100644
--- a/lib/elixir/src/elixir_module.erl
+++ b/lib/elixir/src/elixir_module.erl
@@ -178,7 +178,7 @@ compile(Meta, Module, ModuleAsCharlist, Block, Vars, Prune, E) ->
          'Elixir.Module':'__check_attributes__'(E, DataSet, DataBag),
 
         AfterVerify = bag_lookup_element(DataBag, {accumulate, after_verify}, 2),
-        [elixir_env:trace({remote_function, [], VerifyMod, VerifyFun, 1}, CallbackE) ||
+        [elixir_env:trace({remote_function, [{line, Line}], VerifyMod, VerifyFun, 1}, CallbackE) ||
          {VerifyMod, VerifyFun} <- AfterVerify],
 
         %% Ensure there are no errors before we infer types
@@ -295,7 +295,8 @@ validate_on_load_attribute({on_load, Def}, Defs, Bag, Line, E) ->
       elixir_errors:module_error([{line, Line}], E, ?MODULE, {undefined_function, on_load, Def});
     {_Def, Kind, _Meta, _Clauses} when Kind == defmacro; Kind == defmacrop ->
       elixir_errors:module_error([{line, Line}], E, ?MODULE, {bad_macro, on_load, Def});
-    {_Def, Kind, _Meta, _Clauses} ->
+    {{Name, Arity}, Kind, _Meta, _Clauses} ->
+      elixir_env:trace({local_function, [{line, Line}], Name, Arity}, E),
       (Kind == defp) andalso ets:insert(Bag, {used_private, Def})
   end;
 validate_on_load_attribute(false, _Defs, _Bag, _Line, _E) -> ok.
diff --git a/lib/elixir/test/elixir/kernel/tracers_test.exs b/lib/elixir/test/elixir/kernel/tracers_test.exs
index dcc2f778a28..95cf1dbb7d0 100644
--- a/lib/elixir/test/elixir/kernel/tracers_test.exs
+++ b/lib/elixir/test/elixir/kernel/tracers_test.exs
@@ -10,11 +10,15 @@ defmodule Kernel.TracersTest do
   end
 
   def trace(event, %Macro.Env{} = env) do
-    send(self(), {event, env})
+    for {pid, _} <- Registry.lookup(__MODULE__, :tracers) do
+      send(pid, {event, env})
+    end
+
     :ok
   end
 
   setup_all do
+    start_supervised!({Registry, keys: :duplicate, name: __MODULE__})
     Code.put_compiler_option(:tracers, [__MODULE__])
 
     on_exit(fn ->
@@ -22,6 +26,11 @@ defmodule Kernel.TracersTest do
     end)
   end
 
+  setup do
+    Registry.register(__MODULE__, :tracers, :unused)
+    :ok
+  end
+
   test "traces start and stop" do
     compile_string("""
     Foo
@@ -255,7 +264,7 @@ defmodule Kernel.TracersTest do
     assert meta[:from_brackets]
 
     compile_string("""
-    defmodule Foo do
+    defmodule TracerBracketAccess do
       @foo %{bar: 3}
       def a() do
         @foo[:bar]
@@ -274,6 +283,18 @@ defmodule Kernel.TracersTest do
     assert meta[:from_brackets]
   end
 
+  test "traces on_load" do
+    compile_string("""
+    defmodule TracerOnLoad do
+      @on_load :init
+      def init, do: :ok
+    end
+    """)
+
+    assert_received {{:local_function, meta, :init, 0}, _}
+    assert meta[:line] == 1
+  end
+
   test "traces super" do
     compile_string("""
     defmodule TracerOverridable do

From afe47046667862ef2d2a89dce3caae33e179c101 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jos=C3=A9=20Valim?= <jose.valim@dashbit.co>
Date: Fri, 25 Apr 2025 16:58:13 +0200
Subject: [PATCH 130/139] Trace on definition callbacks, closes #14427

---
 lib/elixir/src/elixir_def.erl                 |  9 ++++--
 .../test/elixir/kernel/tracers_test.exs       | 29 +++++++++++++++++++
 2 files changed, 35 insertions(+), 3 deletions(-)

diff --git a/lib/elixir/src/elixir_def.erl b/lib/elixir/src/elixir_def.erl
index da348e7eeb3..81faa896211 100644
--- a/lib/elixir/src/elixir_def.erl
+++ b/lib/elixir/src/elixir_def.erl
@@ -222,7 +222,7 @@ store_definition(Meta, Kind, CheckClauses, Name, Arity, DefaultsArgs, Guards, Bo
   [store_definition(none, Kind, Meta, Name, length(DefaultArgs), File,
                     Module, 0, [Default]) || {_, DefaultArgs, _, _} = Default <- Defaults],
 
-  run_on_definition_callbacks(Kind, Module, Name, DefaultsArgs, Guards, Body, E),
+  run_on_definition_callbacks(Meta, Kind, Module, Name, DefaultsArgs, Guards, Body, E),
   Tuple.
 
 env_for_expansion(Kind, Tuple, E) when Kind =:= defmacro; Kind =:= defmacrop ->
@@ -267,10 +267,13 @@ def_to_clauses(Kind, Meta, Args, Guards, Body, E) ->
       elixir_errors:file_error(Meta, E, elixir_expand, {missing_option, Kind, [do]})
   end.
 
-run_on_definition_callbacks(Kind, Module, Name, Args, Guards, Body, E) ->
+run_on_definition_callbacks(Meta, Kind, Module, Name, Args, Guards, Body, E) ->
   {_, Bag} = elixir_module:data_tables(Module),
   Callbacks = ets:lookup_element(Bag, {accumulate, on_definition}, 2),
-  _ = [Mod:Fun(E, Kind, Name, Args, Guards, Body) || {Mod, Fun} <- lists:reverse(Callbacks)],
+  _ = [begin
+    elixir_env:trace({remote_function, Meta, Mod, Fun, 6}, E),
+    Mod:Fun(E, Kind, Name, Args, Guards, Body)
+  end || {Mod, Fun} <- lists:reverse(Callbacks)],
   ok.
 
 store_definition(CheckClauses, Kind, Meta, Name, Arity, File, Module, Defaults, Clauses)
diff --git a/lib/elixir/test/elixir/kernel/tracers_test.exs b/lib/elixir/test/elixir/kernel/tracers_test.exs
index 95cf1dbb7d0..441c51e2723 100644
--- a/lib/elixir/test/elixir/kernel/tracers_test.exs
+++ b/lib/elixir/test/elixir/kernel/tracers_test.exs
@@ -295,6 +295,35 @@ defmodule Kernel.TracersTest do
     assert meta[:line] == 1
   end
 
+  def __before_compile__(_), do: :ok
+  def __after_compile__(_, _), do: :ok
+  def __after_verify__(_), do: :ok
+  def __on_definition__(_, _, _, _, _, _), do: :ok
+
+  test "traces compile time attributes" do
+    compile_string("""
+    defmodule TracerCompileAttributes do
+      @before_compile Kernel.TracersTest
+      @after_compile Kernel.TracersTest
+      @on_definition Kernel.TracersTest
+      @after_verify Kernel.TracersTest
+      def hello, do: :world
+    end
+    """)
+
+    assert_received {{:remote_function, meta, __MODULE__, :__before_compile__, 1}, _}
+    assert meta[:line] == 1
+
+    assert_received {{:remote_function, meta, __MODULE__, :__after_compile__, 2}, _}
+    assert meta[:line] == 1
+
+    assert_received {{:remote_function, meta, __MODULE__, :__after_verify__, 1}, _}
+    assert meta[:line] == 1
+
+    assert_received {{:remote_function, meta, __MODULE__, :__on_definition__, 6}, _}
+    assert meta[:line] == 6
+  end
+
   test "traces super" do
     compile_string("""
     defmodule TracerOverridable do

From b3ab3696eca781c4d4971f83c7f271e93aa3f54f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jos=C3=A9=20Valim?= <jose.valim@dashbit.co>
Date: Fri, 9 May 2025 13:23:30 +0200
Subject: [PATCH 131/139] Update CI

---
 .github/workflows/ci-markdown.yml | 8 ++++----
 .github/workflows/notify.yml      | 6 +++---
 2 files changed, 7 insertions(+), 7 deletions(-)

diff --git a/.github/workflows/ci-markdown.yml b/.github/workflows/ci-markdown.yml
index 79edbec6088..188cec438b6 100644
--- a/.github/workflows/ci-markdown.yml
+++ b/.github/workflows/ci-markdown.yml
@@ -3,12 +3,12 @@ name: CI for Markdown content
 on:
   push:
     branches:
-      - 'main'
+      - "main"
     paths:
-      - 'lib/**/*.md'
+      - "lib/**/*.md"
   pull_request:
     paths:
-      - 'lib/**/*.md'
+      - "lib/**/*.md"
   workflow_dispatch:
 
 jobs:
@@ -18,7 +18,7 @@ jobs:
     strategy:
       fail-fast: false
 
-    runs-on: ubuntu-20.04
+    runs-on: ubuntu-latest
 
     steps:
       - name: Check out the repository
diff --git a/.github/workflows/notify.yml b/.github/workflows/notify.yml
index 482d5ad241c..1a5e9f1c943 100644
--- a/.github/workflows/notify.yml
+++ b/.github/workflows/notify.yml
@@ -10,7 +10,7 @@ permissions:
 
 jobs:
   notify:
-    runs-on: ubuntu-20.04
+    runs-on: ubuntu-latest
     name: Notify
     steps:
       - uses: actions/checkout@v4
@@ -18,8 +18,8 @@ jobs:
           fetch-depth: 50
       - uses: erlef/setup-beam@v1
         with:
-          otp-version: '25.0'
-          elixir-version: '1.14.0'
+          otp-version: "27.3"
+          elixir-version: "1.18.3"
       - name: Run Elixir script
         env:
           ELIXIR_FORUM_TOKEN: ${{ secrets.ELIXIR_FORUM_TOKEN }}

From 08a9d4b9fccad7a693a6a2924a7fa708fe49091e Mon Sep 17 00:00:00 2001
From: Ahmed Elawad <73728149+ahmed-el-awad@users.noreply.github.com>
Date: Sun, 18 May 2025 11:00:36 +0400
Subject: [PATCH 132/139] docs: Debugging page typo (#14502)

---
 lib/elixir/pages/getting-started/debugging.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/lib/elixir/pages/getting-started/debugging.md b/lib/elixir/pages/getting-started/debugging.md
index 8b503106381..0d4d848901e 100644
--- a/lib/elixir/pages/getting-started/debugging.md
+++ b/lib/elixir/pages/getting-started/debugging.md
@@ -110,7 +110,7 @@ When using `IEx`, you may pass `--dbg pry` as an option to "stop" the code execu
 $ iex --dbg pry
 ```
 
-Or to debug inside a of a project:
+Or to debug inside of a project:
 
 ```console
 $ iex --dbg pry -S mix

From 14d406fcd93d3f4686e1717ddcbf8ccbdfacc643 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jos=C3=A9=20Valim?= <jose.valim@dashbit.co>
Date: Tue, 20 May 2025 10:35:17 +0200
Subject: [PATCH 133/139] Raise nice error message when escaping maps/structs
 with references, closes #14497

---
 lib/elixir/src/elixir_quote.erl       | 20 ++++++++++++++++----
 lib/elixir/test/elixir/macro_test.exs |  6 ++++++
 2 files changed, 22 insertions(+), 4 deletions(-)

diff --git a/lib/elixir/src/elixir_quote.erl b/lib/elixir/src/elixir_quote.erl
index 41367e6d52b..d55a90314b9 100644
--- a/lib/elixir/src/elixir_quote.erl
+++ b/lib/elixir/src/elixir_quote.erl
@@ -160,10 +160,19 @@ do_escape(BitString, _) when is_bitstring(BitString) ->
   end;
 
 do_escape(Map, Q) when is_map(Map) ->
-  TT = do_quote(lists:sort(maps:to_list(Map)), Q),
+  TT =
+    [if
+      is_reference(V) ->
+        argument_error(<<('Elixir.Kernel':inspect(Map, []))/binary, " contains a reference (",
+                         ('Elixir.Kernel':inspect(V, []))/binary, ") and therefore it cannot be escaped ",
+                         "(it must be defined within a function instead). ", (bad_escape_hint())/binary>>);
+      true ->
+        {do_quote(K, Q), do_quote(V, Q)}
+     end || {K, V} <- lists:sort(maps:to_list(Map))],
   {'%{}', [], TT};
 
-do_escape([], _) -> [];
+do_escape([], _) ->
+  [];
 
 do_escape([H | T], #elixir_quote{unquote=false} = Q) ->
   do_quote_simple_list(T, do_quote(H, Q), Q);
@@ -195,8 +204,11 @@ do_escape(Other, _) ->
 
 bad_escape(Arg) ->
   argument_error(<<"cannot escape ", ('Elixir.Kernel':inspect(Arg, []))/binary, ". ",
-                   "The supported values are: lists, tuples, maps, atoms, numbers, bitstrings, ",
-                   "PIDs and remote functions in the format &Mod.fun/arity">>).
+                   (bad_escape_hint())/binary>>).
+
+bad_escape_hint() ->
+  <<"The supported values are: lists, tuples, maps, atoms, numbers, bitstrings, ",
+    "PIDs and remote functions in the format &Mod.fun/arity">>.
 
 %% Quote entry points
 
diff --git a/lib/elixir/test/elixir/macro_test.exs b/lib/elixir/test/elixir/macro_test.exs
index 297e46f9f13..851dbf16f05 100644
--- a/lib/elixir/test/elixir/macro_test.exs
+++ b/lib/elixir/test/elixir/macro_test.exs
@@ -136,6 +136,12 @@ defmodule MacroTest do
     test "does not add context to quote" do
       assert Macro.escape({:quote, [], [[do: :foo]]}) == {:{}, [], [:quote, [], [[do: :foo]]]}
     end
+
+    test "inspects container when a reference cannot be escaped" do
+      assert_raise ArgumentError, ~r"~r/foo/ contains a reference", fn ->
+        Macro.escape(%{~r/foo/ | re_pattern: make_ref()})
+      end
+    end
   end
 
   describe "expand_once/2" do

From 88f65ae1a6aed9b3acfc77c9760cd3f71a595e94 Mon Sep 17 00:00:00 2001
From: Wojtek Mach <wojtekmach@users.noreply.github.com>
Date: Wed, 21 May 2025 12:45:52 +0200
Subject: [PATCH 134/139] Install Hex and Rebar per OTP release (Elixir v1.18)
 (#14509)

---
 lib/mix/lib/mix/local.ex             |  39 ++++-----
 lib/mix/lib/mix/rebar.ex             |  10 ++-
 lib/mix/lib/mix/tasks/local.hex.ex   |  13 ++-
 lib/mix/lib/mix/tasks/local.rebar.ex |   7 +-
 lib/mix/test/mix/local_test.exs      | 115 +++++++++++++++------------
 lib/mix/test/test_helper.exs         |   3 +-
 6 files changed, 99 insertions(+), 88 deletions(-)

diff --git a/lib/mix/lib/mix/local.ex b/lib/mix/lib/mix/local.ex
index 2192e689f06..c6fc06fd466 100644
--- a/lib/mix/lib/mix/local.ex
+++ b/lib/mix/lib/mix/local.ex
@@ -232,40 +232,35 @@ defmodule Mix.Local do
 
   defp find_latest_eligible_version(entries, artifact_version) do
     elixir_version = Version.parse!(System.version())
+    otp_release = System.otp_release()
 
     entries
     |> Enum.reverse()
-    |> find_version(artifact_version, elixir_version)
+    |> find_version(artifact_version, elixir_version, otp_release)
   end
 
-  defp find_version(entries, _artifact_version = nil, elixir_version) do
-    Enum.find_value(entries, &find_by_elixir_version(&1, elixir_version))
-  end
+  defp find_version(entries, artifact_version, elixir_version, otp_release) do
+    entries =
+      if artifact_version do
+        Enum.filter(entries, &(hd(&1) == artifact_version))
+      else
+        entries
+      end
 
-  defp find_version(entries, artifact_version, elixir_version) do
-    Enum.find_value(entries, &find_by_artifact_version(&1, artifact_version, elixir_version))
+    Enum.find_value(entries, &find_by_elixir_version(&1, elixir_version, otp_release))
   end
 
-  defp find_by_elixir_version([artifact_version, digest | versions], elixir_version) do
-    if version = Enum.find(versions, &(Version.compare(&1, elixir_version) != :gt)) do
-      {version, artifact_version, digest}
-    end
-  end
-
-  defp find_by_artifact_version(
-         [artifact_version, digest | versions],
-         artifact_version,
-         elixir_version
+  defp find_by_elixir_version(
+         [artifact_version, digest, hex_elixir_version, hex_otp_release | _],
+         elixir_version,
+         otp_release
        ) do
-    if version = Enum.find(versions, &(Version.compare(&1, elixir_version) != :gt)) do
-      {version, artifact_version, digest}
+    if Version.compare(hex_elixir_version, elixir_version) != :gt and
+         hex_otp_release <= otp_release do
+      {hex_elixir_version, artifact_version, digest, hex_otp_release}
     end
   end
 
-  defp find_by_artifact_version(_entry, _artifact_version, _elixir_version) do
-    nil
-  end
-
   ## Public keys
 
   @doc """
diff --git a/lib/mix/lib/mix/rebar.ex b/lib/mix/lib/mix/rebar.ex
index 7f0b86c083d..2ecba673b59 100644
--- a/lib/mix/lib/mix/rebar.ex
+++ b/lib/mix/lib/mix/rebar.ex
@@ -49,12 +49,18 @@ defmodule Mix.Rebar do
   @doc """
   Returns the path supposed to host the local copy of `rebar`.
 
-  The rebar3 installation is specific to the Elixir version,
+  The rebar3 installation is specific to the Elixir version and OTP release,
   in order to force updates when new Elixir versions come out.
   """
   def local_rebar_path(:rebar3) do
     [major, minor | _] = String.split(System.version(), ".")
-    Path.join([Mix.Utils.mix_home(), "elixir", "#{major}-#{minor}", "rebar3"])
+
+    Path.join([
+      Mix.Utils.mix_home(),
+      "elixir",
+      "#{major}-#{minor}-otp-#{System.otp_release()}",
+      "rebar3"
+    ])
   end
 
   @doc """
diff --git a/lib/mix/lib/mix/tasks/local.hex.ex b/lib/mix/lib/mix/tasks/local.hex.ex
index 2d43db67554..77a78bfa6e9 100644
--- a/lib/mix/lib/mix/tasks/local.hex.ex
+++ b/lib/mix/lib/mix/tasks/local.hex.ex
@@ -1,8 +1,8 @@
 defmodule Mix.Tasks.Local.Hex do
   use Mix.Task
 
-  @hex_list_path "/installs/hex-1.x.csv"
-  @hex_archive_path "/installs/[ELIXIR_VERSION]/hex-[HEX_VERSION].ez"
+  @hex_list_path "/installs/hex.csv"
+  @hex_archive_path "/installs/[ELIXIR_VERSION]/hex-[HEX_VERSION]-otp-[OTP_RELEASE].ez"
 
   @shortdoc "Installs Hex locally"
 
@@ -68,16 +68,13 @@ defmodule Mix.Tasks.Local.Hex do
   defp run_install(version, argv) do
     hex_url = Mix.Hex.url()
 
-    {elixir_version, hex_version, sha512} =
-      Mix.Local.find_matching_versions_from_signed_csv!(
-        "Hex",
-        version,
-        hex_url <> @hex_list_path
-      )
+    {elixir_version, hex_version, sha512, otp_release} =
+      Mix.Local.find_matching_versions_from_signed_csv!("Hex", version, hex_url <> @hex_list_path)
 
     url =
       (hex_url <> @hex_archive_path)
       |> String.replace("[ELIXIR_VERSION]", elixir_version)
+      |> String.replace("[OTP_RELEASE]", otp_release)
       |> String.replace("[HEX_VERSION]", hex_version)
 
     # Unload the Hex module we loaded earlier to avoid conflicts when Hex is updated
diff --git a/lib/mix/lib/mix/tasks/local.rebar.ex b/lib/mix/lib/mix/tasks/local.rebar.ex
index 255ac4b9c55..0c6b02b9279 100644
--- a/lib/mix/lib/mix/tasks/local.rebar.ex
+++ b/lib/mix/lib/mix/tasks/local.rebar.ex
@@ -1,8 +1,8 @@
 defmodule Mix.Tasks.Local.Rebar do
   use Mix.Task
 
-  @rebar3_list_url "/installs/rebar3-1.x.csv"
-  @rebar3_escript_url "/installs/[ELIXIR_VERSION]/rebar3-[REBAR_VERSION]"
+  @rebar3_list_url "/installs/rebar.csv"
+  @rebar3_escript_url "/installs/[ELIXIR_VERSION]/rebar3-[REBAR_VERSION]-otp-[OTP_RELEASE]"
 
   @shortdoc "Installs Rebar locally"
 
@@ -110,13 +110,14 @@ defmodule Mix.Tasks.Local.Rebar do
     hex_url = Mix.Hex.url()
     list_url = hex_url <> list_url
 
-    {elixir_version, rebar_version, sha512} =
+    {elixir_version, rebar_version, sha512, otp_release} =
       Mix.Local.find_matching_versions_from_signed_csv!("Rebar", _version = nil, list_url)
 
     url =
       (hex_url <> escript_url)
       |> String.replace("[ELIXIR_VERSION]", elixir_version)
       |> String.replace("[REBAR_VERSION]", rebar_version)
+      |> String.replace("[OTP_RELEASE]", otp_release)
 
     install_from_path(manager, url, Keyword.put(opts, :sha512, sha512))
   end
diff --git a/lib/mix/test/mix/local_test.exs b/lib/mix/test/mix/local_test.exs
index a1840487253..b3bc3e93045 100644
--- a/lib/mix/test/mix/local_test.exs
+++ b/lib/mix/test/mix/local_test.exs
@@ -3,67 +3,70 @@ Code.require_file("../test_helper.exs", __DIR__)
 defmodule Mix.LocalTest do
   use MixTest.Case
 
-  # openssl rsa -in elixirest.pem -pubout > elixirest.pub
+  # openssl rsa -in elixirtest.pem -pubout > elixirtest.pub
   @public_key """
   -----BEGIN PUBLIC KEY-----
-  MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA37moKP1dGGLhsP3d8Fwv
-  W25SoYZUY2K+Iq7A0OBV36Rnb8yW3BWjfh5YtmPvUCfYUbNCW2HTMMgBntkQ4YmN
-  B9tHVZazl2uX9lGCfZZPFc/9umvKRojCPkMN81MfTxqnY0oaLHr6DB86RsWHB+ld
-  782Xf+nd9q3LFdUl8SGlKX7uzfVWd4EWYNcL7aLeLSupZWeNg8uVmY3zua0EgIlQ
-  XryalIOZb/R+pwprWZoftCl+20FGYi/mJpo/idFtXsR0sJKF4X0W3NORT9RIRbs9
-  WdjiFi+eIP7Nm8KSF4pbaXCqSmVf9cgvUuGTxc9/P5GcIPAlkcsSrE5peLyUCk5f
-  2QIDAQAB
+  MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAkIaIn8uhwSVp9d+aV+GL
+  tFjMt0TylxDu6x5jwjDvXCD3Of6VAriqcdXDJAqGPN1C4HNTxfihYpLZB+qXCNQM
+  oEa/I1h6OB5CXb8zIOl2Yriforr9LS+EkP+1xVmz7w7lNAagD0rJyJrTDbYDV+2Y
+  EnRq84FpZc/+7z6ojc3RMh9x/5t9jDE4Ft3NCujCmGsy2AcBlMpAQkcNveyplNmu
+  fZH2vId8h8t6rABwEZgSazHObkcHwE3bndynFO7zXvBu6ebNUDuU3DrDJ1Iepy9I
+  jEuZ6h13j7hL5l+j+9bGRQtasWgwvCkCGD+/2Lz4Ehox58voDCzAK09rsCwQ6gAT
+  CQIDAQAB
   -----END PUBLIC KEY-----
   """
 
-  # openssl genrsa -aes256 -out elixirtest.pem -passout stdin 2048
+  # openssl genrsa -aes256 -out elixirtest.pem -passout pass:secret12 2048
   @private_key """
-  -----BEGIN RSA PRIVATE KEY-----
-  Proc-Type: 4,ENCRYPTED
-  DEK-Info: AES-256-CBC,48BA5153DA2F120ECE063B33C1204A49
-
-  5gp3daNWujH7o9S/dJQEt9TYTRP0pPZtU55PlZrzWt52optr7XHW/ENOm84g5J70
-  QCPELp12jfQsNiPwbVWXKy2zD3QlNiAelf65hqLWJTWli7XIXfdP46VXOu67OKf9
-  Ziw4HQ+AdBEwFt20wJst77iy17sNlyxp5DhNDonnSizzIowgUAJkoNI5aBUU6D8X
-  KTSIftZW35Z4SudkazdoHepEfItZTI8mB4rvfn71Q4oOBA1rAuUUmdPWoPBfUHDa
-  hvIp2T2Q8zZYqm0+SjDxZUYOOreE7fuf5NSLhHHt7+jyWQmtaVxnOWms72G+9xT0
-  NGmOEB0WEg1kBsUbYOXXwyCAZhNA6MaKCtgjQczRTK+geS1xNaFc9FDEk3ZjN4Z8
-  PxrKQoqo+2aQGVcatZWCom80Dci3bIv7iZNA/y1rjfBn+MeitMOGscP7/CBrJAbI
-  bh1mvCu0McSnqlN0a+EuCVfJQYFMzjibpRVzKAST0QeaxXd5QxHfcPFPBLOpiVWc
-  NjHaZsHORyoJbUKGA4rgOiSB63mv7SDRA2mvxWpwV/+6MuwBah6t6CGoEsAr1Hbn
-  1ySt5w27bw3QEf2KTiuxDubo8UrF0eYzP5A9MH8vRpSRZHg8T3SBVfPJ/pM16Lnn
-  5BaMUdxDFJeet5HUYoke9Zm3udh2BvwGiKhzc9Pbw/EcsCcvChMimRTasqTaRf+S
-  uIm0Un7o+7kTuvBo2y87j2urCEUzft5QqEynbkR7p3vZnwoLLj+supXh3V8ivW4s
-  Z6ql+ukRcWd/ode+lbSiYfAJCLc1tCqJ3kTnMnADJBlL0TX7YnwBwWuwwPuZgeAv
-  F6nnBE1SBQ1WK+bjSVzIqmNFqsZw34wgpnz2heX0q8msF5pzd6EIeA+uz86k8XYh
-  4eVZYGXxa4Exodh/MqEpRuN1ytWDXvHULh0gml7xwZC3R50UD8uBNt5RGjXUkjXc
-  V0atKuvgzVlsB4xbDhVP7EVYHBF02NfNOsvo7kh0Yl1IcT/42UaCGYuU1o9zotPv
-  9b3SHz/HOmBVj2uCdR5XZ4EolP5Iv9vqIDt9DsuDpOyO+AFOww0FnJNCQ1Hmfb0T
-  qBYPv994oSPYLCGR4a8i/xfmmV8KbAIVEgK3AMbz8RxKr3WBWXWnzQdr4+y4EG24
-  hSnR52XQ42edv/fkqf9ez+fKNQ9i7PtlPE96Q21NeLMNKHh43X8hJFDh+oPz3Aio
-  YSNMCZnoyRdrjBRCsVBpnyoLmuhWwG9RlcrEj3G0BxYPh/weaBOAKAHjSr28yuUj
-  yIa8uddszC6XHSiVUgu7SGO8gQmq++eNdckjX/pEug5MjcWLUqaUg6+YLFWY6NLf
-  uDPOYuivq7ErtKTvP2xl3TBEDKhdfqxA2+RFxbBDmKjffZnRkcknQsxhlzAdbg22
-  Jwa2B1nrfjJpX5F+1Av2jHQGbIKMqZzv8fo1binMKpptFzokbWEOjcPCb3tPuomG
-  ZRkW3qO2pdyYX2N7VXYG9tGi2HrN/oFrWnHPoYF23v85V8WxNkODOCpTz85e6R5v
-  PVu+FCNFj5weEOTRhtEQyJo7mU5qIRwYeZvVxiC6W+XeFs95wdBE/Lvpg8yZ8D9d
-  -----END RSA PRIVATE KEY-----
+  -----BEGIN ENCRYPTED PRIVATE KEY-----
+  MIIFNTBfBgkqhkiG9w0BBQ0wUjAxBgkqhkiG9w0BBQwwJAQQjNQQlREZJuQveXPa
+  0ACpEwICCAAwDAYIKoZIhvcNAgkFADAdBglghkgBZQMEASoEEFoBWTpEyO8y6xdG
+  fuud2AIEggTQDQRXrxBpj1yIbnLcP7i43Gat2tEM+XXkvqbzwL+CdjfEzaqiBGwc
+  FEyWnJj5hENWtNq/AAwzcEALa3QdLJ2W+tX/hc5r1vILpy2oW0vPKfv4yadNyPWq
+  UzolKKU3MbREP4KXsuAVYepolRWAcWSZKozYBm9HUPwsSyM6E9K0aWmNr/YBbZPE
+  UEA949Pq6buQvwT2Gj/6FQlutEvJMuY8woM2I/RKSVHeSoB8FWJGSNd7ZY2DN7DG
+  o6kmhK0OVjxx0SYQwUm++4gh3yFRX6vnTf8ZADOwCOJE/hjMfqivNelJHGojYAD2
+  0c3zdo0HGvVghg2bzUxhc0HJqUFiUDNW1W3AVBrUJiQgHQb/ENYaMrd6klrslHDV
+  cnPkz+dZAq/WC3KFqufkSmHxU97dDs9G6hKdm80JVEdPG4aMJvHzVL6SvS+g/e7r
+  IJGZCFGch9SbikmESKegIfyLJYQrfjuLdXWPHx9J+LAmtRTA1xPwLH/aqCr9ahBB
+  JCl4RaezF0BH2GYLAV+Ailpy2jqsC3v9RW8vpQmU227H8ERFu9ZcWhZITbRhbjRB
+  oh3ERnOmvU3VWIXQ0j1cm9Qjl52y8HZJxef2dYsW8nmTSRj32gi9UnEsyOFL9FB7
+  KcFDq6o+go4nIfsmaAEOF1Hh01JZkbuX1SgmHvGRbNksTuljP/XZn7Wzz7fTcVZR
+  NsTgMbBl1+ctDHUBT7YzsHeEx8tZvLPDP8lO23aEnmRb6bHD4hVorUEM/kOpSm6g
+  DNP0buYGEAu4rZrp+5s46Y/RVAIhU4+663jqzmbmwOwEcuOJJCJn8eUGVweAgWX3
+  1PQTfpkk7RLxxuLeMHxEIynxyZNpSgfX0VGKe5UtplwcTQL8VgeZHayDxnhFwtSm
+  lBfPIM/i4kj7RonuehyPjuBA6n1CyqRVqc49c/3kVMgovqvbUQxQ7TtSfzfkhVyV
+  yRnRxcek+6Gctoahas5DPQl7y8jnwgg5qvUjYo6GIwBoxLUzIw+0MBn7gdMYHNmh
+  Xd9XZpRilAMJF5ynr2QyaE95XaQ3yK62FPgmIDdzfnN7es169O4SiXsgUyaSWsx9
+  VxOHP4r0j6epWLH4W11MQEj4Bb8wG//pCD5Kz2xvwhWaKjkqzuqGDaXSOTKV0FQM
+  NG5GbNNELNCy/ocvlsaC+acWcOfsx4f6yovCGVTpTr+wjbDqd1kEBoRTuyTPY+6P
+  0OU1Vf2GpMA2NDw0ZUOAllGVLrgD+326bu7ipePgiT8SiPNNsagMJNCSk5d3C4+K
+  bc0hakF/MIjPs3a+5/2mwtxk3QYYCIHITN8SIj+kFd7WMXALyClgWt3tcKwLTgMa
+  BSatAvwsoi2kiBu2A49msvka7YD8fENhtUl8vU12ZZdTih1chyPDifgfRgOfkZFW
+  a1XiLKjnWqeBqvJ3hBKuXweN6P7nvZidBYSESl3yrM00pA97Eta2in7ok80MRYH3
+  bsqzC636h43Mu8PbTKGpK66j1ts6uY9YONAGfGCcfj/wyBWuqNViv7vK38th8wj8
+  AG4rKyIlKxbFwo4Oh3jZTbfVF1Azl8pr0rK2P3W/EhfvWIrZVfB4Upy0wL6MjysE
+  RZ/57N2wFq4KBb9x9hCMFxignWmgyzPK+1J0iPRkSmpn9P/SzywDBIg=
+  -----END ENCRYPTED PRIVATE KEY-----
   """
 
   @csv """
-  1.2.5,ABC,0.9.0
-  1.2.3,DEF,1.0.0
-  1.2.4,GHI,1.0.0
+  1.2.5,ABC,0.9.0,25
+  1.2.5,ABC,0.9.0,26
+  1.2.3,DEF,1.0.0,25
+  1.2.3,DEF,1.0.0,26
+  1.2.4,GHI,1.0.0,25
+  1.2.4,GHI,1.0.0,26
   """
 
   # openssl dgst -sha512 -sign elixirtest.pem hex-1.x.csv | openssl base64 > elixirtest.csv.signed
   @csv_signed """
-  VRydmXOdEXQcKJu/SK/nKnE00T+s/T4mpXrYROMSXhD/s8ClvdimnGg61ie3YBS6
-  LXOjlEhbtMHRM2rTOUvv4z7FcyzwvSxSjunlVi2g3c1pVOZ78MonnYhGb44tZw/q
-  SOVmV+jJhc9EZFMIAAM3plMoyssyw2pMh7ZB/DxCQTIem3Qf0Ujzc2bYkLVlw7R+
-  1Rn6dcYEgCzyldVkAUMaYBwieyweWALA+YVDCMudJJK2J7p1OnuoPSVV+N3OkB/Z
-  T6Jj5ljD+54XnuxAMcgCoF9lpOwXscnw/Ma+8JqIoWo0jNFE3ji+8dGCUzQUdSe8
-  llLXgJJE2tGpDhEXBA3idg==
+  CVkhTiuCAfooYPhjyynDq40QhmDwLAEJvpwYytPCf6mpLXVrLXo/d/A2L8iBRJVx
+  uk4PNVksLRZ1ChBzGFvEqaFjrH+ndQAYLbwqcaMIn743YNUjGNVfTZkU47nBybtJ
+  BwDSBaAsow0Iitsl+UkDN/QvVoOLiX/x2cpnwCMrCgbTMroTzhH07vfLo3uCf8iY
+  cncImd07ffCewt77AsVPpKgJNOLzn+EBnvh4LbGWQya8EkgyQKuMBuNU86MYtFiW
+  NVpR7vbvqgWpEyr1XeknxKkhzYpna3+irXdMxGZw65WvFNWGJKnpuBTNHnHL+wX8
+  oNQLUfakH8/VMV/8v6Irbg==
   """
 
   # We don't actually use it but it exists for documentation purposes.
@@ -83,8 +86,10 @@ defmodule Mix.LocalTest do
       File.write!("csv", @csv)
       File.write!("csv.signed", @csv_signed)
 
-      assert {"1.0.0", "1.2.4", "GHI"} =
+      assert {"1.0.0", "1.2.4", "GHI", otp_release} =
                Mix.Local.find_matching_versions_from_signed_csv!("name", nil, "csv")
+
+      assert otp_release <= System.otp_release()
     end)
   end
 
@@ -94,12 +99,16 @@ defmodule Mix.LocalTest do
       File.write!("csv", @csv)
       File.write!("csv.signed", @csv_signed)
 
-      assert {"0.9.0", "1.2.5", "ABC"} =
+      assert {"0.9.0", "1.2.5", "ABC", otp_release} =
                Mix.Local.find_matching_versions_from_signed_csv!("name", "1.2.5", "csv")
 
-      assert {"1.0.0", "1.2.3", "DEF"} =
+      assert otp_release <= System.otp_release()
+
+      assert {"1.0.0", "1.2.3", "DEF", otp_release} =
                Mix.Local.find_matching_versions_from_signed_csv!("name", "1.2.3", "csv")
 
+      assert otp_release <= System.otp_release()
+
       assert_raise Mix.Error, "Could not find a version of name matching: 1.3.0", fn ->
         Mix.Local.find_matching_versions_from_signed_csv!("name", "1.3.0", "csv")
       end
@@ -109,7 +118,9 @@ defmodule Mix.LocalTest do
   @tag :tmp_dir
   test "raise on bad signature", %{tmp_dir: tmp_dir} do
     File.cd!(tmp_dir, fn ->
-      csv_signed = String.replace(@csv_signed, "VRy", "BAD")
+      <<_, _, _>> <> rest = @csv_signed
+      csv_signed = "BAD" <> rest
+
       File.write!("csv", @csv)
       File.write!("csv.signed", csv_signed)
 
diff --git a/lib/mix/test/test_helper.exs b/lib/mix/test/test_helper.exs
index 0fc0abc486e..e80be357cc0 100644
--- a/lib/mix/test/test_helper.exs
+++ b/lib/mix/test/test_helper.exs
@@ -254,7 +254,8 @@ end
 
 rebar3_source = System.get_env("REBAR3") || Path.expand("fixtures/rebar3", __DIR__)
 [major, minor | _] = String.split(System.version(), ".")
-rebar3_target = Path.join([mix, "elixir", "#{major}-#{minor}", "rebar3"])
+version_dir = "#{major}-#{minor}-otp-#{System.otp_release()}"
+rebar3_target = Path.join([mix, "elixir", version_dir, "rebar3"])
 File.mkdir_p!(Path.dirname(rebar3_target))
 File.cp!(rebar3_source, rebar3_target)
 

From c69f4407fda020058b2357bd2b5352b8e6db5de8 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jos=C3=A9=20Valim?= <jose.valim@dashbit.co>
Date: Wed, 21 May 2025 11:51:15 +0200
Subject: [PATCH 135/139] Fix ExUnit suite on Erlang/OTP 28

---
 lib/ex_unit/test/ex_unit/capture_io_test.exs | 28 ++++++++++----------
 1 file changed, 14 insertions(+), 14 deletions(-)

diff --git a/lib/ex_unit/test/ex_unit/capture_io_test.exs b/lib/ex_unit/test/ex_unit/capture_io_test.exs
index c437a8bd2df..eb85f4ad772 100644
--- a/lib/ex_unit/test/ex_unit/capture_io_test.exs
+++ b/lib/ex_unit/test/ex_unit/capture_io_test.exs
@@ -178,38 +178,38 @@ defmodule ExUnit.CaptureIOTest do
 
     test "with get password" do
       capture_io(fn ->
-        assert :io.get_password() == :eof
+        assert :io.get_password(Process.group_leader()) == :eof
       end)
 
       capture_io("", fn ->
-        assert :io.get_password() == :eof
+        assert :io.get_password(Process.group_leader()) == :eof
       end)
 
       capture_io("abc", fn ->
-        assert :io.get_password() == "abc"
-        assert :io.get_password() == :eof
+        assert :io.get_password(Process.group_leader()) == "abc"
+        assert :io.get_password(Process.group_leader()) == :eof
       end)
 
       capture_io("abc\n", fn ->
-        assert :io.get_password() == "abc\n"
-        assert :io.get_password() == :eof
+        assert :io.get_password(Process.group_leader()) == "abc\n"
+        assert :io.get_password(Process.group_leader()) == :eof
       end)
 
       capture_io("\n", fn ->
-        assert :io.get_password() == "\n"
-        assert :io.get_password() == :eof
+        assert :io.get_password(Process.group_leader()) == "\n"
+        assert :io.get_password(Process.group_leader()) == :eof
       end)
 
       capture_io("a\nb", fn ->
-        assert :io.get_password() == "a\n"
-        assert :io.get_password() == "b"
-        assert :io.get_password() == :eof
+        assert :io.get_password(Process.group_leader()) == "a\n"
+        assert :io.get_password(Process.group_leader()) == "b"
+        assert :io.get_password(Process.group_leader()) == :eof
       end)
 
       capture_io("あい\nう", fn ->
-        assert :io.get_password() == "あい\n"
-        assert :io.get_password() == "う"
-        assert :io.get_password() == :eof
+        assert :io.get_password(Process.group_leader()) == "あい\n"
+        assert :io.get_password(Process.group_leader()) == "う"
+        assert :io.get_password(Process.group_leader()) == :eof
       end)
     end
 

From 7b20c281d521aa7aa2ad2baa1e9ae6c579d79d0c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jos=C3=A9=20Valim?= <jose.valim@dashbit.co>
Date: Wed, 21 May 2025 13:47:42 +0200
Subject: [PATCH 136/139] Release v1.18.4

---
 CHANGELOG.md                                  | 37 +++++++++++++++++++
 VERSION                                       |  2 +-
 bin/elixir                                    |  2 +-
 bin/elixir.bat                                |  2 +-
 .../compatibility-and-deprecations.md         |  2 +-
 5 files changed, 41 insertions(+), 4 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index a3c83b55b39..4f38f049e20 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -223,6 +223,43 @@ You may also prefer to write using guards:
 
     def foo(x, y, z) when x == y and y == z
 
+## v1.18.4 (2025-05-21)
+
+This release includes initial support for Erlang/OTP 28, for those who want to try it out. In such cases, you may use Elixir v1.18.4 precompiled for Erlang/OTP 27, as it is binary compatible with Erlang/OTP 28.
+
+### 1. Enhancements
+
+#### IEx
+
+  * [IEx.Helpers] Add `IEx.Helpers.process_info/1` which prints process information
+
+#### Mix
+
+  * [mix compile] Support the `--no-listeners` option
+  * [mix local] Retry HTTP requests with disabled middlebox comp mode depending on the failure reason
+  * [mix local.hex] Install Hex per OTP release
+  * [mix local.rebar] Install Hex per OTP release
+  * [mix run] Support the `--no-listeners` option
+
+### 2. Bug fixes
+
+#### Elixir
+
+  * [Kernel] Emit trace events for `@on_definition` callbacks
+  * [Kernel] Emit trace events for `@on_load` callbacks
+  * [Kernel] Emit trace events for `super` calls
+  * [Kernel] Emit trace events for imported function calls
+  * [Kernel] Optimize map unions to avoid building long lists
+  * [Kernel] Do not crash when type checking nested bitstrings in patterns
+  * [Kernel] Do not crash when non-binary bitstring is given as struct default value
+  * [Kernel] Recompile regexes when escaped from module attributes for Erlang/OTP 28 compatibility
+  * [Kernel] Preserve backwards compatibility in `elixir_erl`
+
+#### Mix
+
+  * [mix deps.get] Ensure git checkout works when there are untracked files in the dependency
+  * [mix loadpaths] Do not run listeners when not checking the deps
+
 ## v1.18.3 (2025-03-06)
 
 ### 1. Enhancements
diff --git a/VERSION b/VERSION
index 72582753e34..4eee8f6d150 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-1.18.3
\ No newline at end of file
+1.18.4
\ No newline at end of file
diff --git a/bin/elixir b/bin/elixir
index 79cb49484fe..a4ed0a202f9 100755
--- a/bin/elixir
+++ b/bin/elixir
@@ -1,7 +1,7 @@
 #!/bin/sh
 set -e
 
-ELIXIR_VERSION=1.18.3
+ELIXIR_VERSION=1.18.4
 
 if [ $# -eq 0 ] || { [ $# -eq 1 ] && { [ "$1" = "--help" ] || [ "$1" = "-h" ]; }; }; then
   cat <<USAGE >&2
diff --git a/bin/elixir.bat b/bin/elixir.bat
index eeec4db500a..76a792f7c6f 100644
--- a/bin/elixir.bat
+++ b/bin/elixir.bat
@@ -1,6 +1,6 @@
 @echo off
 
-set ELIXIR_VERSION=1.18.3
+set ELIXIR_VERSION=1.18.4
 
 if    ""%1""==""""                if ""%2""=="""" goto documentation
 if /I ""%1""==""--help""          if ""%2""=="""" goto documentation
diff --git a/lib/elixir/pages/references/compatibility-and-deprecations.md b/lib/elixir/pages/references/compatibility-and-deprecations.md
index 81a9a57b515..e147e36201e 100644
--- a/lib/elixir/pages/references/compatibility-and-deprecations.md
+++ b/lib/elixir/pages/references/compatibility-and-deprecations.md
@@ -42,7 +42,7 @@ Erlang/OTP versioning is independent from the versioning of Elixir. Erlang relea
 
 Elixir version | Supported Erlang/OTP versions
 :------------- | :-------------------------------
-1.18           | 25 - 27
+1.18           | 25 - 27 (and Erlang/OTP 28 from v1.18.4)
 1.17           | 25 - 27
 1.16           | 24 - 26
 1.15           | 24 - 26

From aeb2ea1dda6910a21a22560712f4d747fe42a55f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jos=C3=A9=20Valim?= <jose.valim@dashbit.co>
Date: Wed, 21 May 2025 15:50:59 +0200
Subject: [PATCH 137/139] Clarify regexes in OTP 28

---
 CHANGELOG.md | 13 ++++++++++++-
 1 file changed, 12 insertions(+), 1 deletion(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 4f38f049e20..79704f4426d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -225,7 +225,18 @@ You may also prefer to write using guards:
 
 ## v1.18.4 (2025-05-21)
 
-This release includes initial support for Erlang/OTP 28, for those who want to try it out. In such cases, you may use Elixir v1.18.4 precompiled for Erlang/OTP 27, as it is binary compatible with Erlang/OTP 28.
+This release includes initial support for Erlang/OTP 28, for those who want to try it out. In such cases, you may use Elixir v1.18.4 precompiled for Erlang/OTP 27, as it is binary compatible with Erlang/OTP 28. Note, however, that Erlang/OTP 28 no longer allows regexes to be defined in the module body and interpolated into an attribute. If you do this:
+
+```elixir
+@some_attribute ~r/foo/
+def some_fun, do: @some_attribute
+```
+
+You must rewrite it to:
+
+```elixir
+def some_fun, do: ~r/foo/
+```
 
 ### 1. Enhancements
 

From 71846797880c7649446a44eed89a4f1ccb00e8d3 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jos=C3=A9=20Valim?= <jose.valim@dashbit.co>
Date: Thu, 29 May 2025 17:16:44 +0200
Subject: [PATCH 138/139] Reduce filesystem names on Mix.install, see #14543

---
 lib/mix/lib/mix.ex | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/lib/mix/lib/mix.ex b/lib/mix/lib/mix.ex
index dc4181a33ab..b712fcc1710 100644
--- a/lib/mix/lib/mix.ex
+++ b/lib/mix/lib/mix.ex
@@ -894,7 +894,7 @@ defmodule Mix do
       {deps, config, system_env, consolidate_protocols?}
       |> :erlang.term_to_binary()
       |> :erlang.md5()
-      |> Base.encode16(case: :lower)
+      |> Base.url_encode64(case: :lower, padding: false)
 
     force? = System.get_env("MIX_INSTALL_FORCE") in ["1", "true"] or Keyword.fetch!(opts, :force)
 
@@ -1068,7 +1068,7 @@ defmodule Mix do
       System.get_env("MIX_INSTALL_DIR") ||
         Path.join(Mix.Utils.mix_cache(), "installs")
 
-    version = "elixir-#{System.version()}-erts-#{:erlang.system_info(:version)}"
+    version = "ex-#{System.version()}-erl-#{:erlang.system_info(:version)}"
     Path.join([install_root, version, cache_id])
   end
 

From a3dda2203546b9ee4ad648473f3fb266f209048c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jos=C3=A9=20Valim?= <jose.valim@dashbit.co>
Date: Tue, 22 Jul 2025 11:55:07 +0200
Subject: [PATCH 139/139] Do not mention OTP 28 yet on v1.18

---
 lib/elixir/pages/references/compatibility-and-deprecations.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/lib/elixir/pages/references/compatibility-and-deprecations.md b/lib/elixir/pages/references/compatibility-and-deprecations.md
index e147e36201e..81a9a57b515 100644
--- a/lib/elixir/pages/references/compatibility-and-deprecations.md
+++ b/lib/elixir/pages/references/compatibility-and-deprecations.md
@@ -42,7 +42,7 @@ Erlang/OTP versioning is independent from the versioning of Elixir. Erlang relea
 
 Elixir version | Supported Erlang/OTP versions
 :------------- | :-------------------------------
-1.18           | 25 - 27 (and Erlang/OTP 28 from v1.18.4)
+1.18           | 25 - 27
 1.17           | 25 - 27
 1.16           | 24 - 26
 1.15           | 24 - 26
