Compare commits

...
27 Commits
Author SHA1 Message Date
José Valim 2765e0f210 Release v1.13.0-rc.1 2021-11-16 21:36:19 +01:00
José Valim c1fd4dd8da Revert "Clarify risk of duplicate keys in Map.map/2 (#11360)"
This reverts commit 6074846794.
2021-11-15 21:10:51 +01:00
José Valim 6d408bb0c0 Reject bidirectional formatting characters (#11391) 2021-11-15 21:10:51 +01:00
José Valim ffd891a349 Support escaping of terminators in uppercase sigils heredocs for consistency, closes #11390 2021-11-15 14:21:28 +01:00
José Valim d7b1591e83 Do not raise on variable looking like an empty tuple 2021-11-13 12:37:59 +01:00
Rudolf 24547bbe96 remsh: stop evaluator before exit iex_server (#11386)
Closes #11385.
2021-11-12 09:17:41 +01:00
Tyler A. Young 9b4a01fe33 Corrections & clarifications to Task.Supervisor docs (#11384) 2021-11-12 09:06:13 +01:00
José Valim 94993e0036 Ensure fixtures are executed in archive test
Closes #11382.
2021-11-11 22:05:28 +01:00
Eksperimental 00a0f07824 Allow Application.compile_env* to accept attributes (#11383) 2021-11-11 22:05:28 +01:00
José Valim c28073b4fc Fix warning from call to dynamically generated module 2021-11-11 22:04:47 +01:00
José Valim 8387dd70bc Fix bootstrap 2021-11-10 19:37:34 +01:00
José Valim 0e59c68428 Do not crash on duplicate bindings, closes #11378 2021-11-10 19:27:59 +01:00
José Valim 913d401a7a Perform implicit Application require as the compiler is aware of its constructs 2021-11-09 18:13:48 +01:00
José Valim fbfb1b4a12 Do not extract comments without a newline 2021-11-08 18:05:59 +01:00
José Valim cf5762ad24 Use keyword syntax by default in tuple formatting 2021-11-08 17:25:36 +01:00
Wojtek Mach fab158c0de Add Mix.installed?/0 (#11374) 2021-11-08 13:14:13 +01:00
José Valim 3a484a777d Fix CHANGELOG 2021-11-07 12:24:50 +01:00
José Valim 8c1b563a3c Keep yes?/1 as a callback, closes #11372 2021-11-07 12:22:52 +01:00
José Valim ece0530870 Fix regression on URI.parse/1, closes #11363 2021-11-07 10:48:26 +01:00
Guillaume Duboc 48b838a840 Fix formatting of map-like types in warnings (#11351)
This came up in issue #11204:

When emitting a warning for a type unification error,
the compiler overly simplifies the formatting of types
when these are maps or unions of maps.

To address this, we recursively check for maps inside
of a union type when comparing it to another map-like type.
2021-11-07 09:31:00 +01:00
José Valim 5aa1c9a839 Do not reinstall dependencies on same Mix.install/2 2021-11-05 00:05:15 +01:00
José Valim 3a8a7efecc Ensure structs can be in release configs, closes #11364 2021-11-04 08:41:15 +01:00
Tyler A. Young 3d038680da Clarify risk of duplicate keys in Map.map/2 (#11360) 2021-11-03 23:46:14 +01:00
Tyler A. Young 1766731dd4 Improvements to Enum.slide/3 (#11361)
- Support negative insertion indices
- Give a clear RuntimeError, rather than a baffling CondClauseError, when you ask for an insertion point that matches the last element of your range
2021-11-03 23:41:25 +01:00
José Valim b65b8e2cd3 Fix broken link in Task docs 2021-11-01 22:30:25 +01:00
José Valim 5062a154fa Release v1.13.0-rc.0 2021-11-01 20:58:47 +01:00
José Valim d7fea95157 Branch out v1.13 2021-11-01 20:53:52 +01:00
40 changed files with 529 additions and 240 deletions
+64 -9
View File
@@ -74,7 +74,7 @@ authority: fragment: host: path: port:
query: scheme: userinfo:
```
As you fill them in, the already filled structs no longer show up:
As you fill a field in, the already filled fields no longer show up:
```elixir
iex(1)> %URI{path: "/example",
@@ -82,6 +82,16 @@ authority: fragment: host: port: query:
scheme: userinfo:
```
Along the same lines, `SyntaxError` and `TokenMissingError` were improved to show a code snippet whenever possible:
```elixir
$ elixir -e "hello + * world"
** (SyntaxError) nofile:1:9: syntax error before: '*'
|
1 | hello + * world
| ^
```
Finally, new compilation tracers have been added, alongside a handful of functions in `Module` to retrieve module metadata, which can be used to enrich suggestions in programming environments.
## Extended code formatting
@@ -118,7 +128,43 @@ Now any application can use your formatter as follows:
Finally, the `Code` module has also been augmented with two functions: `Code.string_to_quoted_with_comments/2` and `Code.quoted_to_algebra/2`. Those functions allow someone to retrieve the Elixir AST with their original source code comments, and then convert this AST to formatted code. In other words, those functions provide a wrapper around the Elixir Code Formatter, supporting developers who wish to create tools that directly manipulate and custom format Elixir source code.
## v1.13.0-dev
## v1.13.0-rc.1 (2021-11-16)
### 1. Enhancements
#### Mix
* [Mix] Add `Mix.installed?/0`
### 2. Bug fixes
#### Elixir
* [Application] Allow any expression as first argument of `compile_env`
* [Kernel] Reject bidirectional formatting characters in strings and comments
* [Kernel] Support escaping of terminators in uppercase sigils heredocs for consistency
#### IEx
* [IEx] Stop evaluator before exiting IEx server
#### Mix
* [mix release] Raise proper error message when non-serializable values are in configs
### 3. Regressions
#### Elixir
* [Code] Do not crash on duplicate bindings during eval
* [Macro] Do not raise on variables that look like an empty tuple in `Macro.to_string/1`
* [URI] Make sure parsed URIs with empty paths have the path field set to nil
#### Mix
* [Mix.Shell] Re-add `yes?/1` as a callback
## v1.13.0-rc.0 (2021-11-01)
### 1. Enhancements
@@ -136,6 +182,7 @@ Finally, the `Code` module has also been augmented with two functions: `Code.str
* [Code] Allow custom sigil formatting on `Code.format_string!/2`
* [Code] Add `{:on_module, bytecode, :none}` trace to compilation tracers
* [Enum] Optimize `Enum.concat/1` for lists of lists
* [Enum] Add `Enum.slide/3`
* [Exception] Better format Elixir exceptions in Erlang
* [Inspect] Allow default inspect fun to be set globally with `Inspect.Opts.default_inspect_fun/1`
* [IO] Allow `:eof` to be given as limit to `IO.getn/2`
@@ -159,9 +206,11 @@ Finally, the `Code` module has also been augmented with two functions: `Code.str
* [Task] Add `Task.completed/1`
* [Task] Add `Task.ignore/1` to keep a task running but ignoring all of its results
* [Task] Reduce the amount of copying `Task.async*` functions
* [URI] Add `URI.new/1` and `URI.new!/1`
#### ExUnit
* [ExUnit] Show hint if comparing different but equivalent strings
* [ExUnit.CaptureIO] Add `with_io/3` to return result with captured io
* [ExUnit.CaptureLog] Add `with_log/2` to return result with captured logs
@@ -177,6 +226,9 @@ Finally, the `Code` module has also been augmented with two functions: `Code.str
#### Mix
* [Mix] Add `MIX_INSTALL_FORCE` environment variable support
* [Mix] Support `:config` and `:system_env` in `Mix.install/2`
* [Mix.Shell] Add `:default` option to `Mix.Shell.yes?`
* [mix archive.install] Run `loadconfig` before building archive
* [mix compile] Move Elixir version check to before deps are compiled, in order to give feedback earlier
* [mix compile.elixir] Do not recompile files if their modification time change but their contents are still the same and the .beam files are still on disk
@@ -206,6 +258,7 @@ Finally, the `Code` module has also been augmented with two functions: `Code.str
#### EEx
* [EEx] Accept comments in EEx between do and the first clause
* [EEx] Accept EEx expressions where `->` is followed by newline
#### Elixir
@@ -213,6 +266,7 @@ Finally, the `Code` module has also been augmented with two functions: `Code.str
* [Application] Warn if `Application.compile_env` or `Application.compile_env!` are called without a require
* [Code] Make sure `:static_atoms_encoder` in `Code.string_to_quoted/2` also applies to quoted keyword keys
* [Code] Ensure bindings with no context are returned as atoms instead of `{binding, nil}` in eval operations
* [Inspect] Fix a bug when inspecting a non-binary bitstring with colors
* [Kernel] Raise if `__CALLER__` or `__ENV__` or `__STACKTRACE__` are used in match
* [Kernel] Improve error message on invalid argument for `byte_size` from binary concat
* [Kernel] Raise when aliasing non-Elixir modules without `:as`
@@ -243,26 +297,27 @@ Finally, the `Code` module has also been augmented with two functions: `Code.str
#### Logger
* [Logger] Raise clear error message for invalid `:compile_time_purge_matching` configuration
* [Logger] Fix a bug where Logger would not reset its discard counter under some scenarios
#### Mix
* [mix compile.elixir] Recompile file if `@external_resource` is deleted
* [mix compile.elixir] Print number of compiling files on all compiler cycles. This will make the `Compiling N files (.ex)` show up multiple times if necessary
* [mix deps] Raise if local dep is unavailable while compiling
* [mix deps.unlock] Fix blank output when dependency is not locked
* [mix deps.unlock] Fix blank output when unlocking a dependency that is not locked
* [mix local.install] Do not respect `MIX_DEPS_PATH` for install commands
* [mix release] Improve release scripts to make sure shell errors cascade by avoiding exporting and defining variables at once
* [mix release] Do not boot release if RELEASE_COOKIE is empty
* [mix release] Allow release running as a daemon to be restarted
* [mix test] Allow coverage engine to also tag `case`, `cond`, and `receive` branches where the right side is a literal
* [Mix.Shell] Add `default` option to `Mix.Shell.yes?`
* [mix release] Improve release scripts by making sure shell errors cascade (this is done by avoiding exporting and defining variables in a single step)
* [mix release] Do not boot release if `RELEASE_COOKIE` is empty
* [mix release] Allow releases running as a daemon to be restarted
* [mix test] Fix coverage engine to also tag `case`, `cond`, and `receive` branches where the right side is a literal
### 3. Soft-deprecations (no warnings emitted)
#### Elixir
* [IO] `:all` on `IO.getn` is deprecated in favor of `:eof`
* [Code] Environment options in `Code.eval_quoted/3` and `Code.eval_string/3`, such as `:aliases` and `:tracers`, have been deprecated in favor of passing an environment
* [IO] `:all` on `IO.getn` is deprecated in favor of `:eof`
* [URI] `URI.parse/1` is deprecated in favor of `URI.new/1` and `URI.new!/1`
#### Mix
+1 -1
View File
@@ -2,7 +2,7 @@ PREFIX ?= /usr/local
TEST_FILES ?= "*_test.exs"
SHARE_PREFIX ?= $(PREFIX)/share
MAN_PREFIX ?= $(SHARE_PREFIX)/man
#CANONICAL := MAJOR.MINOR/
CANONICAL := 1.13/
CANONICAL ?= master/
ELIXIRC := bin/elixirc --ignore-module-conflict $(ELIXIRC_OPTS)
ERLC := erlc -I lib/elixir/include
+1 -1
View File
@@ -32,7 +32,7 @@
2. Update tables in /SECURITY.md and "Compatibility and Deprecations"
3. Commit "Prepare vMAJOR.MINOR for release"
3. Commit "Branch out vMAJOR.MINOR"
### Back in master
+2 -3
View File
@@ -6,12 +6,11 @@ Elixir applies bug fixes only to the latest minor branch. Security patches are a
Elixir version | Support
:------------- | :-----------------------------
1.13 | Development
1.12 | Bug fixes and security patches
1.13 | Bug fixes and security patches
1.12 | Security patches only
1.11 | Security patches only
1.10 | Security patches only
1.9 | Security patches only
1.8 | Security patches only
## Announcements
+1 -1
View File
@@ -1 +1 @@
1.13.0-dev
1.13.0-rc.1
+1 -1
View File
@@ -1,7 +1,7 @@
#!/bin/sh
set -e
ELIXIR_VERSION=1.13.0-dev
ELIXIR_VERSION=1.13.0-rc.1
if [ $# -eq 0 ] || { [ $# -eq 1 ] && { [ "$1" = "--help" ] || [ "$1" = "-h" ]; }; }; then
cat <<USAGE >&2
+1 -1
View File
@@ -1,6 +1,6 @@
@if defined ELIXIR_CLI_ECHO (@echo on) else (@echo off)
set ELIXIR_VERSION=1.13.0-dev
set ELIXIR_VERSION=1.13.0-rc.1
setlocal enabledelayedexpansion
if ""%1""=="""" if ""%2""=="""" goto documentation
+2 -2
View File
@@ -508,7 +508,7 @@ defmodule Application do
# compile time instead of compile_env
@doc since: "1.10.0"
@spec compile_env(app, key | list, value) :: value
defmacro compile_env(app, key_or_path, default \\ nil) when is_atom(app) do
defmacro compile_env(app, key_or_path, default \\ nil) do
if __CALLER__.function do
raise "Application.compile_env/3 cannot be called inside functions, only in the module body"
end
@@ -545,7 +545,7 @@ defmodule Application do
"""
@doc since: "1.10.0"
@spec compile_env!(app, key | list) :: value
defmacro compile_env!(app, key_or_path) when is_atom(app) do
defmacro compile_env!(app, key_or_path) do
if __CALLER__.function do
raise "Application.compile_env!/2 cannot be called inside functions, only in the module body"
end
+24 -16
View File
@@ -270,7 +270,7 @@ defmodule Code.Formatter do
{doc, state} =
entries
|> prepend_heredoc_line()
|> interpolation_to_algebra(:heredoc, state, @double_heredoc, @double_heredoc)
|> interpolation_to_algebra(~s["""], state, @double_heredoc, @double_heredoc)
{force_unfit(doc), state}
@@ -292,7 +292,7 @@ defmodule Code.Formatter do
{doc, state} =
entries
|> prepend_heredoc_line()
|> list_interpolation_to_algebra(:heredoc, state, @single_heredoc, @single_heredoc)
|> list_interpolation_to_algebra(~s['''], state, @single_heredoc, @single_heredoc)
{force_unfit(doc), state}
@@ -355,7 +355,7 @@ defmodule Code.Formatter do
defp quoted_to_algebra({:__block__, meta, [list]}, _context, state) when is_list(list) do
case meta[:delimiter] do
~s['''] ->
string = list |> List.to_string() |> escape_heredoc()
string = list |> List.to_string() |> escape_heredoc(~s['''])
{@single_heredoc |> concat(string) |> concat(@single_heredoc) |> force_unfit(), state}
~s['] ->
@@ -369,7 +369,7 @@ defmodule Code.Formatter do
defp quoted_to_algebra({:__block__, meta, [string]}, _context, state) when is_binary(string) do
if meta[:delimiter] == ~s["""] do
string = escape_heredoc(string)
string = escape_heredoc(string, ~s["""])
{@double_heredoc |> concat(string) |> concat(@double_heredoc) |> force_unfit(), state}
else
string = escape_string(string, @double_quote)
@@ -1339,7 +1339,7 @@ defmodule Code.Formatter do
{doc, state} =
entries
|> prepend_heredoc_line()
|> interpolation_to_algebra(:heredoc, state, doc, closing_delimiter)
|> interpolation_to_algebra(opening_delimiter, state, doc, closing_delimiter)
{force_unfit(doc), state}
else
@@ -1548,11 +1548,13 @@ defmodule Code.Formatter do
end
end
defp escape_heredoc(string) do
defp escape_heredoc(string, escape) do
string = String.replace(string, escape, "\\" <> escape)
heredoc_to_algebra(["" | String.split(string, "\n")])
end
defp escape_string(string, :heredoc) do
defp escape_string(string, <<_, _, _>> = escape) do
string = String.replace(string, escape, "\\" <> escape)
heredoc_to_algebra(String.split(string, "\n"))
end
@@ -1873,19 +1875,25 @@ defmodule Code.Formatter do
end
defp each_quoted_to_algebra_with_comments([arg | args], acc, max_line, state, comments?, fun) do
{doc_start, doc_end} = traverse_line(arg, {@max_line, @min_line})
case traverse_line(arg, {@max_line, @min_line}) do
{@max_line, @min_line} ->
{doc_triplet, state} = fun.(arg, args, state)
acc = [doc_triplet | acc]
each_quoted_to_algebra_with_comments(args, acc, max_line, state, comments?, fun)
{acc, comments, comments?} =
extract_comments_before(doc_start, acc, state.comments, comments?)
{doc_start, doc_end} ->
{acc, comments, comments?} =
extract_comments_before(doc_start, acc, state.comments, comments?)
{doc_triplet, state} = fun.(arg, args, %{state | comments: comments})
{doc_triplet, state} = fun.(arg, args, %{state | comments: comments})
{acc, comments, comments?} =
extract_comments_trailing(doc_start, doc_end, acc, state.comments, comments?)
{acc, comments, comments?} =
extract_comments_trailing(doc_start, doc_end, acc, state.comments, comments?)
acc = [adjust_trailing_newlines(doc_triplet, doc_end, comments) | acc]
state = %{state | comments: comments}
each_quoted_to_algebra_with_comments(args, acc, max_line, state, comments?, fun)
acc = [adjust_trailing_newlines(doc_triplet, doc_end, comments) | acc]
state = %{state | comments: comments}
each_quoted_to_algebra_with_comments(args, acc, max_line, state, comments?, fun)
end
end
defp extract_comments_before(max, acc, [%{line: line} = comment | rest], _) when line < max do
+18 -18
View File
@@ -63,15 +63,17 @@ defmodule Code.Normalizer do
end
# Bit containers
defp do_normalize({:<<>>, _, _} = quoted, state) do
defp do_normalize({:<<>>, _, args} = quoted, state) when is_list(args) do
normalize_bitstring(quoted, state)
end
# Atoms with interpolations
defp do_normalize(
{{:., dot_meta, [:erlang, :binary_to_atom]}, call_meta, [{:<<>>, _, _} = string, :utf8]},
{{:., dot_meta, [:erlang, :binary_to_atom]}, call_meta,
[{:<<>>, _, args} = string, :utf8]},
state
) do
)
when is_list(args) do
dot_meta = patch_meta_line(dot_meta, state.parent_meta)
call_meta = patch_meta_line(call_meta, dot_meta)
@@ -166,8 +168,8 @@ defmodule Code.Normalizer do
end
# Sigils
defp do_normalize({sigil, meta, [{:<<>>, _, _} = string, modifiers]} = quoted, state)
when is_atom(sigil) do
defp do_normalize({sigil, meta, [{:<<>>, _, args} = string, modifiers]} = quoted, state)
when is_list(args) and is_atom(sigil) do
case Atom.to_string(sigil) do
<<"sigil_", _name>> ->
meta =
@@ -183,16 +185,15 @@ defmodule Code.Normalizer do
end
# Tuples
defp do_normalize({:{}, meta, args} = quoted, state) do
defp do_normalize({:{}, meta, args} = quoted, state) when is_list(args) do
{last_arg, args} = List.pop_at(args, -1)
with [{{:__block__, key_meta, _}, _} | _] <- last_arg, :keyword <- key_meta[:format] do
args = normalize_kw_args(args, state)
kw_list = normalize_kw_args(last_arg, state)
if args != [] and match?([_ | _], last_arg) and keyword?(last_arg) do
args = normalize_args(args, state)
kw_list = normalize_kw_args(last_arg, state, true)
{:{}, meta, args ++ kw_list}
else
_ ->
normalize_call(quoted, state)
normalize_call(quoted, state)
end
end
@@ -246,11 +247,10 @@ defmodule Code.Normalizer do
meta = patch_meta_line(meta, state.parent_meta)
state = %{state | parent_meta: meta}
with [{{:__block__, key_meta, _}, _} | _] <- right, :keyword <- key_meta[:format] do
{:__block__, meta, [{do_normalize(left, state), normalize_kw_args(right, state)}]}
if match?([_ | _], right) and keyword?(right) do
{:__block__, meta, [{do_normalize(left, state), normalize_kw_args(right, state, true)}]}
else
_ ->
{:__block__, meta, [{do_normalize(left, state), do_normalize(right, state)}]}
{:__block__, meta, [{do_normalize(left, state), do_normalize(right, state)}]}
end
end
@@ -282,7 +282,7 @@ defmodule Code.Normalizer do
meta
end
{:__block__, meta, [normalize_kw_args(list, state)]}
{:__block__, meta, [normalize_kw_args(list, state, false)]}
end
end
@@ -402,7 +402,7 @@ defmodule Code.Normalizer do
end
defp normalize_map_args(args, state) do
Enum.map(normalize_kw_args(args, state), fn
Enum.map(normalize_kw_args(args, state, false), fn
{:__block__, _, [{_, _} = pair]} -> pair
pair -> pair
end)
@@ -435,7 +435,7 @@ defmodule Code.Normalizer do
{form, meta, leading_args ++ [kw_blocks]}
end
defp normalize_kw_args(elems, state, keyword? \\ false)
defp normalize_kw_args(elems, state, keyword?)
defp normalize_kw_args(
[{{:__block__, key_meta, [key]}, value} = first | rest] = current,
+13 -4
View File
@@ -2573,6 +2573,10 @@ defmodule Enum do
iex> Enum.slide([:a, :b, :c, :d, :e, :f, :g], -4..-2, 1)
[:a, :d, :e, :f, :b, :c, :g]
# Insert at negative indices (counting from the end)
iex> Enum.slide([:a, :b, :c, :d, :e, :f, :g], 3, -1)
[:a, :b, :c, :e, :f, :g, :d]
"""
def slide(enumerable, range_or_single_index, insertion_index)
@@ -2587,14 +2591,19 @@ defmodule Enum do
end
# Normalize negative input ranges like Enum.slice/2
def slide(enumerable, first..last, insertion_index) when first < 0 or last < 0 do
def slide(enumerable, first..last, insertion_index)
when first < 0 or last < 0 or insertion_index < 0 do
count = Enum.count(enumerable)
normalized_first = if first >= 0, do: first, else: first + count
normalized_last = if last >= 0, do: last, else: last + count
if normalized_first >= 0 and normalized_first < count and normalized_first != insertion_index do
normalized_insertion_index =
if insertion_index >= 0, do: insertion_index, else: insertion_index + count
if normalized_first >= 0 and normalized_first < count and
normalized_first != normalized_insertion_index do
normalized_range = normalized_first..normalized_last//1
slide(enumerable, normalized_range, insertion_index)
slide(enumerable, normalized_range, normalized_insertion_index)
else
Enum.to_list(enumerable)
end
@@ -2605,7 +2614,7 @@ defmodule Enum do
end
def slide(_, first..last, insertion_index)
when insertion_index > first and insertion_index < last do
when insertion_index > first and insertion_index <= last do
raise "Insertion index for slide must be outside the range being moved " <>
"(tried to insert #{first}..#{last} at #{insertion_index})"
end
+5 -1
View File
@@ -354,7 +354,7 @@ defmodule Module.Types do
end
defp simplify_type?(type, other) do
map_type?(type) and not map_type?(other)
map_like_type?(type) and not map_like_type?(other)
end
## EXPRESSION FORMATTING
@@ -505,6 +505,10 @@ defmodule Module.Types do
defp map_type?({:map, _}), do: true
defp map_type?(_other), do: false
defp map_like_type?({:map, _}), do: true
defp map_like_type?({:union, union}), do: Enum.any?(union, &map_like_type?/1)
defp map_like_type?(_other), do: false
defp atom_type?(:atom), do: true
defp atom_type?({:atom, _}), do: false
defp atom_type?({:union, union}), do: Enum.all?(union, &atom_type?/1)
+2 -2
View File
@@ -991,7 +991,7 @@ defmodule Task do
* the task process exited with the reason `:normal`
* the task isn't linked to the caller (the task was started
with `Task.async_nolink/2` or `Task.Supervisor.async_nolink/3`)
with `Task.Supervisor.async_nolink/2` or `Task.Supervisor.async_nolink/4`)
* the caller is trapping exits
A timeout, in milliseconds or `:infinity`, can be given with a default value
@@ -1080,7 +1080,7 @@ defmodule Task do
`Task.yield_many/2` allows developers to spawn multiple tasks
and retrieve the results received in a given timeframe.
If we combine it with `Task.shutdown/2` (or `Task.ignore/2`),
If we combine it with `Task.shutdown/2` (or `Task.ignore/1`),
it allows us to gather those results and cancel (or ignore)
the tasks that have not replied in time.
+12 -12
View File
@@ -74,7 +74,7 @@ defmodule Task.Supervisor do
This function could also receive `:restart` and `:shutdown` as options
but those two options have been deprecated and it is now preferred to
give them directly to `start_child` and `async`.
give them directly to `start_child`.
"""
@spec start_link([option]) :: Supervisor.on_start()
def start_link(options \\ []) do
@@ -105,7 +105,7 @@ defmodule Task.Supervisor do
The `supervisor` must be a reference as defined in `Supervisor`.
The task will still be linked to the caller, see `Task.async/3` for
more information and `async_nolink/2` for a non-linked variant.
more information and `async_nolink/3` for a non-linked variant.
Raises an error if `supervisor` has reached the maximum number of
children.
@@ -126,7 +126,7 @@ defmodule Task.Supervisor do
The `supervisor` must be a reference as defined in `Supervisor`.
The task will still be linked to the caller, see `Task.async/3` for
more information and `async_nolink/2` for a non-linked variant.
more information and `async_nolink/3` for a non-linked variant.
Raises an error if `supervisor` has reached the maximum number of
children.
@@ -235,7 +235,7 @@ defmodule Task.Supervisor do
children.
Note this function requires the task supervisor to have `:temporary`
as the `:restart` option (the default), as `async_nolink/4` keeps a
as the `:restart` option (the default), as `async_nolink/5` keeps a
direct reference to the task which is lost if the task is restarted.
"""
@spec async_nolink(Supervisor.supervisor(), module, atom, [term], Keyword.t()) :: Task.t()
@@ -249,7 +249,7 @@ defmodule Task.Supervisor do
Each element will be prepended to the given `args` and processed by its
own task. The tasks will be spawned under the given `supervisor` and
linked to the caller process, similarly to `async/4`.
linked to the caller process, similarly to `async/5`.
When streamed, each task will emit `{:ok, value}` upon successful
completion or `{:exit, reason}` if the caller is trapping exits.
@@ -308,7 +308,7 @@ defmodule Task.Supervisor do
Each element in `enumerable` is passed as argument to the given function `fun`
and processed by its own task. The tasks will be spawned under the given
`supervisor` and linked to the caller process, similarly to `async/2`.
`supervisor` and linked to the caller process, similarly to `async/3`.
See `async_stream/6` for discussion, options, and examples.
"""
@@ -325,7 +325,7 @@ defmodule Task.Supervisor do
Each element in `enumerable` will be prepended to the given `args` and processed
by its own task. The tasks will be spawned under the given `supervisor` and
will not be linked to the caller process, similarly to `async_nolink/4`.
will not be linked to the caller process, similarly to `async_nolink/5`.
See `async_stream/6` for discussion, options, and examples.
"""
@@ -350,7 +350,7 @@ defmodule Task.Supervisor do
Each element in `enumerable` is passed as argument to the given function `fun`
and processed by its own task. The tasks will be spawned under the given
`supervisor` and will not be linked to the caller process, similarly
to `async_nolink/2`.
to `async_nolink/3`.
See `async_stream/6` for discussion and examples.
"""
@@ -387,7 +387,7 @@ defmodule Task.Supervisor do
Note that the spawned process is not linked to the caller, but
only to the supervisor. This command is useful in case the
task needs to perform side-effects (like I/O) and you have no
interest on its results nor if it completes successfully.
interest in its results nor if it completes successfully.
## Options
@@ -395,9 +395,9 @@ defmodule Task.Supervisor do
`:transient` or `:permanent`. `:temporary` means the task is never
restarted, `:transient` means it is restarted if the exit is not
`:normal`, `:shutdown` or `{:shutdown, reason}`. A `:permanent` restart
strategy means it is always restarted. It defaults to `:temporary`.
strategy means it is always restarted.
* `:shutdown` - `:brutal_kill` if the tasks must be killed directly on shutdown
* `:shutdown` - `:brutal_kill` if the task must be killed directly on shutdown
or an integer indicating the timeout value, defaults to 5000 milliseconds.
"""
@@ -413,7 +413,7 @@ defmodule Task.Supervisor do
@doc """
Starts a task as a child of the given `supervisor`.
Similar to `start_child/2` except the task is specified
Similar to `start_child/3` except the task is specified
by the given `module`, `fun` and `args`.
"""
@spec start_child(Supervisor.supervisor(), module, atom, [term], keyword) ::
+13 -2
View File
@@ -668,9 +668,17 @@ defmodule URI do
{:error, _} -> %URI{path: path}
end
Also note this function sets the authority field, but the field has been
deprecated and it is not set by `URI.new!/1` and `URI.new/1`.
There are two differencws in the behaviour of this function compared to
`URI.new/1`:
* This function sets the deprecated authority field
* This function sets the path to `nil` when it is empty,
while `new/1` consider the path always exists and sets it
to an empty string
"""
# TODO: Deprecate me at least on v1.17
@doc deprecated: "Use URI.new/1 or URI.new!/1 instead"
@spec parse(t | binary) :: t
def parse(%URI{} = uri), do: uri
@@ -705,6 +713,7 @@ defmodule URI do
],
parts
path = nillify(path)
scheme = nillify(scheme)
query = nillify_query(query_with_question_mark)
{authority, userinfo, host, port} = split_authority(authority_with_slashes)
@@ -816,6 +825,8 @@ defmodule URI do
merge(parse(base), parse(rel))
end
# TODO: Deprecate me on Elixir v1.19
defp merge_paths(nil, rel_path), do: merge_paths("/", rel_path)
defp merge_paths("", rel_path), do: merge_paths("/", rel_path)
defp merge_paths(_, "/" <> _ = rel_path), do: remove_dot_segments_from_path(rel_path)
@@ -8,12 +8,11 @@ Elixir applies bug fixes only to the latest minor branch. Security patches are a
Elixir version | Support
:------------- | :-----------------------------
1.13 | Development
1.12 | Bug fixes and security patches
1.13 | Bug fixes and security patches
1.12 | Security patches only
1.11 | Security patches only
1.10 | Security patches only
1.9 | Security patches only
1.8 | 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).
@@ -188,3 +187,4 @@ Version | Deprecated feature | Replaced by (ava
[v1.10]: https://github.com/elixir-lang/elixir/blob/v1.10/CHANGELOG.md#4-hard-deprecations
[v1.11]: https://github.com/elixir-lang/elixir/blob/v1.11/CHANGELOG.md#4-hard-deprecations
[v1.12]: https://github.com/elixir-lang/elixir/blob/v1.12/CHANGELOG.md#4-hard-deprecations
[v1.13]: https://github.com/elixir-lang/elixir/blob/v1.13/CHANGELOG.md#4-hard-deprecations
+7 -7
View File
@@ -254,7 +254,7 @@ eval_quoted(Tree, Binding, #{line := Line} = E) ->
eval_forms(Tree, Binding, Opts) when is_list(Opts) ->
eval_forms(Tree, Binding, env_for_eval(Opts));
eval_forms(Tree, RawBinding, OrigE) ->
{Vars, Binding} = normalize_binding(RawBinding, [], []),
{Vars, Binding} = normalize_binding(RawBinding, [], [], 0),
E = elixir_env:with_vars(OrigE, Vars),
{_, S} = elixir_env:env_to_erl(E),
{Erl, NewErlS, NewExS, NewE} = quoted_to_erl(Tree, E, S),
@@ -275,12 +275,12 @@ eval_forms(Tree, RawBinding, OrigE) ->
{Value, elixir_erl_var:dump_binding(NewBinding, NewExS, NewErlS), NewE}
end.
normalize_binding([{Key, Value} | Binding], Vars, Acc) when is_atom(Key) ->
normalize_binding(Binding, [{Key, nil} | Vars], [{{Key, nil}, Value} | Acc]);
normalize_binding([{Pair, Value} | Binding], Vars, Acc) ->
normalize_binding(Binding, [Pair | Vars], [{Pair, Value} | Acc]);
normalize_binding([], Vars, Acc) ->
{Vars, Acc}.
normalize_binding([{Key, Value} | Binding], Vars, Acc, I) when is_atom(Key) ->
normalize_binding(Binding, [{{Key, nil}, I} | Vars], [{{Key, nil}, Value} | Acc], I + 1);
normalize_binding([{Pair, Value} | Binding], Vars, Acc, I) ->
normalize_binding(Binding, [{Pair, I} | Vars], [{Pair, Value} | Acc], I + 1);
normalize_binding([], Vars, Acc, _I) ->
{maps:from_list(Vars), Acc}.
recur_eval([Expr | Exprs], Binding, Env) ->
{value, Value, NewBinding} =
+3 -14
View File
@@ -16,7 +16,7 @@ default_functions() ->
default_macros() ->
[{?kernel, elixir_imported_macros()}].
default_requires() ->
['Elixir.Kernel', 'Elixir.Kernel.Typespec'].
['Elixir.Application', 'Elixir.Kernel', 'Elixir.Kernel.Typespec'].
%% This is used by elixir_quote. Note we don't record the
%% import locally because at that point there is no
@@ -68,9 +68,7 @@ import_function(Meta, Name, Arity, E) ->
end.
require_function(Meta, Receiver, Name, Arity, E) ->
Required =
is_element(Receiver, ?key(E, requires)) orelse
is_deprecated_require(Meta, Receiver, Name, E),
Required = is_element(Receiver, ?key(E, requires)),
case is_macro({Name, Arity}, Receiver, Required) of
true -> false;
@@ -182,7 +180,7 @@ do_expand_import(Meta, {Name, Arity} = Tuple, Args, Module, S, E, Result) ->
expand_require(Meta, Receiver, {Name, Arity} = Tuple, Args, S, E) ->
Required =
(Receiver == ?key(E, module)) orelse required(Meta) orelse
is_element(Receiver, ?key(E, requires)) orelse is_deprecated_require(Meta, Receiver, Name, E),
is_element(Receiver, ?key(E, requires)),
case is_macro(Tuple, Receiver, Required) of
true ->
@@ -194,13 +192,6 @@ expand_require(Meta, Receiver, {Name, Arity} = Tuple, Args, S, E) ->
error
end.
is_deprecated_require(Meta, 'Elixir.Application', Name, E)
when Name == 'compile_env'; Name == 'compile_env!' ->
elixir_errors:form_warn(Meta, E, ?MODULE, {implicit_application_require, Name}),
true;
is_deprecated_require(_, _, _, _) ->
false.
%% Expansion helpers
expand_macro_fun(Meta, Fun, Receiver, Name, Args, S, E) ->
@@ -296,8 +287,6 @@ prune_stacktrace([], _MFA, Info, _E) ->
%% ERROR HANDLING
format_error({implicit_application_require, Name}) ->
io_lib:format("you must require Application before calling Application.~ts",[Name]);
format_error({macro_conflict, {Receiver, Name, Arity}}) ->
io_lib:format("call to local macro ~ts/~B conflicts with imported ~ts.~ts/~B, "
"please rename the local macro or remove the conflicting import",
+6 -3
View File
@@ -37,9 +37,12 @@ to_caller({Line, #elixir_ex{vars={Read, _}}, Env}) ->
to_caller(#{} = Env) ->
Env.
with_vars(Env, Vars) ->
{VarVersions, _} = lists:mapfoldl(fun(Var, I) -> {{Var, I}, I + 1} end, 0, Vars),
Env#{versioned_vars := maps:from_list(VarVersions)}.
with_vars(Env, Vars) when is_list(Vars) ->
{ReversedVars, _} =
lists:foldl(fun(Var, {Acc, I}) -> {[{Var, I} | Acc], I + 1} end, {[], 0}, Vars),
Env#{versioned_vars := maps:from_list(ReversedVars)};
with_vars(Env, #{} = Vars) ->
Env#{versioned_vars := Vars}.
reset_vars(Env) ->
Env#{versioned_vars := #{}}.
+20 -6
View File
@@ -3,9 +3,7 @@
-export([extract/6, unescape_string/1, unescape_string/2,
unescape_tokens/1, unescape_map/1]).
-include("elixir.hrl").
-define(is_hex(S), ((S >= $0 andalso S =< $9) orelse
(S >= $A andalso S =< $F) orelse
(S >= $a andalso S =< $f))).
-include("elixir_tokenizer.hrl").
%% Extract string interpolations
@@ -37,6 +35,9 @@ extract([$\n | Rest], Buffer, Output, Line, _Column, Scope, Interpol, Last) ->
extract([$\\, Last | Rest], Buffer, Output, Line, Column, Scope, Interpol, Last) ->
extract(Rest, [Last | Buffer], Output, Line, Column+2, Scope, Interpol, Last);
extract([$\\, Last, Last, Last | Rest], Buffer, Output, Line, Column, Scope, Interpol, [Last, Last, Last] = All) ->
extract(Rest, [Last, Last, Last | Buffer], Output, Line, Column+4, Scope, Interpol, All);
extract([$\\, $#, ${ | Rest], Buffer, Output, Line, Column, Scope, true, Last) ->
extract(Rest, [${, $#, $\\ | Buffer], Output, Line, Column+1, Scope, true, Last);
@@ -57,8 +58,8 @@ extract([$#, ${ | Rest], Buffer, Output, Line, Column, Scope, true, Last) ->
{error, {string, Line, Column, "missing interpolation terminator: \"}\"", []}}
end;
extract([$\\, Char | Rest], Buffer, Output, Line, Column, Scope, Interpol, Last) ->
extract(Rest, [Char, $\\ | Buffer], Output, Line, Column + 2, Scope, Interpol, Last);
extract([$\\ | Rest], Buffer, Output, Line, Column, Scope, Interpol, Last) ->
extract_char(Rest, [$\\ | Buffer], Output, Line, Column + 1, Scope, Interpol, Last);
%% Catch all clause
@@ -67,8 +68,21 @@ extract([Char1, Char2 | Rest], Buffer, Output, Line, Column, Scope, Interpol, La
extract([Char2 | Rest], [Char1 | Buffer], Output, Line, Column + 1, Scope, Interpol, Last);
extract(Rest, Buffer, Output, Line, Column, Scope, Interpol, Last) ->
extract_char(Rest, Buffer, Output, Line, Column, Scope, Interpol, Last).
extract_char(Rest, Buffer, Output, Line, Column, Scope, Interpol, Last) ->
[Char | NewRest] = unicode_util:gc(Rest),
extract(NewRest, [Char | Buffer], Output, Line, Column + 1, Scope, Interpol, Last).
if
?bidi(Char) ->
Token = io_lib:format("\\u~4.16.0B", [Char]),
Pre = "invalid bidirectional formatting character in string: ",
Pos = io_lib:format(". If you want to use such character, use it in its escaped ~ts form instead", [Token]),
{error, {Line, Column, {Pre, Pos}, Token}};
true ->
extract(NewRest, [Char | Buffer], Output, Line, Column + 1, Scope, Interpol, Last)
end.
%% Handle newlines. Heredocs require special attention
+29 -35
View File
@@ -1,28 +1,8 @@
-module(elixir_tokenizer).
-include("elixir.hrl").
-include("elixir_tokenizer.hrl").
-export([tokenize/1, tokenize/3, tokenize/4, invalid_do_error/1]).
%% Numbers
-define(is_hex(S), (?is_digit(S) orelse (S >= $A andalso S =< $F) orelse (S >= $a andalso S =< $f))).
-define(is_bin(S), (S >= $0 andalso S =< $1)).
-define(is_octal(S), (S >= $0 andalso S =< $7)).
%% Digits and letters
-define(is_digit(S), (S >= $0 andalso S =< $9)).
-define(is_upcase(S), (S >= $A andalso S =< $Z)).
-define(is_downcase(S), (S >= $a andalso S =< $z)).
%% Others
-define(is_quote(S), (S =:= $" orelse S =:= $')).
-define(is_sigil(S), (S =:= $/ orelse S =:= $< orelse S =:= $" orelse S =:= $' orelse
S =:= $[ orelse S =:= $( orelse S =:= ${ orelse S =:= $|)).
%% Spaces
-define(is_horizontal_space(S), (S =:= $\s orelse S =:= $\t)).
-define(is_vertical_space(S), (S =:= $\r orelse S =:= $\n)).
-define(is_space(S), (?is_horizontal_space(S) orelse ?is_vertical_space(S))).
%% Operators
-define(at_op(T),
T =:= $@).
@@ -198,9 +178,13 @@ tokenize([$0, $o, H | T], Line, Column, Scope, Tokens) when ?is_octal(H) ->
% Comments
tokenize([$# | String], Line, Column, Scope, Tokens) ->
{Rest, Comment} = tokenize_comment(String, [$#]),
preserve_comments(Line, Column, Tokens, Comment, Rest, Scope),
tokenize(Rest, Line, Column, Scope, reset_eol(Tokens));
case tokenize_comment(String, [$#]) of
{error, Char} ->
error_comment(Char, [$# | String], Line, Column, Scope, Tokens);
{Rest, Comment} ->
preserve_comments(Line, Column, Tokens, Comment, Rest, Scope),
tokenize(Rest, Line, Column, Scope, reset_eol(Tokens))
end;
% Sigils
@@ -645,9 +629,7 @@ tokenize([$% | T], Line, Column, Scope, Tokens) ->
tokenize(T, Line, Column + 1, Scope, [{'%', {Line, Column, nil}} | Tokens]);
tokenize([$. | T], Line, Column, Scope, Tokens) ->
DotInfo = {Line, Column, nil},
{Rest, EndLine, EndColumn} = strip_dot_space(T, Line, Column + 1, [{'.', DotInfo}| Tokens], Scope),
handle_dot([$. | Rest], EndLine, EndColumn, DotInfo, Scope, Tokens);
tokenize_dot(T, Line, Column + 1, {Line, Column, nil}, Scope, Tokens);
% Identifiers
@@ -719,18 +701,23 @@ strip_horizontal_space([H | T], Counter) when ?is_horizontal_space(H) ->
strip_horizontal_space(T, Counter) ->
{T, Counter}.
strip_dot_space(T, Line, Column, Tokens, Scope) ->
tokenize_dot(T, Line, Column, DotInfo, Scope, Tokens) ->
case strip_horizontal_space(T, 0) of
{"#" ++ R, _} ->
{Rest, Comment} = tokenize_comment(R, [$#]),
preserve_comments(Line, Column, Tokens, Comment, Rest, Scope),
strip_dot_space(Rest, Line, 1, reset_eol(Tokens), Scope);
{[$# | R], _} ->
case tokenize_comment(R, [$#]) of
{error, Char} ->
error_comment(Char, [$# | R], Line, Column, Scope, Tokens);
{Rest, Comment} ->
preserve_comments(Line, Column, Tokens, Comment, Rest, Scope),
tokenize_dot(Rest, Line, 1, DotInfo, Scope, Tokens)
end;
{"\r\n" ++ Rest, _} ->
strip_dot_space(Rest, Line + 1, 1, eol(Line, Column, Tokens), Scope);
tokenize_dot(Rest, Line + 1, 1, DotInfo, Scope, Tokens);
{"\n" ++ Rest, _} ->
strip_dot_space(Rest, Line + 1, 1, eol(Line, Column, Tokens), Scope);
tokenize_dot(Rest, Line + 1, 1, DotInfo, Scope, Tokens);
{Rest, Length} ->
{Rest, Line, Column + Length}
handle_dot([$. | Rest], Line, Column + Length, DotInfo, Scope, Tokens)
end.
handle_char(0) -> {"\\0", "null byte"};
@@ -1171,11 +1158,18 @@ tokenize_comment("\r\n" ++ _ = Rest, Acc) ->
{Rest, lists:reverse(Acc)};
tokenize_comment("\n" ++ _ = Rest, Acc) ->
{Rest, lists:reverse(Acc)};
tokenize_comment([H | _Rest], _) when ?bidi(H) ->
{error, H};
tokenize_comment([H | Rest], Acc) ->
tokenize_comment(Rest, [H | Acc]);
tokenize_comment([], Acc) ->
{[], lists:reverse(Acc)}.
error_comment(H, Comment, Line, Column, Scope, Tokens) ->
Token = io_lib:format("\\u~4.16.0B", [H]),
Reason = {Line, Column, "invalid bidirectional formatting character in comment: ", Token},
error(Reason, Comment, Scope, Tokens).
preserve_comments(Line, Column, Tokens, Comment, Rest, Scope) ->
case Scope#elixir_tokenizer.preserve_comments of
Fun when is_function(Fun) ->
+31
View File
@@ -0,0 +1,31 @@
%% Numbers
-define(is_hex(S), (?is_digit(S) orelse (S >= $A andalso S =< $F) orelse (S >= $a andalso S =< $f))).
-define(is_bin(S), (S >= $0 andalso S =< $1)).
-define(is_octal(S), (S >= $0 andalso S =< $7)).
%% Digits and letters
-define(is_digit(S), (S >= $0 andalso S =< $9)).
-define(is_upcase(S), (S >= $A andalso S =< $Z)).
-define(is_downcase(S), (S >= $a andalso S =< $z)).
%% Others
-define(is_quote(S), (S =:= $" orelse S =:= $')).
-define(is_sigil(S), (S =:= $/ orelse S =:= $< orelse S =:= $" orelse S =:= $' orelse
S =:= $[ orelse S =:= $( orelse S =:= ${ orelse S =:= $|)).
%% Spaces
-define(is_horizontal_space(S), (S =:= $\s orelse S =:= $\t)).
-define(is_vertical_space(S), (S =:= $\r orelse S =:= $\n)).
-define(is_space(S), (?is_horizontal_space(S) orelse ?is_vertical_space(S))).
%% Bidirectional control
%% Retrieved from https://trojansource.codes/trojan-source.pdf
-define(bidi(C), C =:= 16#202A;
C =:= 16#202B;
C =:= 16#202D;
C =:= 16#202E;
C =:= 16#2066;
C =:= 16#2067;
C =:= 16#2068;
C =:= 16#202C;
C =:= 16#2069).
@@ -6,6 +6,8 @@ defmodule ApplicationTest do
import PathHelpers
import ExUnit.CaptureIO
@app :elixir
test "application environment" do
assert_raise ArgumentError, ~r/because the application was not loaded nor configured/, fn ->
Application.fetch_env!(:unknown, :unknown)
@@ -75,12 +77,14 @@ defmodule ApplicationTest do
assert Application.put_env(:elixir, :unknown, nested: [key: :value]) == :ok
assert compile_env(@app, :unknown, :default) == [nested: [key: :value]]
assert compile_env(:elixir, :unknown, :default) == [nested: [key: :value]]
assert_received {:compile_env, :elixir, [:unknown], {:ok, [nested: [key: :value]]}}
assert compile_env(:elixir, :unknown) == [nested: [key: :value]]
assert_received {:compile_env, :elixir, [:unknown], {:ok, [nested: [key: :value]]}}
assert compile_env!(@app, :unknown) == [nested: [key: :value]]
assert compile_env!(:elixir, :unknown) == [nested: [key: :value]]
assert_received {:compile_env, :elixir, [:unknown], {:ok, [nested: [key: :value]]}}
@@ -15,11 +15,14 @@ defmodule Code.Normalizer.FormatterASTTest do
good = String.trim(good)
to_quoted_opts =
[
literal_encoder: &{:ok, {:__block__, &2, [&1]}},
token_metadata: true,
unescape: false
] ++ opts
Keyword.merge(
[
literal_encoder: &{:ok, {:__block__, &2, [&1]}},
token_metadata: true,
unescape: false
],
opts
)
{quoted, comments} = Code.string_to_quoted_with_comments!(good, to_quoted_opts)
@@ -464,6 +467,29 @@ defmodule Code.Normalizer.FormatterASTTest do
"""
end
test "handles comments with unescaped literal" do
assert_same """
# before
Mix.install([:foo])
# after
""",
literal_encoder: fn literal, _ -> {:ok, literal} end
assert_same """
# before
Mix.install([1 + 2, :foo])
# after
""",
literal_encoder: fn literal, _ -> {:ok, literal} end
assert_same """
# before
Mix.install([:foo, 1 + 2])
# after
""",
literal_encoder: fn literal, _ -> {:ok, literal} end
end
test "before and after expressions with newlines" do
assert_same """
# before comment
@@ -6,6 +6,7 @@ defmodule Code.Normalizer.QuotedASTTest do
describe "quoted_to_algebra/2" do
test "variable" do
assert quoted_to_string(quote(do: foo)) == "foo"
assert quoted_to_string({:{}, [], nil}) == "{}"
end
test "local call" do
@@ -150,7 +151,7 @@ defmodule Code.Normalizer.QuotedASTTest do
assert quoted_to_string(quote do: {1, 2}) == "{1, 2}"
assert quoted_to_string(quote do: {1}) == "{1}"
assert quoted_to_string(quote do: {1, 2, 3}) == "{1, 2, 3}"
assert quoted_to_string(quote do: {1, 2, 3, foo: :bar}) == "{1, 2, 3, [foo: :bar]}"
assert quoted_to_string(quote do: {1, 2, 3, foo: :bar}) == "{1, 2, 3, foo: :bar}"
end
test "tuple call" do
@@ -536,7 +537,7 @@ defmodule Code.Normalizer.QuotedASTTest do
assert quoted_to_string(quote(do: {[]})) == "{[]}"
assert quoted_to_string(quote(do: {[a: b]})) == "{[a: b]}"
assert quoted_to_string(quote(do: {x, a: b})) == "{x, [a: b]}"
assert quoted_to_string(quote(do: {x, a: b})) == "{x, a: b}"
assert quoted_to_string(quote(do: foo(else: a))) == "foo(else: a)"
assert quoted_to_string(quote(do: foo(catch: a))) == "foo(catch: a)"
assert quoted_to_string(quote(do: foo |> [bar: :baz])) == "foo |> [bar: :baz]"
+10 -4
View File
@@ -35,10 +35,6 @@ defmodule CodeTest do
assert {3, _} = Code.eval_string("a + b", [a: 1, b: 2], __ENV__)
end
test "returns bindings from a different context" do
assert Code.eval_string("var!(a, Sample) = 1") == {1, [{{:a, Sample}, 1}]}
end
test "supports unnamed scopes" do
assert {%RuntimeError{}, [a: %RuntimeError{}]} =
Code.eval_string("a = (try do (raise \"hello\") rescue e -> e end)")
@@ -48,6 +44,16 @@ defmodule CodeTest do
assert Code.eval_string("Kernel.if true, do: :ok", [], requires: [Z, Kernel]) == {:ok, []}
end
test "returns bindings from a different context" do
assert Code.eval_string("var!(a, Sample) = 1") == {1, [{{:a, Sample}, 1}]}
end
test "does not raise on duplicate bindings" do
# The order of which values win is not guaranteed, but it should evaluate successfully.
assert Code.eval_string("b = String.Chars.to_string(a)", a: 0, a: 1) ==
{"1", [{:b, "1"}, {:a, 1}]}
end
test "with many options" do
options = [
functions: [{Kernel, [is_atom: 1]}],
+28
View File
@@ -823,6 +823,7 @@ defmodule EnumTest do
end
assert Enum.slide([:a, :b, :c, :d, :e, :f], 3..3, 2) == [:a, :b, :d, :c, :e, :f]
assert Enum.slide([:a, :b, :c, :d, :e, :f], 3, 3) == [:a, :b, :c, :d, :e, :f]
end
test "on a subsection of a list reorders the range correctly" do
@@ -946,6 +947,33 @@ defmodule EnumTest do
assert slide.(list) == slide.(range)
end
end
test "inserts at negative indices" do
for zero_to_5 <- [0..5, Enum.to_list(0..5)] do
assert Enum.slide(zero_to_5, 0, -1) == [1, 2, 3, 4, 5, 0]
assert Enum.slide(zero_to_5, 1, -1) == [0, 2, 3, 4, 5, 1]
assert Enum.slide(zero_to_5, 1..2, -2) == [0, 3, 4, 1, 2, 5]
assert Enum.slide(zero_to_5, -5..-4//1, -2) == [0, 3, 4, 1, 2, 5]
end
assert Enum.slide([:a, :b, :c, :d, :e, :f], -5..-3//1, -2) ==
Enum.slide([:a, :b, :c, :d, :e, :f], 1..3, 4)
end
test "raises when insertion index would fall inside the range" do
for zero_to_5 <- [0..5, Enum.to_list(0..5)] do
assert_raise RuntimeError, fn ->
Enum.slide(zero_to_5, 2..3, -3)
end
end
for zero_to_10 <- [0..10, Enum.to_list(0..10)],
insertion_idx <- 3..5 do
assert_raise RuntimeError, fn ->
assert Enum.slide(zero_to_10, 2..5, insertion_idx)
end
end
end
end
test "scan/2" do
@@ -47,6 +47,15 @@ defmodule Kernel.BinaryTest do
)
end
test "heredoc with heredoc inside interpolation" do
assert """
1
#{"""
2
"""}
""" == "1\n2\n\n"
end
test "UTF-8" do
assert byte_size(" ゆんゆん") == 13
end
@@ -816,6 +816,24 @@ defmodule Kernel.ErrorsTest do
'x = 8; <<a, b::size(^x)>> = <<?a, ?b>>'
end
test "invalid bidi in source" do
assert_eval_raise SyntaxError,
~r"nofile:1:1: invalid bidirectional formatting character in comment: \\u202A",
'# This is a \u202A'
assert_eval_raise SyntaxError,
~r"nofile:1:5: invalid bidirectional formatting character in comment: \\u202A",
'foo. # This is a \u202A'
assert_eval_raise SyntaxError,
~r"nofile:1:12: invalid bidirectional formatting character in string: \\u202A. If you want to use such character, use it in its escaped \\u202A form instead",
'"this is a \u202A"'
assert_eval_raise SyntaxError,
~r"nofile:1:13: invalid bidirectional formatting character in string: \\u202A. If you want to use such character, use it in its escaped \\u202A form instead",
'"this is a \\\u202A"'
end
test "function head with guard" do
assert_eval_raise CompileError, "nofile:2: missing :do option in \"def\"", '''
defmodule Kernel.ErrorsTest.BodyessFunctionWithGuard do
@@ -42,6 +42,14 @@ bar) in ["foo\\\nbar", "foo\\\r\nbar"]
"""
end
test "sigil S with escaping" do
assert "\"" == ~S"\""
assert "\"\"\"\n" == ~S"""
\"""
"""
end
test "sigil s/S expand to binary when possible" do
assert Macro.expand(quote(do: ~s(foo)), __ENV__) == "foo"
assert Macro.expand(quote(do: ~S(foo)), __ENV__) == "foo"
@@ -606,6 +606,41 @@ defmodule Module.Types.TypesTest do
%{foo: ^other_key} = event
"""
end
test "expands map when maps are nested" do
string =
warning(
[map1, map2],
(
[_var1, _var2] = [map1, map2]
%{} = map1
%{} = map2.subkey
)
)
assert string == """
incompatible types:
%{subkey: var1, optional(dynamic()) => dynamic()} !~ %{optional(dynamic()) => dynamic()} | %{optional(dynamic()) => dynamic()}
in expression:
# types_test.ex:5
map2.subkey
where "map2" was given the type %{optional(dynamic()) => dynamic()} | %{optional(dynamic()) => dynamic()} in:
# types_test.ex:3
[_var1, _var2] = [map1, map2]
where "map2" was given the type %{subkey: var1, optional(dynamic()) => dynamic()} (due to calling var.field) in:
# types_test.ex:5
map2.subkey
HINT: "var.field" (without parentheses) implies "var" is a map() while "var.fun()" (with parentheses) implies "var" is an atom()
"""
end
end
describe "regressions" do
-9
View File
@@ -5,15 +5,6 @@ defmodule StringTest do
doctest String
test "heredoc with heredoc inside interpolation" do
assert """
1
#{"""
2
"""}
""" == "1\n2\n\n"
end
test "next_codepoint/1" do
assert String.next_codepoint("ésoj") == {"é", "soj"}
assert String.next_codepoint(<<255>>) == {<<255>>, ""}
+10 -4
View File
@@ -449,7 +449,7 @@ defmodule URITest do
query: nil,
fragment: nil,
port: 443,
path: "",
path: nil,
userinfo: nil
}
@@ -551,7 +551,7 @@ defmodule URITest do
expected_uri = %URI{
scheme: "http",
host: "foo.com",
path: "",
path: nil,
query: nil,
fragment: nil,
port: 4444,
@@ -564,7 +564,7 @@ defmodule URITest do
expected_uri = %URI{
scheme: "https",
host: "foo.com",
path: "",
path: nil,
query: nil,
fragment: nil,
port: 443,
@@ -577,7 +577,7 @@ defmodule URITest do
expected_uri = %URI{
scheme: "http",
host: "foo.com",
path: "",
path: nil,
query: nil,
fragment: nil,
port: 4444,
@@ -655,5 +655,11 @@ defmodule URITest do
test "preserves an empty query" do
assert URI.parse("http://foo.com/?").query == ""
end
test "merges empty path" do
base = URI.parse("http://example.com")
assert URI.merge(base, "/foo") |> to_string == "http://example.com/foo"
assert URI.merge(base, "foo") |> to_string == "http://example.com/foo"
end
end
end
+3 -2
View File
@@ -239,12 +239,13 @@ defmodule IEx.Server do
defp handle_take_over(
{:EXIT, pid, reason},
state,
_evaluator,
_evaluator_ref,
evaluator,
evaluator_ref,
_input,
callback
) do
if pid == Process.group_leader() do
stop_evaluator(evaluator, evaluator_ref)
exit(reason)
else
callback.(state)
+71 -65
View File
@@ -674,7 +674,70 @@ defmodule Mix do
case Mix.State.get(:installed) do
nil ->
:ok
Application.put_all_env(config, persistent: true)
System.put_env(system_env)
installs_root =
System.get_env("MIX_INSTALL_DIR") || Path.join(Mix.Utils.mix_cache(), "installs")
version = "elixir-#{System.version()}-erts-#{:erlang.system_info(:version)}"
dir = Path.join([installs_root, version, id])
if opts[:verbose] do
Mix.shell().info("Mix.install/2 using #{dir}")
end
if force? do
File.rm_rf!(dir)
end
config = [
version: "0.1.0",
build_embedded: false,
build_per_environment: true,
build_path: "_build",
lockfile: "mix.lock",
deps_path: "deps",
deps: deps,
app: :mix_install,
erlc_paths: ["src"],
elixirc_paths: ["lib"],
compilers: [],
consolidate_protocols: Keyword.get(opts, :consolidate_protocols, true)
]
started_apps = Application.started_applications()
:ok = Mix.Local.append_archives()
:ok = Mix.ProjectStack.push(@mix_install_project, config, "nofile")
build_dir = Path.join(dir, "_build")
try do
run_deps? = not File.dir?(build_dir)
File.mkdir_p!(dir)
File.cd!(dir, fn ->
if run_deps? do
Mix.Task.rerun("deps.get")
end
Mix.Task.rerun("deps.loadpaths")
# Hex and SSL can use a good amount of memory after the registry fetching,
# so we stop any app started during deps resolution.
stop_apps(Application.started_applications() -- started_apps)
Mix.Task.rerun("compile")
end)
for app <- Mix.Project.deps_apps() do
Application.ensure_all_started(app)
end
Mix.State.put(:installed, id)
:ok
after
Mix.ProjectStack.pop()
end
^id when not force? ->
:ok
@@ -682,71 +745,14 @@ defmodule Mix do
_ ->
Mix.raise("Mix.install/2 can only be called with the same dependencies in the given VM")
end
end
Application.put_all_env(config, persistent: true)
System.put_env(system_env)
installs_root =
System.get_env("MIX_INSTALL_DIR") || Path.join(Mix.Utils.mix_cache(), "installs")
version = "elixir-#{System.version()}-erts-#{:erlang.system_info(:version)}"
dir = Path.join([installs_root, version, id])
if opts[:verbose] do
Mix.shell().info("Mix.install/2 using #{dir}")
end
if force? do
File.rm_rf!(dir)
end
config = [
version: "0.1.0",
build_embedded: false,
build_per_environment: true,
build_path: "_build",
lockfile: "mix.lock",
deps_path: "deps",
deps: deps,
app: :mix_install,
erlc_paths: ["src"],
elixirc_paths: ["lib"],
compilers: [],
consolidate_protocols: Keyword.get(opts, :consolidate_protocols, true)
]
started_apps = Application.started_applications()
:ok = Mix.Local.append_archives()
:ok = Mix.ProjectStack.push(@mix_install_project, config, "nofile")
build_dir = Path.join(dir, "_build")
try do
run_deps? = not File.dir?(build_dir)
File.mkdir_p!(dir)
File.cd!(dir, fn ->
if run_deps? do
Mix.Task.rerun("deps.get")
end
Mix.Task.rerun("deps.loadpaths")
# Hex and SSL can use a good amount of memory after the registry fetching,
# so we stop any app started during deps resolution.
stop_apps(Application.started_applications() -- started_apps)
Mix.Task.rerun("compile")
end)
for app <- Mix.Project.deps_apps() do
Application.ensure_all_started(app)
end
Mix.State.put(:installed, id)
:ok
after
Mix.ProjectStack.pop()
end
@doc """
Returns whether `Mix.install/2` was called in the current node.
"""
@doc since: "1.13.0"
def installed? do
Mix.State.get(:installed) != nil
end
defp stop_apps([]), do: :ok
+4 -5
View File
@@ -457,13 +457,12 @@ defmodule Mix.Release do
end
end
defp valid_config?(n) when is_number(n), do: true
defp valid_config?(a) when is_atom(a), do: true
defp valid_config?(b) when is_binary(b), do: true
defp valid_config?(m) when is_map(m),
do: Enum.all?(Map.delete(m, :__struct__), &valid_config?/1)
defp valid_config?(l) when is_list(l), do: Enum.all?(l, &valid_config?/1)
defp valid_config?(m) when is_map(m), do: Enum.all?(m, &valid_config?/1)
defp valid_config?(t) when is_tuple(t), do: Enum.all?(Tuple.to_list(t), &valid_config?/1)
defp valid_config?(_), do: false
defp valid_config?(o), do: is_number(o) or is_atom(o) or is_binary(o)
defp merge_provider_config(%{config_providers: []}, sys_config, _), do: {sys_config, false}
+13
View File
@@ -18,6 +18,8 @@ defmodule Mix.Shell do
@doc """
Executes the given command and returns its exit status.
Shortcut for `cmd/2` with empty options.
"""
@callback cmd(command :: String.t()) :: integer
@@ -46,6 +48,17 @@ defmodule Mix.Shell do
@doc """
Prompts the user for confirmation.
Shortcut for `yes?/2` with empty options.
"""
@callback yes?(message :: binary) :: boolean
@doc """
Prompts the user for confirmation.
## Options
* `:default` - `:yes` or `:no` (the default is `:yes`)
"""
@callback yes?(message :: binary, options :: keyword) :: boolean
+5
View File
@@ -585,6 +585,11 @@ defmodule Mix.ReleaseTest do
test "errors on bad config" do
assert {:error, "Could not read configuration file." <> _} =
make_sys_config(release([]), [foo: [bar: self()]], "unused/runtime/path")
env = %{__ENV__ | lexical_tracker: self()}
assert {:error, "Could not read configuration file." <> _} =
make_sys_config(release([]), [foo: [bar: env]], "unused/runtime/path")
end
end
+8 -2
View File
@@ -135,6 +135,7 @@ defmodule Mix.Tasks.ArchiveTest do
end)
end
@compile {:no_warn_undefined, GitRepo.Archive}
test "archive.install from Git" do
in_fixture("git_repo", fn ->
File.mkdir_p!("config")
@@ -148,15 +149,20 @@ defmodule Mix.Tasks.ArchiveTest do
require Application
true = Application.compile_env!(:git_repo, :archive_config)
defmodule GitRepo do
defmodule GitRepo.Archive do
def hello do
"World"
end
end
""")
System.cmd("git", ~w[init])
System.cmd("git", ~w[add .])
System.cmd("git", ~w[commit -m first-commit])
send(self(), {:mix_shell_input, :yes?, true})
Mix.Tasks.Archive.Install.run(["git", File.cwd!()])
assert GitRepo.Archive.hello() == "World"
message = "Generated archive \"git_repo-0.1.0.ez\" with MIX_ENV=prod"
assert_received {:mix_shell, :info, [^message]}
@@ -165,7 +171,7 @@ defmodule Mix.Tasks.ArchiveTest do
assert File.dir?(tmp_path("userhome/.mix/archives/git_repo-0.1.0/git_repo-0.1.0/ebin"))
end)
after
purge([GitRepo, GitRepo.MixProject])
purge([GitRepo.Archive, GitRepo.MixProject])
end
test "archive install, update, and uninstall life-cycle" do
+10
View File
@@ -137,6 +137,16 @@ defmodule MixTest do
Application.delete_env(:unknown_app, :foo, persistent: true)
end
test "install?", %{tmp_dir: tmp_dir} do
refute Mix.installed?()
Mix.install([
{:install_test, path: Path.join(tmp_dir, "install_test")}
])
assert Mix.installed?()
end
defp test_project(%{tmp_dir: tmp_dir}) do
path = :code.get_path()