Compare commits

...
Author SHA1 Message Date
José Valim 97c1e1ec0c Release v1.20.1 2026-06-09 15:36:40 +02:00
José Valim 64e6707233 Limit version numbers to 14 bytes
This avoids parsing too large integers.

CVE-2026-49762
GHSA-w2h8-8x3g-278p
2026-06-09 15:30:13 +02:00
José Valim 657a7b394a Add docs about URI encoding/to_string 2026-06-09 15:30:08 +02:00
José Valim ac76c108ce Validate paths and files when extracting archives 2026-06-09 15:30:03 +02:00
José Valim 2e629aa414 Clarify security considerations and disclaimers to archive/escript install 2026-06-09 15:30:00 +02:00
José Valim 2f2fc9a7f9 Cap width in strftime to 1024 characters 2026-06-09 15:29:55 +02:00
Łukasz Samson bb12270c6b Add capture_arg to Code.Fragment.cursor_context spec (#15441) 2026-06-09 15:29:49 +02:00
Aaron Tinio 6d2e8241c9 Honor --no-compile in mix format (#15443) 2026-06-07 21:34:50 +02:00
José Valim a371208104 Tag lists member as returning boolean 2026-06-07 11:07:18 +02:00
José Valim 3a366c2f0f Deal with empty canonical on doc generation 2026-06-05 15:08:07 +02:00
José Valim d33ea6e963 require_file releases the file if compilation fails 2026-06-04 19:42:49 +02:00
José Valim 853b7cd719 Release v1.20.0 2026-06-03 19:38:11 +02:00
José Valim 0bc04768bf Squash CHANGELOG 2026-06-02 18:45:40 +02:00
Jean Klingler c354a77946 Never suggest back invalid characters in tokenizer hint (#15421) 2026-05-27 10:23:43 +02:00
Jean Klingler 766ece7e3f Inline in empty list in erlang pass (#15413) 2026-05-24 09:06:11 +09:00
José Valim 8bc9a2ed15 Avoid false positives on cascading warnings
Only emit warnings for missing exports in defined
modules to avoid false positives with modules from
the current project which may not have been defined
yet.
2026-05-22 10:41:54 +02:00
José Valim 7150d493ad Improve tracking of g df within the type system 2026-05-22 10:14:46 +02:00
José Valim 7a9ea30d9c Revert expansion of or/and in guards
This partially reverts aad63173 as it caused
massive AST to generate when expanding guards
with `expr in [...]` checks in them, as each
`or` became a full-blown case expression.

Alternative approaches will be explored in
future releases.
2026-05-22 09:51:29 +02:00
José Valim 3a038d1763 Wrap quote in a function that will implement recursive types
Closes #15410.
2026-05-22 09:51:29 +02:00
José Valim e5e66831ac Avoid redundant emptiness checks on list/tuple emptiness (#15412) 2026-05-22 09:51:29 +02:00
José Valim 285c15f1ab Release v1.20.0-rc.6 2026-05-21 13:45:11 +02:00
José Valim 909903962d Branch out v1.20 2026-05-21 13:34:14 +02:00
46 changed files with 595 additions and 334 deletions
+76 -155
View File
@@ -192,34 +192,30 @@ It also introduces a new compiler option called `:module_definition`, which if t
You can enable it by setting `elixirc_options: [module_definition: :interpreted]` in your `mix.exs`.
## v1.20.0-rc.6
## v1.20.1 (2026-06-09)
This release requires Erlang/OTP 27+ and is compatible with Erlang/OTP 29.
### 1. Enhancements
### 1. Security
#### Elixir
* [Kernel] Perform type inference across applications
* [Version] Limit integer components in Version to 14 decimal bytes, to avoid parsing too large integers from untrusted user input. We strongly advise developers parsing versions from user input to limit the data size given to the `Version` module (CVE-2026-49762, GHSA-w2h8-8x3g-278p)
### 2. Bug fixes
#### Elixir
* [Kernel] Fix type checker bug when validating a `case` inside a `cond` condition (regression)
* [Kernel] Preserve evaluation order when rewriting function calls from Elixir modules into Erlang ones
* [Calendar] Cap width in `Calendar.strftime/2` to 1024 characters
* [Code] Ensure `Code.require_file` releases the file if compilation fails
* [Kernel] Fix documentation generation to use the correct version in search
#### Mix
* [mix test] Respect --raise when mix test --warnings-as-errors passes with warnings
* [mix archive.install] Validate paths and files when extracting archives
* [mix format] Honor `--no-compile` option when loading plugins
### 3. Hard deprecations
## v1.20.0 (2026-06-03)
#### Mix
* [mix compile.elixir] `xref: [exclude: ...]` in your `mix.exs` is deprecated in favor of `elixirc_options: [no_warn_undefined: ...]`
## v1.20.0-rc.5 (2026-05-13)
This release requires Erlang/OTP 27+ and is compatible with Erlang/OTP 29.
### 1. Enhancements
@@ -230,45 +226,98 @@ This release requires Erlang/OTP 27+ and is compatible with Erlang/OTP 29.
#### Elixir
* [Base] Optimize Base validation functions by using SWAR techniques
* [Calendar] Optimize `date_from_iso_days` by using the Neri-Schneider algorithm
* [Code] Add `:dbg_callback` option to eval functions
* [Code] Add `module_definition: :interpreted` option to `Code` which 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 `.beam` file, 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
* [Code.Fragment] Allow preserving sigil metadata in `container_cursor_to_quoted`
* [Enum] Add `Enum.min_max` sorter
* [File] Add support for `[:raw]` opts in `File.read/2`
* [File] Skip device, named pipes, etc in `File.cp_r/3` instead of erroring with reason `:eio`
* [Float] Optimize `Float.round/2` by avoiding big integers
* [Inspect] Increase inspect limit to help print deeply nested data structures
* [Inspect] Support printing Erlang records (using Erlang notation)
* [Integer] Add `Integer.ceil_div/2`
* [Integer] Add `Integer.popcount/1`
* [IO] Add `IO.iodata_empty?/1`
* [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] Add occurrence typing on `case`, `cond`, and `with`
* [Registry] Switch `{:duplicate, :key}` key_ets to ordered_set with composite keys
* [Kernel] Detect and warn on redundant clauses
* [Kernel] Perform type inference across applications
* [Kernel] Print intermediate results of `dbg` for pipes
* [Kernel] Show undefined function errors even when missing variables (this helps debug errors caused when the developer forgets to require a macro)
* [Kernel] Warn on unused requires
* [List] Add `List.first!/1` and `List.last!/1`
* [Module] Purge and delete modules if `after_compile/2` callback fails
* [PartitionSupervisor] Support via tuples in `count_children/1` and `stop/3`
* [Process] Add `Process.get_label/1`
* [Registry] Switch `keys: {:duplicate, :key}` to `ordered_set` with composite keys
* [Regex] Add `Regex.import/1` to import regexes defined with `/E`
* [String] SWAR-optimize ASCII fast paths in `String.length/1` and `String.slice/3`
* Add Software Bill of Materials guide to the Documentation
#### ExUnit
* [ExUnit] Show remaining runs when using `--repeat-until-failure`
* [ExUnit.CaptureLog] Add `:formatter` option for custom log formatting
#### IEx
* [IEx] Optimize autocompleting modules
* [IEx.Helpers] Add `source/1`
#### Mix
* [mix app.tree] Support `--output` option
* [mix compile] Add `module_definition: :interpreted` option to `Code` which 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 `.beam` file, which will have the same performance/behaviour as before
* [mix compile] Enforce `:elixirc_paths` to be a list of strings to avoid paths from being discarded (the only documented type was lists of strings)
* [mix deps] Parallelize dep lock status checks during `deps.loadpaths`, improving boot times in projects with many git dependencies
* [mix deps] Support filtering `mix deps` output
* [mix deps.tree] Support `--output` option
* [mix help] Support printing docs for types and callbacks
* [mix format] Support `--no-compile` option
* [mix help] Support printing docs for types and callbacks
* [mix source] Add `mix source MODULE` to print or open a given module/function location
* [mix test] Add `mix test --dry-run`
### 2. Potential breaking changes
#### Elixir
* [Kernel] Disallow raw CR line ending in strings, comments and after `?` for security reasons
* [Kernel] Disallow raw CR line ending in strings, comments, and after `?` for security reasons
* [Kernel] `require SomeModule` no longer expands to the given module at compile-time, but it still returns the module at runtime. Note Elixir does not guarantee macros will expand to certain constructs, only what its execution result, but since this can break code relying on the previous behaviour, such as `require(SomeMod).some_macro()`, we are adding this note to the CHANGELOG
### 3. Bug fixes
#### Elixir
* [Enum] Fix `Enum.slice/2` for ranges with step > 1 sliced by step > 1
* [File] Allowing preserving directory permissions in `File.cp_r/3`
* [File] Fix `File.cp_r/3` infinite loop with symlink cycles
* [File] Fix `File.cp_r/3` infinite loop when copying into subdirectory of source
* [File] Fix `File.Stream`'s `Enumerable.count` for files without trailing newline
* [File] Warn when defining `@type record()` for Erlang/OTP 29
* [Float] Fix `Float.parse/1` inconsistent error handling for non-scientific notation overflow
* [Integer] Fix `Integer.extended_gcd/2` returning negative GCD for zero base cases
* [Integer] Raise when negative out-of-range digits are given to `Integer.undigits/2`
* [Kernel] Fix a compiler crash when importing a module with `only: :sigils` option when the imported module exports non-sigil symbols with `sigil_` prefix
* [Kernel] Protocols should not add compile-time dependencies on `Any` implementation
* [Kernel] Preserve evaluation order when rewriting function calls from Elixir modules into Erlang ones
* [Kernel] Reject negative Duration in `to_timeout/1`
* [Keyword] Raise `ArgumentError` in `Keyword.from_keys/2` for non-atom keys
* [Macro] Fix generation of heredocs in `Macro.to_string/1` with escaped trailing newline
* [Path] Consistently return path as binary in `Path.relative_to_cwd/2`
* [Stream] Raise in `Stream.cycle/1` when enumerable reduce call yields no elements
* [String] Support empty pattern list in `String.count/2`
* [URI] Fix `URI.merge` leaking `:+` marker when base path is empty string
#### ExUnit
* [ExUnit.Diff] Avoid false positives when diffing bitstrings
#### IEx
* [IEx] Ensure pry works across remote nodes
* [IEx] Ensure warnings emitted during IEx parsing are properly displayed/printed
#### Logger
@@ -277,148 +326,16 @@ This release requires Erlang/OTP 27+ and is compatible with Erlang/OTP 29.
#### Mix
* [Mix] Use `non_executable_binary_to_term` on loopback pubsub
* [mix compile.elixir] Fix scenario where Elixir would tag mtimes in the future
## v1.20.0-rc.4 (2026-03-31)
This release requires Erlang/OTP 27+ and is compatible with Erlang/OTP 29.
### 1. Enhancements
#### Elixir
* [Code] Add `:dbg_callback` option to eval functions
* [Code.Fragment] Allow preserving sigil metadata in `container_cursor_to_quoted`
* [File] Add support for `[:raw]` opts in `File.read/2`
* [Kernel] Show undefined function errors even when missing variables (this helps debug errors caused when the developer forgets to require a macro)
* [Module] Purge and delete modules if `after_compile/2` callback fails
* [PartitionSupervisor] Support via tuples in `count_children/1` and `stop/3`
* [Process] Add `Process.get_label/1`
#### Mix
* [mix deps] Allow overriding specific dependencies in `:override`
### 2. Bug fixes
#### Elixir
* [Integer] Fix `Integer.extended_gcd/2` returning negative GCD for zero base cases
* [Integer] Raise when negative out-of-range digits are given to `Integer.undigits/2`
* [Kernel] Protocols should not add compile-time dependencies on `Any` implementation
* [Kernel] Ensure structs trigger recompilation for type checking purposes (regression)
* [Kernel] Ensure type information propagate across `hd/tl` in guards (regression)
* [Keyword] Raise `ArgumentError` in `Keyword.from_keys/2` for non-atom keys
* [URI] Fix `URI.merge` leaking `:+` marker when base path is empty string
#### ExUnit
* [ExUnit.Diff] Avoid false positives when diffing bitstrings
#### Mix
* [mix deps] Use config files to pass project state to avoid argv limits on Windows when using `MIX_OS_DEPS_COMPILE_PARTITION_COUNT`
* [mix compile] Fix compile env change triggering full recompilation of path dependencies
* [mix compile] Add a build lock around protocol consolidation in umbrellas
* [mix compile] Ensure compilation of sibling deps do not mark path deps as changed
* [mix test] Fix `--warnings-as-errors` not catching misnamed test file warnings
## v1.20.0-rc.3 (2026-03-09)
### 1. Enhancements
#### IEx
* [IEx] Optimize autocompleting modules
### 2. Bug fixes
#### Elixir
* [Enum] Fix `Enum.slice/2` for ranges with step > 1 sliced by step > 1
* [File] Allowing preserving directory permissions in `File.cp_r/3`
* [File] Fix `File.cp_r/3` infinite loop with symlink cycles
* [File] Fix `File.cp_r/3` infinite loop when copying into subdirectory of source
* [File] Warn when defining `@type record()`, fixes CI on Erlang/OTP 29
* [File] Fix `File.Stream` `Enumerable.count` for files without trailing newline
* [Float] Fix `Float.parse/1` inconsistent 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/2` in defguard (regression)
## v1.20.0-rc.2 (2026-03-04)
### 1. Enhancements
#### Elixir
* [Code] Add `module_definition: :interpreted` option to `Code` which 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 `.beam` file, 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] 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!/1` and `List.last!/1`
* Add Software Bill of Materials guide to the Documentation
#### Mix
* [mix compile] Add `module_definition: :interpreted` option to `Code` which 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 `.beam` file, 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. Bug fixes
#### IEx
* [IEx] Ensure warnings emitted during IEx parsing are properly displayed/printed
* [IEx] Ensure pry works across remote nodes
#### Mix
* [mix compile] Fix compile env change triggering full recompilation of path dependencies
* [mix compile.elixir] Fix scenario where Elixir would tag mtimes in the future
* [mix compile.erlang] Topsort Erlang modules before compilation for proper dependency resolution
* [mix deps] Use config files to pass project state to avoid argv limits on Windows when using `MIX_OS_DEPS_COMPILE_PARTITION_COUNT`
* [mix test] Fix `--warnings-as-errors` not catching misnamed test file warnings
* [mix test] Respect `--raise` when `mix test --warnings-as-errors` passes with warnings
## v1.20.0-rc.1 (2026-01-13)
### 1. Bug fixes
#### Elixir
* [Kernel] Do not crash on map types with struct keys when performing type operations (regression)
* [Kernel] Mark the outcome of bitstring types as dynamic (regression)
* [Kernel] `<<expr::bitstring>>` will have type `binary` instead of `bitstring` if `expr` is a binary (regression)
* [Kernel] Do not crash on conditional variables when calling a function on a module which is represented by a variable (regression)
## v1.20.0-rc.0 (2026-01-09)
### 1. Enhancements
#### Elixir
* [Calendar] Optimize `date_from_iso_days` by using the Neri-Schneider algorithm
* [Enum] Add `Enum.min_max` sorter
* [Integer] Add `Integer.ceil_div/2`
* [IO] Add `IO.iodata_empty?/1`
* [File] Skip device, named pipes, etc in `File.cp_r/3` instead of erroring with reason `:eio`
* [Kernel] Print intermediate results of `dbg` for pipes
* [Kernel] Warn on unused requires
* [Regex] Add `Regex.import/1` to import regexes defined with `/E`
#### ExUnit
* [ExUnit.CaptureLog] Add `:formatter` option for custom log formatting
#### Mix
* [mix deps] Support filtering `mix deps` output
* [mix compile] Enforce `:elixirc_paths` to be a list of strings to avoid paths from being discarded (the only documented type was lists of strings)
* [mix test] Add `mix test --dry-run`
### 2. Potential breaking changes
#### Elixir
* `require SomeModule` no longer expands to the given module at compile-time, but it still returns the module at runtime. Note Elixir does not guarantee macros will expand to certain constructs, but since this can break code relying on the previous behaviour, such as `require(SomeMod).some_macro()`, we are adding this note to the CHANGELOG
### 3. Hard deprecations
### 4. Hard deprecations
#### Elixir
@@ -431,6 +348,10 @@ This release requires Erlang/OTP 27+ and is compatible with Erlang/OTP 29.
* [Logger] `Logger.*_backend` functions are deprecated in favor of handlers. If you really want to keep on using backends, see the `:logger_backends` package
* [Logger] `Logger.enable/1` and `Logger.disable/1` have been deprecated in favor of `Logger.put_process_level/2` and `Logger.delete_process_level/1`
#### Mix
* [mix compile.elixir] `xref: [exclude: ...]` in your `mix.exs` is deprecated in favor of `elixirc_options: [no_warn_undefined: ...]`
## v1.19
The CHANGELOG for v1.19 releases can be found [in the v1.19 branch](https://github.com/elixir-lang/elixir/blob/v1.19/CHANGELOG.md).
+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 -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.20 | Development
1.19 | Bug fixes and security patches
1.20 | Bug fixes and security patches
1.19 | Security patches only
1.18 | Security patches only
1.17 | Security patches only
1.16 | Security patches only
1.15 | Security patches only
## Announcements
+1 -1
View File
@@ -1 +1 @@
1.20.0-rc.5
1.20.1
+1 -1
View File
@@ -6,7 +6,7 @@
set -e
ELIXIR_VERSION=1.20.0-rc.5
ELIXIR_VERSION=1.20.1
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.20.0-rc.5
set ELIXIR_VERSION=1.20.1
if ""%1""=="""" if ""%2""=="""" goto documentation
if /I ""%1""==""--help"" if ""%2""=="""" goto documentation
+9 -2
View File
@@ -3,6 +3,8 @@
# SPDX-FileCopyrightText: 2012 Plataformatec
defmodule Calendar do
@strftime_max_width 1024
@moduledoc """
This module defines the responsibilities for working with
calendars, dates, times and datetimes in Elixir.
@@ -529,6 +531,7 @@ defmodule Calendar do
* `%`: indicates the start of a formatted section
* `<padding>`: set the padding (see below)
* `<width>`: a number indicating the minimum size of the formatted section
(maximum #{@strftime_max_width})
* `<format>`: the format itself (see below)
### Accepted padding options
@@ -667,9 +670,13 @@ defmodule Calendar do
end
defp parse_modifiers(<<digit, rest::binary>>, width, pad, parser_data) when digit in ?0..?9 do
new_width = (width || 0) * 10 + (digit - ?0)
width = (width || 0) * 10 + (digit - ?0)
parse_modifiers(rest, new_width, pad, parser_data)
if width > @strftime_max_width do
raise ArgumentError, "invalid strftime format: width must be at most #{@strftime_max_width}"
end
parse_modifiers(rest, width, pad, parser_data)
end
# set default padding if none was specified
+12 -6
View File
@@ -1634,13 +1634,19 @@ defmodule Code do
nil
:proceed ->
loaded =
Module.ParallelChecker.verify(fn ->
:elixir_compiler.string(charlist, file, fn _, _ -> :ok end)
end)
try do
loaded =
Module.ParallelChecker.verify(fn ->
:elixir_compiler.string(charlist, file, fn _, _ -> :ok end)
end)
:elixir_code_server.cast({:required, file})
loaded
:elixir_code_server.cast({:required, file})
loaded
catch
kind, reason ->
:elixir_code_server.call({:release, file})
:erlang.raise(kind, reason, __STACKTRACE__)
end
end
end
+1
View File
@@ -204,6 +204,7 @@ defmodule Code.Fragment do
| {:local_arity, charlist}
| {:local_call, charlist}
| {:anonymous_call, inside_caller}
| {:capture_arg, charlist}
| {:module_attribute, charlist}
| {:operator, charlist}
| {:operator_arity, charlist}
+3 -5
View File
@@ -2109,7 +2109,7 @@ defmodule Kernel do
assert_no_match_or_guard_scope(__CALLER__.context, "!")
annotate_case(
[optimize_boolean: true, type_check: {:case, :!}],
[optimize_boolean: true, type_check: {:case, :"!!"}],
quote do
case unquote(value) do
x when unquote(x_is_false_or_nil()) -> false
@@ -4703,10 +4703,8 @@ defmodule Kernel do
false
[] ->
quote do
_ = unquote(left)
false
end
# inlined as false in erlang pass
quote(do: :lists.member(unquote(left), []))
[head | tail] = list ->
case in_body? do
+1 -9
View File
@@ -369,9 +369,7 @@ defmodule Kernel.Utils do
end
# Prefaces `guard` with unquoted versions of `refs`.
defp unquote_refs_once(guard, refs, %{module: module} = env) do
env = %{env | context: nil}
defp unquote_refs_once(guard, refs, %{module: module}) do
{guard, used_refs} =
Macro.postwalk(guard, %{}, fn
{ref, meta, context} = var, acc when is_atom(ref) and is_atom(context) ->
@@ -393,12 +391,6 @@ defmodule Kernel.Utils do
{var, acc}
end
{{:., dot_meta, [:erlang, :orelse]}, meta, [left, right]}, acc ->
{Macro.expand({{:., dot_meta, [Kernel, :or]}, meta, [left, right]}, env), acc}
{{:., dot_meta, [:erlang, :andalso]}, meta, [left, right]}, acc ->
{Macro.expand({{:., dot_meta, [Kernel, :and]}, meta, [left, right]}, env), acc}
node, acc ->
{node, acc}
end)
+5 -1
View File
@@ -242,7 +242,11 @@ defmodule Module.Types.Apply do
{Kernel, :put_elem, [{[open_tuple([]), integer(), term()], dynamic(open_tuple([]))}]},
## Lists
{:lists, :member, [{[term(), list(term())], boolean()}]},
{:lists, :member,
[
{[term(), empty_list()], atom([false])},
{[term(), non_empty_list(term())], boolean()}
]},
## Map
{Map, :delete, [{[open_map(), term()], open_map()}]},
+37 -34
View File
@@ -2205,14 +2205,13 @@ defmodule Module.Types.Descr do
defp list_new(list_type, last_type), do: bdd_leaf_new(list_type, last_type)
defp non_empty_list_literals_intersection(list_literals) do
try do
Enum.reduce(list_literals, {:term, :term}, fn bdd_leaf(next_list, next_last),
{list, last} ->
{non_empty_intersection!(list, next_list), non_empty_intersection!(last, next_last)}
{list, last} =
Enum.reduce(list_literals, {:term, :term}, fn
bdd_leaf(next_list, next_last), {list, last} ->
{intersection(list, next_list), intersection(last, next_last)}
end)
catch
:empty -> :empty
end
if empty?(list) or empty?(last), do: :empty, else: {list, last}
end
# Takes all the lines from the root to the leaves finishing with a 1,
@@ -5026,15 +5025,6 @@ defmodule Module.Types.Descr do
end
end
# Detecting tuples built with none() fields
defp tuple_literal_intersection(:open, [], tag, elements) do
if Enum.any?(elements, &empty?/1) do
:empty
else
{tag, elements}
end
end
defp tuple_literal_intersection(tag1, elements1, tag2, elements2) do
case tuple_sizes_strategy(tag1, length(elements1), tag2, length(elements2)) do
:disjoint ->
@@ -5052,21 +5042,9 @@ defmodule Module.Types.Descr do
end
end
defp tuple_sizes_strategy(:closed, n1, :closed, n2) when n1 != n2, do: :disjoint
defp tuple_sizes_strategy(:closed, n1, :closed, n2) when n1 == n2, do: :left_subtype_of_right
defp tuple_sizes_strategy(:closed, n1, :open, n2) when n1 < n2, do: :disjoint
defp tuple_sizes_strategy(_, n1, :open, n2) when n1 >= n2, do: :left_subtype_of_right
defp tuple_sizes_strategy(:open, n1, :closed, n2) when n1 > n2, do: :disjoint
defp tuple_sizes_strategy(_, _, _, _), do: :none
# Intersects two lists of types, and _appends_ the extra elements to the result.
defp zip_non_empty_intersection!([], types2, acc) do
if Enum.any?(types2, &empty?/1), do: throw(:empty), else: Enum.reverse(acc, types2)
end
defp zip_non_empty_intersection!(types1, [], acc) do
if Enum.any?(types1, &empty?/1), do: throw(:empty), else: Enum.reverse(acc, types1)
end
defp zip_non_empty_intersection!([], types2, acc), do: Enum.reverse(acc, types2)
defp zip_non_empty_intersection!(types1, [], acc), do: Enum.reverse(acc, types1)
defp zip_non_empty_intersection!([type1 | rest1], [type2 | rest2], acc) do
zip_non_empty_intersection!(rest1, rest2, [non_empty_intersection!(type1, type2) | acc])
@@ -5082,6 +5060,13 @@ defmodule Module.Types.Descr do
end
end
defp tuple_sizes_strategy(:closed, n1, :closed, n2) when n1 != n2, do: :disjoint
defp tuple_sizes_strategy(:closed, n1, :closed, n2) when n1 == n2, do: :left_subtype_of_right
defp tuple_sizes_strategy(:closed, n1, :open, n2) when n1 < n2, do: :disjoint
defp tuple_sizes_strategy(_, n1, :open, n2) when n1 >= n2, do: :left_subtype_of_right
defp tuple_sizes_strategy(:open, n1, :closed, n2) when n1 > n2, do: :disjoint
defp tuple_sizes_strategy(_, _, _, _), do: :none
defp tuple_difference(_, bdd_leaf(:open, [])),
do: :bdd_bot
@@ -5112,17 +5097,35 @@ defmodule Module.Types.Descr do
defp non_empty_tuple_literals_intersection(tuples) do
try do
Enum.reduce(tuples, {:open, []}, fn bdd_leaf(next_tag, next_elements), {tag, elements} ->
case tuple_literal_intersection(tag, elements, next_tag, next_elements) do
:empty -> throw(:empty)
next -> next
Enum.reduce(tuples, {:open, []}, fn bdd_leaf(tag1, elements1), {tag2, elements2} ->
case tuple_sizes_strategy(tag1, length(elements1), tag2, length(elements2)) do
:disjoint ->
throw(:empty)
_ ->
tag = if tag1 == :open and tag2 == :open, do: :open, else: :closed
{tag, zip_intersection(elements1, elements2, [])}
end
end)
catch
:empty -> :empty
else
{tag, elements} ->
if Enum.any?(elements, &empty?/1) do
:empty
else
{tag, elements}
end
end
end
defp zip_intersection([], types2, acc), do: Enum.reverse(acc, types2)
defp zip_intersection(types1, [], acc), do: Enum.reverse(acc, types1)
defp zip_intersection([type1 | rest1], [type2 | rest2], acc) do
zip_intersection(rest1, rest2, [intersection(type1, type2) | acc])
end
defp tuple_empty?(bdd) do
bdd_to_dnf(bdd)
|> Enum.all?(fn {pos, negs} ->
+1 -1
View File
@@ -329,7 +329,7 @@ defmodule Module.Types.Helpers do
end
end
{{:., _, [:lists, :member]}, meta, [expr, [_ | _] = args]} = call ->
{{:., _, [:lists, :member]}, meta, [expr, args]} = call when is_list(args) ->
if Enum.any?(args, &match?({:|, _, [_, _]}, &1)) do
call
else
+24 -2
View File
@@ -1564,7 +1564,18 @@ defmodule Module.Types.Pattern do
#{expr_to_string({:!, [], [expr]}) |> indent(4)}
will always evaluate to true because the expression has type:
will always evaluate to true because its inner expression has type:
#{to_quoted_string(type) |> indent(4)}
"""
{:case, :"!!"} ->
"""
the following conditional expression:
#{expr_to_string({:!, [], [{:!, [], [expr]}]}) |> indent(4)}
will always evaluate to false because its inner expression has type:
#{to_quoted_string(type) |> indent(4)}
"""
@@ -1721,7 +1732,18 @@ defmodule Module.Types.Pattern do
#{expr_to_string({:!, [], [expr]}) |> indent(4)}
will always evaluate to false because the expression has type:
will always evaluate to false because its inner expression has type:
#{to_quoted_string(type) |> indent(4)}
"""
op == :"!!" ->
"""
the following conditional expression:
#{expr_to_string({:!, [], [{:!, [], [expr]}]}) |> indent(4)}
will always evaluate to true because its inner expression has type:
#{to_quoted_string(type) |> indent(4)}
"""
+9
View File
@@ -24,6 +24,12 @@ defmodule URI do
[scheme]://[userinfo]@[host]:[port][path]?[query]#[fragment]
The fields contain the encoded URI components as they appear in the URI
itself. For example, a slash inside the userinfo must be stored as `%2F`,
not as `/`. Functions such as `parse/1` and `new/1` preserve existing
percent-encoded sequences in those fields, and functions such as `to_string/1`
expects those fields to already be encoded as needed. Whenever setting or
modifying the fields directly, you must encode them accordingly.
Note the `authority` field is deprecated. `parse/1` will still
populate it for backwards compatibility but you should generally
@@ -897,6 +903,9 @@ defmodule URI do
@doc """
Returns the string representation of the given [URI struct](`t:t/0`).
This function assembles the URI components into a string, assuming each
field is valid and escaped as done by `parse/1` and `new/1`.
## Examples
iex> uri = URI.parse("http://google.com")
+18 -1
View File
@@ -18,12 +18,16 @@ defmodule Version do
MAJOR.MINOR.PATCH
Each numeric component is limited to at most 14 digits.
Pre-releases are supported by optionally appending a hyphen and a series of
period-separated identifiers immediately following the patch version.
Identifiers consist of only ASCII alphanumeric characters and hyphens (`[0-9A-Za-z-]`):
"1.0.0-alpha.3"
Numeric pre-release identifiers are also limited to at most 14 digits.
Build information can be added by appending a plus sign and a series of
dot-separated identifiers immediately following the patch or pre-release version.
Identifiers consist of only ASCII alphanumeric characters and hyphens (`[0-9A-Za-z-]`):
@@ -520,6 +524,8 @@ defmodule Version do
defmodule Parser do
@moduledoc false
@max_numeric_component_digits 14
operators = [
{">=", :>=},
{"<=", :<=},
@@ -621,7 +627,9 @@ defmodule Version do
defp require_digits(nil), do: :error
defp require_digits(string) do
if leading_zero?(string), do: :error, else: parse_digits(string, "")
if leading_zero?(string) or byte_size(string) > @max_numeric_component_digits,
do: :error,
else: parse_digits(string, "")
end
defp leading_zero?(<<?0, _, _::binary>>), do: true
@@ -649,6 +657,11 @@ defmodule Version do
end
end
defp convert_parts_to_integer([part | rest], acc)
when byte_size(part) > @max_numeric_component_digits do
if all_digits?(part), do: :error, else: convert_parts_to_integer(rest, [part | acc])
end
defp convert_parts_to_integer([part | rest], acc) do
case parse_digits(part, "") do
{:ok, integer} ->
@@ -667,6 +680,10 @@ defmodule Version do
{:ok, Enum.reverse(acc)}
end
defp all_digits?(<<char, rest::binary>>) when char in ?0..?9, do: all_digits?(rest)
defp all_digits?(<<>>), do: true
defp all_digits?(_other), do: false
defp valid_identifier?(<<char, rest::binary>>)
when char in ?0..?9
when char in ?a..?z
@@ -14,12 +14,11 @@ Elixir applies bug fixes only to the latest minor branch. Security patches are a
Elixir version | Support
:------------- | :-----------------------------
1.20 | Development
1.19 | Bug fixes and security patches
1.20 | Bug fixes and security patches
1.19 | Security patches only
1.18 | Security patches only
1.17 | Security patches only
1.16 | Security patches only
1.15 | 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).
@@ -243,4 +242,4 @@ Version | Deprecated feature | Replaced by (ava
[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/v1.19/CHANGELOG.md#4-hard-deprecations
[v1.20]: https://github.com/elixir-lang/elixir/blob/main/CHANGELOG.md#4-hard-deprecations
[v1.20]: https://github.com/elixir-lang/elixir/blob/v1.20/CHANGELOG.md#4-hard-deprecations
+5 -1
View File
@@ -3,7 +3,11 @@
# SPDX-FileCopyrightText: 2012 Plataformatec
# Returns config for Elixir docs (exclusively)
canonical = System.fetch_env!("CANONICAL")
canonical =
case System.fetch_env!("CANONICAL") do
"" -> System.version() <> "/"
canonical -> canonical
end
[
search: [
+5 -1
View File
@@ -2,7 +2,11 @@
# SPDX-FileCopyrightText: 2021 The Elixir Team
# Returns config for other apps except Elixir
canonical = System.fetch_env!("CANONICAL")
canonical =
case System.fetch_env!("CANONICAL") do
"" -> System.version() <> "/"
canonical -> canonical
end
[
search: [
+17
View File
@@ -59,6 +59,9 @@ handle_call({acquire, Path}, From, Config) ->
handle_call(required, _From, Config) ->
{reply, [F || {F, true} <- maps:to_list(Config#elixir_code_server.required)], Config};
handle_call({release, Path}, _From, Config) ->
{reply, ok, release(Path, Config)};
handle_call(retrieve_compiler_module, _From, Config) ->
case Config#elixir_code_server.mod_pool of
{Used, [Mod | Unused], Counter} ->
@@ -140,6 +143,20 @@ terminate(_Reason, _Config) ->
code_change(_Old, Config, _Extra) ->
{ok, Config}.
release(Path, Config) ->
Current = Config#elixir_code_server.required,
case maps:find(Path, Current) of
{ok, []} ->
Released = maps:remove(Path, Current),
Config#elixir_code_server{required=Released};
{ok, [Next | Waiting]} ->
_ = gen_server:reply(Next, proceed),
Released = maps:put(Path, Waiting, Current),
Config#elixir_code_server{required=Released};
error ->
Config
end.
compiler_module(I) ->
list_to_atom("elixir_compiler_" ++ integer_to_list(I)).
+4
View File
@@ -576,6 +576,10 @@ translate_remote('Elixir.String.Chars', to_string, Meta, [Arg], S) ->
{clause, Generated, [Var], [[Guard]], [Fast]},
{clause, Generated, [Var], [], [Slow]}
]}, VS};
translate_remote(lists, member, Meta, [Expr, []], S) ->
Ann = ?ann(Meta),
{TExpr, S1} = translate(Expr, Ann, S),
{{block, Ann, [{match, Ann, {var, Ann, '_'}, TExpr}, {atom, Ann, false}]}, S1};
translate_remote(lists, member, Meta, [Expr, [Head | Tail] = List], S) ->
Ann = ?ann(Meta),
+12 -6
View File
@@ -287,9 +287,15 @@ expand({quote, Meta, [Opts, Do]}, S, E) when is_list(Do) ->
_ -> {'{}', [], ['__block__', [], EBinding ++ [EQuoted]]}
end,
case EPrelude of
[] -> {EBindingQuoted, ES, EQ};
_ -> {{'__block__', [], EPrelude ++ [EBindingQuoted]}, ES, EQ}
EBlock =
case EPrelude of
[] -> EBindingQuoted;
_ -> {'__block__', [], EPrelude ++ [EBindingQuoted]}
end,
case ?key(E, context) of
nil -> {{{'.', Meta, [elixir_quote, validate_quote]}, Meta, [EBlock]}, ES, EQ};
_ -> {EBlock, ES, EQ}
end;
expand({quote, Meta, [_, _]}, _S, E) ->
@@ -964,7 +970,7 @@ expand_remote(Receiver, DotMeta, Right, Meta, Args, S, SL, #{context := Context}
{EArgs, {SA, _}, EA} = mapfold(fun expand_arg/3, {SL, S}, E, Args),
SA#elixir_ex.tainted_function andalso is_atom(Receiver) andalso
(not is_loaded_and_exported(Receiver, Right, Args)) andalso
is_loaded_and_not_exported(Receiver, Right, Args) andalso
elixir_errors:file_warn(Meta, E, ?MODULE, {undefined_function, Receiver, Right, length(Args)}),
Rewritten = elixir_rewrite:rewrite(Receiver, DotMeta, Right, AttachedMeta, EArgs),
@@ -987,9 +993,9 @@ expand_remote(Receiver, DotMeta, Right, Meta, Args, _, _, E) ->
Call = {{'.', DotMeta, [Receiver, Right]}, Meta, Args},
file_error(Meta, E, ?MODULE, {invalid_call, Call}).
is_loaded_and_exported(Receiver, Fun, Args) ->
is_loaded_and_not_exported(Receiver, Fun, Args) ->
(code:ensure_loaded(Receiver) =:= {module, Receiver}) andalso
erlang:function_exported(Receiver, Fun, length(Args)).
not erlang:function_exported(Receiver, Fun, length(Args)).
attach_runtime_module(Receiver, Meta, S, _E) ->
case lists:member(Receiver, S#elixir_ex.runtime_modules) of
+43 -32
View File
@@ -7,7 +7,9 @@
-feature(maybe_expr, enable).
-export([escape/3, linify/3, linify_with_context_counter/3, build/7, quote/2, has_unquotes/1, fun_to_quoted/1]).
-export([dot/5, tail_list/3, list/2, validate_runtime/2, shallow_validate_ast/1]). %% Quote callbacks
%% Quote callbacks (appear in code, must be handled by the type system)
-export([dot/5, tail_list/3, list/2, unquote/1, validate_quote/1, validate_runtime/2, shallow_validate_ast/1]).
-include("elixir.hrl").
-define(defs(Kind), Kind == def; Kind == defp; Kind == defmacro; Kind == defmacrop; Kind == '@').
@@ -23,7 +25,7 @@
imports_hygiene=nil,
unquote=true,
generated=false,
shallow_validate=false
validate=false
}).
%% fun_to_quoted
@@ -273,11 +275,14 @@ build(Meta, Line, File, Context, Unquote, Generated, E) ->
unquote=Unquote,
context=VContext,
generated=Generated,
shallow_validate=true
validate=true
},
{Q, VContext, Acc3}.
validate_quote(Expr) ->
Expr.
validate_compile(_Meta, line, Value, Acc) when is_boolean(Value) ->
{Value, Acc};
validate_compile(_Meta, file, nil, Acc) ->
@@ -312,27 +317,6 @@ is_valid(context, Context) -> is_atom(Context) andalso (Context /= nil);
is_valid(generated, Generated) -> is_boolean(Generated);
is_valid(unquote, Unquote) -> is_boolean(Unquote).
shallow_validate_ast(Expr) ->
case shallow_valid_ast(Expr) of
true -> Expr;
false -> argument_error(
<<"tried to unquote invalid AST: ", ('Elixir.Kernel':inspect(Expr))/binary,
"\nDid you forget to escape term using Macro.escape/1?">>)
end.
shallow_valid_ast(Expr) when is_list(Expr) -> valid_ast_list(Expr);
shallow_valid_ast(Expr) -> valid_ast_elem(Expr).
valid_ast_list([]) -> true;
valid_ast_list([Head | Tail]) -> valid_ast_elem(Head) andalso valid_ast_list(Tail);
valid_ast_list(_Improper) -> false.
valid_ast_elem(Expr) when is_list(Expr); is_atom(Expr); is_binary(Expr); is_number(Expr); is_pid(Expr); is_function(Expr) -> true;
valid_ast_elem({Left, Right}) -> valid_ast_elem(Left) andalso valid_ast_elem(Right);
valid_ast_elem({Atom, Meta, Args}) when is_atom(Atom), is_list(Meta), is_atom(Args) orelse is_list(Args) -> true;
valid_ast_elem({Call, Meta, Args}) when is_list(Meta), is_list(Args) -> shallow_valid_ast(Call);
valid_ast_elem(_Term) -> false.
quote({unquote_splicing, _, [_]}, #elixir_quote{unquote=true}) ->
argument_error(<<"unquote_splicing only works inside arguments and block contexts, "
"wrap it in parens if you want it to work with one-liners">>);
@@ -362,9 +346,9 @@ do_quote({quote, Meta, [Opts, Arg]}, Q) when is_list(Meta) ->
{'{}', [], [quote, meta(NewMeta, Q), [TOpts, TArg]]};
do_quote({unquote, Meta, [Expr]}, #elixir_quote{unquote=true, shallow_validate=Validate}) when is_list(Meta) ->
do_quote({unquote, Meta, [Expr]}, #elixir_quote{unquote=true, validate=Validate}) when is_list(Meta) ->
case Validate of
true -> {{'.', Meta, [?MODULE, shallow_validate_ast]}, Meta, [Expr]};
true -> {{'.', Meta, [?MODULE, unquote]}, Meta, [Expr]};
false -> Expr
end;
@@ -490,7 +474,7 @@ collect_trace_import_quoted([], _Mod, Acc, Arities) ->
do_quote_call(Left, Meta, Expr, Args, Q) ->
All = [Left, {unquote, Meta, [Expr]}, Args, Q#elixir_quote.context],
TAll = [do_quote(X, Q) || X <- All],
{{'.', Meta, [elixir_quote, dot]}, Meta, [meta(Meta, Q) | TAll]}.
{{'.', Meta, [?MODULE, dot]}, Meta, [meta(Meta, Q) | TAll]}.
do_quote_tuple({Left, Meta, Right}, Q) ->
do_quote_tuple(Left, Meta, Right, Q).
@@ -533,12 +517,14 @@ do_list_concat([], Right) -> Right;
do_list_concat(Left, Right) -> {{'.', [], [erlang, '++']}, [], [Left, Right]}.
do_runtime_list(Meta, Fun, Args) ->
{{'.', Meta, [elixir_quote, Fun]}, Meta, Args}.
{{'.', Meta, [?MODULE, Fun]}, Meta, Args}.
%% Callbacks
%% Unquote validation callbacks
%%
%% They perform shallow runtime validation for performance
%% reasons but will be type checked in the future for full
%% validation.
%% Some expressions cannot be unquoted at compilation time.
%% This function is responsible for doing runtime unquoting.
dot(Meta, Left, Right, Args, Context) ->
annotate(dot(Meta, Left, Right, Args), Context).
@@ -589,12 +575,37 @@ tail_list(Left, Right, Tail) when is_list(Left) ->
end.
validate_list(List) ->
case valid_ast_list(List) of
case shallow_valid_list(List) of
true -> ok;
false -> argument_error(<<"expected a list with quoted expressions in unquote_splicing/1, got: ",
('Elixir.Kernel':inspect(List))/binary>>)
end.
unquote(Expr) ->
case shallow_valid_ast(Expr) of
true -> Expr;
false -> argument_error(
<<"tried to unquote invalid AST: ", ('Elixir.Kernel':inspect(Expr))/binary,
"\nDid you forget to escape term using Macro.escape/1?">>)
end.
shallow_valid_ast(Expr) when is_list(Expr) -> shallow_valid_list(Expr);
shallow_valid_ast(Expr) -> shallow_valid_elem(Expr).
shallow_valid_list([]) -> true;
shallow_valid_list([Head | Tail]) -> shallow_valid_elem(Head) andalso shallow_valid_list(Tail);
shallow_valid_list(_Improper) -> false.
shallow_valid_elem(Expr) when is_list(Expr); is_atom(Expr); is_binary(Expr); is_number(Expr); is_pid(Expr); is_function(Expr) -> true;
shallow_valid_elem({Left, Right}) -> shallow_valid_elem(Left) andalso shallow_valid_elem(Right);
shallow_valid_elem({Atom, Meta, Args}) when is_atom(Atom), is_list(Meta), is_atom(Args) orelse is_list(Args) -> true;
shallow_valid_elem({Call, Meta, Args}) when is_list(Meta), is_list(Args) -> shallow_valid_ast(Call);
shallow_valid_elem(_Term) -> false.
%% TODO: We keep this with backwards compatibility in previous compiled Elixirw code.
shallow_validate_ast(Expr) ->
unquote(Expr).
argument_error(Message) ->
error('Elixir.ArgumentError':exception([{message, Message}])).
+1 -1
View File
@@ -1438,7 +1438,7 @@ suggest_simpler_unexpected_token_in_error(Wrong, Line, WrongColumn, Scope) ->
{error, _Reason} ->
ConfusableSkeleton = 'Elixir.String.Tokenizer.Security':confusable_skeleton(Wrong),
case (Scope#elixir_tokenizer.identifier_tokenizer):tokenize(ConfusableSkeleton) of
{_, Simpler, _, _, _, _} ->
{_, Simpler, _, _, _, _} when Simpler =/= Wrong ->
Message = suggest_change("Codepoint failed identifier tokenization, but a simpler form was found.",
Wrong,
"You could write the above in a similar way that is accepted by Elixir:",
+3
View File
@@ -233,6 +233,9 @@ returns_boolean({{'.', _, [erlang, Fun]}, _, [_, _]}) when
returns_boolean({{'.', _, [erlang, Fun]}, _, [_, _, _]}) when
Fun == function_exported; Fun == is_record -> true;
returns_boolean({{'.', _, [lists, member]}, _, [_, _]}) ->
true;
returns_boolean({'case', _, [_, [{do, Clauses}]]}) ->
lists:all(fun
({'->', _, [_, Expr]}) -> returns_boolean(Expr)
+26
View File
@@ -340,6 +340,32 @@ defmodule CalendarTest do
assert Calendar.strftime(~N[2019-08-15 17:07:57], "%010A") == "00Thursday"
end
test "limits width to at most 1024 characters" do
assert Calendar.strftime(~D[2019-08-15], "%1024d") |> byte_size() == 1024
assert_raise ArgumentError, "invalid strftime format: width must be at most 1024", fn ->
Calendar.strftime(~D[2019-08-15], "%1025d")
end
assert_raise ArgumentError, "invalid strftime format: width must be at most 1024", fn ->
Calendar.strftime(~D[2019-08-15], "%10000d")
end
end
test "limits width in preferred formats" do
assert_raise ArgumentError, "invalid strftime format: width must be at most 1024", fn ->
Calendar.strftime(~N[2019-08-15 17:07:57], "%c", preferred_datetime: "%1025d")
end
assert_raise ArgumentError, "invalid strftime format: width must be at most 1024", fn ->
Calendar.strftime(~N[2019-08-15 17:07:57], "%x", preferred_date: "%1025d")
end
assert_raise ArgumentError, "invalid strftime format: width must be at most 1024", fn ->
Calendar.strftime(~N[2019-08-15 17:07:57], "%X", preferred_time: "%1025H")
end
end
test "formats Epoch time with %s" do
assert Calendar.strftime(~N[2019-08-15 17:07:57], "%s") == "1565888877"
+19
View File
@@ -514,6 +514,25 @@ defmodule CodeTest do
Code.unrequire_files([fixture_path("code_sample.exs")])
end
test "require_file/1 releases the file when compilation fails" do
path = tmp_path("bad_require_#{System.unique_integer([:positive])}.ex")
try do
File.write!(path, ~s|raise "boom"|)
assert_raise RuntimeError, "boom", fn ->
Code.require_file(path)
end
assert_raise RuntimeError, "boom", fn ->
Code.require_file(path)
end
after
File.rm(path)
Code.unrequire_files([path])
end
end
test "string_to_quoted!/2 errors take lines/columns/indentation into account" do
assert_exception(
SyntaxError,
+1 -17
View File
@@ -161,23 +161,7 @@ defmodule Kernel.ErrorsTest do
test "cascading from undefined variables" do
# Test that we show undefined modules/functions/macros on variable failure,
# as sometimes the variable failure come from a missing module or require
assert_compile_error(
[
"nofile:3:23",
"undefined variable \"bar\"",
"nofile:3:19",
"function UnknownModule.foo/1 is undefined (module UnknownModule is not available)"
],
~c"""
defmodule Sample do
def foo do
UnknownModule.foo(bar)
end
end
"""
)
# as sometimes the variable failure come from a missing require/export
assert_compile_error(
[
"nofile:3:20",
@@ -679,7 +679,8 @@ defmodule Kernel.ExpansionTest do
describe "quote" do
test "expanded to raw forms" do
assert expand(quote(do: quote(do: hello)), []) == {:{}, [], [:hello, [], __MODULE__]}
assert {{:., _, [:elixir_quote, :validate_quote]}, _, [{:{}, [], [:hello, [], __MODULE__]}]} =
expand(quote(do: quote(do: hello)), [])
end
test "raises if the :bind_quoted option is invalid" do
+1 -24
View File
@@ -494,30 +494,7 @@ defmodule Kernel.GuardTest do
assert expand_defguard_to_string(:with_or_and_or, args, nil) == """
{arg1, arg2, arg3} = {1 + 1, 2 + 2, 3 + 3}
case arg1 do
false ->
case arg2 do
false ->
false
true ->
case arg3 do
false -> arg1
true -> true
other -> :erlang.error({:badbool, :or, other})
end
other ->
:erlang.error({:badbool, :and, other})
end
true ->
true
other ->
:erlang.error({:badbool, :or, other})
end
:erlang.orelse(arg1, :erlang.andalso(arg2, :erlang.orelse(arg3, arg1)))
"""
end
@@ -1353,6 +1353,12 @@ defmodule Kernel.ParserTest do
]
assert_syntax_error(message, ~c"fooی𝚳")
# regression test: ǜ (should not suggest back the wrong character)
assert_syntax_error(
["nofile:1:4:", ~s/unexpected token: "#{"\u01DC"}" (column 4, code point U+01DC)/],
~c":fooǜ"
)
end
test "keyword missing space" do
+6 -1
View File
@@ -461,6 +461,11 @@ defmodule KernelTest do
refute 2 in []
refute false in []
refute true in []
# make sure optimization still evaluates the left-hand side
# (do not use assert/refute which handle in/2 differently)
send(self(), :foo) in []
assert_received :foo
end
test "with expressions on right side" do
@@ -697,7 +702,7 @@ defmodule KernelTest do
"""
# Empty list
assert expand_to_string(quote(do: :x in [])) =~ "_ = :x\nfalse"
assert expand_to_string(quote(do: :x in [])) =~ ":lists.member(:x, [])"
assert expand_to_string(quote(do: :x in []), :guard) == "false"
# Lists
+1 -1
View File
@@ -75,7 +75,7 @@ defmodule Macro.EnvTest do
test "to_match/1" do
quote = quote(do: x in [])
assert {:__block__, [], [{:=, [], [{:_, [], Kernel}, {:x, [], Macro.EnvTest}]}, false]} =
assert {{:., [], [:lists, :member]}, [], [{:x, [], Macro.EnvTest}, []]} =
Macro.expand_once(quote, __ENV__)
assert Macro.expand_once(quote, Macro.Env.to_match(__ENV__)) == false
@@ -1818,6 +1818,11 @@ defmodule Module.Types.ExprTest do
end
test "Kernel.in/2" do
assert typecheck!(
[x],
x in []
) == atom([false])
assert typecheck!(
[x],
(
@@ -2375,7 +2380,7 @@ defmodule Module.Types.ExprTest do
!x
will always evaluate to false because the expression has type:
will always evaluate to false because its inner expression has type:
integer()
@@ -2391,7 +2396,7 @@ defmodule Module.Types.ExprTest do
!x
will always evaluate to true because the expression has type:
will always evaluate to true because its inner expression has type:
dynamic(nil)
"""
@@ -2399,9 +2404,9 @@ defmodule Module.Types.ExprTest do
assert typeerror!([x = 123], !!x) =~ ~l"""
the following conditional expression:
!x
!!x
will always evaluate to false because the expression has type:
will always evaluate to true because its inner expression has type:
integer()
@@ -2411,6 +2416,16 @@ defmodule Module.Types.ExprTest do
# from: types_test.ex:LINE
x = 123
"""
assert typewarn!([x = nil], !!x) |> elem(1) =~ ~l"""
the following conditional expression:
!!x
will always evaluate to false because its inner expression has type:
dynamic(nil)
"""
end
end
@@ -20,6 +20,8 @@ defmodule Module.Types.HelpersTest do
assert expr_to_string(quote(do: :erlang.list_to_atom(a))) == "List.to_atom(a)"
assert expr_to_string(quote(do: :erlang.element(1, a))) == "elem(a, 0)"
assert expr_to_string(quote(do: :erlang.element(:erlang.+(a, 1), b))) == "elem(b, a)"
assert expr_to_string(quote(do: :lists.member(a, []))) == "a in []"
assert expr_to_string(quote(do: :lists.member(a, [:foo, :bar]))) == "a in [:foo, :bar]"
end
test "Kernel macros" do
@@ -320,6 +320,20 @@ defmodule Module.Types.InferTest do
]
end
test "from defguard (regression with large code generation)", config do
# As long as it type checks in time, we are fine,
# but it should infer Macro.t in the future.
infer config do
defguard is_erlang_app(app) when app in ~w(
inets ftp os_mon parsetools mnesia eldap eunit observer
dialyzer runtime_tools edoc diameter wx debugger ssh et
sasl ssl asn1 snmp erts tools stdlib reltool kernel crypto
tftp erl_interface syntax_tools megaco public_key
common_test xmerl compiler jinterface
)
end
end
test "from defaults (regression with multiple clauses)", config do
types =
infer config do
+14
View File
@@ -84,9 +84,15 @@ defmodule VersionTest do
assert {:ok, %Version{major: 1, minor: 4, patch: 5, pre: [6, 7, "eight"]}} =
Version.parse("1.4.5-6.7.eight")
assert {:ok, %Version{major: 99_999_999_999_999, minor: 0, patch: 0}} =
Version.parse("99999999999999.0.0")
assert {:ok, %Version{major: 1, minor: 4, patch: 5, pre: ["6-g3318bd5"]}} =
Version.parse("1.4.5-6-g3318bd5+ignore")
assert {:ok, %Version{major: 1, minor: 0, patch: 0, pre: ["100000000000000-alpha"]}} =
Version.parse("1.0.0-100000000000000-alpha")
assert Version.parse("foobar") == :error
assert Version.parse("2") == :error
assert Version.parse("2.") == :error
@@ -105,6 +111,13 @@ defmodule VersionTest do
assert Version.parse("02.3.0") == :error
assert Version.parse("0. 0.0") == :error
assert Version.parse("0.1.0-&&pre") == :error
assert Version.parse("100000000000000.0.0") == :error
assert Version.parse("1.100000000000000.0") == :error
assert Version.parse("1.0.100000000000000") == :error
assert Version.parse("1.0.0-100000000000000") == :error
assert Version.parse("1.0.0+100000000000000") ==
{:ok, %Version{major: 1, minor: 0, patch: 0, build: "100000000000000"}}
end
test "to_string/1" do
@@ -338,6 +351,7 @@ defmodule VersionTest do
assert Version.parse_requirement("1.2.3 and or 4.5.6") == :error
assert Version.parse_requirement(">= 1") == :error
assert Version.parse_requirement("1.2.3 >=") == :error
assert Version.parse_requirement("100000000000000.0.0") == :error
end
test "inspect/1" do
+17
View File
@@ -60,6 +60,23 @@ optimized_or_test() ->
{clause, _, [{atom, _, true}], [], [{atom, _, true}]}]
} = to_erl("is_list([]) or :done").
optimized_in_test() ->
{'block', _,
[{match,_,
{var, _, '_'},
{call, _, {remote, _, {atom, _, 'Elixir.IO'}, {atom, _, puts}}, [{atom, _, hi}]}},
{atom, _, false}]
} = to_erl("IO.puts(:hi) in []"),
{'block', _,
[{match,_,
{var, _, '_1'},
{call, _, {remote, _, {atom, _, 'Elixir.IO'}, {atom, _, puts}}, [{atom, _, hi}]}},
{op, _, 'orelse',
{op, _, '=:=', {var, _ , '_1'}, {integer, _, 1}},
{op, _, '=:=', {var, _ , '_1'}, {integer, _, 2}}
}]
} = to_erl("IO.puts(:hi) in [1, 2]").
no_after_in_try_test() ->
{'try', _, [_], [], [_], []} = to_erl("try do :foo.bar() catch _ -> :ok end").
+5 -3
View File
@@ -181,15 +181,17 @@ defmodule Mix.Local.Installer do
message =
case previous_files do
[] ->
"Are you sure you want to install #{inspect(src)}?"
"Do you trust and want to install #{inspect(src)}?"
[file] ->
"Found existing entry: #{file}\n" <>
"Are you sure you want to replace it with #{inspect(src)}?"
"The existing entry will be replaced.\n" <>
"Do you trust and want to install #{inspect(src)}?"
files ->
"Found existing entries: #{Enum.map_join(files, ", ", &Path.basename/1)}\n" <>
"Are you sure you want to replace them with #{inspect(src)}?"
"The existing entries will be replaced.\n" <>
"Do you trust and want to install #{inspect(src)}?"
end
Mix.shell().yes?(message)
+62 -7
View File
@@ -40,6 +40,15 @@ defmodule Mix.Tasks.Archive.Install do
Note that installing via Git, GitHub, or Hex fetches the source
of the archive and builds it, while using local path uses a pre-built archive.
## Security
Archives must be installed only from sources you trust.
Installing an archive from Git, GitHub, or Hex executes code from the source
during installation, unless a pre-built archive is given. Once an archive is
installed, Mix may load code from it as a plugin on any Mix command, even if
no archive command is executed.
## Command line options
* `--sha512` - checks the archive matches the given SHA-512 checksum. Only
@@ -119,7 +128,8 @@ defmodule Mix.Tasks.Archive.Install do
@impl true
def install(basename, contents, previous) do
ez_path = Path.join(Mix.path_for(:archives), basename)
dir_dest = resolve_destination(ez_path, contents)
archive_name = archive_name!(contents)
dir_dest = Path.join(Path.dirname(ez_path), archive_name)
remove_previous_versions(previous)
@@ -149,17 +159,62 @@ defmodule Mix.Tasks.Archive.Install do
### Private helpers
defp resolve_destination(ez_path, contents) do
with {:ok, [_comment, zip_first_file | _]} <- :zip.list_dir(contents),
{:zip_file, zip_first_path, _, _, _, _} = zip_first_file,
[zip_root_dir | _] = Path.split(zip_first_path) do
Path.join(Path.dirname(ez_path), zip_root_dir)
defp archive_name!(contents) do
with {:ok, files} <- :zip.list_dir(contents),
zip_files = Enum.filter(files, &match?({:zip_file, _, _, _, _, _}, &1)),
true <- zip_files != [] do
Enum.reduce(zip_files, nil, fn zip_file, root ->
validate_archive_path!(zip_file, root)
end)
else
_ ->
Mix.raise("Installation failed: invalid archive file")
Mix.raise("Installation failed: invalid archive file, no files found")
end
end
defp validate_archive_path!({:zip_file, path, file_info, _, _, _}, root) do
type = elem(file_info, 2)
path = zip_path_to_string(path)
unless type in [:regular, :directory] do
Mix.raise(
"Installation failed: invalid archive file, #{inspect(path)} is not a regular file or directory"
)
end
cond do
Path.type(path) != :relative ->
Mix.raise(
"Installation failed: invalid archive file, #{inspect(path)} is an absolute path"
)
String.contains?(path, ["..", "\\", <<0>>]) ->
Mix.raise("Installation failed: invalid archive file, #{inspect(path)} is an unsafe path")
true ->
:ok
end
case String.split(path, "/", trim: true) do
[new_root | _] ->
cond do
root && root != new_root ->
Mix.raise(
"Installation failed: invalid archive file, #{inspect(path)} is outside archive root #{inspect(root)}"
)
true ->
new_root
end
[] ->
Mix.raise("Installation failed: invalid archive file, #{inspect(path)} is empty")
end
end
defp zip_path_to_string(path) when is_list(path), do: List.to_string(path)
defp zip_path_to_string(path) when is_binary(path), do: path
defp archives(name) do
Mix.path_for(:archives)
|> Path.join(name)
+8
View File
@@ -40,6 +40,14 @@ defmodule Mix.Tasks.Escript.Install do
`$PATH` environment variable. For more information, check the wikipedia
article on PATH: https://en.wikipedia.org/wiki/PATH_(variable)
## Security
Escripts must be installed only from sources you trust.
Installing an escript from Git, GitHub, or Hex executes code from the source
during installation, unless a pre-built escript is given. Once an escript is
installed, running it executes code on your machine.
## Command line options
* `--sha512` - checks the escript matches the given SHA-512 checksum. Only
+9 -5
View File
@@ -78,6 +78,10 @@ defmodule Mix.Tasks.Format do
* `--dry-run` - does not save files after formatting.
* `--no-compile` - does not compile, even if compilation is required
to load formatter plugins. If a plugin cannot be loaded, an error
is raised.
* `--verbose` - prints the names of files that were formatted.
* `--dot-formatter` - path to the file with formatter configuration.
@@ -152,7 +156,7 @@ defmodule Mix.Tasks.Format do
]
Notice that, when running the formatter with plugins, your code will be
compiled first.
compiled first, unless the `--no-compile` flag is given.
In addition, the order by which you input your plugins is the format order.
So, in the above `.formatter.exs`, the `MixMarkdownFormatter` will format
@@ -316,7 +320,7 @@ defmodule Mix.Tasks.Format do
plugins =
if plugins != [] do
Keyword.get(opts, :plugin_loader, &plugin_loader/1).(plugins)
Keyword.get(opts, :plugin_loader, &plugin_loader(&1, opts)).(plugins)
else
[]
end
@@ -357,12 +361,12 @@ defmodule Mix.Tasks.Format do
end)}
end
defp plugin_loader(plugins) do
defp plugin_loader(plugins, opts) do
if plugins != [] do
Mix.Task.run("loadpaths", [])
Mix.Task.run("loadpaths", if(opts[:no_compile], do: ["--no-compile"], else: []))
end
if not Enum.all?(plugins, &Code.ensure_loaded?/1) do
if !opts[:no_compile] and not Enum.all?(plugins, &Code.ensure_loaded?/1) do
Mix.Task.run("compile", [])
end
+39
View File
@@ -119,6 +119,45 @@ defmodule Mix.Tasks.ArchiveTest do
end)
end
test "archive install rejects parent directory entries" do
in_tmp("archive install rejects parent directory entries", fn ->
assert {:ok, _} =
:zip.create(~c"bad-0.1.0.ez", [
{~c"../outside", "bad"},
{~c"bad-0.1.0/ebin/bad", "bad"}
])
send(self(), {:mix_shell_input, :yes?, true})
assert_raise Mix.Error, ~r/invalid archive file/, fn ->
Mix.Tasks.Archive.Install.run(["bad-0.1.0.ez"])
end
refute File.exists?(tmp_path("userhome/outside"))
refute File.exists?(tmp_path("userhome/.mix/outside"))
refute File.dir?(tmp_path("userhome/.mix/archives/bad-0.1.0"))
end)
end
test "archive install rejects entries outside the archive root" do
in_tmp("archive install rejects entries outside the archive root", fn ->
assert {:ok, _} =
:zip.create(~c"bad-0.1.0.ez", [
{~c"bad-0.1.0/ebin/bad", "bad"},
{~c"other-0.1.0/ebin/bad", "bad"}
])
send(self(), {:mix_shell_input, :yes?, true})
assert_raise Mix.Error, ~r/invalid archive file/, fn ->
Mix.Tasks.Archive.Install.run(["bad-0.1.0.ez"])
end
refute File.dir?(tmp_path("userhome/.mix/archives/bad-0.1.0"))
refute File.dir?(tmp_path("userhome/.mix/archives/other-0.1.0"))
end)
end
test "archive install missing file" do
message = ~r[Expected "./unlikely-to-exist-0.1.0.ez" to be a local file path]
+45
View File
@@ -597,6 +597,51 @@ defmodule Mix.Tasks.FormatTest do
end)
end
defmodule FormatWithPluginApp do
def project do
[app: :format_with_plugin, version: "0.1.0"]
end
end
test "doesn't compile plugins with --no-compile", context do
in_tmp(context.test, fn ->
Mix.Project.push(__MODULE__.FormatWithPluginApp)
on_exit(fn -> purge([UncompiledPlugin]) end)
File.write!(".formatter.exs", """
[
inputs: ["a.ex"],
plugins: [UncompiledPlugin]
]
""")
File.mkdir_p!("lib")
File.write!("lib/uncompiled_plugin.ex", """
defmodule UncompiledPlugin do
@behaviour Mix.Tasks.Format
def features(_opts), do: [extensions: [".ex"]]
def format(contents, _opts), do: "# formatted\\n" <> contents
end
""")
File.write!("a.ex", """
foo bar
""")
assert_raise Mix.Error, "Formatter plugin UncompiledPlugin cannot be found", fn ->
Mix.Tasks.Format.run(["--no-compile"])
end
refute_received {:mix_shell, :info, ["Compiling" <> _]}
assert File.read!("a.ex") == """
foo bar
"""
end)
end
test "uses extension plugins with --stdin-filename", context do
in_tmp(context.test, fn ->
File.write!(".formatter.exs", """
+4 -3
View File
@@ -67,12 +67,13 @@ defmodule Mix.Tasks.XrefTest do
}
output = [
%{callee: {A, :b, 1}, caller_module: B, file: "lib/b.ex", line: 3},
%{line: 3, file: "lib/b.ex", callee: {A, :b, 1}, caller_module: B},
%{callee: {:elixir_quote, :unquote, 1}, caller_module: A, file: "lib/a.ex", line: 4},
%{
callee: {:elixir_quote, :shallow_validate_ast, 1},
callee: {:elixir_quote, :validate_quote, 1},
caller_module: A,
file: "lib/a.ex",
line: 4
line: 3
}
]