Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
07450832e7 | ||
|
|
34adbcb95a | ||
|
|
286d310ac3 | ||
|
|
2fc916088a | ||
|
|
9a428e9cdd | ||
|
|
090dddcf00 | ||
|
|
089c470e13 | ||
|
|
68481b7377 | ||
|
|
e2dc9ae72e | ||
|
|
51c95b6000 | ||
|
|
12917b00e5 | ||
|
|
ab9ecf2d7f | ||
|
|
eca809285e | ||
|
|
8e71e65aae | ||
|
|
c3a27be073 | ||
|
|
e10f9fb6fe | ||
|
|
f66ffd0733 | ||
|
|
696bad8de1 | ||
|
|
96c8e31410 | ||
|
|
70be440359 | ||
|
|
c6d6683225 | ||
|
|
db2504eca6 | ||
|
|
98c3f5c602 | ||
|
|
db4684b2f5 | ||
|
|
f3f65e4cd1 | ||
|
|
5331174934 | ||
|
|
f4f2be67d7 | ||
|
|
686daadaaa | ||
|
|
6e8563e930 | ||
|
|
65be24a8c2 |
+50
-4
@@ -74,7 +74,7 @@ iex> Enum.sort([~D[2019-12-31], ~D[2020-01-01]], {:desc, Date})
|
||||
|
||||
These API improvements make the code more concise and readable and they have also been added to `Enum.sort_by`, `Enum.min_by`, `Enum.max_by`, and friends.
|
||||
|
||||
### Tracking of compile-time configuration
|
||||
## Tracking of compile-time configuration
|
||||
|
||||
In Elixir, we organize our code in applications. Libraries, your dependencies, and your own project are all separate applications. All applications in Elixir also come with an application environment.
|
||||
|
||||
@@ -124,7 +124,7 @@ By using `compile_env/3`, Elixir will store the values used during compilation a
|
||||
|
||||
In future versions, we will deprecate the use `Application.get_env` at compile-time with a clear message pointing users to configuration best practices, effectively addressing the scenario where users read from the application environment at compile time unaware of its pitfalls.
|
||||
|
||||
### Compiler tracing
|
||||
## Compiler tracing
|
||||
|
||||
This release brings enhancements to the Elixir compiler and adds new capabilities for developers to listen to compilation events.
|
||||
|
||||
@@ -141,7 +141,7 @@ Elixir itself is using the new compiler tracing to provide new functionality. On
|
||||
|
||||
Previously, this information had to be added to the overall project configuration, which was far away from where the optional call effectively happened.
|
||||
|
||||
### Other enhancements
|
||||
## Other enhancements
|
||||
|
||||
Elixir's calendar data types got many improvements, such as sigil support for third-party calendars, as well as the additions of `DateTime.now!/2`, `DateTime.shift_zone!/3`, and `NaiveDateTime.local_now/0`.
|
||||
|
||||
@@ -153,7 +153,51 @@ ExUnit, our test framework, ships two small but important improvements: `ExUnit.
|
||||
assert %{"status" => 200, "body" => %{"key" => "foo"}} = json_payload
|
||||
```
|
||||
|
||||
Now imagine that `json_payload` is a large JSON blob and the `"key"` inside the `"body"` did not have value of `"foo"`. In previous Elixir versions, if the assertion failed, Elixir would print the right side and let you up to your own devices to figure out what went wrong. In Elixir v1.10, we diff the data structure against the pattern so you can see exactly which parts of the data matched the pattern and which ones did not. Note ExUnit already performed diffing when comparing data types, this new version adds diffing when matching data agaainst a pattern.
|
||||
Now imagine that `json_payload` is a large JSON blob and the `"key"` inside the `"body"` did not have value of `"foo"`. In previous Elixir versions, if the assertion failed, Elixir would print the right side and let you up to your own devices to figure out what went wrong. In Elixir v1.10, we diff the data structure against the pattern so you can see exactly which parts of the data matched the pattern and which ones did not. Note ExUnit already performed diffing when comparing data types, this new version adds diffing when matching data against a pattern.
|
||||
|
||||
## v1.10.2 (2020-02-26)
|
||||
|
||||
### 1. Bug fixes
|
||||
|
||||
#### Elixir
|
||||
|
||||
* [Macro] Fix a bug where `Macro.to_string/1` would emit invalid code for sigils
|
||||
* [Task] Do not crash `async_stream` monitor if it receives spurious DOWN messages
|
||||
|
||||
#### Logger
|
||||
|
||||
* [Logger] Fix a bug where the Logger formatter would fail when handling unknown metadata values
|
||||
|
||||
#### Mix
|
||||
|
||||
* [mix compile] Do not write files to disk if `--warnings-as-errors` was given and warnings were emitted
|
||||
|
||||
## v1.10.1 (2020-02-10)
|
||||
|
||||
### 1. Bug fixes
|
||||
|
||||
#### Elixir
|
||||
|
||||
* [Code] Do not emit invalid code when formatting `nil`, `false`, and `true` keys in maps
|
||||
* [Kernel] Ensure `with` clauses properly unpack "implicit guards" (such as matching on the struct name)
|
||||
* [Kernel] Do not warn if commas are used by themselves in `~w`/`~W` sigils
|
||||
* [Kernel] Do not validate the `:line` option in quote (the validation has been moved to v1.11 to give users more time to update their code)
|
||||
* [Module] Ensure the code verifier handles the `:erlang.size/1` guard properly
|
||||
|
||||
#### Logger
|
||||
|
||||
* [Logger] Properly handle the `report_cb/2` option from Erlang
|
||||
* [Logger] Fix truncation for multi-byte characters
|
||||
* [Logger] Do not rebroadcast messages from remote nodes as this is now taken care by Erlang's logger
|
||||
|
||||
#### ExUnit
|
||||
|
||||
* [ExUnit] Ensure `assert_receive` produces valid exception messages in case of errors
|
||||
|
||||
#### Mix
|
||||
|
||||
* [mix release] Make sure the install command (Window specific) works on paths with spaces in the name
|
||||
* [mix release] Allow using `remote` and `rpc` commands with `Application.compile_env/3`
|
||||
|
||||
## v1.10.0 (2020-01-27)
|
||||
|
||||
@@ -179,6 +223,7 @@ Now imagine that `json_payload` is a large JSON blob and the `"key"` inside the
|
||||
* [Kernel] Warn when function head comes immediately after the implementation instead of before the implementation
|
||||
* [Kernel] Warn if duplicate key is found in struct declaration
|
||||
* [Kernel] Print all undefined functions as warnings and then raise. This allows users to see all undefined calls at once, when it would otherwise require them to compile the code multiple times
|
||||
* [Kernel] Allow file, line and context to be dynamically set on `quote`
|
||||
* [Keyword] Add `Keyword.pop!/2` and `Keyword.pop_values/2`
|
||||
* [Map] Add `Map.pop!/2`
|
||||
* [MapSet] Optimize multiple operations
|
||||
@@ -227,6 +272,7 @@ Now imagine that `json_payload` is a large JSON blob and the `"key"` inside the
|
||||
|
||||
* [Enum] Allow positive range slices on infinite streams given to `Enum.slice/2`
|
||||
* [Kernel] Raise error on functions/guards without implementation
|
||||
* [Kernel] Do not expand expressions inside interpolation twice
|
||||
* [Keyword] Ensure keyword replace and update preserve order
|
||||
* [Module] Raise instead of silently failing when performing a write module operation during after-compile
|
||||
* [Module] Fix `@macrocallback` definitions with a `when` clause
|
||||
|
||||
@@ -75,8 +75,8 @@ defmodule Code do
|
||||
function or macro is referenced. `meta` is the call AST metadata, `module`
|
||||
is the invoked module, followed by the `name` and `arity`.
|
||||
|
||||
* `{:local_function, meta, module, name, arity}` and
|
||||
`{:local_macro, meta, module, name, arity}` - traced whenever a local
|
||||
* `{:local_function, meta, name, arity}` and
|
||||
`{:local_macro, meta, name, arity}` - traced whenever a local
|
||||
function or macro is referenced. `meta` is the call AST metadata, `module`
|
||||
is the invoked module, followed by the `name` and `arity`.
|
||||
|
||||
|
||||
@@ -2239,10 +2239,10 @@ defmodule Code.Formatter do
|
||||
defp keyword?(rest), do: rest == []
|
||||
|
||||
defp keyword_key?({:__block__, meta, [atom]}) when is_atom(atom),
|
||||
do: meta[:delimiter] != ":"
|
||||
do: meta[:format] == :keyword
|
||||
|
||||
defp keyword_key?({{:., _, [:erlang, :binary_to_atom]}, meta, [{:<<>>, _, _}, :utf8]}),
|
||||
do: meta[:delimiter] != ":"
|
||||
do: meta[:format] == :keyword
|
||||
|
||||
defp keyword_key?(_),
|
||||
do: false
|
||||
|
||||
@@ -110,7 +110,8 @@ defmodule Config.Provider do
|
||||
:config_path,
|
||||
extra_config: [],
|
||||
prune_after_boot: false,
|
||||
reboot_after_config: true
|
||||
reboot_after_config: true,
|
||||
validate_compile_env: false
|
||||
]
|
||||
|
||||
@doc """
|
||||
@@ -165,54 +166,74 @@ defmodule Config.Provider do
|
||||
# before we go around running Elixir code.
|
||||
{:ok, _} = :application.ensure_all_started(:elixir)
|
||||
|
||||
case :application.get_env(app, key) do
|
||||
{:ok, %Config.Provider{} = provider} ->
|
||||
path = resolve_config_path!(provider.config_path)
|
||||
validate_no_cyclic_boot!(path)
|
||||
loaded_applications = :application.loaded_applications()
|
||||
original_config = read_config!(path)
|
||||
# The key we store if the system already booted
|
||||
booted_key = :"#{key}_booted"
|
||||
|
||||
config =
|
||||
original_config
|
||||
|> Config.__merge__(provider.extra_config)
|
||||
|> run_providers(provider)
|
||||
case :application.get_env(app, booted_key) do
|
||||
{:ok, {:booted, path}} ->
|
||||
path && File.rm(path)
|
||||
|
||||
if provider.reboot_after_config do
|
||||
config
|
||||
|> Config.__merge__([{app, [{key, booted_key(provider, path)}]}])
|
||||
|> write_config!(path)
|
||||
|
||||
restart_fun.()
|
||||
else
|
||||
for {app, _, _} <- loaded_applications, config[app] != original_config[app] do
|
||||
abort("""
|
||||
Cannot configure #{inspect(app)} because :reboot_after_config has been set \
|
||||
to false and #{inspect(app)} has already been loaded, meaning any further \
|
||||
configuration won't have an effect.
|
||||
|
||||
The configuration for #{inspect(app)} before config providers was:
|
||||
|
||||
#{inspect(original_config[app])}
|
||||
|
||||
The configuration for #{inspect(app)} after config providers was:
|
||||
|
||||
#{inspect(config[app])}
|
||||
""")
|
||||
end
|
||||
|
||||
_ = Application.put_all_env(config, persistent: true)
|
||||
:ok
|
||||
with {:ok, %Config.Provider{} = provider} <- :application.get_env(app, key) do
|
||||
maybe_validate_compile_env(provider)
|
||||
end
|
||||
|
||||
{:ok, {:booted, path}} ->
|
||||
File.rm(path)
|
||||
:booted
|
||||
|
||||
{:ok, :booted} ->
|
||||
:booted
|
||||
|
||||
_ ->
|
||||
:skip
|
||||
case :application.get_env(app, key) do
|
||||
{:ok, %Config.Provider{} = provider} ->
|
||||
path = resolve_config_path!(provider.config_path)
|
||||
reboot_config = [{app, [{booted_key, booted_value(provider, path)}]}]
|
||||
boot_providers(path, provider, reboot_config, restart_fun)
|
||||
|
||||
_ ->
|
||||
:skip
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp boot_providers(path, provider, reboot_config, restart_fun) do
|
||||
validate_no_cyclic_boot!(path)
|
||||
loaded_applications = :application.loaded_applications()
|
||||
original_config = read_config!(path)
|
||||
|
||||
config =
|
||||
original_config
|
||||
|> Config.__merge__(provider.extra_config)
|
||||
|> run_providers(provider)
|
||||
|
||||
if provider.reboot_after_config do
|
||||
config
|
||||
|> Config.__merge__(reboot_config)
|
||||
|> write_config!(path)
|
||||
|
||||
restart_fun.()
|
||||
else
|
||||
for {app, _, _} <- loaded_applications, config[app] != original_config[app] do
|
||||
abort("""
|
||||
Cannot configure #{inspect(app)} because :reboot_after_config has been set \
|
||||
to false and #{inspect(app)} has already been loaded, meaning any further \
|
||||
configuration won't have an effect.
|
||||
|
||||
The configuration for #{inspect(app)} before config providers was:
|
||||
|
||||
#{inspect(original_config[app])}
|
||||
|
||||
The configuration for #{inspect(app)} after config providers was:
|
||||
|
||||
#{inspect(config[app])}
|
||||
""")
|
||||
end
|
||||
|
||||
_ = Application.put_all_env(config, persistent: true)
|
||||
maybe_validate_compile_env(provider)
|
||||
:ok
|
||||
end
|
||||
end
|
||||
|
||||
defp maybe_validate_compile_env(provider) do
|
||||
with [_ | _] = compile_env <- provider.validate_compile_env do
|
||||
validate_compile_env(compile_env)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -283,8 +304,8 @@ defmodule Config.Provider do
|
||||
Process.sleep(:infinity)
|
||||
end
|
||||
|
||||
defp booted_key(%{prune_after_boot: true}, path), do: {:booted, path}
|
||||
defp booted_key(%{prune_after_boot: false}, _path), do: :booted
|
||||
defp booted_value(%{prune_after_boot: true}, path), do: {:booted, path}
|
||||
defp booted_value(%{prune_after_boot: false}, _path), do: {:booted, nil}
|
||||
|
||||
defp validate_no_cyclic_boot!(path) do
|
||||
if System.get_env("ELIXIR_CONFIG_PROVIDER_BOOTED") do
|
||||
|
||||
@@ -2263,8 +2263,7 @@ defmodule Kernel do
|
||||
iex> get_in(users, ["john", :age])
|
||||
27
|
||||
|
||||
In case any of the entries in the middle returns `nil`, `nil` will
|
||||
be returned as per the `Access` module:
|
||||
In case any of the keys returns `nil`, `nil` will be returned:
|
||||
|
||||
iex> users = %{"john" => %{age: 27}, "meg" => %{age: 23}}
|
||||
iex> get_in(users, ["unknown", :age])
|
||||
@@ -2280,9 +2279,6 @@ defmodule Kernel do
|
||||
* a function to be invoked next
|
||||
|
||||
This means `get_in/2` can be extended to provide custom lookups.
|
||||
The downside is that functions cannot be stored as keys in the accessed
|
||||
data structures.
|
||||
|
||||
In the example below, we use a function to get all the maps inside
|
||||
a list:
|
||||
|
||||
@@ -5388,7 +5384,8 @@ defmodule Kernel do
|
||||
true ->
|
||||
parts = String.split(string)
|
||||
|
||||
parts_with_trailing_comma = :lists.filter(&(:binary.last(&1) == ?,), parts)
|
||||
parts_with_trailing_comma =
|
||||
:lists.filter(&(byte_size(&1) > 1 and :binary.last(&1) == ?,), parts)
|
||||
|
||||
if parts_with_trailing_comma != [] do
|
||||
stacktrace = Macro.Env.stacktrace(caller)
|
||||
|
||||
@@ -139,15 +139,15 @@ defmodule Kernel.ParallelCompiler do
|
||||
compiler_pid = self()
|
||||
:elixir_code_server.cast({:reset_warnings, compiler_pid})
|
||||
schedulers = max(:erlang.system_info(:schedulers_online), 2)
|
||||
beam_timestamp = Keyword.get(options, :beam_timestamp)
|
||||
|
||||
result =
|
||||
outcome =
|
||||
spawn_workers(files, 0, [], [], %{}, [], %{
|
||||
dest: Keyword.get(options, :dest),
|
||||
each_cycle: Keyword.get(options, :each_cycle, fn -> {:runtime, []} end),
|
||||
each_file: Keyword.get(options, :each_file, fn _, _ -> :ok end) |> each_file(),
|
||||
each_long_compilation: Keyword.get(options, :each_long_compilation, fn _file -> :ok end),
|
||||
each_module: Keyword.get(options, :each_module, fn _file, _module, _binary -> :ok end),
|
||||
beam_timestamp: Keyword.get(options, :beam_timestamp),
|
||||
long_compilation_threshold: Keyword.get(options, :long_compilation_threshold, 15),
|
||||
profile: Keyword.get(options, :profile),
|
||||
cycle_start: System.monotonic_time(),
|
||||
@@ -160,7 +160,7 @@ defmodule Kernel.ParallelCompiler do
|
||||
# compilation status will be set to error.
|
||||
compilation_status = :elixir_code_server.call({:compilation_status, compiler_pid})
|
||||
|
||||
case {result, compilation_status} do
|
||||
case {outcome, compilation_status} do
|
||||
{{:ok, _, warnings}, :error} ->
|
||||
message = "Compilation failed due to warnings while using the --warnings-as-errors option"
|
||||
IO.puts(:stderr, message)
|
||||
@@ -169,8 +169,11 @@ defmodule Kernel.ParallelCompiler do
|
||||
{{:error, errors, warnings}, :error} ->
|
||||
{:error, errors ++ warnings, []}
|
||||
|
||||
_ ->
|
||||
result
|
||||
{{:ok, outcome, warnings}, _} ->
|
||||
{:ok, write_module_binaries(outcome, output, beam_timestamp), warnings}
|
||||
|
||||
{{:error, errors, warnings}, _} ->
|
||||
{:error, errors, warnings}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -187,6 +190,74 @@ defmodule Kernel.ParallelCompiler do
|
||||
end
|
||||
end
|
||||
|
||||
defp write_module_binaries(result, {:compile, path}, timestamp) do
|
||||
Enum.flat_map(result, fn
|
||||
{{:module, module}, {binary, _map}} ->
|
||||
full_path = Path.join(path, Atom.to_string(module) <> ".beam")
|
||||
File.write!(full_path, binary)
|
||||
if timestamp, do: File.touch!(full_path, timestamp)
|
||||
[module]
|
||||
|
||||
_ ->
|
||||
[]
|
||||
end)
|
||||
end
|
||||
|
||||
defp write_module_binaries(result, _output, _timestamp) do
|
||||
for {{:module, module}, _} <- result, do: module
|
||||
end
|
||||
|
||||
## Verification
|
||||
|
||||
defp verify_modules(result, warnings, dependent_modules, state) do
|
||||
checker_warnings = maybe_check_modules(result, dependent_modules, state)
|
||||
warnings = Enum.reverse(warnings, checker_warnings)
|
||||
{:ok, result, warnings}
|
||||
end
|
||||
|
||||
defp maybe_check_modules(result, runtime_modules, state) do
|
||||
%{schedulers: schedulers, profile: profile} = state
|
||||
|
||||
if :elixir_config.get(:bootstrap) do
|
||||
[]
|
||||
else
|
||||
compiled_modules = checker_compiled_modules(result)
|
||||
runtime_modules = checker_runtime_modules(runtime_modules)
|
||||
|
||||
profile_checker(profile, compiled_modules, runtime_modules, fn ->
|
||||
Module.ParallelChecker.verify(compiled_modules, runtime_modules, schedulers)
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
defp checker_compiled_modules(result) do
|
||||
for {{:module, _module}, {binary, module_map}} <- result do
|
||||
{module_map, binary}
|
||||
end
|
||||
end
|
||||
|
||||
defp checker_runtime_modules(modules) do
|
||||
for module <- modules,
|
||||
path = :code.which(module),
|
||||
is_list(path) do
|
||||
{module, File.read!(path)}
|
||||
end
|
||||
end
|
||||
|
||||
defp profile_checker(_profile = :time, compiled_modules, runtime_modules, fun) do
|
||||
{time, result} = :timer.tc(fun)
|
||||
time = div(time, 1000)
|
||||
num_modules = length(compiled_modules) + length(runtime_modules)
|
||||
IO.puts(:stderr, "[profile] Finished group pass check of #{num_modules} modules in #{time}ms")
|
||||
result
|
||||
end
|
||||
|
||||
defp profile_checker(_profile = nil, _compiled_modules, _runtime_modules, fun) do
|
||||
fun.()
|
||||
end
|
||||
|
||||
## Compiler worker spawning
|
||||
|
||||
# We already have n=schedulers currently running, don't spawn new ones
|
||||
defp spawn_workers(
|
||||
queue,
|
||||
@@ -253,10 +324,10 @@ defmodule Kernel.ParallelCompiler do
|
||||
|
||||
case each_cycle_return(state.each_cycle.()) do
|
||||
{:runtime, dependent_modules} ->
|
||||
write_and_verify_modules(result, warnings, dependent_modules, state)
|
||||
verify_modules(result, warnings, dependent_modules, state)
|
||||
|
||||
{:compile, []} ->
|
||||
write_and_verify_modules(result, warnings, [], state)
|
||||
verify_modules(result, warnings, [], state)
|
||||
|
||||
{:compile, more} ->
|
||||
spawn_workers(more, 0, [], [], result, warnings, state)
|
||||
@@ -354,71 +425,6 @@ defmodule Kernel.ParallelCompiler do
|
||||
defp each_cycle_return(modules) when is_list(modules), do: {:compile, modules}
|
||||
defp each_cycle_return(other), do: other
|
||||
|
||||
defp write_and_verify_modules(result, warnings, dependent_modules, state) do
|
||||
modules = write_module_binaries(result, state)
|
||||
checker_warnings = maybe_check_modules(result, dependent_modules, state)
|
||||
warnings = Enum.reverse(warnings, checker_warnings)
|
||||
{:ok, modules, warnings}
|
||||
end
|
||||
|
||||
defp write_module_binaries(result, %{output: {:compile, path}, beam_timestamp: timestamp}) do
|
||||
Enum.flat_map(result, fn
|
||||
{{:module, module}, {binary, _map}} ->
|
||||
full_path = Path.join(path, Atom.to_string(module) <> ".beam")
|
||||
File.write!(full_path, binary)
|
||||
if timestamp, do: File.touch!(full_path, timestamp)
|
||||
[module]
|
||||
|
||||
_ ->
|
||||
[]
|
||||
end)
|
||||
end
|
||||
|
||||
defp write_module_binaries(result, _state) do
|
||||
for {{:module, module}, _} <- result, do: module
|
||||
end
|
||||
|
||||
defp maybe_check_modules(result, runtime_modules, state) do
|
||||
%{schedulers: schedulers, profile: profile} = state
|
||||
|
||||
if :elixir_config.get(:bootstrap) do
|
||||
[]
|
||||
else
|
||||
compiled_modules = checker_compiled_modules(result)
|
||||
runtime_modules = checker_runtime_modules(runtime_modules)
|
||||
|
||||
profile_checker(profile, compiled_modules, runtime_modules, fn ->
|
||||
Module.ParallelChecker.verify(compiled_modules, runtime_modules, schedulers)
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
defp checker_compiled_modules(result) do
|
||||
for {{:module, _module}, {binary, module_map}} <- result do
|
||||
{module_map, binary}
|
||||
end
|
||||
end
|
||||
|
||||
defp checker_runtime_modules(modules) do
|
||||
for module <- modules,
|
||||
path = :code.which(module),
|
||||
is_list(path) do
|
||||
{module, File.read!(path)}
|
||||
end
|
||||
end
|
||||
|
||||
defp profile_checker(_profile = :time, compiled_modules, runtime_modules, fun) do
|
||||
{time, result} = :timer.tc(fun)
|
||||
time = div(time, 1000)
|
||||
num_modules = length(compiled_modules) + length(runtime_modules)
|
||||
IO.puts(:stderr, "[profile] Finished group pass check of #{num_modules} modules in #{time}ms")
|
||||
result
|
||||
end
|
||||
|
||||
defp profile_checker(_profile = nil, _compiled_modules, _runtime_modules, fun) do
|
||||
fun.()
|
||||
end
|
||||
|
||||
# The goal of this function is to find leaves in the dependency graph,
|
||||
# i.e. to find code that depends on code that we know is not being defined.
|
||||
defp without_definition(waiting, files) do
|
||||
|
||||
@@ -163,8 +163,9 @@ defmodule Macro do
|
||||
with parens. The `:closing` does not delimit the end of expression if
|
||||
there are `:do` and `:end` metadata (when `:token_metadata` is true)
|
||||
* `:column` - the column number of the AST node (when `:columns` is true)
|
||||
* `:delimiter` - contains the opening delimiter for sigils, strings, atoms,
|
||||
and charlists as a string (such as `"{"`, `"/"`, `":"`, and the like)
|
||||
* `:delimiter` - contains the opening delimiter for sigils, strings,
|
||||
and charlists as a string (such as `"{"`, `"/"`, `"'"`, and the like)
|
||||
* `:format` - set to `:keyword` when an atom is defined as a keyword
|
||||
* `:do` - contains metadata about the `do` location in a function call with
|
||||
`do/end` blocks (when `:token_metadata` is true)
|
||||
* `:end` - contains metadata about the `end` location in a function call with
|
||||
@@ -1021,11 +1022,11 @@ defmodule Macro do
|
||||
|
||||
binary when is_binary(binary) ->
|
||||
binary = inspect_no_limit(binary)
|
||||
binary_part(binary, 1, byte_size(binary) - 2)
|
||||
binary = binary_part(binary, 1, byte_size(binary) - 2)
|
||||
escape_sigil(binary, left)
|
||||
end)
|
||||
|
||||
escaped = escape_sigil(parts, left)
|
||||
<<left::binary, escaped::binary, right::binary>>
|
||||
<<left::binary, parts::binary, right::binary>>
|
||||
end
|
||||
|
||||
defp escape_sigil(parts, "("), do: String.replace(parts, ")", ~S"\)")
|
||||
|
||||
@@ -265,8 +265,6 @@ defmodule Map do
|
||||
|
||||
iex> Map.fetch!(%{a: 1}, :a)
|
||||
1
|
||||
iex> Map.fetch!(%{a: 1}, :b)
|
||||
** (KeyError) key :b not found in: %{a: 1}
|
||||
|
||||
"""
|
||||
@spec fetch!(map, key) :: value
|
||||
|
||||
@@ -275,6 +275,7 @@ defmodule Module.Types.Pattern do
|
||||
{:binary_part, 3} => {[:binary, :integer, :integer], :binary},
|
||||
{:bit_size, 1} => {[:binary], :integer},
|
||||
{:byte_size, 1} => {[:binary], :integer},
|
||||
{:size, 1} => {[{:union, [:binary, :tuple]}], :boolean},
|
||||
{:div, 2} => {[:integer, :integer], :integer},
|
||||
{:rem, 2} => {[:integer, :integer], :integer},
|
||||
{:node, 0} => {[], :atom},
|
||||
|
||||
@@ -107,6 +107,7 @@ defmodule Stream do
|
||||
@type index :: non_neg_integer
|
||||
|
||||
@type default :: any
|
||||
@type timer :: non_neg_integer | :infinity
|
||||
|
||||
# Require Stream.Reducers and its callbacks
|
||||
require Stream.Reducers, as: R
|
||||
@@ -505,8 +506,10 @@ defmodule Stream do
|
||||
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
|
||||
|
||||
"""
|
||||
@spec interval(non_neg_integer) :: Enumerable.t()
|
||||
def interval(n) when is_integer(n) and n >= 0 do
|
||||
@spec interval(timer()) :: Enumerable.t()
|
||||
def interval(n)
|
||||
when is_integer(n) and n >= 0
|
||||
when n == :infinity do
|
||||
unfold(0, fn count ->
|
||||
Process.sleep(n)
|
||||
{count, count + 1}
|
||||
@@ -787,8 +790,10 @@ defmodule Stream do
|
||||
[0]
|
||||
|
||||
"""
|
||||
@spec timer(non_neg_integer) :: Enumerable.t()
|
||||
def timer(n) when is_integer(n) and n >= 0 do
|
||||
@spec timer(timer()) :: Enumerable.t()
|
||||
def timer(n)
|
||||
when is_integer(n) and n >= 0
|
||||
when n == :infinity do
|
||||
take(interval(n), 1)
|
||||
end
|
||||
|
||||
|
||||
@@ -539,7 +539,7 @@ defmodule Task.Supervised do
|
||||
|
||||
# One of the spawned processes went down. We inform the parent process of
|
||||
# this and keep going.
|
||||
{:DOWN, ref, _, _, reason} ->
|
||||
{:DOWN, ref, _, _, reason} when is_map_key(running_tasks, ref) ->
|
||||
{task, running_tasks} = Map.pop(running_tasks, ref)
|
||||
%{position: position, timer_ref: timer_ref, timed_out?: timed_out?} = task
|
||||
|
||||
|
||||
@@ -97,6 +97,20 @@ dispatch_import(Meta, Name, Args, E, Callback) ->
|
||||
Callback()
|
||||
end.
|
||||
|
||||
dispatch_require(Meta, 'Elixir.System', stacktrace, [], #{contextual_vars := Vars} = E, Callback) ->
|
||||
case lists:member('__STACKTRACE__', Vars) of
|
||||
true ->
|
||||
{{'__STACKTRACE__', [], nil}, E};
|
||||
false ->
|
||||
Message =
|
||||
"System.stacktrace/0 outside of rescue/catch clauses is deprecated. "
|
||||
"If you want to support only Elixir v1.7+, you must access __STACKTRACE__ "
|
||||
"inside a rescue/catch. If you want to support earlier Elixir versions, "
|
||||
"move System.stacktrace/0 inside a rescue/catch",
|
||||
elixir_errors:erl_warn(?line(Meta), ?key(E, file), Message),
|
||||
Callback('Elixir.System', stacktrace, [])
|
||||
end;
|
||||
|
||||
dispatch_require(Meta, Receiver, Name, Args, E, Callback) when is_atom(Receiver) ->
|
||||
Arity = length(Args),
|
||||
|
||||
@@ -353,18 +367,6 @@ check_deprecated(_, erlang, _, _, _) ->
|
||||
ok;
|
||||
check_deprecated(_, _, _, _, #{module := 'Elixir.HashDict'}) ->
|
||||
ok;
|
||||
check_deprecated(Meta, 'Elixir.System', stacktrace, 0, #{contextual_vars := Vars} = E) ->
|
||||
case lists:member('__STACKTRACE__', Vars) of
|
||||
true ->
|
||||
ok;
|
||||
false ->
|
||||
Message =
|
||||
"System.stacktrace/0 outside of rescue/catch clauses is deprecated. "
|
||||
"If you want to support only Elixir v1.7+, you must access __STACKTRACE__ "
|
||||
"inside a rescue/catch. If you want to support earlier Elixir versions, "
|
||||
"move System.stacktrace/0 inside a rescue/catch",
|
||||
elixir_errors:erl_warn(?line(Meta), ?key(E, file), Message)
|
||||
end;
|
||||
check_deprecated(Meta, Receiver, Name, Arity, E) ->
|
||||
case (?key(E, function) == nil) andalso is_ensure_loaded(Receiver) of
|
||||
true ->
|
||||
|
||||
@@ -54,13 +54,8 @@ definition_to_anonymous(Module, Kind, Meta, Clauses) ->
|
||||
{value, Result, _Binding} = erl_eval:expr(Fun, [], {value, LocalHandler}),
|
||||
Result.
|
||||
|
||||
invoke_local(Module, RawName, Args) ->
|
||||
%% If we have a macro, its arity in the table is
|
||||
%% actually one less than in the function call
|
||||
{Name, Arity} = case atom_to_list(RawName) of
|
||||
"MACRO-" ++ Rest -> {list_to_atom(Rest), length(Args) - 1};
|
||||
_ -> {RawName, length(Args)}
|
||||
end,
|
||||
invoke_local(Module, ErlName, Args) ->
|
||||
{Name, Arity} = elixir_utils:erl_fa_to_elixir_fa(ErlName, length(Args)),
|
||||
|
||||
case elixir_def:local_for(Module, Name, Arity, all) of
|
||||
false ->
|
||||
|
||||
@@ -134,6 +134,14 @@ custom_format(sys_core_fold, {eval_failure, Error}) ->
|
||||
#{'__struct__' := Struct} = 'Elixir.Exception':normalize(error, Error),
|
||||
["this expression will fail with ", elixir_aliases:inspect(Struct)];
|
||||
|
||||
custom_format(sys_core_fold, {nomatch_shadow,Line,{ErlName,ErlArity}}) ->
|
||||
{Name, Arity} = elixir_utils:erl_fa_to_elixir_fa(ErlName, ErlArity),
|
||||
|
||||
io_lib:format(
|
||||
"this clause for ~ts/~B cannot match because a previous clause at line ~B always matches",
|
||||
[Name, Arity, Line]
|
||||
);
|
||||
|
||||
custom_format([], Desc) ->
|
||||
io_lib:format("~p", [Desc]);
|
||||
|
||||
|
||||
@@ -409,8 +409,8 @@ translate_with_do([{'<-', Meta, [Left, Expr]} | Rest], Do, Else, S) ->
|
||||
{Args, Guards} = elixir_utils:extract_guards(Left),
|
||||
{TExpr, SR} = elixir_erl_pass:translate(Expr, S),
|
||||
{TArgs, SA} = elixir_erl_clauses:match(fun elixir_erl_pass:translate/2, Args, SR),
|
||||
TGuards = elixir_erl_clauses:guards(Guards, [], SA),
|
||||
{TBody, SB} = translate_with_do(Rest, Do, Else, SA),
|
||||
TGuards = elixir_erl_clauses:guards(Guards, SA#elixir_erl.extra_guards, SA),
|
||||
{TBody, SB} = translate_with_do(Rest, Do, Else, SA#elixir_erl{extra_guards=[]}),
|
||||
|
||||
Clause = {clause, ?ann(Meta), [TArgs], TGuards, unblock(TBody)},
|
||||
{{'case', ?ann(?generated(Meta)), TExpr, [Clause, Else]}, SB};
|
||||
|
||||
@@ -259,9 +259,9 @@ access_expr -> bin_heredoc : build_bin_heredoc('$1').
|
||||
access_expr -> list_heredoc : build_list_heredoc('$1').
|
||||
access_expr -> bit_string : '$1'.
|
||||
access_expr -> sigil : build_sigil('$1').
|
||||
access_expr -> atom : handle_literal(?exprs('$1'), '$1', delimiter(<<$:>>)).
|
||||
access_expr -> atom_safe : build_quoted_atom('$1', true, delimiter(<<$:>>)).
|
||||
access_expr -> atom_unsafe : build_quoted_atom('$1', false, delimiter(<<$:>>)).
|
||||
access_expr -> atom : handle_literal(?exprs('$1'), '$1').
|
||||
access_expr -> atom_safe : build_quoted_atom('$1', true, []).
|
||||
access_expr -> atom_unsafe : build_quoted_atom('$1', false, []).
|
||||
access_expr -> dot_alias : '$1'.
|
||||
access_expr -> parens_call : '$1'.
|
||||
|
||||
@@ -526,12 +526,12 @@ call_args_parens -> open_paren call_args_parens_base ',' kw_base ',' close_paren
|
||||
|
||||
% KV
|
||||
|
||||
kw_eol -> kw_identifier : handle_literal(?exprs('$1'), '$1').
|
||||
kw_eol -> kw_identifier eol : handle_literal(?exprs('$1'), '$1').
|
||||
kw_eol -> kw_identifier_safe : build_quoted_atom('$1', true, []).
|
||||
kw_eol -> kw_identifier_safe eol : build_quoted_atom('$1', true, []).
|
||||
kw_eol -> kw_identifier_unsafe : build_quoted_atom('$1', false, []).
|
||||
kw_eol -> kw_identifier_unsafe eol : build_quoted_atom('$1', false, []).
|
||||
kw_eol -> kw_identifier : handle_literal(?exprs('$1'), '$1', [{format, keyword}]).
|
||||
kw_eol -> kw_identifier eol : handle_literal(?exprs('$1'), '$1', [{format, keyword}]).
|
||||
kw_eol -> kw_identifier_safe : build_quoted_atom('$1', true, [{format, keyword}]).
|
||||
kw_eol -> kw_identifier_safe eol : build_quoted_atom('$1', true, [{format, keyword}]).
|
||||
kw_eol -> kw_identifier_unsafe : build_quoted_atom('$1', false, [{format, keyword}]).
|
||||
kw_eol -> kw_identifier_unsafe eol : build_quoted_atom('$1', false, [{format, keyword}]).
|
||||
|
||||
kw_base -> kw_eol container_expr : [{'$1', '$2'}].
|
||||
kw_base -> kw_base ',' kw_eol container_expr : [{'$3', '$4'} | '$1'].
|
||||
|
||||
@@ -36,6 +36,8 @@ build(Meta, Line, File, Context, Unquote, Generated) ->
|
||||
|
||||
{Q, Acc3}.
|
||||
|
||||
validate_compile(_Meta, file, nil, Acc) ->
|
||||
{nil, Acc};
|
||||
validate_compile(Meta, Key, Value, Acc) ->
|
||||
case is_valid(Key, Value) of
|
||||
true ->
|
||||
@@ -55,13 +57,13 @@ validate_runtime(Key, Value) ->
|
||||
erlang:error(
|
||||
'Elixir.ArgumentError':exception(
|
||||
<<"invalid value for option :", (erlang:atom_to_binary(Key, utf8))/binary,
|
||||
"in quote, got: ", ('Elixir.Kernel':inspect(Value))/binary>>
|
||||
" in quote, got: ", ('Elixir.Kernel':inspect(Value))/binary>>
|
||||
)
|
||||
)
|
||||
end.
|
||||
|
||||
is_valid(line, Line) -> is_integer(Line) orelse is_boolean(Line);
|
||||
is_valid(file, File) -> is_binary(File) orelse (File == nil);
|
||||
is_valid(line, _Line) -> true;
|
||||
is_valid(file, File) -> is_binary(File);
|
||||
is_valid(context, Context) -> is_atom(Context) andalso (Context /= nil);
|
||||
is_valid(generated, Generated) -> is_boolean(Generated);
|
||||
is_valid(unquote, Unquote) -> is_boolean(Unquote).
|
||||
|
||||
@@ -7,15 +7,19 @@
|
||||
read_file_type/1, read_file_type/2, read_link_type/1, read_posix_mtime_and_size/1,
|
||||
change_posix_time/2, change_universal_time/2,
|
||||
guard_op/2, extract_splat_guards/1, extract_guards/1,
|
||||
erlang_comparison_op_to_elixir/1]).
|
||||
erlang_comparison_op_to_elixir/1, erl_fa_to_elixir_fa/2]).
|
||||
-include("elixir.hrl").
|
||||
-include_lib("kernel/include/file.hrl").
|
||||
|
||||
% Builds the macro name
|
||||
|
||||
macro_name(Macro) ->
|
||||
list_to_atom("MACRO-" ++ atom_to_list(Macro)).
|
||||
|
||||
erl_fa_to_elixir_fa(Name, Arity) ->
|
||||
case atom_to_list(Name) of
|
||||
"MACRO-" ++ Rest -> {list_to_atom(Rest), Arity - 1};
|
||||
_ -> {Name, Arity}
|
||||
end.
|
||||
|
||||
guard_op('andalso', 2) ->
|
||||
true;
|
||||
guard_op('orelse', 2) ->
|
||||
|
||||
@@ -412,6 +412,11 @@ defmodule Code.Formatter.ContainersTest do
|
||||
assert_same map, @medium_length
|
||||
end
|
||||
|
||||
test "preserves user choice in regards to =>" do
|
||||
assert_same "%{:hello => 1, :world => 2}"
|
||||
assert_format "%{:true => 1, :false => 2}", "%{true => 1, false => 2}"
|
||||
end
|
||||
|
||||
test "preserves user choice even when it fits" do
|
||||
assert_same """
|
||||
%{
|
||||
|
||||
@@ -338,20 +338,22 @@ defmodule CodeTest do
|
||||
assert string_to_quoted.("0xEF") == {:__block__, [token: "0xEF", line: 1], [239]}
|
||||
assert string_to_quoted.("12.3") == {:__block__, [token: "12.3", line: 1], [12.3]}
|
||||
assert string_to_quoted.("nil") == {:__block__, [line: 1], [nil]}
|
||||
assert string_to_quoted.(":one") == {:__block__, [delimiter: ":", line: 1], [:one]}
|
||||
assert string_to_quoted.(":one") == {:__block__, [line: 1], [:one]}
|
||||
|
||||
args = [[{:__block__, [token: "1", line: 1], [1]}]]
|
||||
assert string_to_quoted.("[one: :two]") == {
|
||||
:__block__,
|
||||
[{:closing, [line: 1]}, {:line, 1}],
|
||||
[
|
||||
[
|
||||
{{:__block__, [format: :keyword, line: 1], [:one]},
|
||||
{:__block__, [line: 1], [:two]}}
|
||||
]
|
||||
]
|
||||
}
|
||||
|
||||
assert string_to_quoted.("[1]") ==
|
||||
{:__block__, [closing: [line: 1], line: 1], args}
|
||||
|
||||
args = [
|
||||
{{:__block__, [delimiter: ":", line: 1], [:ok]},
|
||||
{:__block__, [delimiter: ":", line: 1], [:test]}}
|
||||
]
|
||||
|
||||
assert string_to_quoted.("{:ok, :test}") ==
|
||||
{:__block__, [closing: [line: 1], line: 1], args}
|
||||
{:__block__, [closing: [line: 1], line: 1],
|
||||
[[{:__block__, [token: "1", line: 1], [1]}]]}
|
||||
|
||||
assert string_to_quoted.(~s("""\nhello\n""")) ==
|
||||
{:__block__, [delimiter: ~s["""], line: 1], ["hello\n"]}
|
||||
@@ -359,16 +361,15 @@ defmodule CodeTest do
|
||||
assert string_to_quoted.("'''\nhello\n'''") ==
|
||||
{:__block__, [delimiter: ~s['''], line: 1], ['hello\n']}
|
||||
|
||||
args = [
|
||||
{:->, [line: 1],
|
||||
[
|
||||
[{:__block__, [token: "1", line: 1, closing: [line: 1], line: 1], [1]}],
|
||||
{:__block__, [delimiter: "\"", line: 1], ["hello"]}
|
||||
]}
|
||||
]
|
||||
|
||||
assert string_to_quoted.(~s[fn (1) -> "hello" end]) ==
|
||||
{:fn, [closing: [line: 1], line: 1], args}
|
||||
{:fn, [closing: [line: 1], line: 1],
|
||||
[
|
||||
{:->, [line: 1],
|
||||
[
|
||||
[{:__block__, [token: "1", line: 1, closing: [line: 1], line: 1], [1]}],
|
||||
{:__block__, [delimiter: "\"", line: 1], ["hello"]}
|
||||
]}
|
||||
]}
|
||||
end
|
||||
|
||||
test "raises on bad literal" do
|
||||
|
||||
@@ -20,6 +20,7 @@ defmodule Config.ProviderTest do
|
||||
|
||||
on_exit(fn ->
|
||||
Application.delete_env(@config_app, :config_providers)
|
||||
Application.delete_env(@config_app, :config_providers_booted)
|
||||
System.delete_env(@env_var)
|
||||
end)
|
||||
end
|
||||
@@ -79,7 +80,7 @@ defmodule Config.ProviderTest do
|
||||
init_and_assert_boot()
|
||||
config = consult(@sys_config)
|
||||
assert config[:my_app] == [key: :value]
|
||||
assert config[@config_app] == [config_providers: :booted]
|
||||
assert config[@config_app] == [config_providers_booted: {:booted, nil}]
|
||||
end
|
||||
|
||||
@tag sys_config: [my_app: [encoding: {:"£", "£", '£'}]]
|
||||
@@ -113,6 +114,18 @@ defmodule Config.ProviderTest do
|
||||
refute File.exists?(@sys_config)
|
||||
end
|
||||
|
||||
test "returns :booted if already booted and runs validate_compile_env" do
|
||||
init_and_assert_boot(
|
||||
prune_after_boot: true,
|
||||
validate_compile_env: [{:elixir, [:unknown], {:ok, :value}}]
|
||||
)
|
||||
|
||||
Application.put_all_env(Keyword.take(consult(@sys_config), [@config_app]))
|
||||
|
||||
assert capture_abort(fn -> boot() end) =~
|
||||
"the application :elixir has a different value set for key :unknown"
|
||||
end
|
||||
|
||||
test "raises if booting twice in a row" do
|
||||
init_and_assert_boot()
|
||||
|
||||
|
||||
@@ -278,14 +278,17 @@ defmodule Kernel.ParallelCompilerTest do
|
||||
"""
|
||||
)
|
||||
|
||||
output = tmp_path("not_to_be_used")
|
||||
|
||||
try do
|
||||
Code.compiler_options(warnings_as_errors: true)
|
||||
|
||||
msg =
|
||||
capture_io(:stderr, fn ->
|
||||
assert {:error, [error], []} = Kernel.ParallelCompiler.compile([fixture])
|
||||
msg = "this clause cannot match because a previous clause at line 2 always matches"
|
||||
assert error == {fixture, 3, msg}
|
||||
assert {:error, [error], []} =
|
||||
Kernel.ParallelCompiler.compile_to_path([fixture], output)
|
||||
|
||||
assert {^fixture, 3, "this clause " <> _} = error
|
||||
end)
|
||||
|
||||
assert msg =~
|
||||
@@ -294,6 +297,8 @@ defmodule Kernel.ParallelCompilerTest do
|
||||
Code.compiler_options(warnings_as_errors: warnings_as_errors)
|
||||
purge([WarningsSample])
|
||||
end
|
||||
|
||||
refute File.exists?(output)
|
||||
end
|
||||
|
||||
test "does not use incorrect line number when error originates in another file" do
|
||||
@@ -428,10 +433,7 @@ defmodule Kernel.ParallelCompilerTest do
|
||||
capture_io(:stderr, fn ->
|
||||
assert {:error, [error], []} = Kernel.ParallelCompiler.require([fixture])
|
||||
|
||||
message =
|
||||
"this clause cannot match because a previous clause at line 2 always matches"
|
||||
|
||||
assert error == {fixture, 3, message}
|
||||
assert {^fixture, 3, "this clause " <> _} = error
|
||||
end)
|
||||
|
||||
assert msg =~
|
||||
|
||||
@@ -25,11 +25,6 @@ defmodule Kernel.QuoteTest do
|
||||
test "quote line var" do
|
||||
line = __ENV__.line
|
||||
assert quote(line: line, do: bar(1, 2, 3)) == {:bar, [line: line], [1, 2, 3]}
|
||||
|
||||
assert_raise ArgumentError, fn ->
|
||||
line = "oops"
|
||||
quote(line: line, do: bar(1, 2, 3))
|
||||
end
|
||||
end
|
||||
|
||||
test "quote context var" do
|
||||
|
||||
@@ -66,6 +66,7 @@ bar) in ["foo\\\nbar", "foo\\\r\nbar"]
|
||||
|
||||
test "sigil w" do
|
||||
assert ~w() == []
|
||||
assert ~w([ , ]) == ["[", ",", "]"]
|
||||
assert ~w(foo bar baz) == ["foo", "bar", "baz"]
|
||||
assert ~w(foo #{:bar} baz) == ["foo", "bar", "baz"]
|
||||
|
||||
@@ -100,6 +101,7 @@ bar) in ["foo\\\nbar", "foo\\\r\nbar"]
|
||||
|
||||
test "sigil W" do
|
||||
assert ~W() == []
|
||||
assert ~W([ , ]) == ["[", ",", "]"]
|
||||
assert ~W(foo #{bar} baz) == ["foo", "\#{bar}", "baz"]
|
||||
|
||||
assert ~W(foo\ bar) == ["foo\\", "bar"]
|
||||
|
||||
@@ -661,24 +661,6 @@ defmodule Kernel.WarningTest do
|
||||
purge(Sample)
|
||||
end
|
||||
|
||||
test "previous clause always matches" do
|
||||
assert capture_err(fn ->
|
||||
Code.eval_string("""
|
||||
defmodule Sample do
|
||||
def binary_cond do
|
||||
v = "bc"
|
||||
cond do
|
||||
is_binary(v) -> :bin
|
||||
true -> :ok
|
||||
end
|
||||
end
|
||||
end
|
||||
""")
|
||||
end) =~ "this clause cannot match because a previous clause at line 5 always matches"
|
||||
after
|
||||
purge(Sample)
|
||||
end
|
||||
|
||||
test "late function heads" do
|
||||
assert capture_err(fn ->
|
||||
Code.eval_string("""
|
||||
@@ -740,7 +722,8 @@ defmodule Kernel.WarningTest do
|
||||
def hello, do: nil
|
||||
end
|
||||
""")
|
||||
end) =~ "this clause cannot match because a previous clause at line 2 always matches"
|
||||
end) =~
|
||||
~r"this clause( for hello/0)? cannot match because a previous clause at line 2 always matches"
|
||||
after
|
||||
purge(Sample)
|
||||
end
|
||||
@@ -760,7 +743,8 @@ defmodule Kernel.WarningTest do
|
||||
use Sample
|
||||
end
|
||||
""")
|
||||
end) =~ "this clause cannot match because a previous clause at line 10 always matches"
|
||||
end) =~
|
||||
~r"this clause( for hello/0)? cannot match because a previous clause at line 10 always matches"
|
||||
after
|
||||
purge(Sample)
|
||||
purge(UseSample)
|
||||
@@ -1628,7 +1612,8 @@ defmodule Kernel.WarningTest do
|
||||
defguard foo(baz) when baz == :baz
|
||||
end
|
||||
""")
|
||||
end) =~ "this clause cannot match because a previous clause at line 2 always matches"
|
||||
end) =~
|
||||
~r"this clause( for foo/1)? cannot match because a previous clause at line 2 always matches"
|
||||
after
|
||||
purge(Sample)
|
||||
end
|
||||
@@ -1641,7 +1626,8 @@ defmodule Kernel.WarningTest do
|
||||
defmacro foo(bar), do: bar == :bar
|
||||
end
|
||||
""")
|
||||
end) =~ "this clause cannot match because a previous clause at line 2 always matches"
|
||||
end) =~
|
||||
~r"this clause( for foo/1)? cannot match because a previous clause at line 2 always matches"
|
||||
after
|
||||
purge(Sample)
|
||||
end
|
||||
|
||||
@@ -73,6 +73,16 @@ defmodule Kernel.WithTest do
|
||||
assert state == 1
|
||||
end
|
||||
|
||||
test "with extra guards" do
|
||||
var =
|
||||
with %_{} = a <- struct(URI),
|
||||
%_{} <- a do
|
||||
:ok
|
||||
end
|
||||
|
||||
assert var == :ok
|
||||
end
|
||||
|
||||
test "errors in with" do
|
||||
assert_raise RuntimeError, fn ->
|
||||
with({:ok, res} <- oops(), do: res)
|
||||
|
||||
@@ -344,6 +344,7 @@ defmodule MacroTest do
|
||||
assert Macro.to_string(quote(do: ~R"\n123")) == ~S/~R"\n123"/
|
||||
|
||||
assert Macro.to_string(quote(do: ~S["'(123)'"])) == ~S/~S["'(123)'"]/
|
||||
assert Macro.to_string(quote(do: ~s"#{"foo"}")) == ~S/~s"#{"foo"}"/
|
||||
|
||||
assert Macro.to_string(
|
||||
quote do
|
||||
@@ -704,6 +705,12 @@ defmodule MacroTest do
|
||||
assert Macro.to_string(quote(do: 'abc')) == "'abc'"
|
||||
end
|
||||
|
||||
test "string" do
|
||||
assert Macro.to_string(quote(do: "")) == ~S/""/
|
||||
assert Macro.to_string(quote(do: "abc")) == ~S/"abc"/
|
||||
assert Macro.to_string(quote(do: "#{"abc"}")) == ~S/"#{"abc"}"/
|
||||
end
|
||||
|
||||
test "last arg keyword list" do
|
||||
assert Macro.to_string(quote(do: foo([]))) == "foo([])"
|
||||
assert Macro.to_string(quote(do: foo(x: y))) == "foo(x: y)"
|
||||
|
||||
@@ -161,6 +161,11 @@ defmodule Module.TypesTest do
|
||||
quoted_head([x], [is_tuple(is_atom(x))])
|
||||
end
|
||||
|
||||
test "erlang-only guards" do
|
||||
assert quoted_head([x], [:erlang.size(x)]) ==
|
||||
{:ok, [{:union, [:binary, :tuple]}]}
|
||||
end
|
||||
|
||||
test "failing guard functions" do
|
||||
assert quoted_head([x], [length([])]) == {:ok, [{:var, 0}]}
|
||||
|
||||
|
||||
@@ -499,13 +499,17 @@ defmodule StreamTest do
|
||||
|
||||
test "interval/1" do
|
||||
stream = Stream.interval(10)
|
||||
|
||||
{time_us, value} = :timer.tc(fn -> Enum.take(stream, 5) end)
|
||||
|
||||
assert value == [0, 1, 2, 3, 4]
|
||||
assert time_us >= 50000
|
||||
end
|
||||
|
||||
test "interval/1 with infinity" do
|
||||
stream = Stream.interval(:infinity)
|
||||
spawn(Stream, :run, [stream])
|
||||
end
|
||||
|
||||
test "into/2 and run/1" do
|
||||
Process.put(:stream_cont, [])
|
||||
Process.put(:stream_done, false)
|
||||
@@ -1119,6 +1123,11 @@ defmodule StreamTest do
|
||||
assert time_us >= 5000
|
||||
end
|
||||
|
||||
test "timer/1 with infinity" do
|
||||
stream = Stream.timer(:infinity)
|
||||
spawn(Stream, :run, [stream])
|
||||
end
|
||||
|
||||
test "unfold/2" do
|
||||
stream = Stream.unfold(10, fn x -> if x > 0, do: {x, x - 1} end)
|
||||
assert Enum.take(stream, 5) == [10, 9, 8, 7, 6]
|
||||
|
||||
@@ -584,6 +584,21 @@ defmodule TaskTest do
|
||||
[ok: :ok] = Task.async_stream([1], fn _ -> :ok end, timeout: :infinity) |> Enum.to_list()
|
||||
end
|
||||
|
||||
test "streams with fake down messages on the inbox" do
|
||||
parent = self()
|
||||
|
||||
assert Task.async_stream([:ok], fn :ok ->
|
||||
{:links, links} = Process.info(self(), :links)
|
||||
|
||||
for link <- links do
|
||||
send(link, {:DOWN, make_ref(), :process, parent, :oops})
|
||||
end
|
||||
|
||||
:ok
|
||||
end)
|
||||
|> Enum.to_list() == [ok: :ok]
|
||||
end
|
||||
|
||||
test "with $callers" do
|
||||
grandparent = self()
|
||||
|
||||
|
||||
@@ -338,7 +338,7 @@ defmodule ExUnit.Formatter do
|
||||
{:match, pins},
|
||||
formatter,
|
||||
padding_size + 5,
|
||||
width - 5
|
||||
width
|
||||
)
|
||||
|
||||
[
|
||||
|
||||
@@ -375,6 +375,7 @@ defmodule ExUnit.AssertionsTest do
|
||||
|
||||
"assert_received({:status, ^status, ^status})" = Macro.to_string(error.expr)
|
||||
"{:status, ^status, ^status}" = Macro.to_string(error.left)
|
||||
"\n\nAssertion failed" <> _ = Exception.message(error)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -32,14 +32,6 @@ defmodule Logger.Config do
|
||||
Application.fetch_env!(:logger, :discard_threshold_periodic_check)}
|
||||
end
|
||||
|
||||
def handle_event({_type, gl, _msg} = event, state) when node(gl) != node() do
|
||||
# Cross node messages are always async which also
|
||||
# means this handler won't crash in case Logger
|
||||
# is not installed in the other node.
|
||||
:gen_event.notify({Logger, node(gl)}, event)
|
||||
{:ok, update_counter(state, false)}
|
||||
end
|
||||
|
||||
def handle_event(_event, state) do
|
||||
{:ok, update_counter(state, false)}
|
||||
end
|
||||
|
||||
@@ -168,22 +168,42 @@ defmodule Logger.Formatter do
|
||||
defp levelpad(:warn), do: " "
|
||||
defp levelpad(:error), do: ""
|
||||
|
||||
defp metadata([{:report_cb, _} | metadata]), do: metadata(metadata)
|
||||
defp metadata([{:time, _} | metadata]), do: metadata(metadata)
|
||||
defp metadata([{:gl, _} | metadata]), do: metadata(metadata)
|
||||
defp metadata([{:crash_reason, _} | metadata]), do: metadata(metadata)
|
||||
defp metadata([{:ancestors, _} | metadata]), do: metadata(metadata)
|
||||
defp metadata([{:callers, _} | metadata]), do: metadata(metadata)
|
||||
defp metadata([{_, nil} | metadata]), do: metadata(metadata)
|
||||
|
||||
defp metadata([{key, value} | metadata]) do
|
||||
[to_string(key), ?=, metadata(key, value), ?\s | metadata(metadata)]
|
||||
if formatted = metadata(key, value) do
|
||||
[to_string(key), ?=, formatted, ?\s | metadata(metadata)]
|
||||
else
|
||||
metadata(metadata)
|
||||
end
|
||||
end
|
||||
|
||||
defp metadata([]) do
|
||||
[]
|
||||
end
|
||||
|
||||
defp metadata(:time, _), do: nil
|
||||
defp metadata(:gl, _), do: nil
|
||||
|
||||
defp metadata(_, nil), do: nil
|
||||
defp metadata(_, string) when is_binary(string), do: string
|
||||
defp metadata(_, integer) when is_integer(integer), do: Integer.to_string(integer)
|
||||
defp metadata(_, float) when is_float(float), do: Float.to_string(float)
|
||||
defp metadata(_, pid) when is_pid(pid), do: :erlang.pid_to_list(pid)
|
||||
|
||||
defp metadata(_, atom) when is_atom(atom) do
|
||||
case Atom.to_string(atom) do
|
||||
"Elixir." <> rest -> rest
|
||||
"nil" -> ""
|
||||
binary -> binary
|
||||
end
|
||||
end
|
||||
|
||||
defp metadata(_, ref) when is_reference(ref) do
|
||||
'#Ref' ++ rest = :erlang.ref_to_list(ref)
|
||||
rest
|
||||
end
|
||||
|
||||
defp metadata(:file, file) when is_list(file), do: file
|
||||
|
||||
defp metadata(:domain, [head | tail]) when is_atom(head) do
|
||||
Enum.map_intersperse([head | tail], ?., &Atom.to_string/1)
|
||||
end
|
||||
@@ -198,22 +218,5 @@ defmodule Logger.Formatter do
|
||||
Exception.format_mfa(mod, fun, arity)
|
||||
end
|
||||
|
||||
defp metadata(_, pid) when is_pid(pid) do
|
||||
:erlang.pid_to_list(pid)
|
||||
end
|
||||
|
||||
defp metadata(_, ref) when is_reference(ref) do
|
||||
'#Ref' ++ rest = :erlang.ref_to_list(ref)
|
||||
rest
|
||||
end
|
||||
|
||||
defp metadata(_, atom) when is_atom(atom) do
|
||||
case Atom.to_string(atom) do
|
||||
"Elixir." <> rest -> rest
|
||||
"nil" -> ""
|
||||
binary -> binary
|
||||
end
|
||||
end
|
||||
|
||||
defp metadata(_, other), do: to_string(other)
|
||||
defp metadata(_, _), do: nil
|
||||
end
|
||||
|
||||
@@ -230,7 +230,7 @@ defmodule Logger.Handler do
|
||||
translate_fallback(:format, callback.(data), meta, truncate)
|
||||
end
|
||||
|
||||
defp translate_fallback(:report, {:logger, data}, %{report_cb: callback} = meta, truncate)
|
||||
defp translate_fallback(:report, {:logger, data}, %{report_cb: callback}, _truncate)
|
||||
when is_function(callback, 2) do
|
||||
translator_opts =
|
||||
struct(Inspect.Opts, Application.fetch_env!(:logger, :translator_inspect_opts))
|
||||
@@ -241,7 +241,7 @@ defmodule Logger.Handler do
|
||||
single_line: false
|
||||
}
|
||||
|
||||
translate_fallback(:format, callback.(data, opts), meta, truncate)
|
||||
callback.(data, opts)
|
||||
end
|
||||
|
||||
defp translate_fallback(:format, {format, args}, _meta, truncate) do
|
||||
|
||||
@@ -81,7 +81,7 @@ defmodule Logger.Utils do
|
||||
end
|
||||
|
||||
defp fix_binary(<<h, t::binary>>, acc) do
|
||||
fix_binary(t, <<h, acc::binary>>)
|
||||
fix_binary(t, <<acc::binary, h>>)
|
||||
end
|
||||
|
||||
defp fix_binary(<<>>, _acc) do
|
||||
|
||||
@@ -77,9 +77,9 @@ defmodule Logger.FormatterTest do
|
||||
"2014-12-30 12:06:30.100"
|
||||
end
|
||||
|
||||
test "format discards callers/ancestors/crash_reason" do
|
||||
test "format discards unknown formats" do
|
||||
compiled = compile("$metadata $message")
|
||||
metadata = [callers: :discard, ancestors: :discard, crash_reason: :discard, foo: :bar]
|
||||
metadata = [ancestors: [self()], crash_reason: {:some, :tuple}, foo: :bar]
|
||||
|
||||
assert format(compiled, :error, "hello", nil, metadata) ==
|
||||
[["foo", 61, "bar", 32], " ", "hello"]
|
||||
|
||||
@@ -147,6 +147,6 @@ defmodule Logger.HandlerTest do
|
||||
defp format_report(report, opts) do
|
||||
send(self(), {:format, report, opts})
|
||||
|
||||
{'~p', [report]}
|
||||
inspect(report)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -23,6 +23,7 @@ defmodule Logger.UtilsTest do
|
||||
assert truncate("olá", 4) == "olá"
|
||||
assert truncate("ááááá:", 10) == ["ááááá", " (truncated)"]
|
||||
assert truncate("áááááá:", 10) == ["ááááá", " (truncated)"]
|
||||
assert truncate("𠜎𠜱𠝹𠱓", 15) == ["𠜎𠜱𠝹", " (truncated)"]
|
||||
|
||||
# Charlists
|
||||
assert truncate('olá', 2) == ['olá', " (truncated)"]
|
||||
|
||||
+18
-18
@@ -425,13 +425,29 @@ defmodule Mix.Release do
|
||||
opts = [
|
||||
extra_config: initial_config,
|
||||
prune_after_boot: prune_after_boot,
|
||||
reboot_after_config: reboot?
|
||||
reboot_after_config: reboot?,
|
||||
validate_compile_env: validate_compile_env(release)
|
||||
]
|
||||
|
||||
init = Config.Provider.init(release.config_providers, config_path, opts)
|
||||
{Config.Reader.merge(sys_config, [elixir: [config_providers: init]] ++ extra_config), reboot?}
|
||||
end
|
||||
|
||||
defp validate_compile_env(release) do
|
||||
with true <- Keyword.get(release.options, :validate_compile_env, true),
|
||||
[_ | _] = compile_env <- compile_env(release) do
|
||||
compile_env
|
||||
else
|
||||
_ -> false
|
||||
end
|
||||
end
|
||||
|
||||
defp compile_env(release) do
|
||||
for {_, properties} <- release.applications,
|
||||
triplet <- Keyword.get(properties, :compile_env, []),
|
||||
do: triplet
|
||||
end
|
||||
|
||||
defp start_distribution(%{options: opts}) do
|
||||
reboot? = Keyword.get(opts, :reboot_system_after_config, true)
|
||||
early_distribution? = Keyword.get(opts, :start_distribution_during_config, false)
|
||||
@@ -613,8 +629,7 @@ defmodule Mix.Release do
|
||||
&(not match?({:apply, {:application, :start_boot, [:stdlib, _]}}, &1))
|
||||
)
|
||||
|
||||
pre ++
|
||||
[stdlib] ++ config_provider_apply(release) ++ validate_compile_env_apply(release) ++ post
|
||||
pre ++ [stdlib] ++ config_provider_apply(release) ++ post
|
||||
end
|
||||
|
||||
defp config_provider_apply(%{config_providers: []}),
|
||||
@@ -623,21 +638,6 @@ defmodule Mix.Release do
|
||||
defp config_provider_apply(_),
|
||||
do: [{:apply, {Config.Provider, :boot, [:elixir, :config_providers]}}]
|
||||
|
||||
defp validate_compile_env_apply(release) do
|
||||
with true <- Keyword.get(release.options, :validate_compile_env, true),
|
||||
[_ | _] = compile_env <- compile_env(release) do
|
||||
[{:apply, {Config.Provider, :validate_compile_env, [compile_env]}}]
|
||||
else
|
||||
_ -> []
|
||||
end
|
||||
end
|
||||
|
||||
defp compile_env(release) do
|
||||
for {_, properties} <- release.applications,
|
||||
triplet <- Keyword.get(properties, :compile_env, []),
|
||||
do: triplet
|
||||
end
|
||||
|
||||
defp prepend_paths_to_script(instructions, []), do: instructions
|
||||
|
||||
defp prepend_paths_to_script(instructions, prepend_paths) do
|
||||
|
||||
@@ -416,9 +416,9 @@ defmodule Mix.Tasks.Release.Init do
|
||||
goto end
|
||||
)
|
||||
|
||||
!ERLSRV! add !RELEASE_NAME!_!RELEASE_NAME! ^
|
||||
"!ERLSRV!" add "!RELEASE_NAME!_!RELEASE_NAME!" ^
|
||||
-!RELEASE_DISTRIBUTION! "!RELEASE_NODE!" ^
|
||||
-env RELEASE_ROOT=!RELEASE_ROOT! -env RELEASE_NAME=!RELEASE_NAME! -env RELEASE_VSN=!RELEASE_VSN! -env RELEASE_COOKIE=!RELEASE_COOKIE! -env RELEASE_NODE=!RELEASE_NODE! -env RELEASE_VM_ARGS=!RELEASE_VM_ARGS! -env RELEASE_TMP=!RELEASE_TMP! -env RELEASE_SYS_CONFIG=!RELEASE_SYS_CONFIG! ^
|
||||
-env RELEASE_ROOT="!RELEASE_ROOT!" -env RELEASE_NAME="!RELEASE_NAME!" -env RELEASE_VSN="!RELEASE_VSN!" -env RELEASE_COOKIE="!RELEASE_COOKIE!" -env RELEASE_NODE="!RELEASE_NODE!" -env RELEASE_VM_ARGS="!RELEASE_VM_ARGS!" -env RELEASE_TMP="!RELEASE_TMP!" -env RELEASE_SYS_CONFIG="!RELEASE_SYS_CONFIG!" ^
|
||||
-args "-setcookie !RELEASE_COOKIE! -config !RELEASE_SYS_CONFIG! -mode !RELEASE_MODE! -boot !REL_VSN_DIR!\start -boot_var RELEASE_LIB !RELEASE_ROOT!\lib -args_file !REL_VSN_DIR!\vm.args"
|
||||
|
||||
if %ERRORLEVEL% EQU 0 (
|
||||
|
||||
@@ -514,7 +514,18 @@ defmodule Mix.Tasks.ReleaseTest do
|
||||
in_fixture("release_test", fn ->
|
||||
config = [releases: [permanent1: [include_erts: false]]]
|
||||
|
||||
# We write the compile env to guarantee rpc still works
|
||||
File.write!("lib/compile_env.ex", """
|
||||
_ = Application.compile_env(:release_test, :static)
|
||||
""")
|
||||
|
||||
Mix.Project.in_project(:release_test, ".", config, fn _ ->
|
||||
Mix.Task.run("loadconfig", [])
|
||||
|
||||
File.write!("config/releases.exs", """
|
||||
import Config
|
||||
""")
|
||||
|
||||
root = Path.absname("_build/dev/rel/permanent1")
|
||||
Mix.Task.run("release")
|
||||
script = Path.join(root, "bin/permanent1")
|
||||
|
||||
Reference in New Issue
Block a user