Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
98485daab0 | ||
|
|
59f47949a9 | ||
|
|
fca5f1cf73 | ||
|
|
153ec434de | ||
|
|
061a989935 | ||
|
|
21f10f7296 | ||
|
|
336452f81b | ||
|
|
511a51ba89 | ||
|
|
7e4f9c5647 | ||
|
|
b37ecb0f8e | ||
|
|
ac947a5d1d | ||
|
|
82a511aafe | ||
|
|
7c2e65f418 | ||
|
|
3d04cb0aa0 | ||
|
|
8fa9d728f1 | ||
|
|
2eee2fc6a4 | ||
|
|
b1a10f2bb8 | ||
|
|
dcb4f22af2 | ||
|
|
6ba4cf00ab | ||
|
|
3eeded089d | ||
|
|
f4c827b68c | ||
|
|
9ef89241ed | ||
|
|
49351886cf | ||
|
|
9c7edf3117 | ||
|
|
32825ebaa0 | ||
|
|
5e715aa16e | ||
|
|
9b54efb9dd | ||
|
|
82182818cb | ||
|
|
53f16f5a82 | ||
|
|
d86751501f | ||
|
|
906df8c981 | ||
|
|
04717e4c19 | ||
|
|
a3e313b582 | ||
|
|
a4044d898d | ||
|
|
b932a52d55 | ||
|
|
8d269932a6 | ||
|
|
c1c4b1ace9 | ||
|
|
e284007968 | ||
|
|
b6ef2bd656 | ||
|
|
12b0906d61 | ||
|
|
2b59405756 | ||
|
|
4231a62c67 | ||
|
|
35e7c8c38c | ||
|
|
8e4d718b93 | ||
|
|
3ad9193b4b | ||
|
|
d655a2884e | ||
|
|
5529c5fa4e |
+115
-2
@@ -1,6 +1,104 @@
|
||||
# Changelog for Elixir v1.8
|
||||
|
||||
## v1.8.0-dev
|
||||
Elixir v1.8 comes with many improvements at the infrastructure level, improving compilation time, speeding up common patterns, and adding features around introspection of the system.
|
||||
|
||||
## Custom struct inspections
|
||||
|
||||
Elixir now provides a derivable implementation of the `Inspect` protocol. In a nutshell, this means it is really easy to filter data from your data structures whenever they are inspected. For example, imagine you have a user struct with security and privacy sensitive information:
|
||||
|
||||
```elixir
|
||||
defmodule User do
|
||||
defstruct [:id, :name, :age, :email, :encrypted_password]
|
||||
end
|
||||
```
|
||||
|
||||
By default, if you inspect a user via `inspect(user)`, it will include all fields. This can cause fields such as `:email` and `:encrypted_password` to appear in logs, error reports, etc. You could always define a custom implementation of the `Inspect` protocol for such cases but Elixir v1.8 makes it simpler by allowing you to derive the `Inspect` protocol:
|
||||
|
||||
```elixir
|
||||
defmodule User do
|
||||
@derive {Inspect, only: [:id, :name, :age]}
|
||||
defstruct [:id, :name, :age, :email, :encrypted_password]
|
||||
end
|
||||
```
|
||||
|
||||
Now all user structs will be printed with all remaining fields collapsed:
|
||||
|
||||
#User<id: 1, name: "Jane", age: 33, ...>
|
||||
|
||||
You can also pass `@derive {Inspect, except: [...]}` in case you want to keep all fields by default and exclude only some.
|
||||
|
||||
## Time zone database support
|
||||
|
||||
In Elixir v1.3, Elixir added four types, known as Calendar types, to work with dates and times: `Time`, `Date`, `NaiveDateTime` (without time zone) and `DateTime` (with time zone). Over the last releases we have added many enhancements to the Calendar types but the `DateTime` module always evolved at a slower pace since Elixir did not provide support for a time zone database.
|
||||
|
||||
Elixir v1.8 now defines a `Calendar.TimeZoneDatabase` behaviour, allowing developers to bring in their own time zone databases. By defining an explicit contract for time zone behaviours, Elixir can now extend the `DateTime` API, adding functions such as `DateTime.shift_zone/3`. By default, Elixir ships with a time zone database called `Calendar.UTCOnlyTimeZoneDatabase` that only handles UTC.
|
||||
|
||||
Other Calendar related improvements include the addition of `Date.day_of_year/1`, `Date.quarter_of_year/1`, `Date.year_of_era/1`, and `Date.day_of_era/1`.
|
||||
|
||||
## Faster compilation and other performance improvements
|
||||
|
||||
Due to improvements to the compiler made over the last year, Elixir v1.8 should compile code about 5% faster on average. This is yet another release where we have been able to reduce compilation times and provide a more joyful development experience to everyone.
|
||||
|
||||
The compiler also emits more efficient code for range checks in guards (such as `x in y..z`), for charlists with interpolation (such as `'foo #{bar} baz'`), and when working with records via the `Record` module.
|
||||
|
||||
Finally, EEx templates got their own share of optimizations, emitting more compact code that runs faster.
|
||||
|
||||
## Improved instrumentation and ownership with `$callers`
|
||||
|
||||
The `Task` module is one of the most common ways to spawn light-weight processes to perform work concurrently. Whenever you spawn a new process, Elixir annotates the parent of that process through the `$ancestors` key. This information can be used by instrumentation tools to track the relationship between events occurring within multiple processes. However, many times, tracking only the `$ancestors` is not enough.
|
||||
|
||||
For example, we recommend developers to always start tasks under a supervisor. This provides more visibility and allows us to control how those tasks are terminated when a node shuts down. In your code, this can be done by invoking something like: `Task.Supervisor.start_child(MySupervisor, task_specification)`. This means that, although your code is the one who invokes the task, the actual parent of the task would be the supervisor, as the supervisor is the one spawning it. We would list the supervisor as one of the `$ancestors` for the task, but the relationship between your code and the task is lost.
|
||||
|
||||
In Elixir v1.8, we now track the relationship between your code and the task via the `$callers` key in the process dictionary, which aligns well with the existing `$ancestors` key. Therefore, assuming the `Task.Supervisor` call above, we have:
|
||||
|
||||
[your code] -- calls --> [supervisor] ---- spawns --> [task]
|
||||
|
||||
which means we store the following relationships:
|
||||
|
||||
[your code] [supervisor] <-- ancestor -- [task]
|
||||
^ |
|
||||
|--------------------- caller ---------------------|
|
||||
|
||||
When a task is spawned directly from your code, without a supervisor, then the process running your code will be listed under both `$ancestors` and `$callers`.
|
||||
|
||||
This small feature is very powerful. It allows instrumentation and monitoring tools to better track and relate the events happening in your system. This feature can also be used by tools like the "Ecto Sandbox". The "Ecto Sandbox" allows developers to run tests concurrently against the database, by using transactions and an ownership mechanism where each process explicitly gets a connection assigned to it. Without `$callers`, every time you spawned a task that queries the database, the task would not know its caller, and therefore it would be unable to know which connection was assigned to it. This often meant features that relies on tasks could not be tested concurrently. With `$callers`, figuring out this relationship is trivial and you have more tests using the full power of your machine.
|
||||
|
||||
## v1.8.2 (2019-05-11)
|
||||
|
||||
### 1. Bug fixes
|
||||
|
||||
#### EEx
|
||||
|
||||
* [EEx] Raise readable error message on bad EEx state
|
||||
|
||||
#### Elixir
|
||||
|
||||
* [Protocol] Ensure `:debug_info` is kept in protocols
|
||||
|
||||
#### Logger
|
||||
|
||||
* [Logger] Make sure Logger v1.8 does not get stuck in discard mode
|
||||
* [Logger.Translator] Translate remote process crash in Logger
|
||||
|
||||
## v1.8.1 (2019-01-30)
|
||||
|
||||
### 1. Bug fixes
|
||||
|
||||
#### Elixir
|
||||
|
||||
* [Float] Fix rounding for subnormal floats
|
||||
|
||||
#### IEx
|
||||
|
||||
* [IEx] Fix `IEx.pry` crash when IEx isn't running
|
||||
* [IEx.CLI] Add IEx warning when using `--remsh` with dumb terminal
|
||||
* [IEx.Helpers] Sort results by arity on `h` helper
|
||||
|
||||
#### Mix
|
||||
|
||||
* [mix compile] Do not include optional dependencies in extra applications as it is incompatible with shared deps in umbrellas
|
||||
|
||||
## v1.8.0 (2019-01-14)
|
||||
|
||||
### 1. Enhancements
|
||||
|
||||
@@ -23,12 +121,16 @@
|
||||
* [Kernel] Add `:delegate_to` `@doc` metadata tag when using `defdelegate`
|
||||
* [Kernel] Improve compile-time building of ranges via the `..` operator
|
||||
* [Kernel] Compile charlist interpolation more efficiently
|
||||
* [Kernel] Add `floor/1` and `ceil/1` guards
|
||||
* [Kernel.SpecialForms] Add `:reduce` option to `for` comprehensions
|
||||
* [List] Add `List.myers_difference/3` and `List.improper?/1`
|
||||
* [Macro] Add `Macro.struct!/2` for proper struct resolution during compile time
|
||||
* [Map] Optimize and merge nested maps `put` and `merge` operations
|
||||
* [Range] Add `Range.disjoint?/2`
|
||||
* [Record] Reduce memory allocation when updating multiple fields in a record
|
||||
* [Registry] Allow associating a value on `:via` tuple
|
||||
* [String] Add `String.bag_distance/2`
|
||||
* [Task] Add `$callers` tracking to `Task` - this makes it easier to find which process spawned a task and use it for tracking ownership and monitoring
|
||||
|
||||
#### ExUnit
|
||||
|
||||
@@ -58,12 +160,18 @@
|
||||
* [Calendar] Allow printing dates with more than 9999 years
|
||||
* [Exception] Exclude deprecated functions in "did you mean?" hints
|
||||
* [Float] Handle subnormal floats in `Float.ratio/1`
|
||||
* [Kernel] Remove `Guard test tuple_size(...) can never succeed` dialyzer warning on try
|
||||
* [Kernel] Remove `Guard test tuple_size(...) can never succeed` Dialyzer warning on `try`
|
||||
* [Kernel] Expand operands in `size*unit` bitstring modifier instead of expecting `size` and `unit` to be literal integers
|
||||
* [Kernel] Do not deadlock on circular struct dependencies in typespecs
|
||||
* [Kernel] Raise proper error message when passing flags to the Erlang compiler that Elixir cannot handle
|
||||
* [Kernel] Do not leak variables in `cond` clauses with a single matching at compile-time clause
|
||||
* [NaiveDateTime] Do not accept leap seconds in builder and parsing functions
|
||||
* [String] Fix ZWJ handling in Unicode grapheme clusters
|
||||
* [StringIO] Handle non-printable args in StringIO gracefully
|
||||
|
||||
#### IEx
|
||||
|
||||
* [IEx.Helpers] Use typespec info (instead of docs chunk) and properly format callbacks in `b/1`
|
||||
|
||||
#### Logger
|
||||
|
||||
@@ -71,6 +179,8 @@
|
||||
|
||||
#### Mix
|
||||
|
||||
* [mix compile] Ensure changes in deps propagate to all umbrella children - this fix a long standing issue where updating a dependency would not recompile all projects accordingly, requiring a complete removal of `_build`
|
||||
* [mix compile] Avoid time drift when checking and updating compiler manifest files
|
||||
* [mix compile.app] Respect the `:only` option between umbrella siblings
|
||||
* [mix compile.protocols] Reconsolidate protocols if local dependencies are stale
|
||||
* [mix deps] Properly mark dependencies with different `:system_env` as diverged
|
||||
@@ -78,6 +188,8 @@
|
||||
|
||||
### 3. Soft-deprecations (no warnings emitted)
|
||||
|
||||
None.
|
||||
|
||||
### 4. Hard-deprecations
|
||||
|
||||
#### Elixir
|
||||
@@ -85,6 +197,7 @@
|
||||
* [Enum] Passing a non-empty list to `Enum.into/2` was inconsistent with maps and is deprecated in favor of `Kernel.++/2` or `Keyword.merge/2`
|
||||
* [Inspect.Algebra] `surround/3` is deprecated in favor of `Inspect.Algebra.concat/2` and `Inspect.Algebra.nest/2`
|
||||
* [Inspect.Algebra] `surround_many/6` is deprecated in favor of `container_doc/6`
|
||||
* [Kernel] Using `@since` will now emit a unused attribute warning. Use `@doc since: "1.7.2"` instead
|
||||
* [Kernel] Passing a non-empty list as `:into` in `for` comprehensions was inconsistent with maps and is deprecated in favor of `Kernel.++/2` or `Keyword.merge/2`
|
||||
* [Kernel.ParallelCompiler] `files/2` is deprecated in favor of `compile/2`
|
||||
* [Kernel.ParallelCompiler] `files_to_path/2` is deprecated in favor of `compile_to_path/2`
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
PREFIX ?= /usr/local
|
||||
SHARE_PREFIX ?= $(PREFIX)/share
|
||||
MAN_PREFIX ?= $(SHARE_PREFIX)/man
|
||||
CANONICAL := master/ # master/ or vMAJOR.MINOR/
|
||||
CANONICAL := v1.8/ # master/ or vMAJOR.MINOR/
|
||||
ELIXIRC := bin/elixirc --verbose --ignore-module-conflict --warnings-as-errors
|
||||
ERLC := erlc -I lib/elixir/include +warnings_as_errors
|
||||
ERL := erl -I lib/elixir/include -noshell -pa lib/elixir/ebin
|
||||
|
||||
+2
-2
@@ -20,9 +20,9 @@
|
||||
|
||||
9. Publish new zips with `make zips`, upload `Precompiled.zip` and `Docs.zip` to GitHub Releases, and include SHAs+CHANGELOG
|
||||
|
||||
10. Add the release to `elixir.csv` and `_data/elixir-versions.yml` files in `elixir-lang/elixir-lang.github.com`
|
||||
10. Add the release to `elixir.csv` (all releases) and `_data/elixir-versions.yml` (except for RCs) files in `elixir-lang/elixir-lang.github.com`
|
||||
|
||||
11. Send an e-mail to elixir-lang-ann@googlegroups.com with title "Elixir vVERSION released". The body should be a link to the Release page on GitHub. If it is a security release, prefix the title with the `[security]` tag
|
||||
11. Send an e-mail to elixir-lang-ann@googlegroups.com with title "Elixir vVERSION released". The body should be a link to the Release page on GitHub and the checksums. If it is a security release, prefix the title with the `[security]` tag
|
||||
|
||||
## Creating a new vMAJOR.MINOR branch
|
||||
|
||||
|
||||
@@ -158,6 +158,7 @@ defmodule EEx.Engine do
|
||||
|
||||
@doc false
|
||||
def handle_begin(state) do
|
||||
check_state!(state)
|
||||
%{state | binary: [], dynamic: []}
|
||||
end
|
||||
|
||||
@@ -168,6 +169,7 @@ defmodule EEx.Engine do
|
||||
|
||||
@doc false
|
||||
def handle_body(state) do
|
||||
check_state!(state)
|
||||
%{binary: binary, dynamic: dynamic} = state
|
||||
binary = {:<<>>, [], Enum.reverse(binary)}
|
||||
dynamic = [binary | dynamic]
|
||||
@@ -207,4 +209,11 @@ defmodule EEx.Engine do
|
||||
raise EEx.SyntaxError,
|
||||
"unsupported EEx syntax <%#{marker} %> (the syntax is valid but not supported by the current EEx engine)"
|
||||
end
|
||||
|
||||
defp check_state!(%{binary: _, dynamic: _, vars_count: _}), do: :ok
|
||||
|
||||
defp check_state!(state) do
|
||||
raise "unexpected EEx.Engine state: #{inspect(state)}. " <>
|
||||
"This typically means a bug or an outdated EEx.Engine or tool"
|
||||
end
|
||||
end
|
||||
|
||||
+1
-1
@@ -12,7 +12,6 @@
|
||||
Atom,
|
||||
Base,
|
||||
Bitwise,
|
||||
Calendar,
|
||||
Date,
|
||||
DateTime,
|
||||
Exception,
|
||||
@@ -53,6 +52,7 @@
|
||||
System
|
||||
],
|
||||
"Calendar": [
|
||||
Calendar,
|
||||
Calendar.ISO,
|
||||
Calendar.TimeZoneDatabase,
|
||||
Calendar.UTCOnlyTimeZoneDatabase
|
||||
|
||||
+45
-6
@@ -16,8 +16,8 @@ defmodule Agent do
|
||||
defmodule Counter do
|
||||
use Agent
|
||||
|
||||
def start_link do
|
||||
Agent.start_link(fn -> 0 end, name: __MODULE__)
|
||||
def start_link(initial_value) do
|
||||
Agent.start_link(fn -> initial_value end, name: __MODULE__)
|
||||
end
|
||||
|
||||
def value do
|
||||
@@ -31,7 +31,7 @@ defmodule Agent do
|
||||
|
||||
Usage would be:
|
||||
|
||||
Counter.start_link
|
||||
Counter.start_link(0)
|
||||
|
||||
Counter.value #=> 0
|
||||
Counter.increment #=> :ok
|
||||
@@ -71,9 +71,48 @@ defmodule Agent do
|
||||
than in the server can lead to race conditions if multiple clients are trying
|
||||
to update the same state to different values.
|
||||
|
||||
Finally note that `use Agent` defines a `child_spec/1` function, allowing the
|
||||
defined module to be put under a supervision tree. The generated
|
||||
`child_spec/1` can be customized with the following options:
|
||||
## How to supervise
|
||||
|
||||
An `Agent` is most commonly started under a supervision tree.
|
||||
When we invoke `use Agent`, it automatically defines a `child_spec/1`
|
||||
function that allows us to start the agent directly under a supervisor.
|
||||
To start an agent under a supervisor with an initial counter of 0,
|
||||
one may do:
|
||||
|
||||
children = [
|
||||
{Counter, 0}
|
||||
]
|
||||
|
||||
Supervisor.start_link(children, strategy: :one_for_all)
|
||||
|
||||
While one could also simply pass the `Counter` as a child to the supervisor,
|
||||
such as:
|
||||
|
||||
children = [
|
||||
Counter # Same as {Counter, []}
|
||||
]
|
||||
|
||||
Supervisor.start_link(children, strategy: :one_for_all)
|
||||
|
||||
The definition above wouldn't work for this particular example,
|
||||
as it would attempt to start the counter with an initial value
|
||||
of an empty list. However, this may be a viable option in your
|
||||
own agents. A common approach is to use a keyword list, as that
|
||||
would allow setting the initial value and giving a name to the
|
||||
counter process, for example:
|
||||
|
||||
def start_link(opts) do
|
||||
{initial_value, opts} = Keyword.pop(opts, :initial_value, 0)
|
||||
Agent.start_link(fn -> initial_value end, opts)
|
||||
end
|
||||
|
||||
and then you can use `Counter`, `{Counter, name: :my_counter}` or
|
||||
even `{Counter, initial_value: 0, name: :my_counter}` as a child
|
||||
specification.
|
||||
|
||||
`use Agent` also accepts a list of options which configures the
|
||||
child specification and therefore how it runs under a supervisor.
|
||||
The generated `child_spec/1` can be customized with the following options:
|
||||
|
||||
* `:id` - the child specification identifier, defaults to the current module
|
||||
* `:start` - how to start the child process (defaults to calling `__MODULE__.start_link/1`)
|
||||
|
||||
@@ -480,9 +480,11 @@ defmodule DateTime do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> {:ok, datetime} = DateTime.now("Europe/Copenhagen", FakeTimeZoneDatabase)
|
||||
iex> {:ok, datetime} = DateTime.now("Etc/UTC")
|
||||
iex> datetime.time_zone
|
||||
"Europe/Copenhagen"
|
||||
"Etc/UTC"
|
||||
iex> DateTime.now("Europe/Copenhagen")
|
||||
{:error, :utc_only_time_zone_database}
|
||||
iex> DateTime.now("not a real time zone name", FakeTimeZoneDatabase)
|
||||
{:error, :time_zone_not_found}
|
||||
|
||||
|
||||
+25
-27
@@ -924,9 +924,7 @@ defmodule UndefinedFunctionError do
|
||||
|
||||
defp message(:"function not exported", module, function, arity) do
|
||||
formatted_fun = Exception.format_mfa(module, function, arity)
|
||||
fun_message = "function #{formatted_fun} is undefined or private"
|
||||
behaviour_hint = behaviour_hint(module, function, arity)
|
||||
{fun_message <> behaviour_hint, true}
|
||||
{"function #{formatted_fun} is undefined or private", true}
|
||||
end
|
||||
|
||||
defp message(reason, module, function, arity) do
|
||||
@@ -934,28 +932,6 @@ defmodule UndefinedFunctionError do
|
||||
{"function #{formatted_fun} is undefined (#{reason})", false}
|
||||
end
|
||||
|
||||
defp behaviour_hint(module, function, arity) do
|
||||
case behaviours_for(module) do
|
||||
[] ->
|
||||
""
|
||||
|
||||
behaviours ->
|
||||
case Enum.find(behaviours, &expects_callback?(&1, function, arity)) do
|
||||
nil -> ""
|
||||
behaviour -> ", but the behaviour #{inspect(behaviour)} expects it to be present"
|
||||
end
|
||||
end
|
||||
rescue
|
||||
# In case the module was removed while we are computing this
|
||||
UndefinedFunctionError ->
|
||||
[]
|
||||
end
|
||||
|
||||
defp expects_callback?(behaviour, function, arity) do
|
||||
callbacks = behaviour.behaviour_info(:callbacks)
|
||||
Enum.member?(callbacks, {function, arity})
|
||||
end
|
||||
|
||||
@impl true
|
||||
def blame(exception, stacktrace) do
|
||||
%{reason: reason, module: module, function: function, arity: arity} = exception
|
||||
@@ -970,7 +946,8 @@ defmodule UndefinedFunctionError do
|
||||
end
|
||||
|
||||
defp hint(module, function, arity, true) do
|
||||
hint_for_loaded_module(module, function, arity, nil)
|
||||
behaviour_hint(module, function, arity) <>
|
||||
hint_for_loaded_module(module, function, arity, nil)
|
||||
end
|
||||
|
||||
defp hint(_module, _function, _arity, _loaded?) do
|
||||
@@ -1021,12 +998,33 @@ defmodule UndefinedFunctionError do
|
||||
[" * ", Code.Identifier.inspect_as_function(fun), ?/, Integer.to_string(arity), ?\n]
|
||||
end
|
||||
|
||||
defp behaviour_hint(module, function, arity) do
|
||||
case behaviours_for(module) do
|
||||
[] ->
|
||||
""
|
||||
|
||||
behaviours ->
|
||||
case Enum.find(behaviours, &expects_callback?(&1, function, arity)) do
|
||||
nil -> ""
|
||||
behaviour -> ", but the behaviour #{inspect(behaviour)} expects it to be present"
|
||||
end
|
||||
end
|
||||
rescue
|
||||
# In case the module was removed while we are computing this
|
||||
UndefinedFunctionError -> ""
|
||||
end
|
||||
|
||||
defp behaviours_for(module) do
|
||||
:attributes
|
||||
|> module.module_info()
|
||||
|> Keyword.get(:behaviour, [])
|
||||
end
|
||||
|
||||
defp expects_callback?(behaviour, function, arity) do
|
||||
callbacks = behaviour.behaviour_info(:callbacks)
|
||||
Enum.member?(callbacks, {function, arity})
|
||||
end
|
||||
|
||||
defp exports_for(module) do
|
||||
if function_exported?(module, :__info__, 1) do
|
||||
module.__info__(:macros) ++ module.__info__(:functions)
|
||||
@@ -1178,7 +1176,7 @@ defmodule KeyError do
|
||||
def message(exception = %{message: nil}), do: message(exception.key, exception.term)
|
||||
def message(%{message: message}), do: message
|
||||
|
||||
def message(key, term) do
|
||||
defp message(key, term) do
|
||||
message = "key #{inspect(key)} not found"
|
||||
|
||||
if term != nil do
|
||||
|
||||
@@ -268,16 +268,14 @@ defmodule Float do
|
||||
raise ArgumentError, invalid_precision_message(precision)
|
||||
end
|
||||
|
||||
defp round(0.0, _precision, _rounding), do: 0.0
|
||||
|
||||
defp round(float, precision, rounding) do
|
||||
<<sign::1, exp::11, significant::52-bitstring>> = <<float::float>>
|
||||
{num, count, _} = decompose(significant, 1)
|
||||
count = count - exp + 1023
|
||||
|
||||
cond do
|
||||
# There is no decimal precision on subnormal floats
|
||||
count <= 0 or exp == 0 ->
|
||||
float
|
||||
|
||||
# Precision beyond 15 digits
|
||||
count >= 104 ->
|
||||
case rounding do
|
||||
@@ -444,11 +442,11 @@ defmodule Float do
|
||||
{acc, last_count, last_power}
|
||||
end
|
||||
|
||||
@compile {:inline, sign: 2, shift_left: 2}
|
||||
defp sign(0, num), do: num
|
||||
defp sign(1, num), do: -num
|
||||
|
||||
defp shift_left(num, 0), do: num
|
||||
defp shift_left(num, times), do: shift_left(num <<< 1, times - 1)
|
||||
defp shift_left(num, times), do: num <<< times
|
||||
|
||||
defp shift_right(num, 0), do: {num, 0}
|
||||
defp shift_right(1, times), do: {1, times}
|
||||
|
||||
@@ -61,7 +61,9 @@ defmodule GenServer do
|
||||
|
||||
Every time you do a `GenServer.call/3`, the client will send a message
|
||||
that must be handled by the `c:handle_call/3` callback in the GenServer.
|
||||
A `cast/2` message must be handled by `c:handle_cast/2`.
|
||||
A `cast/2` message must be handled by `c:handle_cast/2`. There are 7 possible
|
||||
callbacks to be implemented when you use a `GenServer`. The only required
|
||||
callback is `init/1`.
|
||||
|
||||
## Client / Server APIs
|
||||
|
||||
@@ -111,14 +113,33 @@ defmodule GenServer do
|
||||
the same module. If the server and/or client implementations are growing
|
||||
complex, you may want to have them in different modules.
|
||||
|
||||
## use GenServer and callbacks
|
||||
## How to supervise
|
||||
|
||||
There are 7 callbacks to be implemented when you use a `GenServer`.
|
||||
The only required callback is `init/1`.
|
||||
A `GenServer` is most commonly started under a supervision tree.
|
||||
When we invoke `use GenServer`, it automatically defines a `child_spec/1`
|
||||
function that allows us to start the `Stack` directly under a supervisor.
|
||||
To start a default stack of `[:hello]` under a supervisor, one may do:
|
||||
|
||||
`use GenServer` also defines a `child_spec/1` function, allowing the
|
||||
defined module to be put under a supervision tree. The generated
|
||||
`child_spec/1` can be customized with the following options:
|
||||
children = [
|
||||
{Stack, [:hello]}
|
||||
]
|
||||
|
||||
Supervisor.start_link(children, strategy: :one_for_all)
|
||||
|
||||
Note you can also start it simply as `Stack`, which is the same as
|
||||
`{Stack, []}`:
|
||||
|
||||
children = [
|
||||
Stack # The same as {Stack, []}
|
||||
]
|
||||
|
||||
Supervisor.start_link(children, strategy: :one_for_all)
|
||||
|
||||
In both cases, `Stack.start_link/1` is always invoked.
|
||||
|
||||
`use GenServer` also accepts a list of options which configures the
|
||||
child specification and therefore how it runs under a supervisor.
|
||||
The generated `child_spec/1` can be customized with the following options:
|
||||
|
||||
* `:id` - the child specification identifier, defaults to the current module
|
||||
* `:start` - how to start the child process (defaults to calling `__MODULE__.start_link/1`)
|
||||
|
||||
+20
-11
@@ -2803,7 +2803,7 @@ defmodule Kernel do
|
||||
"invalid write attribute syntax, you probably meant to use: @#{name} expression"
|
||||
|
||||
# Typespecs attributes are currently special cased by the compiler
|
||||
is_list(args) and args != [] and tl(args) == [] and typespec?(name) ->
|
||||
is_list(args) and typespec?(name) ->
|
||||
case bootstrapped?(Kernel.Typespec) do
|
||||
false ->
|
||||
:ok
|
||||
@@ -4567,10 +4567,17 @@ defmodule Kernel do
|
||||
|
||||
Any protocol module contains three extra functions:
|
||||
|
||||
* `__protocol__/1` - returns the protocol name when `:name` is given, a
|
||||
keyword list with the protocol functions and their arities when
|
||||
`:functions` is given, and a list of the implementations when `:impls` is
|
||||
given
|
||||
* `__protocol__/1` - returns the protocol information. The function takes
|
||||
one of the following atoms:
|
||||
|
||||
* `:consolidated?` - returns whether the protocol is consolidated
|
||||
|
||||
* `:functions` - returns keyword list of protocol functions and their arities
|
||||
|
||||
* `:impls` - if consolidated, returns `{:consolidated, modules}` with the list of modules
|
||||
implementing the protocol, otherwise `:not_consolidated`
|
||||
|
||||
* `:module` - the protocol module atom name
|
||||
|
||||
* `impl_for/1` - receives a structure and returns the module that
|
||||
implements the protocol for the structure, `nil` otherwise
|
||||
@@ -4578,14 +4585,16 @@ defmodule Kernel do
|
||||
* `impl_for!/1` - same as above but raises an error if an implementation is
|
||||
not found
|
||||
|
||||
Enumerable.__protocol__(:functions)
|
||||
#=> [count: 1, member?: 2, reduce: 3]
|
||||
For example, for the `Enumerable` protocol we have:
|
||||
|
||||
Enumerable.impl_for([])
|
||||
#=> Enumerable.List
|
||||
iex> Enumerable.__protocol__(:functions)
|
||||
[count: 1, member?: 2, reduce: 3, slice: 1]
|
||||
|
||||
Enumerable.impl_for(42)
|
||||
#=> nil
|
||||
iex> Enumerable.impl_for([])
|
||||
Enumerable.List
|
||||
|
||||
iex> Enumerable.impl_for(42)
|
||||
nil
|
||||
|
||||
## Consolidation
|
||||
|
||||
|
||||
@@ -246,12 +246,17 @@ defmodule Kernel.SpecialForms do
|
||||
iex> {name, species}
|
||||
{"Frank", "Walrus"}
|
||||
|
||||
And the variable can be defined in the match itself:
|
||||
And the variable can be defined in the match itself (prior to its use):
|
||||
|
||||
iex> <<name_size::size(8), name::binary-size(name_size), " the ", species::binary>> = <<5, "Frank the Walrus">>
|
||||
iex> {name, species}
|
||||
{"Frank", "Walrus"}
|
||||
|
||||
However, the size cannot be defined in the match outside the binary/bitstring match:
|
||||
|
||||
{name_size, <<name::binary-size(name_size), _rest::binary>>} = {5, <<"Frank the Walrus">>}
|
||||
** (CompileError): undefined variable "name_size" in bitstring segment
|
||||
|
||||
Failing to specify the size for the non-last causes compilation to fail:
|
||||
|
||||
<<name::binary, " the ", species::binary>> = <<"Frank the Walrus">>
|
||||
|
||||
+119
-100
@@ -219,21 +219,21 @@ defmodule Kernel.Typespec do
|
||||
undefined_type_error_enabled?: true
|
||||
}
|
||||
|
||||
{types, state} = Enum.map_reduce(type_typespecs, state, &translate_type/2)
|
||||
{specs, state} = Enum.map_reduce(take_typespecs(bag, :spec), state, &translate_spec/2)
|
||||
{callbacks, state} = Enum.map_reduce(take_typespecs(bag, :callback), state, &translate_spec/2)
|
||||
{types, state} = :lists.mapfoldl(&translate_type/2, state, type_typespecs)
|
||||
{specs, state} = :lists.mapfoldl(&translate_spec/2, state, take_typespecs(bag, :spec))
|
||||
{callbacks, state} = :lists.mapfoldl(&translate_spec/2, state, take_typespecs(bag, :callback))
|
||||
|
||||
{macrocallbacks, state} =
|
||||
Enum.map_reduce(take_typespecs(bag, :macrocallback), state, &translate_spec/2)
|
||||
:lists.mapfoldl(&translate_spec/2, state, take_typespecs(bag, :macrocallback))
|
||||
|
||||
optional_callbacks = List.flatten(get_typespecs(bag, :optional_callbacks))
|
||||
optional_callbacks = :lists.flatten(get_typespecs(bag, :optional_callbacks))
|
||||
used_types = filter_used_types(types, state)
|
||||
|
||||
{used_types, specs, callbacks, macrocallbacks, optional_callbacks}
|
||||
end
|
||||
|
||||
defp collect_defined_type_pairs(type_typespecs) do
|
||||
Enum.reduce(type_typespecs, %{}, fn {_kind, expr, pos}, type_pairs ->
|
||||
fun = fn {_kind, expr, pos}, type_pairs ->
|
||||
%{file: file, line: line} = env = :elixir_locals.get_cached_env(pos)
|
||||
|
||||
case type_to_signature(expr) do
|
||||
@@ -252,19 +252,23 @@ defmodule Kernel.Typespec do
|
||||
:error ->
|
||||
compile_error(env, "invalid type specification: #{Macro.to_string(expr)}")
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
:lists.foldl(fun, %{}, type_typespecs)
|
||||
end
|
||||
|
||||
defp filter_used_types(types, state) do
|
||||
Enum.filter(types, fn {_kind, {name, arity} = type_pair, _line, _type, export} ->
|
||||
if type_pair not in state.used_type_pairs and not export do
|
||||
fun = fn {_kind, {name, arity} = type_pair, _line, _type, export} ->
|
||||
if not export and not :lists.member(type_pair, state.used_type_pairs) do
|
||||
%{^type_pair => {file, line}} = state.defined_type_pairs
|
||||
:elixir_errors.warn(line, file, "type #{name}/#{arity} is unused")
|
||||
false
|
||||
else
|
||||
true
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
:lists.filter(fun, types)
|
||||
end
|
||||
|
||||
defp translate_type({kind, {:::, _, [{name, _, args}, definition]}, pos}, state) do
|
||||
@@ -278,10 +282,10 @@ defmodule Kernel.Typespec do
|
||||
for(arg <- args, do: variable(arg))
|
||||
end
|
||||
|
||||
vars = for {:var, _, var} <- args, do: var
|
||||
state = Enum.reduce(vars, state, &update_local_vars(&2, &1))
|
||||
{spec, state} = typespec(definition, vars, caller, state)
|
||||
vars = for {:var, _, _} = var <- args, do: var
|
||||
vars = :lists.filter(&match?({:var, _, _}, &1), args)
|
||||
var_names = :lists.map(&elem(&1, 2), vars)
|
||||
state = :lists.foldl(&update_local_vars(&2, &1), state, var_names)
|
||||
{spec, state} = typespec(definition, var_names, caller, state)
|
||||
type = {name, spec, vars}
|
||||
arity = length(args)
|
||||
|
||||
@@ -294,10 +298,10 @@ defmodule Kernel.Typespec do
|
||||
:opaque -> {:opaque, true}
|
||||
end
|
||||
|
||||
invalid_args = Enum.reject(args, &valid_variable_ast?/1)
|
||||
invalid_args = :lists.filter(&(not valid_variable_ast?(&1)), args)
|
||||
|
||||
unless invalid_args == [] do
|
||||
invalid_args = invalid_args |> Enum.map(&Macro.to_string/1) |> Enum.join(", ")
|
||||
invalid_args = :lists.join(", ", :lists.map(&Macro.to_string/1, invalid_args))
|
||||
|
||||
message =
|
||||
"@type definitions expect all arguments to be variables. The type " <>
|
||||
@@ -390,7 +394,7 @@ defmodule Kernel.Typespec do
|
||||
defp built_in_type?(name, arity), do: :erl_internal.is_type(name, arity)
|
||||
|
||||
defp ensure_no_defaults!(args) do
|
||||
Enum.each(args, fn
|
||||
fun = fn
|
||||
{:::, _, [left, right]} ->
|
||||
ensure_not_default(left)
|
||||
ensure_not_default(right)
|
||||
@@ -399,7 +403,9 @@ defmodule Kernel.Typespec do
|
||||
other ->
|
||||
ensure_not_default(other)
|
||||
other
|
||||
end)
|
||||
end
|
||||
|
||||
:lists.foreach(fun, args)
|
||||
end
|
||||
|
||||
defp ensure_not_default({:\\, _, [_, _]}) do
|
||||
@@ -411,17 +417,19 @@ defmodule Kernel.Typespec do
|
||||
defp guard_to_constraints(guard, vars, meta, caller, state) do
|
||||
line = line(meta)
|
||||
|
||||
Enum.flat_map_reduce(guard, state, fn
|
||||
{_name, {:var, _, context}}, state when is_atom(context) ->
|
||||
{[], state}
|
||||
fun = fn
|
||||
{_name, {:var, _, context}}, {constraints, state} when is_atom(context) ->
|
||||
{constraints, state}
|
||||
|
||||
{name, type}, state ->
|
||||
{name, type}, {constraints, state} ->
|
||||
{spec, state} = typespec(type, vars, caller, state)
|
||||
constraint = [{:atom, line, :is_subtype}, [{:var, line, name}, spec]]
|
||||
state = update_local_vars(state, name)
|
||||
{[{:type, line, :constraint, constraint} | constraints], state}
|
||||
end
|
||||
|
||||
{[{:type, line, :constraint, constraint}], state}
|
||||
end)
|
||||
{constraints, state} = :lists.foldl(fun, {[], state}, guard)
|
||||
{:lists.reverse(constraints), state}
|
||||
end
|
||||
|
||||
## To typespec conversion
|
||||
@@ -433,7 +441,7 @@ defmodule Kernel.Typespec do
|
||||
# Handle unions
|
||||
defp typespec({:|, meta, [_, _]} = exprs, vars, caller, state) do
|
||||
exprs = collect_union(exprs)
|
||||
{union, state} = Enum.map_reduce(exprs, state, &typespec(&1, vars, caller, &2))
|
||||
{union, state} = :lists.mapfoldl(&typespec(&1, vars, caller, &2), state, exprs)
|
||||
{{:type, line(meta), :union, union}, state}
|
||||
end
|
||||
|
||||
@@ -493,45 +501,45 @@ defmodule Kernel.Typespec do
|
||||
end
|
||||
|
||||
defp typespec({:%{}, meta, fields} = map, vars, caller, state) do
|
||||
{fields, state} =
|
||||
Enum.map_reduce(fields, state, fn
|
||||
{k, v}, state when is_atom(k) ->
|
||||
{arg1, state} = typespec(k, vars, caller, state)
|
||||
{arg2, state} = typespec(v, vars, caller, state)
|
||||
{{:type, line(meta), :map_field_exact, [arg1, arg2]}, state}
|
||||
fun = fn
|
||||
{k, v}, state when is_atom(k) ->
|
||||
{arg1, state} = typespec(k, vars, caller, state)
|
||||
{arg2, state} = typespec(v, vars, caller, state)
|
||||
{{:type, line(meta), :map_field_exact, [arg1, arg2]}, state}
|
||||
|
||||
{{:required, meta2, [k]}, v}, state ->
|
||||
{arg1, state} = typespec(k, vars, caller, state)
|
||||
{arg2, state} = typespec(v, vars, caller, state)
|
||||
{{:type, line(meta2), :map_field_exact, [arg1, arg2]}, state}
|
||||
{{:required, meta2, [k]}, v}, state ->
|
||||
{arg1, state} = typespec(k, vars, caller, state)
|
||||
{arg2, state} = typespec(v, vars, caller, state)
|
||||
{{:type, line(meta2), :map_field_exact, [arg1, arg2]}, state}
|
||||
|
||||
{{:optional, meta2, [k]}, v}, state ->
|
||||
{arg1, state} = typespec(k, vars, caller, state)
|
||||
{arg2, state} = typespec(v, vars, caller, state)
|
||||
{{:type, line(meta2), :map_field_assoc, [arg1, arg2]}, state}
|
||||
{{:optional, meta2, [k]}, v}, state ->
|
||||
{arg1, state} = typespec(k, vars, caller, state)
|
||||
{arg2, state} = typespec(v, vars, caller, state)
|
||||
{{:type, line(meta2), :map_field_assoc, [arg1, arg2]}, state}
|
||||
|
||||
{k, v}, state ->
|
||||
# TODO: Warn on Elixir v1.8 (since v1.6 is the first version to drop support for 18 and
|
||||
# older)
|
||||
# warning =
|
||||
# "invalid map specification. %{foo => bar} is deprecated in favor of " <>
|
||||
# "%{required(foo) => bar} and %{optional(foo) => bar}."
|
||||
# :elixir_errors.warn(caller.line, caller.file, warning)
|
||||
{arg1, state} = typespec(k, vars, caller, state)
|
||||
{arg2, state} = typespec(v, vars, caller, state)
|
||||
{{:type, line(meta), :map_field_assoc, [arg1, arg2]}, state}
|
||||
{k, v}, state ->
|
||||
# TODO: Warn on Elixir v1.8 (since v1.6 is the first version to drop support for 18 and
|
||||
# older)
|
||||
# warning =
|
||||
# "invalid map specification. %{foo => bar} is deprecated in favor of " <>
|
||||
# "%{required(foo) => bar} and %{optional(foo) => bar}."
|
||||
# :elixir_errors.warn(caller.line, caller.file, warning)
|
||||
{arg1, state} = typespec(k, vars, caller, state)
|
||||
{arg2, state} = typespec(v, vars, caller, state)
|
||||
{{:type, line(meta), :map_field_assoc, [arg1, arg2]}, state}
|
||||
|
||||
{:|, _, [_, _]}, _state ->
|
||||
error =
|
||||
"invalid map specification. When using the | operator in the map key, " <>
|
||||
"make sure to wrap the key type in parentheses: #{Macro.to_string(map)}"
|
||||
{:|, _, [_, _]}, _state ->
|
||||
error =
|
||||
"invalid map specification. When using the | operator in the map key, " <>
|
||||
"make sure to wrap the key type in parentheses: #{Macro.to_string(map)}"
|
||||
|
||||
compile_error(caller, error)
|
||||
compile_error(caller, error)
|
||||
|
||||
_, _state ->
|
||||
compile_error(caller, "invalid map specification: #{Macro.to_string(map)}")
|
||||
end)
|
||||
_, _state ->
|
||||
compile_error(caller, "invalid map specification: #{Macro.to_string(map)}")
|
||||
end
|
||||
|
||||
{fields, state} = :lists.mapfoldl(fun, state, fields)
|
||||
{{:type, line(meta), :map, fields}, state}
|
||||
end
|
||||
|
||||
@@ -543,7 +551,7 @@ defmodule Kernel.Typespec do
|
||||
struct =
|
||||
module
|
||||
|> Macro.struct!(caller)
|
||||
|> Map.from_struct()
|
||||
|> Map.delete(:__struct__)
|
||||
|> Map.to_list()
|
||||
|
||||
unless Keyword.keyword?(fields) do
|
||||
@@ -551,43 +559,48 @@ defmodule Kernel.Typespec do
|
||||
end
|
||||
|
||||
types =
|
||||
Enum.map(struct, fn {field, _} ->
|
||||
{field, Keyword.get(fields, field, quote(do: term()))}
|
||||
end)
|
||||
:lists.map(
|
||||
fn {field, _} -> {field, Keyword.get(fields, field, quote(do: term()))} end,
|
||||
struct
|
||||
)
|
||||
|
||||
Enum.each(fields, fn {field, _} ->
|
||||
fun = fn {field, _} ->
|
||||
unless Keyword.has_key?(struct, field) do
|
||||
compile_error(
|
||||
caller,
|
||||
"undefined field #{inspect(field)} on struct #{Macro.to_string(name)}"
|
||||
)
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
:lists.foreach(fun, fields)
|
||||
typespec({:%{}, meta, [__struct__: module] ++ types}, vars, caller, state)
|
||||
end
|
||||
|
||||
# Handle records
|
||||
defp typespec({:record, meta, [atom]}, vars, caller, state) when is_atom(atom) do
|
||||
defp typespec({:record, meta, [atom]}, vars, caller, state) do
|
||||
typespec({:record, meta, [atom, []]}, vars, caller, state)
|
||||
end
|
||||
|
||||
defp typespec({:record, meta, [tag, field_specs]}, vars, caller, state) when is_atom(tag) do
|
||||
defp typespec({:record, meta, [tag, field_specs]}, vars, caller, state)
|
||||
when is_atom(tag) and is_list(field_specs) do
|
||||
# We cannot set a function name to avoid tracking
|
||||
# as a compile time dependency because for records it actually is one.
|
||||
case Macro.expand({tag, [], [{:{}, [], []}]}, caller) do
|
||||
{_, _, [name, fields | _]} when is_list(fields) ->
|
||||
types =
|
||||
Enum.map(fields, fn {field, _} ->
|
||||
Keyword.get(field_specs, field, quote(do: term()))
|
||||
end)
|
||||
:lists.map(
|
||||
fn {field, _} -> Keyword.get(field_specs, field, quote(do: term())) end,
|
||||
fields
|
||||
)
|
||||
|
||||
Enum.each(field_specs, fn {field, _} ->
|
||||
fun = fn {field, _} ->
|
||||
unless Keyword.has_key?(fields, field) do
|
||||
compile_error(caller, "undefined field #{field} on record #{inspect(tag)}")
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
:lists.foreach(fun, field_specs)
|
||||
typespec({:{}, meta, [name | types]}, vars, caller, state)
|
||||
|
||||
_ ->
|
||||
@@ -595,8 +608,9 @@ defmodule Kernel.Typespec do
|
||||
end
|
||||
end
|
||||
|
||||
defp typespec({:record, _meta, _args}, _vars, caller, _state) do
|
||||
compile_error(caller, "invalid record specification, expected the record name to be an atom")
|
||||
defp typespec({:record, _meta, [_tag, _field_specs]}, _vars, caller, _state) do
|
||||
message = "invalid record specification, expected the record name to be an atom literal"
|
||||
compile_error(caller, message)
|
||||
end
|
||||
|
||||
# Handle ranges
|
||||
@@ -621,9 +635,9 @@ defmodule Kernel.Typespec do
|
||||
end
|
||||
|
||||
# Handle funs
|
||||
defp typespec([{:->, meta, [arguments, return]}], vars, caller, state)
|
||||
when is_list(arguments) do
|
||||
{args, state} = fn_args(meta, arguments, return, vars, caller, state)
|
||||
defp typespec([{:->, meta, [args, return]}], vars, caller, state)
|
||||
when is_list(args) do
|
||||
{args, state} = fn_args(meta, args, return, vars, caller, state)
|
||||
{{:type, line(meta), :fun, args}, state}
|
||||
end
|
||||
|
||||
@@ -725,7 +739,7 @@ defmodule Kernel.Typespec do
|
||||
end
|
||||
|
||||
defp typespec({:{}, meta, t}, vars, caller, state) when is_list(t) do
|
||||
{args, state} = Enum.map_reduce(t, state, &typespec(&1, vars, caller, &2))
|
||||
{args, state} = :lists.mapfoldl(&typespec(&1, vars, caller, &2), state, t)
|
||||
{{:type, line(meta), :tuple, args}, state}
|
||||
end
|
||||
|
||||
@@ -740,7 +754,7 @@ defmodule Kernel.Typespec do
|
||||
|
||||
# Handle variables or local calls
|
||||
defp typespec({name, meta, atom}, vars, caller, state) when is_atom(atom) do
|
||||
if name in vars do
|
||||
if :lists.member(name, vars) do
|
||||
state = update_local_vars(state, name)
|
||||
{{:var, line(meta), name}, state}
|
||||
else
|
||||
@@ -749,7 +763,7 @@ defmodule Kernel.Typespec do
|
||||
end
|
||||
|
||||
# Handle local calls
|
||||
defp typespec({:string, meta, arguments}, vars, caller, state) do
|
||||
defp typespec({:string, meta, args}, vars, caller, state) do
|
||||
warning =
|
||||
"string() type use is discouraged. " <>
|
||||
"For character lists, use charlist() type, for strings, String.t()\n" <>
|
||||
@@ -757,11 +771,11 @@ defmodule Kernel.Typespec do
|
||||
|
||||
:elixir_errors.warn(caller.line, caller.file, warning)
|
||||
|
||||
{arguments, state} = Enum.map_reduce(arguments, state, &typespec(&1, vars, caller, &2))
|
||||
{{:type, line(meta), :string, arguments}, state}
|
||||
{args, state} = :lists.mapfoldl(&typespec(&1, vars, caller, &2), state, args)
|
||||
{{:type, line(meta), :string, args}, state}
|
||||
end
|
||||
|
||||
defp typespec({:nonempty_string, meta, arguments}, vars, caller, state) do
|
||||
defp typespec({:nonempty_string, meta, args}, vars, caller, state) do
|
||||
warning =
|
||||
"nonempty_string() type use is discouraged. " <>
|
||||
"For non-empty character lists, use nonempty_charlist() type, for strings, String.t()\n" <>
|
||||
@@ -769,8 +783,8 @@ defmodule Kernel.Typespec do
|
||||
|
||||
:elixir_errors.warn(caller.line, caller.file, warning)
|
||||
|
||||
{arguments, state} = Enum.map_reduce(arguments, state, &typespec(&1, vars, caller, &2))
|
||||
{{:type, line(meta), :nonempty_string, arguments}, state}
|
||||
{args, state} = :lists.mapfoldl(&typespec(&1, vars, caller, &2), state, args)
|
||||
{{:type, line(meta), :nonempty_string, args}, state}
|
||||
end
|
||||
|
||||
# TODO: Remove char_list type by 2.0
|
||||
@@ -800,17 +814,17 @@ defmodule Kernel.Typespec do
|
||||
end
|
||||
|
||||
defp typespec({:fun, meta, args}, vars, caller, state) do
|
||||
{args, state} = Enum.map_reduce(args, state, &typespec(&1, vars, caller, &2))
|
||||
{args, state} = :lists.mapfoldl(&typespec(&1, vars, caller, &2), state, args)
|
||||
{{:type, line(meta), :fun, args}, state}
|
||||
end
|
||||
|
||||
defp typespec({name, meta, arguments}, vars, caller, state) do
|
||||
{arguments, state} = Enum.map_reduce(arguments, state, &typespec(&1, vars, caller, &2))
|
||||
arity = length(arguments)
|
||||
defp typespec({name, meta, args}, vars, caller, state) do
|
||||
{args, state} = :lists.mapfoldl(&typespec(&1, vars, caller, &2), state, args)
|
||||
arity = length(args)
|
||||
|
||||
case :erl_internal.is_type(name, arity) do
|
||||
true ->
|
||||
{{:type, line(meta), name, arguments}, state}
|
||||
{{:type, line(meta), name, args}, state}
|
||||
|
||||
false ->
|
||||
if state.undefined_type_error_enabled? and
|
||||
@@ -819,13 +833,13 @@ defmodule Kernel.Typespec do
|
||||
end
|
||||
|
||||
state =
|
||||
if {name, arity} in state.used_type_pairs do
|
||||
if :lists.member({name, arity}, state.used_type_pairs) do
|
||||
state
|
||||
else
|
||||
%{state | used_type_pairs: [{name, arity} | state.used_type_pairs]}
|
||||
end
|
||||
|
||||
{{:user_type, line(meta), name, arguments}, state}
|
||||
{{:user_type, line(meta), name, args}, state}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -855,12 +869,14 @@ defmodule Kernel.Typespec do
|
||||
end
|
||||
|
||||
defp typespec(list, vars, caller, state) when is_list(list) do
|
||||
[head | tail] = Enum.reverse(list)
|
||||
[head | tail] = :lists.reverse(list)
|
||||
|
||||
union =
|
||||
Enum.reduce(tail, validate_kw(head, list, caller), fn elem, acc ->
|
||||
{:|, [], [validate_kw(elem, list, caller), acc]}
|
||||
end)
|
||||
:lists.foldl(
|
||||
fn elem, acc -> {:|, [], [validate_kw(elem, list, caller), acc]} end,
|
||||
validate_kw(head, list, caller),
|
||||
tail
|
||||
)
|
||||
|
||||
typespec({:list, [], [union]}, vars, caller, state)
|
||||
end
|
||||
@@ -875,9 +891,9 @@ defmodule Kernel.Typespec do
|
||||
raise CompileError, file: caller.file, line: caller.line, description: desc
|
||||
end
|
||||
|
||||
defp remote_type({remote, meta, name, arguments}, vars, caller, state) do
|
||||
{arguments, state} = Enum.map_reduce(arguments, state, &typespec(&1, vars, caller, &2))
|
||||
{{:remote_type, line(meta), [remote, name, arguments]}, state}
|
||||
defp remote_type({remote, meta, name, args}, vars, caller, state) do
|
||||
{args, state} = :lists.mapfoldl(&typespec(&1, vars, caller, &2), state, args)
|
||||
{{:remote_type, line(meta), [remote, name, args]}, state}
|
||||
end
|
||||
|
||||
defp collect_union({:|, _, [a, b]}), do: [a | collect_union(b)]
|
||||
@@ -924,7 +940,7 @@ defmodule Kernel.Typespec do
|
||||
end
|
||||
|
||||
defp fn_args(meta, args, vars, caller, state) do
|
||||
{args, state} = Enum.map_reduce(args, state, &typespec(&1, vars, caller, &2))
|
||||
{args, state} = :lists.mapfoldl(&typespec(&1, vars, caller, &2), state, args)
|
||||
{{:type, line(meta), :product, args}, state}
|
||||
end
|
||||
|
||||
@@ -947,8 +963,11 @@ defmodule Kernel.Typespec do
|
||||
end
|
||||
|
||||
defp ensure_no_unused_local_vars!(caller, local_vars) do
|
||||
for {name, :used_once} <- local_vars do
|
||||
compile_error(caller, "type variable #{name} is unused")
|
||||
fun = fn
|
||||
{name, :used_once} -> compile_error(caller, "type variable #{name} is unused")
|
||||
_ -> :ok
|
||||
end
|
||||
|
||||
:lists.foreach(fun, :maps.to_list(local_vars))
|
||||
end
|
||||
end
|
||||
|
||||
@@ -704,8 +704,8 @@ defmodule Keyword do
|
||||
@spec merge(t, t) :: t
|
||||
def merge(keywords1, keywords2)
|
||||
|
||||
def merge(keywords1, []), do: keywords1
|
||||
def merge([], keywords2), do: keywords2
|
||||
def merge(keywords1, []) when is_list(keywords1), do: keywords1
|
||||
def merge([], keywords2) when is_list(keywords2), do: keywords2
|
||||
|
||||
def merge(keywords1, keywords2) when is_list(keywords1) and is_list(keywords2) do
|
||||
if keyword?(keywords2) do
|
||||
|
||||
+13
-6
@@ -748,15 +748,18 @@ defmodule List do
|
||||
@doc """
|
||||
Converts a charlist to an atom.
|
||||
|
||||
Currently Elixir does not support conversions from charlists
|
||||
which contains Unicode codepoints greater than 0xFF.
|
||||
Elixir supports conversions from charlists which contains any Unicode
|
||||
codepoint.
|
||||
|
||||
Inlined by the compiler.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> List.to_atom('elixir')
|
||||
:elixir
|
||||
iex> List.to_atom('Elixir')
|
||||
:Elixir
|
||||
|
||||
iex> List.to_atom('🌢 Elixir')
|
||||
:"🌢 Elixir"
|
||||
|
||||
"""
|
||||
@spec to_atom(charlist) :: atom
|
||||
@@ -768,8 +771,8 @@ defmodule List do
|
||||
Converts a charlist to an existing atom. Raises an `ArgumentError`
|
||||
if the atom does not exist.
|
||||
|
||||
Currently Elixir does not support conversions from charlists
|
||||
which contains Unicode codepoints greater than 0xFF.
|
||||
Elixir supports conversions from charlists which contains any Unicode
|
||||
codepoint.
|
||||
|
||||
Inlined by the compiler.
|
||||
|
||||
@@ -779,6 +782,10 @@ defmodule List do
|
||||
iex> List.to_existing_atom('my_atom')
|
||||
:my_atom
|
||||
|
||||
iex> _ = :"🌢 Elixir"
|
||||
iex> List.to_existing_atom('🌢 Elixir')
|
||||
:"🌢 Elixir"
|
||||
|
||||
iex> List.to_existing_atom('this_atom_will_never_exist')
|
||||
** (ArgumentError) argument error
|
||||
|
||||
|
||||
@@ -1463,9 +1463,7 @@ defmodule Module do
|
||||
message =
|
||||
"@behaviour #{inspect(behaviour)} does not exist (in module #{inspect(env.module)})"
|
||||
|
||||
unless standard_behaviour?(behaviour) do
|
||||
:elixir_errors.warn(env.line, env.file, message)
|
||||
end
|
||||
:elixir_errors.warn(env.line, env.file, message)
|
||||
|
||||
acc
|
||||
|
||||
@@ -1473,9 +1471,7 @@ defmodule Module do
|
||||
message =
|
||||
"module #{inspect(behaviour)} is not a behaviour (in module #{inspect(env.module)})"
|
||||
|
||||
unless standard_behaviour?(behaviour) do
|
||||
:elixir_errors.warn(env.line, env.file, message)
|
||||
end
|
||||
:elixir_errors.warn(env.line, env.file, message)
|
||||
|
||||
acc
|
||||
|
||||
@@ -1506,16 +1502,6 @@ defmodule Module do
|
||||
Map.put(acc, callback, {kind, behaviour, original in optional_callbacks})
|
||||
end
|
||||
|
||||
defp standard_behaviour?(behaviour) do
|
||||
behaviour in [
|
||||
Collectable,
|
||||
Enumerable,
|
||||
Inspect,
|
||||
List.Chars,
|
||||
String.Chars
|
||||
]
|
||||
end
|
||||
|
||||
defp check_callbacks(env, callbacks, all_definitions) do
|
||||
for {callback, {kind, behaviour, optional?}} <- callbacks do
|
||||
case :lists.keyfind(callback, 1, all_definitions) do
|
||||
|
||||
@@ -1,6 +1,23 @@
|
||||
defmodule OptionParser do
|
||||
@moduledoc """
|
||||
This module contains functions to parse command line options.
|
||||
Functions for parsing command line options.
|
||||
|
||||
The main function in this module is `parse/2`, which allows
|
||||
developers to parse a list of arguments into options:
|
||||
|
||||
iex> OptionParser.parse(["--debug"], strict: [debug: :boolean])
|
||||
{[debug: true], [], []}
|
||||
|
||||
`OptionParser` provides some conveniences out of the box,
|
||||
such as aliases and automatic handling of negation switches.
|
||||
|
||||
The `parse_head/2` function is an alternative to `parse/2`
|
||||
which stops parsing as soon as it finds a value that is not
|
||||
a switch nor a value for a previous switch.
|
||||
|
||||
This module also provides low-level functions, such as `next/2`,
|
||||
for parsing switches manually, as well as `split/1` and `to_argv/1`
|
||||
for parsing from and converting switches to strings.
|
||||
"""
|
||||
|
||||
@type argv :: [String.t()]
|
||||
@@ -65,10 +82,9 @@ defmodule OptionParser do
|
||||
* `:switches` - defines some switches and their types. This function
|
||||
still attempts to parse switches that are not in this list.
|
||||
|
||||
Both these options accept a keyword list of `{name, type}` tuples where `name`
|
||||
is an atom defining the name of the switch and `type` is an atom that
|
||||
specifies the type for the value of this switch (see the "Types" section below
|
||||
for the possible types and more information about type casting).
|
||||
Both these options accept a keyword list where the key is an atom
|
||||
defining the name of the switch and value is the `type` of the
|
||||
switch (see the "Types" section below for more information).
|
||||
|
||||
Note that you should only supply the `:switches` or the `:strict` option.
|
||||
If you supply both, an `ArgumentError` exception will be raised.
|
||||
|
||||
@@ -279,6 +279,8 @@ defmodule StringIO do
|
||||
{_, _, _} ->
|
||||
{{:error, req}, state}
|
||||
end
|
||||
rescue
|
||||
ArgumentError -> {{:error, req}, state}
|
||||
end
|
||||
|
||||
## get_chars
|
||||
|
||||
+89
-111
@@ -99,48 +99,9 @@ defmodule Supervisor do
|
||||
workers and/or supervisors as children, with each one having its own
|
||||
configuration (as outlined in the "Child specification" section).
|
||||
|
||||
The rest of this document will cover how child processes are started,
|
||||
how they can be specified, different supervision strategies and more.
|
||||
|
||||
## Start and shutdown
|
||||
|
||||
When the supervisor starts, it traverses all child specifications and
|
||||
then starts each child in the order they are defined. This is done by
|
||||
calling the function defined under the `:start` key in the child
|
||||
specification and typically defaults to `start_link/1`.
|
||||
|
||||
The `start_link/1` (or a custom) is then called for each child process.
|
||||
The `start_link/1` function must return `{:ok, pid}` where `pid` is the
|
||||
process identifier of a new process that is linked to the supervisor.
|
||||
The child process usually starts its work by executing the `init/1`
|
||||
callback. Generally speaking, the `init` callback is where we initialize
|
||||
and configure the child process.
|
||||
|
||||
The shutdown process happens in reverse order.
|
||||
|
||||
When a supervisor shuts down, it terminates all children in the opposite
|
||||
order they are listed. The termination happens by sending a shutdown exit
|
||||
signal, via `Process.exit(child_pid, :shutdown)`, to the child process and
|
||||
then awaiting for a time interval for the child process to terminate. This
|
||||
interval defaults to 5000 milliseconds. If the child process does not
|
||||
terminate in this interval, the supervisor abruptly terminates the child
|
||||
with reason `:kill`. The shutdown time can be configured in the child
|
||||
specification which is fully detailed in the next section.
|
||||
|
||||
If the child process is not trapping exits, it will shutdown immediately
|
||||
when it receives the first exit signal. If the child process is trapping
|
||||
exits, then the `terminate` callback is invoked, and the child process
|
||||
must terminate in a reasonable time interval before being abruptly
|
||||
terminated by the supervisor.
|
||||
|
||||
In other words, if it is important that a process cleans after itself
|
||||
when your application or the supervision tree is shutting down, then
|
||||
this process must trap exits and its child specification should specify
|
||||
the proper `:shutdown` value, ensuring it terminates within a reasonable
|
||||
interval.
|
||||
|
||||
Now that we understand the start and shutdown process, let's take a
|
||||
complete look at all of the options provided in the child specification.
|
||||
The rest of this document will cover how child processes are specified,
|
||||
how they can be started and stopped, different supervision strategies
|
||||
and more.
|
||||
|
||||
## Child specification
|
||||
|
||||
@@ -247,30 +208,31 @@ defmodule Supervisor do
|
||||
|
||||
The supervisor will then invoke `Stack.child_spec([:hello])` to retrieve a
|
||||
child specification. Now the `Stack` module is responsible for building its
|
||||
own specification. By default, `use GenServer` defines a `Stack.child_spec/1`
|
||||
function which returns the same child specification we had before:
|
||||
own specification, for example, we could write:
|
||||
|
||||
%{
|
||||
id: Stack,
|
||||
start: {Stack, :start_link, [[:hello]]}
|
||||
}
|
||||
def child_spec(arg) do
|
||||
%{
|
||||
id: Stack,
|
||||
start: {Stack, :start_link, [arg]}
|
||||
}
|
||||
end
|
||||
|
||||
It is also possible to simply pass the `Stack` module as a child:
|
||||
Luckily for us, `use GenServer` already defines a `Stack.child_spec/1`
|
||||
exactly like above. If you need to customize the `GenServer`, you can
|
||||
pass the options directly to `use GenServer`:
|
||||
|
||||
use GenServer, restart: :transient
|
||||
|
||||
Finally, note it is also possible to simply pass the `Stack` module as
|
||||
a child:
|
||||
|
||||
children = [
|
||||
Stack
|
||||
]
|
||||
|
||||
When only the module name is given, it is equivalent to `{Stack, []}`. In this
|
||||
case, we will end-up with a child specification that looks like this:
|
||||
|
||||
%{
|
||||
id: Stack,
|
||||
start: {Stack, :start_link, [[]]}
|
||||
}
|
||||
|
||||
When only the module name is given, it is equivalent to `{Stack, []}`.
|
||||
By replacing the map specification by `{Stack, [:hello]}` or `Stack`, we keep
|
||||
the child specification encapsulated in the Stack module, using the default
|
||||
the child specification encapsulated in the `Stack` module, using the default
|
||||
implementation defined by `use GenServer`. We can now share our `Stack` worker
|
||||
with other developers and they can add it directly to their supervision tree
|
||||
without worrying about the low-level details of the worker.
|
||||
@@ -294,55 +256,6 @@ defmodule Supervisor do
|
||||
Supervisor.child_spec({Stack, [:hello]}, id: MyStack, shutdown: 10_000)
|
||||
]
|
||||
|
||||
The call to `Supervisor.child_spec/2` above will return the following specification:
|
||||
|
||||
%{
|
||||
id: MyStack,
|
||||
start: {Stack, :start_link, [[:hello]]},
|
||||
shutdown: 10_000
|
||||
}
|
||||
|
||||
You may also configure the child specification in the Stack module itself to
|
||||
use a different `:id` or `:shutdown` value by passing options to `use GenServer`:
|
||||
|
||||
defmodule Stack do
|
||||
use GenServer, id: MyStack, shutdown: 10_000
|
||||
|
||||
The options above will customize the `Stack.child_spec/1` function defined
|
||||
by `use GenServer`. It accepts the same options as the `Supervisor.child_spec/2`
|
||||
function.
|
||||
|
||||
You may also completely override the `child_spec/1` function in the Stack module
|
||||
and return your own child specification. Note there is no guarantee the `child_spec/1`
|
||||
function will be called by the Supervisor process, as other processes may invoke
|
||||
it to retrieve the child specification before reaching the supervisor.
|
||||
|
||||
## Exit reasons and restarts
|
||||
|
||||
A supervisor restarts a child process depending on its `:restart`
|
||||
configuration. For example, when `:restart` is set to `:transient`, the
|
||||
supervisor does not restart the child in case it exits with reason `:normal`,
|
||||
`:shutdown` or `{:shutdown, term}`.
|
||||
|
||||
So one may ask: which exit reason should I choose when exiting? There are
|
||||
three options:
|
||||
|
||||
* `:normal` - in such cases, the exit won't be logged, there is no restart
|
||||
in transient mode, and linked processes do not exit
|
||||
|
||||
* `:shutdown` or `{:shutdown, term}` - in such cases, the exit won't be
|
||||
logged, there is no restart in transient mode, and linked processes exit
|
||||
with the same reason unless they're trapping exits
|
||||
|
||||
* any other term - in such cases, the exit will be logged, there are
|
||||
restarts in transient mode, and linked processes exit with the same
|
||||
reason unless they're trapping exits
|
||||
|
||||
Notice that the supervisor that reaches maximum restart intensity will exit with
|
||||
`:shutdown` reason. In this case the supervisor will only be restarted if its
|
||||
child specification was defined with the `:restart` option set to `:permanent`
|
||||
(the default).
|
||||
|
||||
## Module-based supervisors
|
||||
|
||||
In the example above, a supervisor was started by passing the supervision
|
||||
@@ -375,7 +288,8 @@ defmodule Supervisor do
|
||||
`c:init/1` callback.
|
||||
|
||||
`use Supervisor` also defines a `child_spec/1` function which allows
|
||||
us to run `MyApp.Supervisor` as a child of another supervisor:
|
||||
us to run `MyApp.Supervisor` as a child of another supervisor or
|
||||
at the top of your supervision tree as:
|
||||
|
||||
children = [
|
||||
MyApp.Supervisor
|
||||
@@ -387,8 +301,9 @@ defmodule Supervisor do
|
||||
module only at the top of your supervision tree, generally in the
|
||||
`c:Application.start/2` callback. We recommend using module-based
|
||||
supervisors for any other supervisor in your application, so they
|
||||
can run as a child of another supervision in the tree. The generated
|
||||
`child_spec/1` can be customized with the following options:
|
||||
can run as a child of another supervisor in the tree. The `child_spec/1`
|
||||
generated automatically by `Supervisor` can be customized with the
|
||||
following options:
|
||||
|
||||
* `:id` - the child specification identifier, defaults to the current module
|
||||
* `:start` - how to start the child process (defaults to calling `__MODULE__.start_link/1`)
|
||||
@@ -461,10 +376,73 @@ defmodule Supervisor do
|
||||
differently when this strategy was used. See the `DynamicSupervisor` module
|
||||
for more information and migration strategies.
|
||||
|
||||
## Name registration
|
||||
### Name registration
|
||||
|
||||
A supervisor is bound to the same name registration rules as a `GenServer`.
|
||||
Read more about these rules in the documentation for `GenServer`.
|
||||
|
||||
## Start and shutdown
|
||||
|
||||
When the supervisor starts, it traverses all child specifications and
|
||||
then starts each child in the order they are defined. This is done by
|
||||
calling the function defined under the `:start` key in the child
|
||||
specification and typically defaults to `start_link/1`.
|
||||
|
||||
The `start_link/1` (or a custom) is then called for each child process.
|
||||
The `start_link/1` function must return `{:ok, pid}` where `pid` is the
|
||||
process identifier of a new process that is linked to the supervisor.
|
||||
The child process usually starts its work by executing the `init/1`
|
||||
callback. Generally speaking, the `init` callback is where we initialize
|
||||
and configure the child process.
|
||||
|
||||
The shutdown process happens in reverse order.
|
||||
|
||||
When a supervisor shuts down, it terminates all children in the opposite
|
||||
order they are listed. The termination happens by sending a shutdown exit
|
||||
signal, via `Process.exit(child_pid, :shutdown)`, to the child process and
|
||||
then awaiting for a time interval for the child process to terminate. This
|
||||
interval defaults to 5000 milliseconds. If the child process does not
|
||||
terminate in this interval, the supervisor abruptly terminates the child
|
||||
with reason `:kill`. The shutdown time can be configured in the child
|
||||
specification which is fully detailed in the next section.
|
||||
|
||||
If the child process is not trapping exits, it will shutdown immediately
|
||||
when it receives the first exit signal. If the child process is trapping
|
||||
exits, then the `terminate` callback is invoked, and the child process
|
||||
must terminate in a reasonable time interval before being abruptly
|
||||
terminated by the supervisor.
|
||||
|
||||
In other words, if it is important that a process cleans after itself
|
||||
when your application or the supervision tree is shutting down, then
|
||||
this process must trap exits and its child specification should specify
|
||||
the proper `:shutdown` value, ensuring it terminates within a reasonable
|
||||
interval.
|
||||
|
||||
## Exit reasons and restarts
|
||||
|
||||
A supervisor restarts a child process depending on its `:restart` configuration.
|
||||
For example, when `:restart` is set to `:transient`, the supervisor does not
|
||||
restart the child in case it exits with reason `:normal`, `:shutdown` or
|
||||
`{:shutdown, term}`.
|
||||
|
||||
So one may ask: which exit reason should I choose when exiting? There are
|
||||
three options:
|
||||
|
||||
* `:normal` - in such cases, the exit won't be logged, there is no restart
|
||||
in transient mode, and linked processes do not exit
|
||||
|
||||
* `:shutdown` or `{:shutdown, term}` - in such cases, the exit won't be
|
||||
logged, there is no restart in transient mode, and linked processes exit
|
||||
with the same reason unless they're trapping exits
|
||||
|
||||
* any other term - in such cases, the exit will be logged, there are
|
||||
restarts in transient mode, and linked processes exit with the same
|
||||
reason unless they're trapping exits
|
||||
|
||||
Notice that the supervisor that reaches maximum restart intensity will exit with
|
||||
`:shutdown` reason. In this case the supervisor will only be restarted if its
|
||||
child specification was defined with the `:restart` option set to `:permanent`
|
||||
(the default).
|
||||
"""
|
||||
|
||||
@doc false
|
||||
|
||||
@@ -88,11 +88,11 @@ defmodule Task do
|
||||
], strategy: :one_for_one)
|
||||
|
||||
Since these tasks are supervised and not directly linked to
|
||||
the caller, they cannot be awaited on. Note that `start_link/1`,
|
||||
unlike `async/1`, returns `{:ok, pid}` (which is the result
|
||||
expected by supervisors).
|
||||
the caller, they cannot be awaited on. `start_link/1`, unlike
|
||||
`async/1`, returns `{:ok, pid}` (which is the result expecte
|
||||
by supervisors).
|
||||
|
||||
Note that `use Task` defines a `child_spec/1` function, allowing the
|
||||
`use Task` defines a `child_spec/1` function, allowing the
|
||||
defined module to be put under a supervision tree. The generated
|
||||
`child_spec/1` can be customized with the following options:
|
||||
|
||||
|
||||
@@ -8,11 +8,11 @@ Elixir applies bug fixes only to the latest minor branch. Security patches are a
|
||||
|
||||
Elixir version | Support
|
||||
:------------- | :-----------------------------
|
||||
1.7 | Bug fixes and security patches
|
||||
1.8 | Bug fixes and security patches
|
||||
1.7 | Security patches only
|
||||
1.6 | Security patches only
|
||||
1.5 | Security patches only
|
||||
1.4 | Security patches only
|
||||
1.3 | 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).
|
||||
|
||||
@@ -50,6 +50,7 @@ Elixir version | Supported Erlang/OTP versions
|
||||
1.5 | 18 - 20
|
||||
1.6 | 19 - 20 (and Erlang/OTP 21 from v1.6.6)
|
||||
1.7 | 19 - 21
|
||||
1.8 | 20 - 21
|
||||
|
||||
While Elixir often adds compatibility to new Erlang/OTP versions on released branches, such as support for Erlang/OTP 20 in v1.4.5, those releases usually contain the minimum changes for Elixir to run without errors. Only the next minor release, in this case v1.5.0, does effectively leverage the new features provided by the latest Erlang/OTP release.
|
||||
|
||||
@@ -78,6 +79,7 @@ Passing a non-empty list to `:into` in `for` | [v1.8] | `Kernel.++/2`
|
||||
`Kernel.ParallelCompiler.files_to_path/2` | [v1.8] | `Kernel.ParallelCompiler.compile_to_path/2` (v1.6)
|
||||
`Kernel.ParallelRequire.files/2` | [v1.8] | `Kernel.ParallelCompiler.require/2` (v1.6)
|
||||
`System.cwd/0` and `System.cwd!/0` | [v1.8] | `File.cwd/0` and `File.cwd!/0` (v1.0)
|
||||
`mix compile.erlang` returning `{:ok, contents}` or `:error` as the callback in `Mix.Compilers.Erlang.compile/6`| [v1.8] | Return `{:ok, contents, warnings}` or `{:error, errors, warnings}`
|
||||
`Code.get_docs/2` | [v1.7] | `Code.fetch_docs/1` (v1.7)
|
||||
Calling `super/1` on GenServer callbacks | [v1.7] | Not calling `super/1` (v1.0)
|
||||
`Enum.chunk/2`[`/3/4`](`Enum.chunk/4`) | [v1.7] | `Enum.chunk_every/2`[`/3/4`](`Enum.chunk_every/4`) (v1.5)
|
||||
@@ -148,4 +150,4 @@ Non-map as second argument in `URI.decode_query/2` | [v1.3] | Use a map (v1
|
||||
[v1.5]: https://github.com/elixir-lang/elixir/blob/v1.5/CHANGELOG.md#4-deprecations
|
||||
[v1.6]: https://github.com/elixir-lang/elixir/blob/v1.6/CHANGELOG.md#4-deprecations
|
||||
[v1.7]: https://github.com/elixir-lang/elixir/blob/v1.7/CHANGELOG.md#4-hard-deprecations
|
||||
[v1.8]: https://github.com/elixir-lang/elixir/blob/master/CHANGELOG.md#4-hard-deprecations
|
||||
[v1.8]: https://github.com/elixir-lang/elixir/blob/v1.8/CHANGELOG.md#4-hard-deprecations
|
||||
|
||||
@@ -21,11 +21,13 @@ Integers (`1234`) and floats (`123.4`) in Elixir are represented as a sequence o
|
||||
|
||||
### Atoms
|
||||
|
||||
Atoms in Elixir start with a colon (`:`) which must be followed by a non-combining Unicode character or underscore. The atom may continue using a sequence of Unicode characters, including letters, numbers, underscore, and `@`. Atoms may end in `!` or `?`. See [Unicode Syntax](unicode-syntax.html) for a formal specification.
|
||||
Unquoted atoms start with a colon (`:`) which must be inmediately followed by an underscore or a Unicode letter. The atom may continue using a sequence of Unicode letters, numbers, underscores, and `@`. Atoms may end in `!` or `?`. See [Unicode Syntax](unicode-syntax.html) for a formal specification. Valid unquoted atoms are: `:ok`, `:ISO8601`, and `:integer?`.
|
||||
|
||||
All operators in Elixir are also valid atoms. Valid examples are `:foo`, `:FOO`, `:foo_42`, `:foo@bar` and `:++`. Invalid examples are `:@foo` (`@` is not allowed at start), `:123` (numbers are not allowed at start) and `:(*)` (not a valid operator).
|
||||
If the colon is immediately followed by a pair of double- or single-quotes surrounding the atom name, the atom is considered quoted. In contrast with an unquoted atom, this one can be made of any Unicode character (not only letters), such as `:'🌢 Elixir'`, `:"++olá++"`, and `:"123"`.
|
||||
|
||||
If the colon is followed by a double- or single-quote, the atom can be made of any character, such as `:"++olá++"`.
|
||||
Quoted and unquoted atoms with the same name are considered equivalent, so `:atom`, `:"atom"`, and `:'atom'` represent the same atom. The only catch is that the compiler will warn when quotes are used in atoms that do not need to be quoted.
|
||||
|
||||
All operators in Elixir are also valid atoms. Valid examples are `:foo`, `:FOO`, `:foo_42`, `:foo@bar`, and `:++`. Invalid examples are `:@foo` (`@` is not allowed at start), `:123` (numbers are not allowed at start), and `:(*)` (not a valid operator).
|
||||
|
||||
`true`, `false`, and `nil` are reserved words that are represented by the atoms `:true`, `:false` and `:nil` respectively.
|
||||
|
||||
@@ -80,13 +82,13 @@ Structs built on the map syntax by passing the struct name between `%` and `{`.
|
||||
|
||||
### Variables
|
||||
|
||||
Variables in Elixir must start with underscore or a non-combining Unicode character that is not in uppercase or titlecase. The variable may continue using a sequence of Unicode characters, including numbers and underscore. Variables may end in `?` or `!`. See [Unicode Syntax](unicode-syntax.html) for a formal specification.
|
||||
Variables in Elixir must start with an underscore or a Unicode letter that is not in uppercase or titlecase. The variable may continue using a sequence of Unicode letters, numbers, and underscores. Variables may end in `?` or `!`. See [Unicode Syntax](unicode-syntax.html) for a formal specification.
|
||||
|
||||
[Elixir's naming conventions](naming-conventions.html) recommend variables to be in `snake_case` format.
|
||||
|
||||
### Non-qualified calls (local calls)
|
||||
|
||||
Non-qualified calls, such as `add(1, 2)`, must start with underscore or a non-combining Unicode character that is not in uppercase or titlecase. The call may continue using a sequence of Unicode characters, including numbers and underscore. Calls may end in `?` or `!`. See [Unicode Syntax](unicode-syntax.html) for a formal specification.
|
||||
Non-qualified calls, such as `add(1, 2)`, must start with an underscore or a Unicode letter that is not in uppercase or titlecase. The call may continue using a sequence of Unicode letters, numbers, and underscore. Calls may end in `?` or `!`. See [Unicode Syntax](unicode-syntax.html) for a formal specification.
|
||||
|
||||
Parentheses for non-qualified calls are optional, except for zero-arity calls, which would then be ambiguous with variables. If parentheses are used, they must immediately follow the function name *without spaces*. For example, `add (1, 2)` is a syntax error, since `(1, 2)` is treated as an invalid block which is attempted to be given as a single argument to `add`.
|
||||
|
||||
@@ -98,7 +100,7 @@ As many programming languages, Elixir also support operators as non-qualified ca
|
||||
|
||||
### Qualified calls (remote calls)
|
||||
|
||||
Qualified calls, such as `Math.add(1, 2)`, must start with underscore or a non-combining Unicode character that is not in uppercase or titlecase. The call may continue using a sequence of Unicode characters, including numbers and underscore. Calls may end in `?` or `!`. See [Unicode Syntax](unicode-syntax.html) for a formal specification.
|
||||
Qualified calls, such as `Math.add(1, 2)`, must start with an underscore or a Unicode letter that is not in uppercase or titlecase. The call may continue using a sequence of Unicode letters, numbers, and underscores. Calls may end in `?` or `!`. See [Unicode Syntax](unicode-syntax.html) for a formal specification.
|
||||
|
||||
[Elixir's naming conventions](naming-conventions.html) recommend calls to be in `snake_case` format.
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@ Documentation in Elixir is usually attached to module attributes. Let's see an e
|
||||
@moduledoc """
|
||||
This is the Hello module.
|
||||
"""
|
||||
@moduledoc since: "1.0.0"
|
||||
|
||||
@doc """
|
||||
Says hello to the given `name`.
|
||||
@@ -29,12 +30,13 @@ Documentation in Elixir is usually attached to module attributes. Let's see an e
|
||||
:ok
|
||||
|
||||
"""
|
||||
@doc since: "1.3.0"
|
||||
def world(name) do
|
||||
IO.puts "hello #{name}"
|
||||
end
|
||||
end
|
||||
|
||||
The `@moduledoc` attribute is used to add documentation to the module. `@doc` is used before a function to provide documentation for it. Besides the attributes above, `@typedoc` can also be used to attach documentation to types defined as part of typespecs.
|
||||
The `@moduledoc` attribute is used to add documentation to the module. `@doc` is used before a function to provide documentation for it. Besides the attributes above, `@typedoc` can also be used to attach documentation to types defined as part of typespecs. Elixir also allows metadata to be attached to documentation, by passing a keyword list to `@doc` and friends.
|
||||
|
||||
## Function Arguments
|
||||
|
||||
@@ -51,6 +53,20 @@ The compiler will infer this argument as `map`. Sometimes the inference will be
|
||||
size
|
||||
end
|
||||
|
||||
## Documentation metadata
|
||||
|
||||
Elixir allows developers to attach arbitrary metadata to the documentation. This is done by passing a keyword list to the relevant attribute (such as `@moduledoc`, `@typedoc`, and `@doc`). A commonly used metadata is `:since`, which annotates in which version that particular module, function, type, or callback was added, as shown in the example above.
|
||||
|
||||
Another common metadata is `:deprecated`, which emits a warning in the documentation, explaining that its usage is discouraged:
|
||||
|
||||
@doc deprecated: "Use Foo.bar/2 instead"
|
||||
|
||||
Note the `:deprecated` key does not warn when a developer invokes the functions. If you want the code to also emit a warning, you can use the `@deprecated` attribute:
|
||||
|
||||
@deprecated "Use Foo.bar/2 instead"
|
||||
|
||||
Metadata can have any key. Documentation tools often use metadata to provide more data to readers and to enrich the user experience.
|
||||
|
||||
## Recommendations
|
||||
|
||||
When writing documentation:
|
||||
@@ -59,7 +75,7 @@ When writing documentation:
|
||||
|
||||
* Reference modules by their full name.
|
||||
|
||||
Markdown uses backticks (`` ` ``) to quote code. Elixir builds on top of that to automatically generate links when module or function names are referenced. For this reason, always use full module names. If you have a module called `MyApp.Hello`, always reference it as `` `MyApp.Hello` `` and never as `` `Hello` ``.
|
||||
Markdown uses backticks (`` ` ``) to quote code. Elixir builds on top of that to automatically generate links when module or function names are referenced. For this reason, always use full module names. If you have a module called `MyApp.Hello`, always reference it as `` `MyApp.Hello` `` and never as `` `Hello` ``.
|
||||
|
||||
* Reference functions by name and arity if they are local, as in `` `world/1` ``, or by module, name and arity if pointing to an external module: `` `MyApp.Hello.world/1` ``.
|
||||
|
||||
@@ -70,6 +86,8 @@ When writing documentation:
|
||||
* Start new sections with second level Markdown headers `##`. First level headers are reserved for module and function names.
|
||||
|
||||
* Place documentation before the first clause of multi-clause functions. Documentation is always per function and arity and not per clause.
|
||||
|
||||
* Use the `:since` key in the documentation metadata to annotate whenever new functions or modules are added to your API.
|
||||
|
||||
## Doctests
|
||||
|
||||
@@ -77,19 +95,26 @@ We recommend that developers include examples in their documentation, often unde
|
||||
|
||||
Notice doctests have limitations. When you cannot doctest a function, because it relies on state or side-effects, we recommend developers include examples directly without the `iex>` prompt.
|
||||
|
||||
## Documentation != Comments
|
||||
## Documentation != Code comments
|
||||
|
||||
Elixir treats documentation and code comments as different concepts. Documentation is for users of your Application Programming Interface (API), be it your co-worker or your future self. Modules and functions must always be documented if they are part of your API.
|
||||
Elixir treats documentation and code comments as different concepts. Documentation is an explicit contract between you and users of your Application Programming Interface (API), be them third-party developers, co-workers, or your future self. Modules and functions must always be documented if they are part of your API.
|
||||
|
||||
Code comments are for developers reading the code. They are useful to mark improvements, leave notes for developers reading the code (for example, you decided not to call a function due to a bug in a library) and so forth.
|
||||
Code comments are aimed at developers reading the code. They are useful for marking improvements, leaving notes (for example, why you had to resort to a workaround due to a bug in a library), and so forth. They are tied to the source code: you can completely rewrite a function and remove all existing code comments, and it will continue to behave the same, with no change to either its behaviour or its documentation.
|
||||
|
||||
In other words: documentation is required, code comments are optional.
|
||||
Because private functions cannot be accessed externally, Elixir will warn if a private function has a `@doc` attribute and will discard its content. However, you can add code comments to private functions, as with any other piece of code, and we recommend developers to do so whenever they believe it will add relevant information to the readers and maintainers of such code.
|
||||
|
||||
Finally, beware of redundant code comments, such as the ones describing the exact same that the code does:
|
||||
|
||||
# Total is the sum of the batch and individual entries
|
||||
total = batch_sum + individual_sum
|
||||
|
||||
In summary, documentation is a contract with users of your API, who may not necessarily have access to the source code; whereas code comments are for those who interact directly with the source. You can learn and express different guarantees about your software by separating those two concepts.
|
||||
|
||||
## Hiding Internal Modules and Functions
|
||||
|
||||
Besides the modules and functions libraries provide as part of their public interface, libraries may also implement important functionality that is not part of their API. While these modules and functions can be accessed, they are meant to be internal to the library and thus should not have documentation for end users.
|
||||
|
||||
Luckily, Elixir allows developers to hide modules and functions from the documentation. For example, one common practice for documenting internal behaviour is to set the `@moduledoc` attribute to `false` while documenting each function:
|
||||
Conveniently, Elixir allows developers to hide modules and functions from the documentation, by setting `@doc false` to hide a particular function, or `@moduledoc false` to hide the whole module. If a module is hidden, you may even document the functions in the module, but the module itself won't be listed in the documentation:
|
||||
|
||||
defmodule MyApp.Hidden do
|
||||
@moduledoc false
|
||||
@@ -102,25 +127,12 @@ Luckily, Elixir allows developers to hide modules and functions from the documen
|
||||
end
|
||||
end
|
||||
|
||||
Similarly, developers can add `@doc false` to functions they do not want to be publicly exposed:
|
||||
|
||||
defmodule MyApp.Sample do
|
||||
@doc false
|
||||
def add(a, b), do: a + b
|
||||
end
|
||||
|
||||
However, keep in mind that adding `@doc false` does not make the function private. The function above can still be invoked as `MyApp.Sample.add(1, 2)`. Not only that, if `MyApp.Sample` is imported, the `add/2` function will also be imported into the caller. For those reasons, be cautious when adding `@doc false` to functions, instead use one of these two options:
|
||||
|
||||
* Move the undocumented function to a module with `@moduledoc false`, like `MyApp.Hidden`, ensuring the function won't be accidentally exposed or imported. Remember you can use `@moduledoc false` to hide a whole module and still document each function with `@doc`. Tools will still ignore the module.
|
||||
|
||||
* Start the function name with one or two underscores, for example, `__add__/2`, and add `@doc false`. The compiler does not import functions with leading underscores and they hint to anyone reading the code of their intended private usage.
|
||||
|
||||
## Documenting Private Functions
|
||||
|
||||
Elixir warns if a private function has a `@doc` attribute and discards its content, because `@doc` is intended to be used only for your public interface.
|
||||
|
||||
Private functions may still need internal documentation for maintainers, though. That can be accomplished with code comments.
|
||||
|
||||
## Code.fetch_docs/1
|
||||
|
||||
Elixir stores documentation inside pre-defined chunks in the bytecode. It can be accessed from Elixir by using the `Code.fetch_docs/1` function. This also means documentation is only accessed when required and not when modules are loaded by the Virtual Machine. The only downside is that modules defined in-memory, like the ones defined in IEx, cannot have their documentation accessed as they do not have their bytecode written to disk.
|
||||
|
||||
@@ -7,7 +7,7 @@ expand(Meta, Args, E, RequireSize) ->
|
||||
case ?key(E, context) of
|
||||
match ->
|
||||
{EArgs, Alignment, EE} =
|
||||
expand(Meta, fun elixir_expand:expand/2, Args, [], E, 0, RequireSize),
|
||||
expand(Meta, fun elixir_expand:expand/2, Args, [], E, E, 0, RequireSize),
|
||||
|
||||
case find_match(EArgs) of
|
||||
false ->
|
||||
@@ -17,13 +17,13 @@ expand(Meta, Args, E, RequireSize) ->
|
||||
end;
|
||||
_ ->
|
||||
{EArgs, Alignment, {_EC, EV}} =
|
||||
expand(Meta, fun elixir_expand:expand_arg/2, Args, [], {E, E}, 0, RequireSize),
|
||||
expand(Meta, fun elixir_expand:expand_arg/2, Args, [], {E, E}, E, 0, RequireSize),
|
||||
{{'<<>>', [{alignment, Alignment} | Meta], EArgs}, EV}
|
||||
end.
|
||||
|
||||
expand(_BitstrMeta, _Fun, [], Acc, E, Alignment, _RequireSize) ->
|
||||
expand(_BitstrMeta, _Fun, [], Acc, E, _OriginalE, Alignment, _RequireSize) ->
|
||||
{lists:reverse(Acc), Alignment, E};
|
||||
expand(BitstrMeta, Fun, [{'::', Meta, [Left, Right]} | T], Acc, E, Alignment, RequireSize) ->
|
||||
expand(BitstrMeta, Fun, [{'::', Meta, [Left, Right]} | T], Acc, E, OriginalE, Alignment, RequireSize) ->
|
||||
{ELeft, EL} = expand_expr(Meta, Left, Fun, E),
|
||||
|
||||
%% Variables defined outside the binary can be accounted
|
||||
@@ -38,7 +38,7 @@ expand(BitstrMeta, Fun, [{'::', Meta, [Left, Right]} | T], Acc, E, Alignment, Re
|
||||
end,
|
||||
|
||||
EType = expr_type(ELeft),
|
||||
{ERight, EAlignment, ES} = expand_specs(EType, Meta, Right, EM, RequireSize or MatchSize),
|
||||
{ERight, EAlignment, ES} = expand_specs(EType, Meta, Right, EM, OriginalE, RequireSize or MatchSize),
|
||||
|
||||
EE =
|
||||
case EL of
|
||||
@@ -65,15 +65,15 @@ expand(BitstrMeta, Fun, [{'::', Meta, [Left, Right]} | T], Acc, E, Alignment, Re
|
||||
prepend_unless_bitstring_in_match(EType, Meta, ELeft, ERight, Acc, E)
|
||||
end,
|
||||
|
||||
expand(BitstrMeta, Fun, T, EAcc, EE, alignment(Alignment, EAlignment), RequireSize);
|
||||
expand(BitstrMeta, Fun, [{_, Meta, _} = H | T], Acc, E, Alignment, RequireSize) ->
|
||||
expand(BitstrMeta, Fun, T, EAcc, EE, OriginalE, alignment(Alignment, EAlignment), RequireSize);
|
||||
expand(BitstrMeta, Fun, [{_, Meta, _} = H | T], Acc, E, OriginalE, Alignment, RequireSize) ->
|
||||
{Expr, ES} = expand_expr(Meta, H, Fun, E),
|
||||
{EAcc, EAlignment} = wrap_expr(Expr, Acc),
|
||||
expand(BitstrMeta, Fun, T, EAcc, ES, alignment(Alignment, EAlignment), RequireSize);
|
||||
expand(Meta, Fun, [H | T], Acc, E, Alignment, RequireSize) ->
|
||||
expand(BitstrMeta, Fun, T, EAcc, ES, OriginalE, alignment(Alignment, EAlignment), RequireSize);
|
||||
expand(Meta, Fun, [H | T], Acc, E, OriginalE, Alignment, RequireSize) ->
|
||||
{Expr, ES} = expand_expr(Meta, H, Fun, E),
|
||||
{EAcc, EAlignment} = wrap_expr(Expr, Acc),
|
||||
expand(Meta, Fun, T, EAcc, ES, alignment(Alignment, EAlignment), RequireSize).
|
||||
expand(Meta, Fun, T, EAcc, ES, OriginalE, alignment(Alignment, EAlignment), RequireSize).
|
||||
|
||||
prepend_unless_bitstring_in_match(Type, Meta, Left, Right, Acc, E) ->
|
||||
Expr = {'::', Meta, [Left, Right]},
|
||||
@@ -157,7 +157,7 @@ env_for_error(E) -> E.
|
||||
|
||||
%% Expands and normalizes types of a bitstring.
|
||||
|
||||
expand_specs(ExprType, Meta, Info, E, RequireSize) ->
|
||||
expand_specs(ExprType, Meta, Info, E, OriginalE, RequireSize) ->
|
||||
Default =
|
||||
#{size => default,
|
||||
unit => default,
|
||||
@@ -165,7 +165,7 @@ expand_specs(ExprType, Meta, Info, E, RequireSize) ->
|
||||
type => default,
|
||||
endianness => default},
|
||||
{#{size := Size, unit := Unit, type := Type, endianness := Endianness, sign := Sign}, ES} =
|
||||
expand_each_spec(Meta, unpack_specs(Info, []), Default, E),
|
||||
expand_each_spec(Meta, unpack_specs(Info, []), Default, E, OriginalE),
|
||||
MergedType = type(Meta, ExprType, Type, E),
|
||||
validate_size_required(Meta, RequireSize, ExprType, MergedType, Size, ES),
|
||||
SizeAndUnit = size_and_unit(Meta, ExprType, Size, Unit, ES),
|
||||
@@ -190,11 +190,11 @@ type(_, default, Type, _) ->
|
||||
type(Meta, Other, Value, E) ->
|
||||
form_error(Meta, ?key(E, file), ?MODULE, {bittype_mismatch, Value, Other, type}).
|
||||
|
||||
expand_each_spec(Meta, [{Expr, _, Args} = H | T], Map, E) when is_atom(Expr) ->
|
||||
expand_each_spec(Meta, [{Expr, _, Args} = H | T], Map, E, OriginalE) when is_atom(Expr) ->
|
||||
case validate_spec(Expr, Args) of
|
||||
{Key, Arg} ->
|
||||
{Value, EE} = expand_spec_arg(Arg, E),
|
||||
validate_spec_arg(Meta, Key, Value, EE),
|
||||
validate_spec_arg(Meta, Key, Value, EE, OriginalE),
|
||||
|
||||
case maps:get(Key, Map) of
|
||||
default -> ok;
|
||||
@@ -202,18 +202,18 @@ expand_each_spec(Meta, [{Expr, _, Args} = H | T], Map, E) when is_atom(Expr) ->
|
||||
Other -> form_error(Meta, ?key(E, file), ?MODULE, {bittype_mismatch, Value, Other, Key})
|
||||
end,
|
||||
|
||||
expand_each_spec(Meta, T, maps:put(Key, Value, Map), EE);
|
||||
expand_each_spec(Meta, T, maps:put(Key, Value, Map), EE, OriginalE);
|
||||
none ->
|
||||
case 'Elixir.Macro':expand(H, elixir_env:linify({?line(Meta), E})) of
|
||||
H ->
|
||||
form_error(Meta, ?key(E, file), ?MODULE, {undefined_bittype, H});
|
||||
NewTypes ->
|
||||
expand_each_spec(Meta, unpack_specs(NewTypes, []) ++ T, Map, E)
|
||||
expand_each_spec(Meta, unpack_specs(NewTypes, []) ++ T, Map, E, OriginalE)
|
||||
end
|
||||
end;
|
||||
expand_each_spec(Meta, [Expr | _], _Map, E) ->
|
||||
expand_each_spec(Meta, [Expr | _], _Map, E, _OriginalE) ->
|
||||
form_error(Meta, ?key(E, file), ?MODULE, {undefined_bittype, Expr});
|
||||
expand_each_spec(_Meta, [], Map, E) ->
|
||||
expand_each_spec(_Meta, [], Map, E, _OriginalE) ->
|
||||
{Map, E}.
|
||||
|
||||
unpack_specs({'-', _, [H, T]}, Acc) ->
|
||||
@@ -251,17 +251,37 @@ validate_spec(_, _) -> none.
|
||||
expand_spec_arg(Expr, E) when is_atom(Expr); is_integer(Expr) -> {Expr, E};
|
||||
expand_spec_arg(Expr, E) -> elixir_expand:expand(Expr, E).
|
||||
|
||||
validate_spec_arg(Meta, size, Value, E) ->
|
||||
validate_spec_arg(Meta, size, Value, E, OriginalE) ->
|
||||
case Value of
|
||||
{Var, _, Context} when is_atom(Var) and is_atom(Context) -> ok;
|
||||
_ when is_integer(Value) -> ok;
|
||||
_ -> form_error(Meta, ?key(E, file), ?MODULE, {bad_size_argument, Value})
|
||||
{Var, VarMeta, Context} when is_atom(Var) and is_atom(Context) ->
|
||||
Tuple = {Var, elixir_utils:var_context(VarMeta, Context)},
|
||||
|
||||
case is_valid_spec_arg_var(Tuple, E, OriginalE) of
|
||||
true -> ok;
|
||||
false -> form_error(Meta, ?key(E, file), ?MODULE, {undefined_var_in_spec, Value})
|
||||
end;
|
||||
|
||||
_ when is_integer(Value) ->
|
||||
ok;
|
||||
|
||||
_ ->
|
||||
form_error(Meta, ?key(E, file), ?MODULE, {bad_size_argument, Value})
|
||||
end;
|
||||
validate_spec_arg(Meta, unit, Value, E) when not is_integer(Value) ->
|
||||
validate_spec_arg(Meta, unit, Value, E, _OriginalE) when not is_integer(Value) ->
|
||||
form_error(Meta, ?key(E, file), ?MODULE, {bad_unit_argument, Value});
|
||||
validate_spec_arg(_Meta, _Key, _Value, _E) ->
|
||||
validate_spec_arg(_Meta, _Key, _Value, _E, _OriginalE) ->
|
||||
ok.
|
||||
|
||||
is_valid_spec_arg_var(Var, E, #{context := match} = OriginalE) ->
|
||||
case ?key(OriginalE, prematch_vars) of
|
||||
#{Var := _} ->
|
||||
true;
|
||||
_ ->
|
||||
maps:is_key(Var, ?key(E, current_vars)) andalso
|
||||
not maps:is_key(Var, ?key(OriginalE, current_vars))
|
||||
end;
|
||||
is_valid_spec_arg_var(_Var, _E, _OriginalE) -> true.
|
||||
|
||||
validate_size_required(Meta, true, default, Type, default, E) when Type == binary; Type == bitstring ->
|
||||
form_error(Meta, ?key(E, file), ?MODULE, unsized_binary);
|
||||
validate_size_required(_, _, _, _, _, _) ->
|
||||
@@ -375,4 +395,10 @@ format_error({nested_match, Expr}) ->
|
||||
Message =
|
||||
"cannot pattern match inside a bitstring "
|
||||
"that is already in match, got: ~ts",
|
||||
io_lib:format(Message, ['Elixir.Macro':to_string(Expr)]).
|
||||
io_lib:format(Message, ['Elixir.Macro':to_string(Expr)]);
|
||||
format_error({undefined_var_in_spec, Var}) ->
|
||||
Message =
|
||||
"undefined variable \"~ts\" in bitstring segment. If the size of the binary is a "
|
||||
"variable, the variable must be defined prior to its use in the binary/bitstring match "
|
||||
"itself, or outside the pattern match",
|
||||
io_lib:format(Message, ['Elixir.Macro':to_string(Var)]).
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
-export([elixir_to_erl/1, definition_to_anonymous/4, compile/1, consolidate/3,
|
||||
get_ann/1, debug_info/4, scope/1, format_error/1]).
|
||||
-include("elixir.hrl").
|
||||
-define(typespecs, 'Elixir.Kernel.Typespec').
|
||||
|
||||
%% debug_info callback
|
||||
|
||||
@@ -131,9 +132,10 @@ compile(#{module := Module} = Map) ->
|
||||
{Set, Bag} = elixir_module:data_tables(Module),
|
||||
|
||||
TranslatedTypespecs =
|
||||
case elixir_config:get(bootstrap) of
|
||||
case elixir_config:get(bootstrap) andalso
|
||||
(code:ensure_loaded(?typespecs) /= {module, ?typespecs}) of
|
||||
true -> {[], [], [], [], []};
|
||||
false -> 'Elixir.Kernel.Typespec':translate_typespecs_for_module(Set, Bag)
|
||||
false -> ?typespecs:translate_typespecs_for_module(Set, Bag)
|
||||
end,
|
||||
|
||||
elixir_erl_compiler:spawn(fun spawned_compile/4, [Map, Set, Bag, TranslatedTypespecs]).
|
||||
@@ -444,10 +446,10 @@ debug_opts(Map, Specs, Opts) ->
|
||||
end.
|
||||
|
||||
take_debug_opts(Opts) ->
|
||||
case lists:keytake(debug_info, 1, Opts) of
|
||||
{value, {debug_info, true}, Rest} -> {true, Rest};
|
||||
{value, {debug_info, false}, Rest} -> {false, Rest};
|
||||
false -> {elixir_compiler:get_opt(debug_info), Opts}
|
||||
case proplists:get_value(debug_info, Opts) of
|
||||
true -> {true, proplists:delete(debug_info, Opts)};
|
||||
false -> {false, proplists:delete(debug_info, Opts)};
|
||||
undefined -> {elixir_compiler:get_opt(debug_info), Opts}
|
||||
end.
|
||||
|
||||
extra_chunks_opts([], Opts) -> Opts;
|
||||
@@ -531,7 +533,7 @@ get_type_docs(Set, Types) ->
|
||||
signature_to_binary(_Module, Name, _Signature) when Name == '__aliases__'; Name == '__block__' ->
|
||||
<<(atom_to_binary(Name, utf8))/binary, "(args)">>;
|
||||
|
||||
signature_to_binary(_Module, fn, _Signature) ->
|
||||
signature_to_binary(_Module, fn, _Signature) ->
|
||||
<<"fn">>;
|
||||
|
||||
signature_to_binary(_Module, Name, _Signature)
|
||||
|
||||
@@ -178,7 +178,7 @@ translate({with, Meta, [_ | _] = Args}, S) ->
|
||||
%% Variables
|
||||
|
||||
translate({'^', Meta, [{Name, VarMeta, Kind}]}, #elixir_erl{context=match} = S) when is_atom(Name), is_atom(Kind) ->
|
||||
Tuple = {Name, var_context(VarMeta, Kind)},
|
||||
Tuple = {Name, elixir_utils:var_context(VarMeta, Kind)},
|
||||
{ok, {_Counter, Value}} = maps:find(Tuple, S#elixir_erl.backup_vars),
|
||||
|
||||
PAnn = ?ann(?generated(Meta)),
|
||||
@@ -186,7 +186,7 @@ translate({'^', Meta, [{Name, VarMeta, Kind}]}, #elixir_erl{context=match} = S)
|
||||
|
||||
case S#elixir_erl.extra of
|
||||
pin_guard ->
|
||||
{TVar, TS} = elixir_erl_var:translate(VarMeta, Name, var_context(VarMeta, Kind), S),
|
||||
{TVar, TS} = elixir_erl_var:translate(VarMeta, Name, elixir_utils:var_context(VarMeta, Kind), S),
|
||||
Guard = {op, PAnn, '=:=', PVar, TVar},
|
||||
{TVar, TS#elixir_erl{extra_guards=[Guard | TS#elixir_erl.extra_guards]}};
|
||||
_ ->
|
||||
@@ -197,7 +197,7 @@ translate({'_', Meta, Kind}, #elixir_erl{context=match} = S) when is_atom(Kind)
|
||||
{{var, ?ann(Meta), '_'}, S};
|
||||
|
||||
translate({Name, Meta, Kind}, S) when is_atom(Name), is_atom(Kind) ->
|
||||
elixir_erl_var:translate(Meta, Name, var_context(Meta, Kind), S);
|
||||
elixir_erl_var:translate(Meta, Name, elixir_utils:var_context(Meta, Kind), S);
|
||||
|
||||
%% Local calls
|
||||
|
||||
@@ -283,12 +283,6 @@ build_list([H | T], Acc) ->
|
||||
build_list([], Acc) ->
|
||||
Acc.
|
||||
|
||||
var_context(Meta, Kind) ->
|
||||
case lists:keyfind(counter, 1, Meta) of
|
||||
{counter, Counter} -> Counter;
|
||||
false -> Kind
|
||||
end.
|
||||
|
||||
%% Pack a list of expressions from a block.
|
||||
unblock({'block', _, Exprs}) -> Exprs;
|
||||
unblock(Expr) -> [Expr].
|
||||
|
||||
@@ -353,7 +353,7 @@ expand({'_', Meta, Kind}, E) when is_atom(Kind) ->
|
||||
|
||||
expand({Name, Meta, Kind} = Var, #{context := match} = E) when is_atom(Name), is_atom(Kind) ->
|
||||
#{unused_vars := Unused, current_vars := Current, prematch_vars := Prematch} = E,
|
||||
Pair = {Name, var_context(Meta, Kind)},
|
||||
Pair = {Name, elixir_utils:var_context(Meta, Kind)},
|
||||
PrematchVersion = var_version(Prematch, Pair),
|
||||
|
||||
EE =
|
||||
@@ -381,7 +381,7 @@ expand({Name, Meta, Kind} = Var, #{context := match} = E) when is_atom(Name), is
|
||||
{Var, EE};
|
||||
expand({Name, Meta, Kind} = Var, E) when is_atom(Name), is_atom(Kind) ->
|
||||
#{unused_vars := Unused, current_vars := Current} = E,
|
||||
Pair = {Name, var_context(Meta, Kind)},
|
||||
Pair = {Name, elixir_utils:var_context(Meta, Kind)},
|
||||
|
||||
case Current of
|
||||
#{Pair := {Version, _}} ->
|
||||
@@ -618,12 +618,6 @@ var_version(Map, Pair) ->
|
||||
_ -> -1
|
||||
end.
|
||||
|
||||
var_context(Meta, Kind) ->
|
||||
case lists:keyfind(counter, 1, Meta) of
|
||||
{counter, Counter} -> Counter;
|
||||
false -> Kind
|
||||
end.
|
||||
|
||||
maybe_warn_underscored_var_repeat(Meta, Name, Kind, E) ->
|
||||
case should_warn(Meta) andalso atom_to_list(Name) of
|
||||
"_" ++ _ ->
|
||||
|
||||
@@ -183,14 +183,13 @@ validate_on_load_attribute(false, _Defs, _File, _Line) -> ok.
|
||||
%% exception message suggesting the current module is not loaded. This is
|
||||
%% misleading so use a custom reason.
|
||||
compile_undef(Module, Fun, Arity, Stack) ->
|
||||
ExMod = 'Elixir.UndefinedFunctionError',
|
||||
case code:ensure_loaded(ExMod) of
|
||||
{module, _} ->
|
||||
case elixir_config:get(bootstrap) of
|
||||
false ->
|
||||
Opts = [{module, Module}, {function, Fun}, {arity, Arity},
|
||||
{reason, 'function not available'}],
|
||||
Exception = 'Elixir.UndefinedFunctionError':exception(Opts),
|
||||
erlang:raise(error, Exception, Stack);
|
||||
{_, _} ->
|
||||
true ->
|
||||
erlang:raise(error, undef, Stack)
|
||||
end.
|
||||
|
||||
|
||||
@@ -140,9 +140,8 @@ annotate(Tree, _Context) -> Tree.
|
||||
|
||||
has_unquotes({unquote, _, [_]}) -> true;
|
||||
has_unquotes({unquote_splicing, _, [_]}) -> true;
|
||||
has_unquotes({{'.', _, [_, unquote]}, _, [_]}) -> true;
|
||||
has_unquotes({Var, _, Ctx}) when is_atom(Var), is_atom(Ctx) -> false;
|
||||
has_unquotes({Name, _, Args}) when is_atom(Name), is_list(Args) ->
|
||||
lists:any(fun has_unquotes/1, Args);
|
||||
has_unquotes({Name, _, Args}) when is_list(Args) ->
|
||||
has_unquotes(Name) orelse lists:any(fun has_unquotes/1, Args);
|
||||
has_unquotes({Left, Right}) ->
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
%% Convenience functions used throughout elixir source code
|
||||
%% for ast manipulation and querying.
|
||||
-module(elixir_utils).
|
||||
-export([get_line/1, split_last/1, noop/0,
|
||||
-export([get_line/1, split_last/1, noop/0, var_context/2,
|
||||
characters_to_list/1, characters_to_binary/1, relative_to_cwd/1,
|
||||
macro_name/1, returns_boolean/1, caller/4, meta_keep/1,
|
||||
read_file_type/1, read_file_type/2, read_link_type/1, read_posix_mtime_and_size/1,
|
||||
@@ -37,6 +37,12 @@ erlang_comparison_op_to_elixir('=:=') -> '===';
|
||||
erlang_comparison_op_to_elixir('=/=') -> '!==';
|
||||
erlang_comparison_op_to_elixir(Other) -> Other.
|
||||
|
||||
var_context(Meta, Kind) ->
|
||||
case lists:keyfind(counter, 1, Meta) of
|
||||
{counter, Counter} -> Counter;
|
||||
false -> Kind
|
||||
end.
|
||||
|
||||
% Extract guards
|
||||
|
||||
extract_guards({'when', _, [Left, Right]}) -> {Left, extract_or_guards(Right)};
|
||||
|
||||
@@ -533,31 +533,31 @@ defmodule ExceptionTest do
|
||||
test "annotates band arithmetic errors" do
|
||||
use Bitwise
|
||||
|
||||
assert blame_message(:foo, &band(10, &1)) ==
|
||||
"bad argument in arithmetic expression: Bitwise.band(10, :foo)"
|
||||
assert blame_message(:foo, &band(&1, 10)) ==
|
||||
"bad argument in arithmetic expression: Bitwise.band(:foo, 10)"
|
||||
|
||||
assert blame_message(:foo, &(10 &&& &1)) ==
|
||||
"bad argument in arithmetic expression: Bitwise.band(10, :foo)"
|
||||
assert blame_message(:foo, &(&1 &&& 10)) ==
|
||||
"bad argument in arithmetic expression: Bitwise.band(:foo, 10)"
|
||||
end
|
||||
|
||||
test "annotates bor arithmetic errors" do
|
||||
use Bitwise
|
||||
|
||||
assert blame_message(:foo, &bor(10, &1)) ==
|
||||
"bad argument in arithmetic expression: Bitwise.bor(10, :foo)"
|
||||
assert blame_message(:foo, &bor(&1, 10)) ==
|
||||
"bad argument in arithmetic expression: Bitwise.bor(:foo, 10)"
|
||||
|
||||
assert blame_message(:foo, &(10 ||| &1)) ==
|
||||
"bad argument in arithmetic expression: Bitwise.bor(10, :foo)"
|
||||
assert blame_message(:foo, &(&1 ||| 10)) ==
|
||||
"bad argument in arithmetic expression: Bitwise.bor(:foo, 10)"
|
||||
end
|
||||
|
||||
test "annotates bxor arithmetic errors" do
|
||||
use Bitwise
|
||||
|
||||
assert blame_message(:foo, &bxor(10, &1)) ==
|
||||
"bad argument in arithmetic expression: Bitwise.bxor(10, :foo)"
|
||||
assert blame_message(:foo, &bxor(&1, 10)) ==
|
||||
"bad argument in arithmetic expression: Bitwise.bxor(:foo, 10)"
|
||||
|
||||
assert blame_message(:foo, &(10 ^^^ &1)) ==
|
||||
"bad argument in arithmetic expression: Bitwise.bxor(10, :foo)"
|
||||
assert blame_message(:foo, &(&1 ^^^ 10)) ==
|
||||
"bad argument in arithmetic expression: Bitwise.bxor(:foo, 10)"
|
||||
end
|
||||
|
||||
test "annotates bsl arithmetic errors" do
|
||||
@@ -739,7 +739,8 @@ defmodule ExceptionTest do
|
||||
@behaviour Behaviour
|
||||
end
|
||||
|
||||
assert %UndefinedFunctionError{module: Implementation, function: :callback, arity: 0}
|
||||
assert Exception.blame(:error, :undef, [{Implementation, :callback, 0, []}])
|
||||
|> elem(0)
|
||||
|> message ==
|
||||
"function ExceptionTest.Implementation.callback/0 is undefined or private" <>
|
||||
", but the behaviour ExceptionTest.Behaviour expects it to be present"
|
||||
|
||||
@@ -55,21 +55,41 @@ defmodule FloatTest do
|
||||
assert Float.floor(1.32453e-10) === 0.0
|
||||
end
|
||||
|
||||
test "floor/2 with precision" do
|
||||
assert Float.floor(12.524235, 0) === 12.0
|
||||
assert Float.floor(-12.524235, 0) === -13.0
|
||||
describe "floor/2" do
|
||||
test "with 0.0" do
|
||||
for precision <- 0..15 do
|
||||
assert Float.floor(0.0, precision) === 0.0
|
||||
assert Float.floor(-0.0, precision) === -0.0
|
||||
end
|
||||
end
|
||||
|
||||
assert Float.floor(12.52, 2) === 12.51
|
||||
assert Float.floor(-12.52, 2) === -12.52
|
||||
test "floor/2 with precision" do
|
||||
assert Float.floor(12.524235, 0) === 12.0
|
||||
assert Float.floor(-12.524235, 0) === -13.0
|
||||
|
||||
assert Float.floor(12.524235, 2) === 12.52
|
||||
assert Float.floor(-12.524235, 3) === -12.525
|
||||
assert Float.floor(12.52, 2) === 12.51
|
||||
assert Float.floor(-12.52, 2) === -12.52
|
||||
|
||||
assert Float.floor(12.32453e-20, 2) === 0.0
|
||||
assert Float.floor(-12.32453e-20, 2) === -0.01
|
||||
assert Float.floor(12.524235, 2) === 12.52
|
||||
assert Float.floor(-12.524235, 3) === -12.525
|
||||
|
||||
assert_raise ArgumentError, "precision 16 is out of valid range of 0..15", fn ->
|
||||
Float.floor(1.1, 16)
|
||||
assert Float.floor(12.32453e-20, 2) === 0.0
|
||||
assert Float.floor(-12.32453e-20, 2) === -0.01
|
||||
|
||||
assert_raise ArgumentError, "precision 16 is out of valid range of 0..15", fn ->
|
||||
Float.floor(1.1, 16)
|
||||
end
|
||||
end
|
||||
|
||||
test "with subnormal floats" do
|
||||
assert Float.floor(-5.0e-324, 0) === -1.0
|
||||
assert Float.floor(-5.0e-324, 1) === -0.1
|
||||
assert Float.floor(-5.0e-324, 2) === -0.01
|
||||
assert Float.floor(-5.0e-324, 15) === -0.000000000000001
|
||||
|
||||
for precision <- 0..15 do
|
||||
assert Float.floor(5.0e-324, precision) === 0.0
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -88,36 +108,72 @@ defmodule FloatTest do
|
||||
assert Float.ceil(0.0) === 0.0
|
||||
end
|
||||
|
||||
test "ceil/2 with precision" do
|
||||
assert Float.ceil(12.524235, 0) === 13.0
|
||||
assert Float.ceil(-12.524235, 0) === -12.0
|
||||
describe "ceil/2" do
|
||||
test "with 0.0" do
|
||||
for precision <- 0..15 do
|
||||
assert Float.ceil(0.0, precision) === 0.0
|
||||
assert Float.ceil(-0.0, precision) === -0.0
|
||||
end
|
||||
end
|
||||
|
||||
assert Float.ceil(12.52, 2) === 12.52
|
||||
assert Float.ceil(-12.52, 2) === -12.51
|
||||
test "with regular floats" do
|
||||
assert Float.ceil(12.524235, 0) === 13.0
|
||||
assert Float.ceil(-12.524235, 0) === -12.0
|
||||
|
||||
assert Float.ceil(12.524235, 2) === 12.53
|
||||
assert Float.ceil(-12.524235, 3) === -12.524
|
||||
assert Float.ceil(12.52, 2) === 12.52
|
||||
assert Float.ceil(-12.52, 2) === -12.51
|
||||
|
||||
assert Float.ceil(12.32453e-20, 2) === 0.01
|
||||
assert Float.ceil(-12.32453e-20, 2) === 0.0
|
||||
assert Float.ceil(12.524235, 2) === 12.53
|
||||
assert Float.ceil(-12.524235, 3) === -12.524
|
||||
|
||||
assert Float.ceil(0.0, 2) === 0.0
|
||||
assert Float.ceil(12.32453e-20, 2) === 0.01
|
||||
assert Float.ceil(-12.32453e-20, 2) === 0.0
|
||||
|
||||
assert_raise ArgumentError, "precision 16 is out of valid range of 0..15", fn ->
|
||||
Float.ceil(1.1, 16)
|
||||
assert Float.ceil(0.0, 2) === 0.0
|
||||
|
||||
assert_raise ArgumentError, "precision 16 is out of valid range of 0..15", fn ->
|
||||
Float.ceil(1.1, 16)
|
||||
end
|
||||
end
|
||||
|
||||
test "with subnormal floats" do
|
||||
assert Float.ceil(5.0e-324, 0) === 1.0
|
||||
assert Float.ceil(5.0e-324, 1) === 0.1
|
||||
assert Float.ceil(5.0e-324, 2) === 0.01
|
||||
assert Float.ceil(5.0e-324, 15) === 0.000000000000001
|
||||
|
||||
for precision <- 0..15 do
|
||||
assert Float.ceil(-5.0e-324, precision) === -0.0
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
test "round/2" do
|
||||
assert Float.round(5.5675, 3) === 5.567
|
||||
assert Float.round(-5.5674, 3) === -5.567
|
||||
assert Float.round(5.5, 3) === 5.5
|
||||
assert Float.round(5.5e-10, 10) === 5.0e-10
|
||||
assert Float.round(5.5e-10, 8) === 0.0
|
||||
assert Float.round(5.0, 0) === 5.0
|
||||
describe "round/2" do
|
||||
test "with 0.0" do
|
||||
for precision <- 0..15 do
|
||||
assert Float.round(0.0, precision) === 0.0
|
||||
assert Float.round(-0.0, precision) === -0.0
|
||||
end
|
||||
end
|
||||
|
||||
assert_raise ArgumentError, "precision 16 is out of valid range of 0..15", fn ->
|
||||
Float.round(1.1, 16)
|
||||
test "with regular floats" do
|
||||
assert Float.round(5.5675, 3) === 5.567
|
||||
assert Float.round(-5.5674, 3) === -5.567
|
||||
assert Float.round(5.5, 3) === 5.5
|
||||
assert Float.round(5.5e-10, 10) === 5.0e-10
|
||||
assert Float.round(5.5e-10, 8) === 0.0
|
||||
assert Float.round(5.0, 0) === 5.0
|
||||
|
||||
assert_raise ArgumentError, "precision 16 is out of valid range of 0..15", fn ->
|
||||
Float.round(1.1, 16)
|
||||
end
|
||||
end
|
||||
|
||||
test "with subnormal floats" do
|
||||
for precision <- 0..15 do
|
||||
assert Float.round(5.0e-324, precision) === 0.0
|
||||
assert Float.round(-5.0e-324, precision) === -0.0
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -126,6 +182,12 @@ defmodule FloatTest do
|
||||
assert Float.ratio(0.0) == {0, 1}
|
||||
end
|
||||
|
||||
test "with regular floats" do
|
||||
assert Float.ratio(3.14) == {7_070_651_414_971_679, 2_251_799_813_685_248}
|
||||
assert Float.ratio(-3.14) == {-7_070_651_414_971_679, 2_251_799_813_685_248}
|
||||
assert Float.ratio(1.5) == {3, 2}
|
||||
end
|
||||
|
||||
test "with subnormal floats" do
|
||||
assert Float.ratio(5.0e-324) ==
|
||||
{1,
|
||||
|
||||
@@ -2160,6 +2160,17 @@ defmodule Kernel.ExpansionTest do
|
||||
assert expand(before_expansion) |> clean_meta([:alignment]) == after_expansion
|
||||
end
|
||||
|
||||
defmacro offset(size, binary) do
|
||||
quote do
|
||||
offset = unquote(size)
|
||||
<<_::size(offset)>> = unquote(binary)
|
||||
end
|
||||
end
|
||||
|
||||
test "supports size from counters" do
|
||||
assert offset(8, <<0>>)
|
||||
end
|
||||
|
||||
test "merges bitstrings" do
|
||||
import Kernel, except: [-: 2]
|
||||
|
||||
@@ -2233,6 +2244,44 @@ defmodule Kernel.ExpansionTest do
|
||||
assert_raise CompileError, message, fn ->
|
||||
expand(quote(do: <<"foo"::size(:oops)>>))
|
||||
end
|
||||
|
||||
assert_raise CompileError, ~r/undefined variable "foo"/, fn ->
|
||||
code =
|
||||
quote do
|
||||
fn <<_::size(foo)>> -> :ok end
|
||||
end
|
||||
|
||||
expand(code)
|
||||
end
|
||||
|
||||
assert_raise CompileError, ~r/undefined variable "foo"/, fn ->
|
||||
code =
|
||||
quote do
|
||||
fn <<_::size(foo), foo::size(8)>> -> :ok end
|
||||
end
|
||||
|
||||
expand(code)
|
||||
end
|
||||
|
||||
assert_raise CompileError, ~r/undefined variable "foo" in bitstring segment/, fn ->
|
||||
code =
|
||||
quote do
|
||||
fn foo, <<_::size(foo)>> -> :ok end
|
||||
end
|
||||
|
||||
expand(code)
|
||||
end
|
||||
|
||||
message = ~r"size in bitstring expects an integer or a variable as argument, got: foo()"
|
||||
|
||||
assert_raise CompileError, message, fn ->
|
||||
code =
|
||||
quote do
|
||||
fn <<_::size(foo())>> -> :ok end
|
||||
end
|
||||
|
||||
expand(code)
|
||||
end
|
||||
end
|
||||
|
||||
test "raises for invalid unit" do
|
||||
@@ -2384,26 +2433,6 @@ defmodule Kernel.ExpansionTest do
|
||||
|
||||
expand(code)
|
||||
end
|
||||
|
||||
assert_raise CompileError, ~r/undefined variable "foo"/, fn ->
|
||||
code =
|
||||
quote do
|
||||
fn <<_::size(foo)>> -> :ok end
|
||||
end
|
||||
|
||||
expand(code)
|
||||
end
|
||||
|
||||
message = ~r"size in bitstring expects an integer or a variable as argument, got: foo()"
|
||||
|
||||
assert_raise CompileError, message, fn ->
|
||||
code =
|
||||
quote do
|
||||
fn <<_::size(foo())>> -> :ok end
|
||||
end
|
||||
|
||||
expand(code)
|
||||
end
|
||||
end
|
||||
|
||||
## Helpers
|
||||
|
||||
@@ -91,16 +91,24 @@ defmodule Kernel.QuoteTest do
|
||||
assert nested_quote_in_macro() == 1
|
||||
end
|
||||
|
||||
Enum.each([foo: 1, bar: 2, baz: 3], fn {k, v} ->
|
||||
def unquote(k)(arg) do
|
||||
unquote(v) + arg
|
||||
defmodule Dyn do
|
||||
for {k, v} <- [foo: 1, bar: 2, baz: 3] do
|
||||
# Local call unquote
|
||||
def unquote(k)(), do: unquote(v)
|
||||
|
||||
# Remote call unquote
|
||||
def unquote(k)(arg), do: __MODULE__.unquote(k)() + arg
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
test "dynamic definition with unquote" do
|
||||
assert foo(1) == 2
|
||||
assert bar(2) == 4
|
||||
assert baz(3) == 6
|
||||
assert Dyn.foo() == 1
|
||||
assert Dyn.bar() == 2
|
||||
assert Dyn.baz() == 3
|
||||
|
||||
assert Dyn.foo(1) == 2
|
||||
assert Dyn.bar(2) == 4
|
||||
assert Dyn.baz(3) == 6
|
||||
end
|
||||
|
||||
test "splice on root" do
|
||||
@@ -287,7 +295,7 @@ defmodule Kernel.QuoteTest.ErrorsTest do
|
||||
RuntimeError ->
|
||||
mod = Kernel.QuoteTest.ErrorsTest
|
||||
file = __ENV__.file |> Path.relative_to_cwd() |> String.to_charlist()
|
||||
assert [{^mod, :will_raise, 2, [file: ^file, line: 267]} | _] = __STACKTRACE__
|
||||
assert [{^mod, :will_raise, 2, [file: ^file, line: 275]} | _] = __STACKTRACE__
|
||||
else
|
||||
_ -> flunk("expected failure")
|
||||
end
|
||||
@@ -300,7 +308,7 @@ defmodule Kernel.QuoteTest.ErrorsTest do
|
||||
RuntimeError ->
|
||||
mod = Kernel.QuoteTest.ErrorsTest
|
||||
file = __ENV__.file |> Path.relative_to_cwd() |> String.to_charlist()
|
||||
assert [{^mod, _, _, [file: ^file, line: 298]} | _] = __STACKTRACE__
|
||||
assert [{^mod, _, _, [file: ^file, line: 306]} | _] = __STACKTRACE__
|
||||
else
|
||||
_ -> flunk("expected failure")
|
||||
end
|
||||
|
||||
@@ -260,6 +260,25 @@ defmodule ProtocolTest do
|
||||
end
|
||||
end
|
||||
|
||||
defmodule Protocol.DebugInfoTest do
|
||||
use ExUnit.Case
|
||||
|
||||
test "protocols always keep debug_info" do
|
||||
Code.compiler_options(debug_info: false)
|
||||
|
||||
{:module, _, binary, _} =
|
||||
defprotocol DebugInfoProto do
|
||||
end
|
||||
|
||||
assert {:ok, {DebugInfoProto, [debug_info: debug_info]}} =
|
||||
:beam_lib.chunks(binary, [:debug_info])
|
||||
|
||||
assert {:debug_info_v1, :elixir_erl, {:elixir_v1, _, _}} = debug_info
|
||||
after
|
||||
Code.compiler_options(debug_info: true)
|
||||
end
|
||||
end
|
||||
|
||||
path = Path.expand("../ebin", __DIR__)
|
||||
File.mkdir_p!(path)
|
||||
|
||||
|
||||
@@ -137,6 +137,14 @@ defmodule StringIOTest do
|
||||
assert StringIO.contents(pid) == {"", "あいう"}
|
||||
end
|
||||
|
||||
test "IO.write with non-printable arguments" do
|
||||
{:ok, pid} = StringIO.open("")
|
||||
|
||||
assert_raise ArgumentError, fn ->
|
||||
IO.write(pid, [<<1::1>>])
|
||||
end
|
||||
end
|
||||
|
||||
test "IO.binwrite" do
|
||||
{:ok, pid} = StringIO.open("")
|
||||
assert IO.binwrite(pid, "foo") == :ok
|
||||
@@ -160,6 +168,14 @@ defmodule StringIOTest do
|
||||
assert StringIO.contents(pid) == {"", "abc\n"}
|
||||
end
|
||||
|
||||
test "IO.puts with non-printable arguments" do
|
||||
{:ok, pid} = StringIO.open("")
|
||||
|
||||
assert_raise ArgumentError, fn ->
|
||||
IO.puts(pid, [<<1::1>>])
|
||||
end
|
||||
end
|
||||
|
||||
test "IO.inspect" do
|
||||
{:ok, pid} = StringIO.open("")
|
||||
assert IO.inspect(pid, {}, []) == {}
|
||||
|
||||
@@ -571,14 +571,25 @@ defmodule TypespecTest do
|
||||
end
|
||||
end
|
||||
|
||||
test "@type with invalid record" do
|
||||
assert_raise CompileError, ~r"invalid record specification", fn ->
|
||||
test "@type with a record which declares the name as the type `atom` rather than an atom literal" do
|
||||
assert_raise CompileError, ~r"expected the record name to be an atom literal", fn ->
|
||||
test_module do
|
||||
@type my_type :: record(atom)
|
||||
@type my_type :: record(atom, field: :foo)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
test "@type can be named record" do
|
||||
bytecode =
|
||||
test_module do
|
||||
@type record :: binary
|
||||
@spec foo?(record) :: boolean
|
||||
def foo?(_), do: true
|
||||
end
|
||||
|
||||
assert [type: {:record, {:type, _, :binary, []}, []}] = types(bytecode)
|
||||
end
|
||||
|
||||
test "@type with an invalid map notation" do
|
||||
assert_raise CompileError, ~r"invalid map specification", fn ->
|
||||
test_module do
|
||||
|
||||
@@ -82,7 +82,13 @@ defmodule IEx.Broker do
|
||||
@spec take_over(binary, keyword) ::
|
||||
{:ok, server :: pid, group_leader :: pid} | {:error, :no_iex | :refused}
|
||||
def take_over(identifier, opts) do
|
||||
GenServer.call(@name, {:take_over, identifier, opts}, :infinity)
|
||||
case GenServer.whereis(@name) do
|
||||
nil ->
|
||||
{:error, :no_iex}
|
||||
|
||||
_pid ->
|
||||
GenServer.call(@name, {:take_over, identifier, opts}, :infinity)
|
||||
end
|
||||
end
|
||||
|
||||
## Callbacks
|
||||
|
||||
@@ -53,6 +53,13 @@ defmodule IEx.CLI do
|
||||
if tty_works?() do
|
||||
:user_drv.start([:"tty_sl -c -e", tty_args()])
|
||||
else
|
||||
if get_remsh(:init.get_plain_arguments()) do
|
||||
IO.puts(
|
||||
:stderr,
|
||||
"warning: the --remsh option will be ignored because IEx is running on limited shell"
|
||||
)
|
||||
end
|
||||
|
||||
:application.set_env(:stdlib, :shell_prompt_func, {__MODULE__, :prompt})
|
||||
:user.start()
|
||||
local_start()
|
||||
|
||||
@@ -283,6 +283,7 @@ defmodule IEx.Introspection do
|
||||
true ->
|
||||
module.module_info(:exports)
|
||||
end
|
||||
|> Enum.sort()
|
||||
|
||||
result =
|
||||
for {^function, arity} <- exports,
|
||||
|
||||
@@ -343,6 +343,12 @@ defmodule IEx.HelpersTest do
|
||||
"""
|
||||
|
||||
assert capture_io(fn -> h(:timer.send_interval()) end) == """
|
||||
* :timer.send_interval/2
|
||||
|
||||
@spec send_interval(time, message) :: {:ok, tRef} | {:error, reason}
|
||||
when time: time(), message: term(), tRef: tref(), reason: term()
|
||||
|
||||
Module was compiled without docs. Showing only specs.
|
||||
* :timer.send_interval/3
|
||||
|
||||
@spec send_interval(time, pid, message) :: {:ok, tRef} | {:error, reason}
|
||||
@@ -352,12 +358,6 @@ defmodule IEx.HelpersTest do
|
||||
tRef: tref(),
|
||||
reason: term()
|
||||
|
||||
Module was compiled without docs. Showing only specs.
|
||||
* :timer.send_interval/2
|
||||
|
||||
@spec send_interval(time, message) :: {:ok, tRef} | {:error, reason}
|
||||
when time: time(), message: term(), tRef: tref(), reason: term()
|
||||
|
||||
Module was compiled without docs. Showing only specs.
|
||||
"""
|
||||
end
|
||||
|
||||
@@ -555,6 +555,7 @@ defmodule Logger do
|
||||
:level,
|
||||
:utc_log,
|
||||
:discard_threshold,
|
||||
:discard_threshold_periodic_check,
|
||||
:translator_inspect_opts
|
||||
]
|
||||
@spec configure(keyword) :: :ok
|
||||
|
||||
@@ -4,6 +4,7 @@ defmodule Logger.Config do
|
||||
@behaviour :gen_event
|
||||
@name __MODULE__
|
||||
@table __MODULE__
|
||||
@check_discard {__MODULE__, :check_discard}
|
||||
|
||||
def start_link do
|
||||
GenServer.start_link(__MODULE__, :ok, name: @name)
|
||||
@@ -76,29 +77,7 @@ defmodule Logger.Config do
|
||||
end
|
||||
|
||||
def handle_event(_event, {state, thresholds}) do
|
||||
%{mode: mode} = state
|
||||
|
||||
case compute_mode(mode, thresholds) do
|
||||
^mode ->
|
||||
{:ok, {state, thresholds}}
|
||||
|
||||
new_mode ->
|
||||
if new_mode == :discard do
|
||||
message =
|
||||
"Logger has #{message_queue_length()} messages in its queue, " <>
|
||||
"which is above :discard_threshold. Messages will be discarded " <>
|
||||
"until the message queue goes back to 75% of the threshold size"
|
||||
|
||||
log(:warn, message, state)
|
||||
end
|
||||
|
||||
if mode == :discard do
|
||||
log(:warn, "Logger has stopped discarding messages", state)
|
||||
end
|
||||
|
||||
state = persist(%{state | mode: new_mode})
|
||||
{:ok, {state, thresholds}}
|
||||
end
|
||||
{:ok, {compute_mode_and_persist_state(state, thresholds), thresholds}}
|
||||
end
|
||||
|
||||
def handle_call({:configure, options}, {%{mode: mode}, _}) do
|
||||
@@ -127,6 +106,16 @@ defmodule Logger.Config do
|
||||
{:ok, old, state}
|
||||
end
|
||||
|
||||
def handle_info(@check_discard, {%{mode: :discard} = state, thresholds}) do
|
||||
state = compute_mode_and_persist_state(state, thresholds)
|
||||
|
||||
if state.mode == :discard do
|
||||
Process.send_after(self(), @check_discard, state.discard_threshold_periodic_check)
|
||||
end
|
||||
|
||||
{:ok, {state, thresholds}}
|
||||
end
|
||||
|
||||
def handle_info(_msg, state) do
|
||||
{:ok, state}
|
||||
end
|
||||
@@ -139,6 +128,31 @@ defmodule Logger.Config do
|
||||
{:ok, state}
|
||||
end
|
||||
|
||||
defp compute_mode_and_persist_state(%{mode: mode} = state, thresholds) do
|
||||
case compute_mode(mode, thresholds) do
|
||||
^mode ->
|
||||
state
|
||||
|
||||
new_mode ->
|
||||
if new_mode == :discard do
|
||||
Process.send_after(self(), @check_discard, state.discard_threshold_periodic_check)
|
||||
|
||||
message =
|
||||
"Logger has #{message_queue_length()} messages in its queue, " <>
|
||||
"which is above :discard_threshold. Messages will be discarded " <>
|
||||
"until the message queue goes back to 75% of the threshold size"
|
||||
|
||||
log(:warn, message, state)
|
||||
end
|
||||
|
||||
if mode == :discard do
|
||||
log(:warn, "Logger has stopped discarding messages", state)
|
||||
end
|
||||
|
||||
persist(%{state | mode: new_mode})
|
||||
end
|
||||
end
|
||||
|
||||
## Helpers
|
||||
|
||||
defp log(level, message, state) do
|
||||
@@ -163,7 +177,9 @@ defmodule Logger.Config do
|
||||
level: Application.get_env(:logger, :level),
|
||||
translators: Application.get_env(:logger, :translators),
|
||||
truncate: Application.get_env(:logger, :truncate),
|
||||
utc_log: Application.get_env(:logger, :utc_log)
|
||||
utc_log: Application.get_env(:logger, :utc_log),
|
||||
discard_threshold_periodic_check:
|
||||
Application.get_env(:logger, :discard_threshold_periodic_check)
|
||||
})
|
||||
end
|
||||
|
||||
|
||||
@@ -126,6 +126,19 @@ defmodule Logger.Translator do
|
||||
|
||||
{:ok, msg, metadata}
|
||||
|
||||
{'Error in process ' ++ _, [pid, node, {reason, stack}]} ->
|
||||
reason = Exception.normalize(:error, reason, stack)
|
||||
|
||||
msg = [
|
||||
"Process ",
|
||||
inspect(pid),
|
||||
" on node ",
|
||||
inspect(node),
|
||||
" raised an exception" | format(:error, reason, stack)
|
||||
]
|
||||
|
||||
{:ok, msg, [crash_reason: exit_reason(:error, reason, stack)]}
|
||||
|
||||
{'Error in process ' ++ _, [pid, {reason, stack}]} ->
|
||||
reason = Exception.normalize(:error, reason, stack)
|
||||
msg = ["Process ", inspect(pid), " raised an exception" | format(:error, reason, stack)]
|
||||
|
||||
@@ -24,6 +24,7 @@ defmodule Logger.MixProject do
|
||||
handle_otp_reports: true,
|
||||
handle_sasl_reports: false,
|
||||
discard_threshold_for_error_logger: 500,
|
||||
discard_threshold_periodic_check: 30_000,
|
||||
compile_time_purge_level: :debug,
|
||||
compile_time_purge_matching: [],
|
||||
compile_time_application: nil,
|
||||
|
||||
@@ -12,11 +12,29 @@ defmodule Logger.ConfigTest do
|
||||
end
|
||||
|
||||
test "log/2 relies on discard_threshold" do
|
||||
Logger.remove_backend(:console)
|
||||
Logger.configure(discard_threshold: 0)
|
||||
for _ <- 1..1000, do: Logger.log(:info, "some message")
|
||||
after
|
||||
Logger.configure(discard_threshold: 10000)
|
||||
end
|
||||
|
||||
test "log/2 recovers from discard_threshold" do
|
||||
Logger.remove_backend(:console)
|
||||
Logger.configure(discard_threshold: 1, discard_threshold_periodic_check: 0)
|
||||
|
||||
:sys.suspend(Logger)
|
||||
assert Logger.log(:info, "BEGIN") == :ok
|
||||
send(Logger, :garbage)
|
||||
:sys.resume(Logger)
|
||||
|
||||
# Simulate a sync operation to make sure we are in :discard mode
|
||||
Logger.add_translator({Unknown, :translate})
|
||||
Logger.remove_translator({Unknown, :translate})
|
||||
|
||||
# It should eventually heal
|
||||
wait_for_mode(:async)
|
||||
after
|
||||
Logger.configure(discard_threshold: 10000, discard_threshold_periodic_check: 30000)
|
||||
Logger.add_backend(:console)
|
||||
end
|
||||
|
||||
@@ -37,4 +55,11 @@ defmodule Logger.ConfigTest do
|
||||
Logger.configure(level: :debug)
|
||||
end
|
||||
end
|
||||
|
||||
def wait_for_mode(mode) do
|
||||
unless Logger.Config.__data__().mode == mode do
|
||||
Process.sleep(10)
|
||||
wait_for_mode(mode)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -981,6 +981,19 @@ defmodule Logger.TranslatorTest do
|
||||
assert {:stop, [_ | _]} = process_metadata[:crash_reason]
|
||||
end
|
||||
|
||||
test "translates process crash with erts" do
|
||||
assert {:ok, msg, meta} =
|
||||
Logger.Translator.translate(
|
||||
:error,
|
||||
:error,
|
||||
:format,
|
||||
{'Error in process ~p on node ~p with exit value:~n~p~n',
|
||||
[self(), :"name@127.0.0.1", {:badarith, [{:erlang, :/, [1, 0], []}]}]}
|
||||
)
|
||||
|
||||
assert Keyword.get(meta, :crash_reason)
|
||||
end
|
||||
|
||||
test "reports :undefined MFA properly" do
|
||||
defmodule WeirdFunctionNamesGenServer do
|
||||
use GenServer
|
||||
|
||||
@@ -116,8 +116,6 @@ defmodule Mix do
|
||||
is `:host` but it can be set via the `MIX_TARGET` environment variable.
|
||||
The target can be read via `Mix.target/0`.
|
||||
|
||||
This feature is considered experimental and may change in future releases.
|
||||
|
||||
## Aliases
|
||||
|
||||
Aliases are shortcuts or tasks specific to the current project.
|
||||
|
||||
@@ -132,13 +132,18 @@ defmodule Mix.Dep do
|
||||
for dep <- deps,
|
||||
dep.app == app,
|
||||
child <- dep.deps,
|
||||
do: {child.app, true},
|
||||
do: {child.app, Keyword.get(child.opts, :optional, false)},
|
||||
into: %{}
|
||||
|
||||
Enum.map(children, fn %{app: app} = dep ->
|
||||
Enum.map(children, fn %{app: app, opts: opts} = dep ->
|
||||
# optional only matters at the top level. Any non-top level dependency
|
||||
# that is optional and is still available means it has been fulfilled.
|
||||
case top_level do
|
||||
%{^app => _} -> %{dep | top_level: true}
|
||||
%{} -> %{dep | top_level: false}
|
||||
%{^app => optional} ->
|
||||
%{dep | top_level: true, opts: Keyword.put(opts, :optional, optional)}
|
||||
|
||||
%{} ->
|
||||
%{dep | top_level: false, opts: Keyword.delete(opts, :optional)}
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
@@ -212,7 +212,7 @@ defmodule Mix.Tasks.Compile.App do
|
||||
apps =
|
||||
properties
|
||||
|> Keyword.get(:applications)
|
||||
|> Kernel.||(apps_from_prod_deps(properties, config))
|
||||
|> Kernel.||(apps_from_prod_non_optional_deps(properties, config))
|
||||
|> normalize_apps(extra, config)
|
||||
|
||||
Keyword.put(properties, :applications, apps)
|
||||
@@ -317,11 +317,12 @@ defmodule Mix.Tasks.Compile.App do
|
||||
end)
|
||||
end
|
||||
|
||||
defp apps_from_prod_deps(properties, config) do
|
||||
defp apps_from_prod_non_optional_deps(properties, config) do
|
||||
included_applications = Keyword.get(properties, :included_applications, [])
|
||||
non_runtime_deps = non_runtime_deps(config)
|
||||
|
||||
for %{app: app, top_level: true} <- Mix.Dep.cached(),
|
||||
for %{app: app, opts: opts, top_level: true} <- Mix.Dep.cached(),
|
||||
not Keyword.get(opts, :optional, false),
|
||||
not Map.has_key?(non_runtime_deps, app),
|
||||
app not in included_applications,
|
||||
do: app
|
||||
|
||||
@@ -82,7 +82,8 @@ defmodule Mix.Tasks.Deps do
|
||||
* `:targets` - the dependency is made available only for the given targets.
|
||||
By default the dependency will be available in all environments. The value
|
||||
of this option can either be a single target (like `:host`) or a list of
|
||||
environments (like `[:host, :rpi3]`)
|
||||
environments (like `[:host, :rpi3]`). This option is **experimental**
|
||||
and it may change behaviour or be removed in future releases.
|
||||
|
||||
* `:override` - if set to `true` the dependency will override any other
|
||||
definitions of itself by other dependencies
|
||||
|
||||
@@ -196,6 +196,31 @@ defmodule Mix.DepTest do
|
||||
end)
|
||||
end
|
||||
|
||||
test "nested deps with optional matching" do
|
||||
Process.put(:custom_deps_git_repo_opts, optional: true)
|
||||
|
||||
# deps_repo brings git_repo but it is optional
|
||||
deps = [
|
||||
{:deps_repo, "0.1.0", path: "custom/deps_repo"},
|
||||
{:git_repo, "0.1.0", git: MixTest.Case.fixture_path("git_repo")}
|
||||
]
|
||||
|
||||
with_deps(deps, fn ->
|
||||
in_fixture("deps_status", fn ->
|
||||
File.mkdir_p!("custom/deps_repo/lib")
|
||||
|
||||
File.write!("custom/deps_repo/lib/a.ex", """
|
||||
# Check that the child dependency is top_level and optional
|
||||
[%Mix.Dep{app: :git_repo, top_level: true, opts: opts}] = Mix.Dep.cached()
|
||||
true = Keyword.fetch!(opts, :optional)
|
||||
""")
|
||||
|
||||
Mix.Tasks.Deps.Get.run([])
|
||||
Mix.Tasks.Deps.Compile.run([])
|
||||
end)
|
||||
end)
|
||||
end
|
||||
|
||||
test "nested deps with convergence and optional dependencies" do
|
||||
deps = [
|
||||
{:deps_repo, "0.1.0", path: "custom/deps_repo"},
|
||||
|
||||
@@ -99,7 +99,7 @@ defmodule Mix.Tasks.Compile.AppTest do
|
||||
properties = parse_resource_file(:custom_deps)
|
||||
|
||||
assert properties[:applications] ==
|
||||
[:kernel, :stdlib, :elixir, :logger, :ok1, :ok3, :ok4, :ok6, :ok7]
|
||||
[:kernel, :stdlib, :elixir, :logger, :ok1, :ok3, :ok4, :ok7]
|
||||
end)
|
||||
end
|
||||
|
||||
|
||||
@@ -10,11 +10,12 @@ unless {1, 7, 4} <= Mix.SCM.Git.git_version() do
|
||||
ExUnit.configure(exclude: :git_sparse)
|
||||
end
|
||||
|
||||
# Clear proxy variables that may affect tests
|
||||
# Clear environment variables that may affect tests
|
||||
System.delete_env("http_proxy")
|
||||
System.delete_env("https_proxy")
|
||||
System.delete_env("HTTP_PROXY")
|
||||
System.delete_env("HTTPS_PROXY")
|
||||
System.delete_env("MIX_ENV")
|
||||
|
||||
defmodule MixTest.Case do
|
||||
use ExUnit.CaseTemplate
|
||||
|
||||
Reference in New Issue
Block a user