Compare commits

...
23 Commits
Author SHA1 Message Date
José Valim 32825ebaa0 Release v1.8.0-rc.1 2019-01-02 20:27:59 +01:00
nico piderman 5e715aa16e User defined types with the name record fail to compile in 1.8.0-rc.0 (#8569)
Closes https://github.com/elixir-lang/elixir/issues/8564

This is my first attempt to contribute code to elixir, so please forgive me if this PR is completely naive.

Besides adding the `is_list/1` guards, I have updated the error message to specify that record specifications must use an atom `literal` as the name, to make it clear that the type `atom` is also not valid.


Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2019-01-02 20:22:43 +01:00
José Valim 9b54efb9dd Move Calendar to its group 2019-01-02 16:03:34 +01:00
José Valim 82182818cb More docs to child specs 2019-01-02 16:02:23 +01:00
Jon Anderson 53f16f5a82 Add additional guards for Keywords.merge/3 (#8574) 2019-01-02 16:02:16 +01:00
José Valim d86751501f Clear up MIX_ENV env variable before tests, closes #8584 2019-01-02 16:01:38 +01:00
Wojtek Mach 906df8c981 Make KeyError.message/2 private (#8573)
It was introduced accidentally in #7803 (no docs, no specs)
2018-12-29 11:09:36 +01:00
Eksperimental 04717e4c19 Add missing deprecation related to mix compile.erlang (#8577) 2018-12-29 11:09:24 +01:00
José Valim a3e313b582 Improve docs for supervised proceesses 2018-12-27 23:03:34 +01:00
José Valim a4044d898d Add note about @since to changelog, closes #8570 2018-12-27 19:24:27 +01:00
José Valim b932a52d55 Avoid race conditions on behaviour checks, closes #8568 2018-12-27 16:28:30 +01:00
José Valim 8d269932a6 Streamline supervisor docs
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2018-12-27 14:54:23 +01:00
José Valim c1c4b1ace9 Remove trailing new line in VERSION 2018-12-26 17:49:08 +01:00
José Valim e284007968 Update VERSION 2018-12-26 17:47:08 +01:00
Kim Shrier b6ef2bd656 Fix changelog typo (#8565) 2018-12-24 19:33:30 +01:00
José Valim 12b0906d61 Improve release instructions 2018-12-24 15:49:35 +01:00
José Valim 2b59405756 Release v1.8.0-rc.0 2018-12-24 15:37:02 +01:00
José Valim 4231a62c67 Update CHANGELOG (#8550) 2018-12-24 12:00:51 +01:00
José Valim 35e7c8c38c Swap argument order to avoid failures on OTP master 2018-12-22 10:05:21 +01:00
José Valim 8e4d718b93 Provide a quick introduction to OptionParser in mdocs 2018-12-22 00:36:10 +01:00
Eksperimental 3ad9193b4b Add Supported Erlang/OTP versions for v1.8 (#8549) 2018-12-21 17:58:31 +01:00
Wojtek Mach d655a2884e Improve defprotocol/2 docs (#8548)
* `__protocol__` doesn't accept `:name`
* Elaborate on atoms that `__protocol__` accepts
* Change code example to doctest and fix it

Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2018-12-21 17:44:07 +01:00
José Valim 5529c5fa4e Prepare for v1.8 release 2018-12-21 13:12:42 +01:00
21 changed files with 463 additions and 296 deletions
+86 -2
View File
@@ -1,6 +1,77 @@
# 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.0-rc.1 (2018-01-03)
### 1. Bug fixes
#### Elixir
* [Kernel] Only validate the argument of `record/1` and `record/2` types in typespecs (regression)
## v1.8.0-rc.0 (2018-12-24)
### 1. Enhancements
@@ -23,12 +94,15 @@
* [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.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,19 +132,26 @@
* [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
#### IEx
* [IEx.Helpers] Use typespec info (instead of docs chunk) and properly format callbacks in `b/1`
#### Logger
* [Logger] Allow Logger backends to be dynamically removed when an application is shutting down
#### 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 +159,8 @@
### 3. Soft-deprecations (no warnings emitted)
None.
### 4. Hard-deprecations
#### Elixir
@@ -85,6 +168,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 -1
View File
@@ -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
View File
@@ -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
+1 -1
View File
@@ -1 +1 @@
1.8.0-dev
1.8.0-rc.1
+1 -1
View File
@@ -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
View File
@@ -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`)
+1 -1
View File
@@ -1178,7 +1178,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
+28 -7
View File
@@ -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 alwaus 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
View File
@@ -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
+119 -100
View File
@@ -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
+2 -2
View File
@@ -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
+2 -16
View File
@@ -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
+21 -5
View File
@@ -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.
+89 -111
View File
@@ -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
+4 -4
View File
@@ -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
+5 -3
View File
@@ -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]).
@@ -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)
+3 -4
View File
@@ -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.
+12 -12
View File
@@ -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
+14 -3
View File
@@ -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
+2 -1
View File
@@ -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