Compare commits
27
Commits
v1.14.2
...
v1.13.0-rc.1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2765e0f210 | ||
|
|
c1fd4dd8da | ||
|
|
6d408bb0c0 | ||
|
|
ffd891a349 | ||
|
|
d7b1591e83 | ||
|
|
24547bbe96 | ||
|
|
9b4a01fe33 | ||
|
|
94993e0036 | ||
|
|
00a0f07824 | ||
|
|
c28073b4fc | ||
|
|
8387dd70bc | ||
|
|
0e59c68428 | ||
|
|
913d401a7a | ||
|
|
fbfb1b4a12 | ||
|
|
cf5762ad24 | ||
|
|
fab158c0de | ||
|
|
3a484a777d | ||
|
|
8c1b563a3c | ||
|
|
ece0530870 | ||
|
|
48b838a840 | ||
|
|
5aa1c9a839 | ||
|
|
3a8a7efecc | ||
|
|
3d038680da | ||
|
|
1766731dd4 | ||
|
|
b65b8e2cd3 | ||
|
|
5062a154fa | ||
|
|
d7fea95157 |
+64
-9
@@ -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
|
||||
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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} =
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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 := #{}}.
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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) ->
|
||||
|
||||
@@ -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]"
|
||||
|
||||
@@ -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]}],
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>>, ""}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user