Releases: elixir-lang/elixir
main-latest
Automated release for latest main.
v1.20.0-rc.3
1. Enhancements
IEx
- [IEx] Optimize autocompleting modules
2. Bug fixes
Elixir
- [Enum] Fix
Enum.slice/2for ranges with step > 1 sliced by step > 1 - [File] Preserve directory permissions in
File.cp_r/3 - [File] Fix
File.cp_r/3infinite loop with symlink cycles - [File] Fix
File.cp_r/3infinite loop when copying into subdirectory of source - [File] Warn when defining
@type record(), fixes CI on Erlang/OTP 29 - [File] Fix
File.StreamEnumerable.countfor files without trailing newline - [Float] Fix
Float.parse/1inconsistent error handling for non-scientific notation overflow - [Kernel] Process fields even when structs are unknown (regression)
- [Kernel] Improve performance on several corner cases in the type system (regression)
- [Kernel] Fix regression when using
Kernel.in/2in defguard (regression)
v1.20.0-rc.2
Overall, the compiler finds more bugs, for free, and it has never been faster:
-
Infers types across clauses, finding more bugs and dead code
-
Compiles ~10% faster and has a new interpreted mode (up to 5x faster, scales to the number of cores). For more information, follow the benchmarks
-
Modifying a struct definition recompiles fewer files (it no longer requires files that only pattern match or update structs to recompile)
1. Enhancements
Elixir
- [Code] Add
module_definition: :interpretedoption toCodewhich allows module definitions to be evaluated instead of compiled. In some applications/architectures, this can lead to drastic improvements to compilation times. Note this does not affect the generated.beamfile, which will have the same performance/behaviour as before - [Code] Make module purging opt-in and move temporary module deletion to the background to speed up compilation times
- [Integer] Add
Integer.popcount/1 - [Kernel] Move struct validation in patterns and updates to type checker, this means adding and remove struct fields will cause fewer files to be recompiled
- [Kernel] Add type inference across clauses. For example, if one clause says
x when is_integer(x), then the next clause may no longer be an integer - [Kernel] Detect and warn on redundant clauses
- [List] Add
List.first!/1andList.last!/1 - Add Software Bill of Materials guide to the Documentation
Mix
- [mix compile] Add
module_definition: :interpretedoption toCodewhich allows module definitions to be evaluated instead of compiled. In some applications/architectures, this can lead to drastic improvements to compilation times. Note this does not affect the generated.beamfile, which will have the same performance/behaviour as before - [mix deps] Parallelize dep lock status checks during
deps.loadpaths, improving boot times in projects with many git dependencies
2. Potential breaking changes
Elixir
map.foo()(accessing a map field with parens) andmod.foo(invoking a function without parens) will now raise instead of emitting runtime warnings, aligning themselves with the type system behaviour
3. Bug fixes
IEx
- [IEx] Ensure warnings emitted during IEx parsing are properly displayed/printed
- [IEx] Ensure pry works across remote nodes
Mix
- [mix compile.erlang] Topsort Erlang modules before compilation for proper dependency resolution
v1.20.0-rc.1
1. Bug fixes
Elixir
- [Kernel] Improve the performance of the type system when working with large unions of open maps
- [Kernel] Do not crash on map types with struct keys when performing type operations
- [Kernel] Mark the outcome of bitstring types as dynamic
- [Kernel]
<<expr::bitstring>>will have typebinaryinstead ofbitstringifexpris a binary - [Kernel] Do not crash on conditional variables when calling a function on a module which is represented by a variable
v1.20.0-rc.0
Type system improvements
This release includes type inference of all constructs.
Type inference of function calls
Elixir now performs inference of whole functions. The best way to show the new capabilities are with examples. Take the following code:
def add_foo_and_bar(data) do
data.foo + data.bar
endElixir now infers that the function expects a map as first argument, and the map must have the keys .foo and .bar whose values are either integer() or float(). The return type will be either integer() or float().
Here is another example:
def sum_to_string(a, b) do
Integer.to_string(a + b)
endEven though the + operator works with both integers and floats, Elixir infers that a and b must be both integers, as the result of + is given to a function that expects an integer. The inferred type information is then used during type checking to find possible typing errors.
Type inference of guards
This release also performs inference of guards! Let's see some examples:
def example(x, y) when is_list(x) and is_integer(y)The code above correctly infers x is a list and y is an integer.
def example({:ok, x} = y) when is_binary(x) or is_integer(x)The one above infers x is a binary or an integer, and y is a two element tuple with :ok as first element and a binary or integer as second.
def example(x) when is_map_key(x, :foo)The code above infers x is a map which has the :foo key, represented as %{..., foo: dynamic()}. Remember the leading ... indicates the map may have other keys.
def example(x) when not is_map_key(x, :foo)And the code above infers x does not have the :foo key (hence x.foo will raise a typing violation), which has the type: %{..., foo: not_set()}.
You can also have expressions that assert on the size of data structures:
def example(x) when tuple_size(x) < 3Elixir will correctly track the tuple has at most two elements, and therefore accessing elem(x, 3) will emit a typing violation. In other words, Elixir can look at complex guards, infer types, and use this information to find bugs in our code, without a need to introduce type signatures (yet).
Complete typing of maps keys
Maps were one of the first data-structures we implemented within the Elixir type system however, up to this point, they only supported atom keys. If they had additional keys, those keys were simply marked as dynamic().
As of Elixir v1.20, we can track all possible domains as map keys. For example, the map:
%{123 => "hello", 456.0 => :ok}will have the type:
%{integer() => binary(), float() => :ok}It is also possible to mix domain keys, as above, with atom keys, yielding the following:
%{integer() => integer(), root: integer()}This system is an implementation of Typing Records, Maps, and Structs, by Giuseppe Castagna (2023).
Typing of map operations
We have typed the majority of the functions in the Map module, allowing the type system to track how keys are added, updated, and removed across all possible key types.
For example, imagine we are calling the following Map functions with a variable map, which we don't know the exact shape of, and an atom key:
Map.put(map, :key, 123)
#=> returns type %{..., key: integer()}
Map.delete(map, :key)
#=> returns type %{..., key: not_set()}As you can see, we track when keys are set and also when they are removed.
Some operations, like Map.replace/3, only replace the key if it exists, and that is also propagated by the type system:
Map.replace(map, :key, 123)
#=> returns type %{..., key: if_set(integer())}In other words, if the key exists, it would have been replaced by an integer value. Furthermore, whenever calling a function in the Map module and the given key is statically proven to never exist in the map, an error is emitted.
By combining full type inference with bang operations like Map.fetch!/2, Map.pop!/2, Map.replace!/3, and Map.update!/3, Elixir is able to propagate information about the desired keys. Take this module:
defmodule User do
def name(map), do: Map.fetch!(map, :name)
end
defmodule CallsUser do
def calls_name do
User.name(%{})
end
endThe code above has a type violation, which is now caught by the type system:
warning: incompatible types given to User.name/1:
User.name(%{})
given types:
%{name: not_set()}
but expected one of:
dynamic(%{..., name: term()})
typing violation found at:
│
16 │ User.name(%{})
│ ~
│
└─ lib/calls_user.ex:7:5: CallsUser.calls_name/0
Acknowledgements
The type system was made possible thanks to a partnership between CNRS and Remote. The development work is currently sponsored by Fresha and Tidewave.
v1.20.0-rc.0 (2026-01-09)
1. Enhancements
Elixir
- [Calendar] Optimize
date_from_iso_daysby using the Neri-Schneider algorithm - [Enum] Add
Enum.min_maxsorter - [Integer] Add
Integer.ceil_div/2 - [IO] Add
IO.iodata_empty?/1 - [File] Skip device, named pipes, etc in
File.cp_r/3instead of erroring with reason:eio - [Kernel] Print intermediate results of
dbgfor pipes - [Kernel] Warn on unused requires
- [Regex] Add
Regex.import/1to import regexes defined with/E
ExUnit
- [ExUnit.CaptureLog] Add
:formatteroption for custom log formatting
Mix
- [mix deps] Support filtering
mix depsoutput - [mix test] Add
mix test --dry-run
2. Hard deprecations
Elixir
- [File]
File.stream!(path, modes, lines_or_bytes)is deprecated in favor ofFile.stream!(path, lines_or_bytes, modes) - [Kernel] Matching on the size inside a bit pattern now requires the pin operator for consistency, such as
<<x::size(^existing_var)>> - [Kernel.ParallelCompiler]
Kernel.ParallelCompiler.async/1is deprecated in favor ofKernel.ParallelCompiler.pmap/2, which is more performant and addresses known limitations
Logger
- [Logger]
Logger.*_backendfunctions are deprecated in favor of handlers. If you really want to keep on using backends, see the:logger_backendspackage - [Logger]
Logger.enable/1andLogger.disable/1have been deprecated in favor ofLogger.put_process_level/2andLogger.delete_process_level/1
v1.19.5
1. Enhancements
Elixir
- [Protocol] Optimize protocol consolidation to no longer load structs
2. Bug fixes
Elixir
- [Kernel] Fix unnecessary recompilation when
dbg_callbackis modified at runtime - [Kernel] Fix parser crash on missing parentheses on expression following operator
not in - [Kernel] Support fetching abstract code for modules compiled with Elixir v1.14 and earlier
- [Protocol] Ensure protocol consolidation no longer stores outdated struct types. As a consequence, protocols types only track struct names at the moment
- [Stream] Revert optimization which caused nested streams in
Stream.flat_map/2to crash
IEx
- [IEx] Fix usage of
#iex:breakas part of multi-line prompts
Logger
- [Logger.Backends] Do not crash on invalid metadata
v1.19-latest
Automated release for latest v1.19.
v1.19.4
1. Enhancements
Mix
- [mix xref] Add
--min-cycle-labelto help projects adapt to the more precisemix xref graphreports in Elixir v1.19. In previous versions, Elixir would break a large compilation cycle into several smaller ones, and therefore developers would check for--min-cycle-sizeon CI. However, the issue is not the size of the cycle (it has no implication in the amount of compiled files), but how many compile-time dependencies (aka compile labels) in a cycle. The new option allows developers to filter on the label parameter
2. Bug fixes
Elixir
- [File] Ensure
File.cp_r/3reports non-existing destination properly (instead of source)
ExUnit
- [ExUnit] Fix formatter crash when diffing takes too long
- [ExUnit] Ensure parallel matches in
assertpropagate type information
Logger
- [Logger] Fix regression where formatter would crash when given chardata (the crash would happen when logging non-ASCII characters)
Mix
- [mix help] Ensure
app:APPworks when the project or its dependencies were not yet compiled - [mix escript.build] Ensure the
hexapplication can be included in escripts
v1.19.3
1. Enhancements
Elixir
- [Kernel] Support /E modifier for regular expressions in config files
Mix
- [mix compile] Allow forcing specific compilers, such as
--force-elixir,--force-app, etc - [mix help app:APP] Support showing helps for apps in Elixir and Erlang standard libraries
2. Bug fixes
ExUnit
- [ExUnit.Case] Fix crash when formatting errors caused by a linked/trapped exit during
setup_all
Mix
- [mix compile.app] Ensure functions in the format
&Mod.fun/aritycan be written to .app files - [mix compile.app] Ensure strings with Unicode characters can be written to .app files
v1.19.2
1. Enhancements
Elixir
- [Kernel] Measure and optimize writing of
.beamfiles in the compiler - [Kernel] Optimize rare scenarios where type checking took too long
Mix
- [mix compile] Add flag
--no-check-cwdto skip compiler check to aid debugging
2. Bug fixes
Elixir
- [IO] Fix dialyzer warning on
IO.inspect :label - [Kernel] Ensure we warn on deprecated
~~~unary operator
Logger
- [Logger] Reset ansi escapes before newlines in Logger
Mix
- [mix compile] Warn if
elixirc_pathsis not a list of string paths - [mix compile] Address regression where umbrella children were compiled too early and without respecting compilation flags
- [mix deps.compile] Improve reliability of
MIX_OS_DEPS_COMPILE_PARTITION_COUNTacrossmix escript.install,mix archive.install, and others