Compare commits

...
15 changed files with 18 additions and 311 deletions
+1 -23
View File
@@ -8,28 +8,6 @@
## Type system improvements
### More type inference
Elixir now performs inference of whole functions. The best way to show the new capabilities are with examples. Take the following code:
```elixir
def add_foo_and_bar(data) do
data.foo + data.bar
end
```
Elixir 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:
```elixir
def sum_to_string(a, b) do
Integer.to_string(a + b)
end
```
Even 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 checking of protocol dispatch and implementations
This release also adds type checking when dispatching and implementing protocols.
@@ -216,7 +194,7 @@ These additions offer greater transparency into the components and licenses of e
This work was performed by Jonatan Männchen and sponsored by the Erlang Ecosystem Foundation.
## v1.19.0-dev
## v1.19.0-rc.0 (2025-06-09)
### 1. Enhancements
+1 -1
View File
@@ -6,7 +6,7 @@ PREFIX ?= /usr/local
TEST_FILES ?= "*_test.exs"
SHARE_PREFIX ?= $(PREFIX)/share
MAN_PREFIX ?= $(SHARE_PREFIX)/man
CANONICAL := main/
# CANONICAL := main/
ELIXIRC := bin/elixirc --ignore-module-conflict $(ELIXIRC_OPTS)
ELIXIRC_MIN_SIG := $(ELIXIRC) -e 'Code.put_compiler_option :infer_signatures, []'
ERLC := erlc -I lib/elixir/include
+2 -2
View File
@@ -8,7 +8,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
- If this release addresses any publicly known security vulnerabilities with
@@ -34,7 +34,7 @@
### In the new branch
1. Comment the `CANONICAL=` in /Makefile
1. Comment out `CANONICAL := main/` in /Makefile
2. Update tables in /SECURITY.md and "Compatibility and Deprecations"
+2 -3
View File
@@ -12,12 +12,11 @@ Elixir applies bug fixes only to the latest minor branch. Security patches are a
Elixir version | Support
:------------- | :-----------------------------
1.19 | Development
1.18 | Bug fixes and security patches
1.19 | Bug fixes and security patches
1.18 | Security patches only
1.17 | Security patches only
1.16 | Security patches only
1.15 | Security patches only
1.14 | Security patches only
## Announcements
+1 -1
View File
@@ -1 +1 @@
1.19.0-dev
1.19.0-rc.0
+1 -1
View File
@@ -6,7 +6,7 @@
set -e
ELIXIR_VERSION=1.19.0-dev
ELIXIR_VERSION=1.19.0-rc.0
if [ $# -eq 0 ] || { [ $# -eq 1 ] && { [ "$1" = "--help" ] || [ "$1" = "-h" ]; }; }; then
cat <<USAGE >&2
+1 -1
View File
@@ -4,7 +4,7 @@
:: SPDX-FileCopyrightText: 2021 The Elixir Team
:: SPDX-FileCopyrightText: 2012 Plataformatec
set ELIXIR_VERSION=1.19.0-dev
set ELIXIR_VERSION=1.19.0-rc.0
if ""%1""=="""" if ""%2""=="""" goto documentation
if /I ""%1""==""--help"" if ""%2""=="""" goto documentation
+1 -1
View File
@@ -419,7 +419,7 @@ defmodule Module.Types do
local_handler: handler,
# Control if variable refinement is enabled.
# It is disabled only on dynamic dispatches.
refine_vars: true
refine_vars: false
}
end
@@ -292,7 +292,7 @@ For convenience, the markup notation to generate the admonition block above is t
#### Problem
This anti-pattern is the opposite of ["Compile-time dependencies"](#compile-time-dependencies) and it happens when a compile-time dependency is accidentally bypassed, making the Elixir compiler is to track dependencies and recompile files correctly. This happens when building aliases (in other words, module names) dynamically, either within a module or within a macro.
This anti-pattern is the opposite of ["Compile-time dependencies"](#compile-time-dependencies) and it happens when a compile-time dependency is accidentally bypassed, making the Elixir compiler unable to track dependencies and recompile files correctly. This happens when building aliases (in other words, module names) dynamically, either within a module or within a macro.
#### Example
@@ -14,12 +14,11 @@ Elixir applies bug fixes only to the latest minor branch. Security patches are a
Elixir version | Support
:------------- | :-----------------------------
1.19 | Development
1.18 | Bug fixes and security patches
1.19 | Bug fixes and security patches
1.18 | Security patches only
1.17 | Security patches only
1.16 | Security patches only
1.15 | Security patches only
1.14 | 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).
@@ -49,7 +48,7 @@ Erlang/OTP versioning is independent from the versioning of Elixir. Erlang relea
Elixir version | Supported Erlang/OTP versions
:------------- | :-------------------------------
1.19 | 26 - 27
1.19 | 26 - 28
1.18 | 25 - 27
1.17 | 25 - 27
1.16 | 24 - 26
@@ -236,4 +235,4 @@ Version | Deprecated feature | Replaced by (ava
[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/v1.17/CHANGELOG.md#4-hard-deprecations
[v1.18]: https://github.com/elixir-lang/elixir/blob/v1.18/CHANGELOG.md#4-hard-deprecations
[v1.19]: https://github.com/elixir-lang/elixir/blob/main/CHANGELOG.md#4-hard-deprecations
[v1.19]: https://github.com/elixir-lang/elixir/blob/v1.19/CHANGELOG.md#4-hard-deprecations
@@ -43,16 +43,6 @@ defmodule Module.Types.ExprTest do
assert typecheck!([x], [:ok | x]) == dynamic(non_empty_list(term(), term()))
end
test "inference" do
assert typecheck!(
[x, y, z],
(
List.to_integer([x, y | z])
{x, y, z}
)
) == dynamic(tuple([integer(), integer(), list(integer())]))
end
test "hd" do
assert typecheck!([x = [123, :foo]], hd(x)) == dynamic(union(atom([:foo]), integer()))
assert typecheck!([x = [123 | :foo]], hd(x)) == dynamic(integer())
@@ -127,16 +117,6 @@ defmodule Module.Types.ExprTest do
end
describe "funs" do
test "infers calls" do
assert typecheck!(
[x],
(
x.(1, 2)
x
)
) == dynamic(fun(2))
end
test "infers functions" do
assert typecheck!(& &1) == fun([dynamic()], dynamic())
assert typecheck!(fn -> :ok end) == fun([], atom([:ok]))
@@ -263,53 +243,6 @@ defmodule Module.Types.ExprTest do
assert typecheck!([%x{}], x.foo_bar()) == dynamic()
end
test "infers atoms" do
assert typecheck!(
[x],
(
x.foo_bar()
x
)
) == dynamic(atom())
assert typecheck!(
[x],
(
x.foo_bar(123)
x
)
) == dynamic(atom())
assert typecheck!(
[x],
(
&x.foo_bar/1
x
)
) == dynamic(atom())
end
test "infers maps" do
assert typecheck!(
[x],
(
:foo = x.foo_bar
123 = x.baz_bat
x
)
) == dynamic(open_map(foo_bar: atom([:foo]), baz_bat: integer()))
end
test "infers args" do
assert typecheck!(
[x, y],
(
z = Integer.to_string(x + y)
{x, y, z}
)
) == dynamic(tuple([integer(), integer(), binary()]))
end
test "undefined function warnings" do
assert typewarn!(URI.unknown("foo")) ==
{dynamic(), "URI.unknown/1 is undefined or private"}
@@ -535,16 +468,6 @@ defmodule Module.Types.ExprTest do
end
describe "binaries" do
test "inference" do
assert typecheck!(
[x, y],
(
<<x::float-size(y)>>
{x, y}
)
) == dynamic(tuple([union(float(), integer()), integer()]))
end
test "warnings" do
assert typeerror!([<<x::binary-size(2)>>], <<x::float>>) ==
~l"""
@@ -644,16 +567,6 @@ defmodule Module.Types.ExprTest do
assert typecheck!([x], {:ok, x}) == dynamic(tuple([atom([:ok]), term()]))
end
test "inference" do
assert typecheck!(
[x, y],
(
{:ok, :error} = {x, y}
{x, y}
)
) == dynamic(tuple([atom([:ok]), atom([:error])]))
end
test "elem/2" do
assert typecheck!(elem({:ok, 123}, 0)) == atom([:ok])
assert typecheck!(elem({:ok, 123}, 1)) == integer()
@@ -1458,16 +1371,6 @@ defmodule Module.Types.ExprTest do
) == dynamic(atom([:ok, :error, :timeout]))
end
test "infers type for timeout" do
assert typecheck!(
[x],
receive do
after
x -> x
end
) == dynamic(union(integer(), atom([:infinity])))
end
test "resets branches" do
assert typecheck!(
[x, timeout = :infinity],
@@ -1854,16 +1757,6 @@ defmodule Module.Types.ExprTest do
"""
end
test "infers binary generators" do
assert typecheck!(
[x],
(
for <<_ <- x>>, do: :ok
x
)
) == dynamic(binary())
end
test ":into" do
assert typecheck!([binary], for(<<x <- binary>>, do: x)) == list(integer())
assert typecheck!([binary], for(<<x <- binary>>, do: x, into: [])) == list(integer())
@@ -1901,20 +1794,6 @@ defmodule Module.Types.ExprTest do
end
) == union(atom([:ok]), union(integer(), float()))
end
test ":reduce inference" do
assert typecheck!(
[list, x],
(
123 =
for _ <- list, reduce: x do
x -> x
end
x
)
) == dynamic(integer())
end
end
describe "info" do
@@ -51,20 +51,6 @@ defmodule Module.Types.InferTest do
assert types[{:fun4, 4}] == {:infer, nil, [{args, atom([:ok])}]}
end
test "infer types from expressions", config do
types =
infer config do
def fun(x) do
x.foo + x.bar
end
end
number = union(integer(), float())
assert types[{:fun, 1}] ==
{:infer, nil, [{[dynamic(open_map(foo: number, bar: number))], dynamic(number)}]}
end
test "infer with Elixir built-in", config do
types =
infer config do
@@ -95,33 +81,6 @@ defmodule Module.Types.InferTest do
]}
end
test "infers return types from private functions", config do
types =
infer config do
def pub(x), do: priv(x)
defp priv(:ok), do: :ok
defp priv(:error), do: :error
end
assert types[{:pub, 1}] ==
{:infer, nil, [{[dynamic(atom([:ok, :error]))], dynamic(atom([:ok, :error]))}]}
assert types[{:priv, 1}] == nil
end
test "infers return types from super functions", config do
types =
infer config do
def pub(:ok), do: :ok
def pub(:error), do: :error
defoverridable pub: 1
def pub(x), do: super(x)
end
assert types[{:pub, 1}] ==
{:infer, nil, [{[dynamic(atom([:ok, :error]))], dynamic(atom([:ok, :error]))}]}
end
test "infers return types even with loops", config do
types =
infer config do
@@ -85,77 +85,6 @@ defmodule Module.Types.IntegrationTest do
]
end
test "writes exports with inferred map types" do
files = %{
"a.ex" => """
defmodule A do
defstruct [:x, :y, :z]
def struct_create_with_atom_keys(x) do
infer(y = %A{x: x})
{x, y}
end
def map_create_with_atom_keys(x) do
infer(%{__struct__: A, x: x, y: nil, z: nil})
x
end
def map_update_with_atom_keys(x) do
infer(%{x | y: nil})
x
end
def map_update_with_unknown_keys(x, y) do
infer(%{x | y => 123})
x
end
defp infer(%A{x: <<_::binary>>, y: nil}) do
:ok
end
end
"""
}
modules = compile_modules(files)
exports = read_chunk(modules[A]).exports |> Map.new()
return = fn name, arity ->
pair = {name, arity}
%{^pair => %{sig: {:infer, nil, [{_, return}]}}} = exports
return
end
assert return.(:struct_create_with_atom_keys, 1) ==
dynamic(
tuple([
binary(),
closed_map(
__struct__: atom([A]),
x: binary(),
y: atom([nil]),
z: atom([nil])
)
])
)
assert return.(:map_create_with_atom_keys, 1) == dynamic(binary())
assert return.(:map_update_with_atom_keys, 1) ==
dynamic(
closed_map(
__struct__: atom([A]),
x: binary(),
y: term(),
z: term()
)
)
assert return.(:map_update_with_unknown_keys, 2) ==
dynamic(open_map())
end
test "writes exports with inferred function types" do
files = %{
"a.ex" => """
@@ -106,16 +106,6 @@ defmodule Module.Types.PatternTest do
) == uri_type
end
test "refines types" do
assert typecheck!(
[x, foo = :foo, bar = 123],
(
{^foo, ^bar} = x
x
)
) == dynamic(tuple([atom([:foo]), integer()]))
end
test "reports incompatible types" do
assert typeerror!([x = {:ok, _}], [_ | _] = x) == ~l"""
the following pattern will never match:
+3 -29
View File
@@ -227,9 +227,9 @@ defmodule Mix.Tasks.Deps.Compile do
end)
end
defp do_rebar3(%Mix.Dep{opts: opts} = dep, config) do
if not Mix.Rebar.available?(:rebar3) do
handle_rebar_not_found(dep)
defp do_rebar3(%Mix.Dep{opts: opts, manager: manager} = dep, config) do
if not Mix.Rebar.available?(manager) do
Mix.Tasks.Local.Rebar.run(["--force"])
end
dep_path = opts[:dest]
@@ -285,32 +285,6 @@ defmodule Mix.Tasks.Deps.Compile do
|> Mix.Rebar.serialize_config()
end
defp handle_rebar_not_found(%Mix.Dep{app: app, manager: manager}) do
shell = Mix.shell()
shell.info(
"Could not find \"#{manager}\", which is needed to build dependency #{inspect(app)}"
)
install_question =
"Shall I install #{manager}? (if running non-interactively, " <>
"use \"mix local.rebar --force\")"
if not shell.yes?(install_question) do
error_message =
"Could not find \"#{manager}\" to compile " <>
"dependency #{inspect(app)}, please ensure \"#{manager}\" is available"
Mix.raise(error_message)
end
Mix.Tasks.Local.Rebar.run(["--force"])
if not Mix.Rebar.available?(manager) do
Mix.raise("\"#{manager}\" installation failed")
end
end
defp do_make(dep, config) do
command = make_command(dep)
shell_cmd!(dep, config, command, [{"IS_DEP", "1"}])