Compare commits

...
39 Commits
Author SHA1 Message Date
José Valim 7340ca2d92 Release v1.5.3 2017-12-19 18:12:48 +01:00
José Valim 3beb72e6f4 Update CHANGELOG 2017-12-15 11:16:33 +01:00
José Valim 45f9c9c1ce Add upcoming attributes to no warn list 2017-12-15 10:27:56 +01:00
José Valim cde12f75fc Also consider when acc_count is more than count
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2017-12-15 10:27:19 +01:00
Alberto Almagro Sotelo eb7e0984bd Fix Stream.chunk_every/4 odd results when step > count (#7114)
Stream.chunk_every/4 was giving odd results when step was greater than
count and the last element of the enumerable was the same as last
chunk's last element.

This commit fixes this issue and adds tests for different scenarios when
step > count. It also adds a doctest example to reflect this in the
documentation and test it simultaneously.

Fixes #7096

Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2017-12-15 10:26:58 +01:00
José Valim 57a52b9a05 Remove leftover space 2017-12-12 09:43:16 +01:00
José Valim c352766f43 Update CHANGELOG.md 2017-12-12 09:42:59 +01:00
Aleksei Magusev 15222304eb Consider microseconds in maximum possible Calendar.ISO datetime (#7055) 2017-11-23 09:13:13 +01:00
Glauber Campinho f6aced3072 Remove dialyzer warnings from else in with clauses (#7031)
Add generated: true to guard clauses from else in with clauses

Resolves #6426
2017-11-15 17:55:41 -02:00
Glauber Campinho 5e5eaca78f Warn duplicate definitions in macros (#7037)
Closes #6637
2017-11-15 17:55:16 -02:00
Aleksei Magusev ab61c09b3d Fix compiler warnings in ExUnit.assert_receive/3
Closes #6429.
2017-10-07 23:07:42 +02:00
Aleksei Magusev ccf939e9e7 Fix typo in MapSetTest 2017-10-03 17:39:21 -07:00
Shane Howley 331a70ade9 Return valid MapSet when union-ing a legacy MapSet (#6534) 2017-10-03 17:32:24 -07:00
Scott Riley 4329429823 Fix typo/reference in Supervisor @moduledoc (#6622)
All examples leading up to this snippet have used the module name
`Stack`, this updates the snippet to use the same.
2017-10-01 18:44:42 +02:00
José Valim 05418eaa4b Release v1.5.2 2017-09-29 14:00:01 +02:00
Artur Cygan d1efe7c012 Improve error message on oversized atom (#6612) 2017-09-28 14:05:01 +02:00
José Valim 0494629c4c Properly convert from list to string in git commands 2017-09-28 11:02:02 +02:00
James Long 941ce758ed Fix negative microsecond result from DateTime.from_unix/2 (#6599) 2017-09-26 00:47:11 +02:00
Glauber Campinho 759457ecfa Fix @impl attribute for default arguments (#6595)
`Module.compile_impl/6` was ignoring the default attributes
when compiling the impl, now it checks the count of default
attributes and adds an impl for each one.

Fixes #6550
2017-09-24 12:23:20 +02:00
Wojtek Mach bfa894a15f Fix local.hex --if-missing (#6576) 2017-09-15 22:35:47 +02:00
José Valim 407d2e1835 Raise better child_spec error if module is not defined, closes #6565 2017-09-12 16:59:17 +02:00
Yordis Prieto 94e5c72793 Use the name of a registry as its child spec id (#6560) 2017-09-12 14:17:48 +02:00
José Valim d90c7bbb79 Translate built-in Elixir apps sources, closes #6562 2017-09-11 15:52:36 +02:00
José Valim 5b3e13c90b Fix overridable tests 2017-09-09 12:19:06 -07:00
José Valim 2021fed11a Do not traverse clauses table to retrieve defaults
Closes #6553
2017-09-09 11:52:25 -07:00
José Valim e74701ef07 Backport application docs improvements 2017-09-04 16:23:54 +02:00
Aleksei Magusev 284a7d00d6 Improve error when NaiveDateTime given to DateTime.to_iso8601/2
Closes #6503.
2017-08-24 23:45:48 +02:00
Aleksei Magusev a31549cc7d Ensure Calendar.ISO enforces date limits 2017-08-24 21:56:08 +02:00
Aleksei Magusev be6e54de16 Fix mix_exs option in Mix.Tasks.Run 2017-08-20 22:07:52 +02:00
Glauber Campinho 6fd203ba7b Warn if unary operators are followed by new lines (#6482) 2017-08-20 13:14:56 +02:00
José Valim 0a677fe403 Properly advertise the --no-mix-exs flag 2017-08-20 12:33:55 +02:00
José Valim 5b25761d4c Do not trigger additional error reports on failures when loading tests 2017-08-20 00:20:00 +02:00
José Valim e1f10a9386 Ensure nested evaluator entries exit 2017-08-15 18:14:53 +02:00
Glauber Campinho 37321cd529 Do not crash for unknown var inside binary pattern in match (#6459) 2017-08-15 11:52:48 +02:00
José Valim 8023b5e0c6 Remove --if-missing when calling archive, closes #6453 2017-08-13 10:03:05 +02:00
Steve Domin 19d41f1fdb Update CHANGELOG for v1.5.0 (#6431) 2017-08-04 17:21:11 +02:00
José Valim 907149c8c7 Improve docs for Enum.filter/2 2017-08-02 11:25:14 +02:00
José Valim c99e1cdc9b Do not crash IEx on :init.stop 2017-08-01 21:10:42 +02:00
Tom Gurion 402a33867b Improve formatting of Supervisor documentation (#6416) 2017-08-01 21:10:39 +02:00
45 changed files with 594 additions and 181 deletions
+60 -2
View File
@@ -14,7 +14,17 @@ Or:
saudação = "Bom dia!"
Elixir follows the recommendations in [Unicode Annex #31](http://unicode.org/reports/tr31/) to make the language more accessible to other languages and communities. Identifiers must still be a sequence of letters, followed by digits and combining marks. This means symbols, such as mathematical notations and emoji, are not allowed identifiers.
Elixir follows the recommendations in [Unicode Annex #31](http://unicode.org/reports/tr31/) to make the language more accessible to other languages and communities. Identifiers must start with a letter or underscore, optionally followed by letters, digits, and underscores. Here letter means any UTF-8 letter-character (optionally with a combining mark) and digit means a UTF-8 decimal-digit-character. If you're using ASCII, this does what you'd expect.
Examples of valid variables are:
name josé _age まつもと _42 адрес
Examples of invalid variables include:
name• a±2 42
Symbols, such as mathematical notations and emoji, are not allowed identifiers.
For a complete reference on Elixir syntax, see the [Syntax Reference](https://hexdocs.pm/elixir/syntax-reference.html). For technical details on Unicode support, see [Unicode Syntax](https://hexdocs.pm/elixir/unicode-syntax.html).
@@ -156,6 +166,53 @@ Overall, using `@impl` has the following advantages:
This release brings further improvements to Calendar types. It adds arithmetic and others functions to `Time`, `Date`, `NaiveDateTime` and `Datetime` as well as conversion between different calendars.
## v1.5.3 (2017-12-19)
### 1. Bug fixes
#### Elixir
* [Calendar] Consider microseconds in maximum possible Calendar.ISO datetime
* [Enum] Fix `chunk_every/4` when `step > count`
* [Kernel] Warn duplicate definitions in macros
* [Kernel] Remove dialyzer warnings from else in with clauses
* [Kernel] Do not warning on upcoming `@deprecated` and `@since` attributes for v1.6
* [MapSet] Return valid MapSet when union-ing a legacy MapSet
#### ExUnit
* [ExUnit] Fix compiler warnings in `assert_receive/3`
## v1.5.2 (2017-09-29)
### 1. Enhacements
#### Elixir
* [Kernel] Optimize function definition with multiple clauses by not traversing the internal clauses table
* [Kernel] Warn if unary operators are followed by new lines
* [Registry] Use the name of the Registry as its `:id` in the `child_spec/1` function
### 2. Bug fixes
#### Elixir
* [DateTime] Fix negative microsecond result when passing negative Unix epochs to `from_unix/2`
* [Kernel] Improve error message for oversized atoms
* [Kernel] Ensure `@impl` attribute also propagates to clauses from default arguments
* [Kernel] Emit proper error for unknown vars inside binary pattern in match
#### IEx
* [IEx] Do not crash IEx unexpectedly on `System.stop/0`
* [IEx.Helpers] Ensure exiting a breakpoint set inside a breakpoint does not terminate the shell unexpectedly
#### Mix
* [mix local.hex] Ensure `--if-missing` flag works as advertised
* [mix test] Do not trigger additional error reports when there is a failure when loading test files
* [Mix.SCM.Git] Ensure errors when invoking `git` propagate correctly
## v1.5.1 (2017-08-01)
### 1. Enhancements
@@ -171,7 +228,7 @@ This release brings further improvements to Calendar types. It adds arithmetic a
#### Mix
* [mix profile.*] Allow profile tasks to run without a project
* [mix profile] Allow profile tasks to run without a project
### 2. Bug fixes
@@ -296,6 +353,7 @@ This release brings further improvements to Calendar types. It adds arithmetic a
#### ExUnit
* [ExUnit] Properly account failed tests when `setup_all` fails
* [ExUnit] Having two or more `describe` blocks with the same name will now raise an error.
#### IEx
+1 -1
View File
@@ -1 +1 @@
1.5.1
1.5.3
+58 -14
View File
@@ -9,17 +9,57 @@ defmodule Application do
Applications are defined with an application file named `APP.app` where
`APP` is the application name, usually in `underscore_case`. The application
file must reside in the same `ebin` directory as the compiled modules of the
application.
application. In Elixir, the Mix build tool is responsible for compiling your
source code and generating your application `.app` file. You can learn more
about the generation of `.app` files by typing `mix help compile.app`.
In Elixir, Mix is responsible for compiling your source code and
generating your application `.app` file. Furthermore, Mix is also
responsible for configuring, starting and stopping your application
and its dependencies. For this reason, this documentation will focus
on the remaining aspects of your application: the application environment
and the application callback module.
Once your application is compiled, running your system is a matter of starting
your current application and its dependencies. Differently from other languages,
Elixir does not have a `main` procedure that is responsible for starting your
system. Instead, you start one or more applications, each with their own
initialization and termination logic.
You can learn more about Mix generation of `.app` files by typing
`mix help compile.app`.
Starting an application is done via the "application module callback", which
is a module that defines the `start/2` function. The `start/2` function should
then start a supervisor, which is often called as the top-level supervisor, since
it sits at the root of a potentially long supervision tree. When the system is
shutting down, all applications shut down their top-level supervisor, which
terminates children in the opposite order they are started.
We have mentioned the Mix build tool is responsible for compiling applications,
but it is also capable of running applications. For example, `mix test`
automatically starts your application dependencies and your application itself
before your test runs. `mix run --no-halt` also boots your current project and
can be used to start a long running system. See `mix help run`.
Developers can also use tools like [Distillery](https://github.com/bitwalker/distillery)
that build **releases**. Releases are able to package all of your source code
as well as the Erlang VM into a single directory. Releases also give you explicit
control over how each application is started and in which order. They also provide
a more streamlined mechanism for starting and stopping systems, debugging, logging,
as well as system monitoring.
Finally, Elixir provides tools such as escripts and archives, which are
different mechanisms for packaging your application. Those are typically used
when tools must be shared between developers and not as deployment options.
See `mix help archive.build` and `mix help escript.build` for more detail.
Shutting down a live system cleanly can be done by calling `System.stop/1`.
It will shut down all applications in the opposite order they are started.
Each application will then shutdown its top-level supervisor, if one is
available, which then shuts down its children.
From Erlang/OTP 19.1, a SIGTERM from the operating system will automatically
translate to `System.stop/0`. Erlang/OTP 20 gives user more explicit control
over OS signals via the `:os.set_signal/2` function.
Applications also provide an "application environment", which is how
applications are configured. The application environment can either be set
statically, via a configuration file, or dynamically via `put_env/3` and
friends.
Over the next sections, we will cover the "application environment" and
the "application module callback" in more detail.
## Application environment
@@ -40,9 +80,13 @@ defmodule Application do
Application.get_env(:APP_NAME, :hello)
#=> :world
It is also possible to put and delete values from the application value,
including new values that are not defined in the environment file (although
this should be avoided).
Applications and dependencies in Mix projects are typically configured
via the `config/config.exs` file. For example, someone using your
application can configure the `:hello` key as follows:
config :APP_NAME, hello: :brand_new_world
It is also possible to configure applications dynamically via `put_env/3`.
Keep in mind that each application is responsible for its environment.
Do not use the functions in this module for directly accessing or modifying
@@ -78,8 +122,8 @@ defmodule Application do
The `type` argument passed to `start/2` is usually `:normal` unless in a
distributed setup where application takeovers and failovers are configured.
This particular aspect of applications is explained in more detail in the
OTP documentation:
Distributed applications is beyond the scope of this documentation. For those
interested on the topic, please access the OTP documentation:
* [`:application` module](http://www.erlang.org/doc/man/application.html)
* [Applications – OTP Design Principles](http://www.erlang.org/doc/design_principles/applications.html)
+6 -6
View File
@@ -326,25 +326,25 @@ defmodule DateTime do
@spec to_iso8601(Calendar.datetime, :extended | :basic ) :: String.t
def to_iso8601(datetime, format \\ :extended)
def to_iso8601(_, format) when format not in [:extended, :basic] do
raise ArgumentError, "DateTime.to_iso8601/2 expects format to be :extended or :basic, got: #{inspect format}"
end
def to_iso8601(%{calendar: Calendar.ISO, year: year, month: month, day: day,
hour: hour, minute: minute, second: second, microsecond: microsecond,
time_zone: time_zone, zone_abbr: zone_abbr, utc_offset: utc_offset, std_offset: std_offset}, format) when format in [:extended, :basic] do
time_zone: time_zone, zone_abbr: zone_abbr, utc_offset: utc_offset, std_offset: std_offset}, format) do
Calendar.ISO.datetime_to_iso8601(year, month, day, hour, minute, second, microsecond,
time_zone, zone_abbr, utc_offset, std_offset, format)
end
def to_iso8601(%{calendar: _, year: _, month: _, day: _,
hour: _, minute: _, second: _, microsecond: _,
time_zone: _, zone_abbr: _, utc_offset: _, std_offset: _} = datetime, format) when format in [:extended, :basic] do
time_zone: _, zone_abbr: _, utc_offset: _, std_offset: _} = datetime, format) do
datetime
|> convert!(Calendar.ISO)
|> to_iso8601(format)
end
def to_iso8601(_, format) do
raise ArgumentError, "DateTime.to_iso8601/2 expects format to be :extended or :basic, got: #{inspect format}"
end
@doc """
Parses the extended "Date and time of day" format described by
[ISO 8601:2004](https://en.wikipedia.org/wiki/ISO_8601).
+7 -6
View File
@@ -17,7 +17,7 @@ defmodule Calendar.ISO do
@unix_epoch 62167219200
@unix_start 1_000_000 * -@unix_epoch
@unix_end 1_000_000 * (315569519999 - @unix_epoch)
@unix_end 315569519999999999 - @unix_epoch * 1_000_000
@unix_range_microseconds @unix_start..@unix_end
@type year :: 0..9999
@@ -122,13 +122,13 @@ defmodule Calendar.ISO do
def date_to_iso_days_days(1970, 1, 1) do
719528
end
def date_to_iso_days_days(year, month, day) do
def date_to_iso_days_days(year, month, day) when year <= 9999 do
:calendar.date_to_gregorian_days(year, month, day)
end
# Converts count of days since 0000-01-01 to {year, month, day} tuple.
@doc false
def date_from_iso_days_days(days) do
def date_from_iso_days_days(days) when days <= 3652424 do
:calendar.gregorian_days_to_date(days)
end
@@ -314,10 +314,11 @@ defmodule Calendar.ISO do
def from_unix(integer, unit) when is_integer(integer) do
total = System.convert_time_unit(integer, unit, :microsecond)
if total in @unix_range_microseconds do
microsecond = rem(total, 1_000_000)
microseconds = Integer.mod(total, @microseconds_per_second)
seconds = @unix_epoch + Integer.floor_div(total, @microseconds_per_second)
precision = precision_for_unit(unit)
{date, time} = :calendar.gregorian_seconds_to_datetime(@unix_epoch + div(total, 1_000_000))
{:ok, date, time, {microsecond, precision}}
{date, time} = :calendar.gregorian_seconds_to_datetime(seconds)
{:ok, date, time, {microseconds, precision}}
else
{:error, :invalid_unix_time}
end
+19 -1
View File
@@ -362,6 +362,9 @@ defmodule Enum do
iex> Enum.chunk_every([1, 2, 3, 4], 10)
[[1, 2, 3, 4]]
iex> Enum.chunk_every([1, 2, 3, 4, 5], 2, 3, [])
[[1, 2], [4, 5]]
"""
@spec chunk_every(t, pos_integer, pos_integer, t | :discard) :: [list]
def chunk_every(enumerable, count, step, leftover \\ [])
@@ -832,13 +835,28 @@ defmodule Enum do
Filters the enumerable, i.e. returns only those elements
for which `fun` returns a truthy value.
See also `reject/2`.
See also `reject/2` which discards all elements where the
function returns true.
## Examples
iex> Enum.filter([1, 2, 3], fn(x) -> rem(x, 2) == 0 end)
[2]
Keep in mind that `filter` is not capable of filtering and
transforming an element at the same time. If you would like
to do so, consider using `flat_map/2`. For example, if you
want to convert all strings that represent an integer and
discard the invalid one in one pass:
strings = ["1234", "abc", "12ab"]
Enum.flat_map(strings, fn string ->
case Integer.parse(string) do
{int, _rest} -> [int] # transform to integer
:error -> [] # skip the value
end
end)
"""
@spec filter(t, (element -> as_boolean(term))) :: list
def filter(enumerable, fun) when is_list(enumerable) do
+1 -1
View File
@@ -131,7 +131,7 @@ defmodule Macro do
valid_alias?(charlist) ->
:alias
true ->
case :elixir_config.get(:identifier_tokenizer).tokenize(charlist) do
case :elixir_config.safe_get(:identifier_tokenizer, String.Tokenizer).tokenize(charlist) do
{kind, _acc, [], _, _, special} ->
if kind == :identifier and not :lists.member(?@, special) do
:callable
+2 -1
View File
@@ -353,7 +353,8 @@ defmodule MapSet do
%{map_set | map: Map.merge(map1, map2)}
end
def union(%MapSet{map: map1}, %MapSet{map: map2}) do
new_from_list(Map.keys(map1) ++ Map.keys(map2), [])
map = new_from_list(Map.keys(map1) ++ Map.keys(map2), [])
%MapSet{map: map}
end
defp order_by_size(map1, map2) when map_size(map1) > map_size(map2), do: {map2, map1}
+15 -2
View File
@@ -1192,8 +1192,13 @@ defmodule Module do
case :ets.take(table, :impl) do
[{:impl, value, _, _}] ->
impls = :ets.lookup_element(table, {:elixir, :impls}, 2)
impl = {{name, length(args)}, kind, line, file, value}
:ets.insert(table, {{:elixir, :impls}, [impl | impls]})
{total, defaults} = args_count(args, 0, 0)
impl = for arity <- total..(total - defaults), into: impls do
{{name, arity}, kind, line, file, value}
end
:ets.insert(table, {{:elixir, :impls}, impl})
[] ->
:ok
end
@@ -1201,6 +1206,14 @@ defmodule Module do
:ok
end
defp args_count([{:\\, _, _} | tail], total, defaults) do
args_count(tail, total + 1, defaults + 1)
end
defp args_count([_head | tail], total, defaults) do
args_count(tail, total + 1, defaults)
end
defp args_count([], total, defaults), do: {total, defaults}
@doc false
def check_behaviours_and_impls(env, table, all_definitions, overridable_pairs) do
behaviours = :ets.lookup_element(table, :behaviour, 2)
+1 -1
View File
@@ -304,7 +304,7 @@ defmodule Registry do
@doc false
def child_spec(opts) do
%{
id: Registry,
id: Keyword.get(opts, :name, Registry),
start: {Registry, :start_link, [opts]},
type: :supervisor
}
+1 -1
View File
@@ -22,7 +22,7 @@ defmodule Stream.Reducers do
{:cont, new_state}
end
end, fn {acc_buffer, acc_count} ->
if leftover == :discard or acc_count == 0 do
if leftover == :discard or acc_count == 0 or (step > count and acc_count >= count) do
{:cont, []}
else
{:cont, :lists.reverse(acc_buffer, Enum.take(leftover, count - acc_count)), []}
+50 -42
View File
@@ -82,7 +82,7 @@ defmodule Supervisor do
supervisor will automatically start a new one, with the initial stack
of `[:hello]`:
GenServer.call(MyStack, :pop)
GenServer.call(Stack, :pop)
#=> :hello
Supervisors support different strategies; in the example above, we
@@ -187,7 +187,7 @@ defmodule Supervisor do
`GenServer` with a shutdown limit of 10 seconds (10_000 miliseconds),
one might do:
use GenServer, shutdown: 10000
use GenServer, shutdown: 10_000
Let's understand what the `:shutdown` and `:restart` options control.
@@ -333,14 +333,14 @@ defmodule Supervisor do
The first argument given to `start_link/2` is a list of children which may
be either:
* a module - such as `Stack`. In this case, it is equivalent to passing
`{Stack, []}` (which means `Stack.child_spec/1` is invoked with an empty
keywords list)
* a tuple with a module as first element and the start argument as second -
such as `{Stack, [:hello]}`. When such format is used, the supervisor
will retrieve the child specification from the given module.
* a map representing the child specification itself - such as the child
specification map outlined in the previous section.
* a module - such as `Stack`. In this case, it is equivalent to passing
`{Stack, []}` (which means `Stack.child_spec/1` is invoked with an empty
keywords list)
* a tuple with a module as first element and the start argument as second -
such as `{Stack, [:hello]}`. When such format is used, the supervisor
will retrieve the child specification from the given module.
* a map representing the child specification itself - such as the child
specification map outlined in the previous section.
The second argument is a keyword list of options:
@@ -591,38 +591,7 @@ defmodule Supervisor do
e in UndefinedFunctionError ->
case System.stacktrace do
[{^module, :child_spec, [^arg], _} | _] ->
raise ArgumentError, """
The module #{inspect module} was given as a child to a supervisor
but it does not implement child_spec/1.
If you own the given module, please define a child_spec/1 function
that receives an argument and returns a child specification as a map.
For example:
def child_spec(opts) do
%{
id: __MODULE__,
start: {__MODULE__, :start_link, [opts]},
type: :worker,
restart: :permanent,
shutdown: 500
}
end
Note that "use Agent", "use GenServer" and so on automatically define
this function for you.
However, if you don't own the given module and it doesn't implement
child_spec/1, instead of passing the module name directly as a supervisor
child, you will have to pass a child specification as a map:
%{
id: #{inspect module},
start: {#{inspect module}, :start_link, [arg1, arg2]}
}
See the Supervisor documentation for more information.
"""
raise ArgumentError, child_spec_error(module)
stack ->
reraise e, stack
end
@@ -647,6 +616,45 @@ defmodule Supervisor do
"""
end
defp child_spec_error(module) do
if Code.ensure_loaded?(module) do
"""
The module #{inspect module} was given as a child to a supervisor
but it does not implement child_spec/1.
If you own the given module, please define a child_spec/1 function
that receives an argument and returns a child specification as a map.
For example:
def child_spec(opts) do
%{
id: __MODULE__,
start: {__MODULE__, :start_link, [opts]},
type: :worker,
restart: :permanent,
shutdown: 500
}
end
Note that "use Agent", "use GenServer" and so on automatically define
this function for you.
However, if you don't own the given module and it doesn't implement
child_spec/1, instead of passing the module name directly as a supervisor
child, you will have to pass a child specification as a map:
%{
id: #{inspect module},
start: {#{inspect module}, :start_link, [arg1, arg2]}
}
See the Supervisor documentation for more information.
"""
else
"The module #{inspect module} was given as a child to a supervisor but it does not exist."
end
end
@doc """
Builds and overrides a child specification.
+1 -1
View File
@@ -42,7 +42,7 @@ defmodule URI do
"""
@spec default_port(binary) :: nil | non_neg_integer
def default_port(scheme) when is_binary(scheme) do
:elixir_config.get({:uri, scheme})
:elixir_config.safe_get({:uri, scheme}, nil)
end
@doc """
+2 -2
View File
@@ -22,8 +22,8 @@ expand(BitstrMeta, Fun, [{'::', Meta, [Left, Right]} | T], Acc, E, RequireSize)
%% on subparts, however we can't assign new variables.
{ER, MatchSize} =
case E of
{EExtracted, _} -> {EExtracted, false}; %% expand_arg, no assigns
_ -> {E#{context := nil}, T /= []} %% expand, revert assigns
{EExtracted, _} -> {EExtracted, false}; %% expand_arg, no assigns
_ -> {E#{context := nil, match_vars := warn}, T /= []} %% expand, revert assigns
end,
ERight = expand_specs(expr_type(ELeft), Meta, Right, ER, RequireSize or MatchSize),
+9 -3
View File
@@ -1,6 +1,6 @@
-module(elixir_config).
-compile({no_auto_import, [get/1]}).
-export([new/1, delete/1, put/2, get/1, update/2, get_and_put/2]).
-export([new/1, delete/1, put/2, get/1, safe_get/2, update/2, get_and_put/2]).
-export([start_link/0, init/1, handle_call/3, handle_cast/2,
handle_info/2, code_change/3, terminate/2]).
-behaviour(gen_server).
@@ -19,9 +19,15 @@ put(Key, Value) ->
gen_server:call(?MODULE, {put, Key, Value}).
get(Key) ->
case ets:lookup(?MODULE, Key) of
[{_, Value}] = ets:lookup(?MODULE, Key),
Value.
safe_get(Key, Default) ->
try ets:lookup(?MODULE, Key) of
[{_, Value}] -> Value;
[] -> nil
[] -> Default
catch
_:_ -> Default
end.
update(Key, Fun) ->
+27 -17
View File
@@ -34,10 +34,11 @@ local_for(Module, Name, Arity, Kinds) ->
%% Take a definition out of the table
take_definition(Module, Tuple) ->
take_definition(Module, {Name, Arity} = Tuple) ->
Table = elixir_module:defs_table(Module),
case ets:take(Table, {def, Tuple}) of
[Result] ->
[{{def, Tuple}, _, _, _, _, {Defaults, _, _}} = Result] ->
ets:delete_object(Table, {{default, Name}, Arity, Defaults}),
{Result, [Clause || {_, Clause} <- ets:take(Table, {clauses, Tuple})]};
[] ->
false
@@ -95,6 +96,10 @@ store_definition(Kind, CheckClauses, Call, Body, Pos) ->
%% extract meta information like file and context.
{_, Meta, _} = Call,
DoCheckClauses = (not lists:keymember(context, 1, Meta)) andalso (CheckClauses),
Generated = case lists:keyfind(generated, 1, Meta) of
{generated, true} -> ?generated([]);
_ -> []
end,
%% Check if there is a file information in the definition.
%% If so, we assume this come from another source and
@@ -113,7 +118,6 @@ store_definition(Kind, CheckClauses, Call, Body, Pos) ->
LinifyArgs = elixir_quote:linify(Line, Key, Args),
LinifyGuards = elixir_quote:linify(Line, Key, Guards),
LinifyBody = elixir_quote:linify(Line, Key, Body),
Generated = case DoCheckClauses of true -> []; false -> ?generated([]) end,
{EL, MetaLocation} =
case retrieve_location(Location, ?key(E, module)) of
@@ -186,6 +190,13 @@ store_definition(Check, Kind, Meta, Name, Arity, File, Module, Defaults, Clauses
Tuple = {Name, Arity},
HasBody = Clauses =/= [],
if
Defaults > 0 ->
ets:insert(Defs, {{default, Name}, Arity, Defaults});
true ->
ok
end,
MaxDefaults =
case ets:take(Defs, {def, Tuple}) of
[{_, StoredKind, StoredMeta, StoredFile, StoredCheck,
@@ -194,14 +205,14 @@ store_definition(Check, Kind, Meta, Name, Arity, File, Module, Defaults, Clauses
(Check and StoredCheck) andalso
check_valid_clause(Meta, File, Name, Arity, Kind, Data, StoredMeta, StoredFile),
check_valid_defaults(Meta, File, Name, Arity, Kind, Defaults, StoredDefaults, LastDefaults, LastHasBody),
{max(Defaults, StoredDefaults), HasBody, Defaults};
max(Defaults, StoredDefaults);
[] ->
{Defaults, HasBody, Defaults}
Defaults
end,
Check andalso ets:insert(Data, {?last_def, Tuple}),
ets:insert(Defs, [{{clauses, Tuple}, Clause} || Clause <- Clauses]),
ets:insert(Defs, {{def, Tuple}, Kind, Meta, File, Check, MaxDefaults}).
ets:insert(Defs, {{def, Tuple}, Kind, Meta, File, Check, {MaxDefaults, HasBody, Defaults}}).
%% Handling of defaults
@@ -271,7 +282,7 @@ check_valid_defaults(Meta, File, Name, Arity, Kind, Defaults, 0, 0, _) when Defa
check_valid_defaults(Meta, File, Name, Arity, Kind, 0, _, LastDefaults, true) when LastDefaults > 0 ->
elixir_errors:form_warn(Meta, File, ?MODULE, {clauses_with_defaults, {Kind, Name, Arity}});
% Clause without defaults
check_valid_defaults(_Meta, _File, _Name, _Arity, _Kind, 0, _, _, _) -> [].
check_valid_defaults(_Meta, _File, _Name, _Arity, _Kind, 0, _, _, _) -> ok.
warn_bodyless_function(Check, _Meta, _File, Module, _Kind, _Tuple)
when Check == false; Module == 'Elixir.Module' ->
@@ -289,12 +300,11 @@ invalid_arg({Name, _, Kind}) when is_atom(Name), is_atom(Kind) -> false;
invalid_arg(_) -> true.
check_previous_defaults(Meta, Module, Name, Arity, Kind, Defaults, E) ->
Matches = ets:match(elixir_module:defs_table(Module),
{{def, {Name, '$2'}}, '$1', '_', '_', '_', {'$3', '_', '_'}}),
Matches = ets:lookup(elixir_module:defs_table(Module), {default, Name}),
[begin
elixir_errors:form_error(Meta, ?key(E, file), ?MODULE,
{defs_with_defaults, Name, {Kind, Arity}, {K, A}})
end || [K, A, D] <- Matches, A /= Arity, D /= 0, defaults_conflict(A, D, Arity, Defaults)].
{defs_with_defaults, Kind, Name, Arity, A})
end || {_, A, D} <- Matches, A /= Arity, D /= 0, defaults_conflict(A, D, Arity, Defaults)].
defaults_conflict(A, D, Arity, Defaults) ->
((Arity >= (A - D)) andalso (Arity < A)) orelse
@@ -324,13 +334,13 @@ format_error({bodyless_clause, Kind, {Name, Arity}}) ->
format_error({no_module, {Kind, Name, Arity}}) ->
io_lib:format("cannot define function outside module, invalid scope for ~ts ~ts/~B", [Kind, Name, Arity]);
format_error({defs_with_defaults, Name, {Kind, Arity}, {K, A}}) when Arity > A ->
io_lib:format("~ts ~ts/~B defaults conflicts with ~ts ~ts/~B",
[Kind, Name, Arity, K, Name, A]);
format_error({defs_with_defaults, Kind, Name, Arity, A}) when Arity > A ->
io_lib:format("~ts ~ts/~B defaults conflicts with ~ts/~B",
[Kind, Name, Arity, Name, A]);
format_error({defs_with_defaults, Name, {Kind, Arity}, {K, A}}) when Arity < A ->
io_lib:format("~ts ~ts/~B conflicts with defaults from ~ts ~ts/~B",
[Kind, Name, Arity, K, Name, A]);
format_error({defs_with_defaults, Kind, Name, Arity, A}) when Arity < A ->
io_lib:format("~ts ~ts/~B conflicts with defaults from ~ts/~B",
[Kind, Name, Arity, Name, A]);
format_error({clauses_with_defaults, {Kind, Name, Arity}}) ->
io_lib:format(""
+21 -1
View File
@@ -371,11 +371,31 @@ translate_with_else(Meta, [{else, Else}], S) ->
RaiseExpr = {{'.', Generated, [erlang, error]}, Generated, [{with_clause, RaiseVar}]},
RaiseClause = {'->', Generated, [[RaiseVar], RaiseExpr]},
GeneratedElse = [{'->', ?generated(ElseMeta), ElseArgs} || {'->', ElseMeta, ElseArgs} <- Else],
GeneratedElse = [build_generated_clause(Generated, ElseClause) || ElseClause <- Else],
Case = {'case', [{export_vars, false} | Generated], [ElseVarEx, [{do, GeneratedElse ++ [RaiseClause]}]]},
{TranslatedCase, SC} = elixir_erl_pass:translate(Case, SV),
{{clause, ?ann(Generated), [ElseVarErl], [], [TranslatedCase]}, SC}.
build_generated_clause(Generated, {'->', _, [Args, Clause]}) ->
NewArgs = [build_generated_clause_arg(Generated, Arg) || Arg <- Args],
{'->', Generated, [NewArgs, Clause]}.
build_generated_clause_arg(Generated, Arg) ->
{Expr, Guards} = elixir_utils:extract_guards(Arg),
NewGuards = [build_generated_guard(Generated, Guard) || Guard <- Guards],
concat_guards(Generated, Expr, NewGuards).
build_generated_guard(Generated, {{'.', _, _} = Call, _, Args}) ->
{Call, Generated, [build_generated_guard(Generated, Arg) || Arg <- Args]};
build_generated_guard(_, Expr) ->
Expr.
concat_guards(_Meta, Expr, []) ->
Expr;
concat_guards(Meta, Expr, [Guard | Tail]) ->
{'when', Meta, [Expr, concat_guards(Meta, Guard, Tail)]}.
translate_with_do([{'<-', Meta, [Left, Expr]} | Rest], Do, Else, S) ->
{Args, Guards} = elixir_utils:extract_guards(Left),
{TExpr, SR} = elixir_erl_pass:translate(Expr, S),
+1 -1
View File
@@ -318,7 +318,7 @@ beam_location(#{lexical_tracker := Pid, module := Module}) ->
%% Handle unused attributes warnings and special cases.
warn_unused_attributes(File, Data, PersistedAttrs) ->
ReservedAttrs = [after_compile, before_compile, moduledoc, on_definition | PersistedAttrs],
ReservedAttrs = [after_compile, before_compile, deprecated, moduledoc, on_definition, since | PersistedAttrs],
Keys = ets:select(Data, [{{'$1', '_', '_', '$2'}, [{is_atom, '$1'}, {is_integer, '$2'}], [['$1', '$2']]}]),
[elixir_errors:form_warn([{line, Line}], File, ?MODULE, {unused_attribute, Key}) ||
[Key, Line] <- Keys, not lists:member(Key, ReservedAttrs)].
+21 -2
View File
@@ -378,9 +378,9 @@ capture_op_eol -> capture_op : '$1'.
capture_op_eol -> capture_op eol : '$1'.
unary_op_eol -> unary_op : '$1'.
unary_op_eol -> unary_op eol : '$1'.
unary_op_eol -> unary_op eol : warn_unary_operator_eol('$1'), '$1'.
unary_op_eol -> dual_op : '$1'.
unary_op_eol -> dual_op eol : '$1'.
unary_op_eol -> dual_op eol : warn_unary_operator_eol('$1'), '$1'.
match_op_eol -> match_op : '$1'.
match_op_eol -> match_op eol : '$1'.
@@ -857,6 +857,25 @@ warn_empty_stab_clause({stab_op, {Line, _Begin, _End}, '->'}) ->
"an expression is always required on the right side of ->. "
"Please provide a value after ->").
warn_unary_operator_eol({dual_op, {Line, _Begin, _End}, Op}) ->
elixir_errors:warn(Line, ?file(),
io_lib:format(
"unary operator ~ts followed by new line. "
"This may happen when you try to use a binary operator over multiple lines. "
"Please make sure that the operator and all arguments are on the same line",
[Op]
)
);
warn_unary_operator_eol({unary_op, {Line, _Begin, _End}, Op}) ->
elixir_errors:warn(Line, ?file(),
io_lib:format(
"unary operator ~ts followed by new line. "
"Please make sure the unary operator and the expression that follows "
"the operator are on the same line",
[Op]
)
).
warn_pipe({arrow_op, {Line, _Begin, _End}, Op}, {_, [_ | _], [_ | _]}) ->
elixir_errors:warn(Line, ?file(),
io_lib:format(
+2 -2
View File
@@ -142,7 +142,7 @@ tokenize(String, Line, Column, Opts) ->
existing_atoms_only=ExistingAtomsOnly,
check_terminators=CheckTerminators,
preserve_comments=PreserveComments,
identifier_tokenizer=elixir_config:get(identifier_tokenizer)
identifier_tokenizer=elixir_config:safe_get(identifier_tokenizer, 'Elixir.String.Tokenizer')
}).
tokenize(String, Line, Opts) ->
@@ -670,7 +670,7 @@ eol(Line, Column, Tokens) -> [{eol, {Line, Column, Column + 1}} | Tokens].
unsafe_to_atom(Part, Line, #elixir_tokenizer{}) when
is_binary(Part) andalso size(Part) > 255;
is_list(Part) andalso length(Part) > 255 ->
{error, {Line, "atom length must be less than system limit", ":"}};
{error, {Line, "atom length must be less than system limit: ", [$: | Part]}};
unsafe_to_atom(Binary, _Line, #elixir_tokenizer{existing_atoms_only=true}) when is_binary(Binary) ->
{ok, binary_to_existing_atom(Binary, utf8)};
unsafe_to_atom(Binary, _Line, #elixir_tokenizer{}) when is_binary(Binary) ->
+3 -7
View File
@@ -96,21 +96,17 @@ relative_to_cwd(Path) ->
characters_to_list(Data) when is_list(Data) ->
Data;
characters_to_list(Data) ->
try elixir_config:get(bootstrap) of
case elixir_config:safe_get(bootstrap, true) of
true -> unicode:characters_to_list(Data);
false -> 'Elixir.String':to_charlist(Data)
catch
_:_ -> unicode:characters_to_list(Data)
end.
characters_to_binary(Data) when is_binary(Data) ->
Data;
characters_to_binary(Data) ->
try elixir_config:get(bootstrap) of
true -> unicode:characters_to_binary(Data);
case elixir_config:safe_get(bootstrap, true) of
true -> unicode:characters_to_binary(Data);
false -> 'Elixir.List':to_string(Data)
catch
_:_ -> unicode:characters_to_binary(Data)
end.
%% Returns the caller as a stacktrace entry.
+18 -2
View File
@@ -66,6 +66,15 @@ defmodule DateTest do
{:ok, Calendar.Holocene.date(12000, 01, 01)}
end
test "add/2" do
assert_raise FunctionClauseError, fn ->
Date.add(~D[0000-01-01], 3652425)
end
assert_raise FunctionClauseError, fn ->
Date.add(~D[0000-01-01], -1)
end
end
test "diff/2" do
assert Date.diff(~D[2000-01-31], ~D[2000-01-01]) == 30
assert Date.diff(~D[2000-01-01], ~D[2000-01-31]) == -30
@@ -221,13 +230,20 @@ defmodule DateTimeTest do
assert DateTime.from_unix(-62167219201) == {:error, :invalid_unix_time}
max_datetime = %DateTime{
calendar: Calendar.ISO, day: 31, hour: 23, microsecond: {0, 0},
calendar: Calendar.ISO, day: 31, hour: 23, microsecond: {999999, 6},
minute: 59, month: 12, second: 59, std_offset: 0, time_zone: "Etc/UTC",
utc_offset: 0, year: 9999, zone_abbr: "UTC"
}
assert DateTime.from_unix(253402300799) == {:ok, max_datetime}
assert DateTime.from_unix(253402300799999999, :microsecond) == {:ok, max_datetime}
assert DateTime.from_unix(253402300800) == {:error, :invalid_unix_time}
minus_datetime = %DateTime{
calendar: Calendar.ISO, day: 31, hour: 23, microsecond: {999999, 6},
minute: 59, month: 12, second: 59, std_offset: 0, time_zone: "Etc/UTC",
utc_offset: 0, year: 1969, zone_abbr: "UTC"
}
assert DateTime.from_unix(-1, :microsecond) == {:ok, minus_datetime}
end
test "from_unix!/2" do
+5
View File
@@ -70,6 +70,11 @@ defmodule EnumTest do
assert Enum.chunk_every([1, 2, 3, 4, 5, 6], 3, 2, []) == [[1, 2, 3], [3, 4, 5], [5, 6]]
assert Enum.chunk_every([1, 2, 3, 4, 5, 6], 3, 3, []) == [[1, 2, 3], [4, 5, 6]]
assert Enum.chunk_every([1, 2, 3, 4, 5], 4, 4, 6..10) == [[1, 2, 3, 4], [5, 6, 7, 8]]
assert Enum.chunk_every([1, 2, 3, 4, 5], 2, 3, []) == [[1, 2], [4, 5]]
assert Enum.chunk_every([1, 2, 3, 4, 5, 6], 2, 3, []) == [[1, 2], [4, 5]]
assert Enum.chunk_every([1, 2, 3, 4, 5, 6, 7], 2, 3, []) == [[1, 2], [4, 5], [7]]
assert Enum.chunk_every([1, 2, 3, 4, 5, 6, 7], 2, 3, [8]) == [[1, 2], [4, 5], [7, 8]]
assert Enum.chunk_every([1, 2, 3, 4, 5, 6, 7], 2, 4, []) == [[1, 2], [5, 6]]
end
test "chunk_by/2" do
@@ -1,13 +1,25 @@
defmodule Dialyzer.With do
def with_else do
with :ok <- ok_or_error(),
:ok <- ok_or_other_error() do
:ok <- ok_or_other_error(),
:ok <- ok_or_tuple_error(),
:ok <- ok_or_tuple_list_error() do
:ok
else
:error ->
:error
:other_error ->
:other_error
{:error, msg} when is_list(msg) or is_tuple(msg) ->
:error
{:error, msg} when is_list(msg) when is_tuple(msg) ->
:error
{:error, _msg} ->
:error
end
end
@@ -20,4 +32,14 @@ defmodule Dialyzer.With do
defp ok_or_other_error do
Enum.random([:ok, :other_error])
end
@spec ok_or_tuple_error() :: :ok | {:error, :err}
defp ok_or_tuple_error do
Enum.random([:ok, {:error, :err}])
end
@spec ok_or_tuple_list_error() :: :ok | {:error, [:err]}
defp ok_or_tuple_list_error do
Enum.random([:ok, {:error, [:err]}])
end
end
@@ -238,7 +238,7 @@ defmodule Kernel.ErrorsTest do
test "different defs with defaults" do
assert_compile_fail CompileError,
"nofile:3: def hello/3 defaults conflicts with def hello/2",
"nofile:3: def hello/3 defaults conflicts with hello/2",
~C'''
defmodule Kernel.ErrorsTest.DifferentDefsWithDefaults1 do
def hello(a, b \\ nil), do: a + b
@@ -247,7 +247,7 @@ defmodule Kernel.ErrorsTest do
'''
assert_compile_fail CompileError,
"nofile:3: def hello/2 conflicts with defaults from def hello/3",
"nofile:3: def hello/2 conflicts with defaults from hello/3",
~C'''
defmodule Kernel.ErrorsTest.DifferentDefsWithDefaults2 do
def hello(a, b \\ nil, c \\ nil), do: a + b + c
@@ -1180,6 +1180,14 @@ defmodule Kernel.ExpansionTest do
assert_raise CompileError, ~r"unhandled operator ->", fn ->
expand(quote do: (foo -> bar))
end
assert_raise CompileError,
~r"size in bitstring expects an integer or a variable as argument, got: foo\(\)",
fn ->
expand(quote do
fn <<_::size(foo)>> -> :ok end
end)
end
end
## Helpers
+19 -1
View File
@@ -17,7 +17,11 @@ defmodule Kernel.ImplTest do
end
defmodule Behaviour do
@callback foo :: any
@callback foo() :: any
end
defmodule BehaviourWithArgument do
@callback foo(any) :: any
end
defmodule MacroBehaviour do
@@ -285,6 +289,20 @@ defmodule Kernel.ImplTest do
end) =~ "got @impl Kernel.ImplTest.MacroBehaviour for def bar/0 but the given behaviour was not declared with @behaviour"
end
test "does not warn for @impl when using default arguments" do
assert capture_err(fn ->
Code.eval_string ~S"""
defmodule Kernel.ImplTest.ImplAttributes do
@behaviour Kernel.ImplTest.Behaviour
@behaviour Kernel.ImplTest.BehaviourWithArgument
@impl true
def foo(args \\ []), do: args
end
"""
end) == ""
end
test "does not warn for no @impl when overriding callback" do
assert capture_err(fn ->
Code.eval_string """
@@ -84,6 +84,25 @@ defmodule Kernel.WarningTest do
end) =~ message
end
test "unary operator followed by new line" do
assert capture_err(fn ->
Code.eval_string """
1
+
1
"""
end) =~ "unary operator + followed by new line"
assert capture_err(fn ->
Code.eval_string """
!
1
"""
end) =~ "unary operator ! followed by new line"
after
purge Sample
end
test "useless attr" do
message = capture_err(fn ->
Code.eval_string """
@@ -468,6 +487,27 @@ defmodule Kernel.WarningTest do
purge Sample
end
test "generated clause not match" do
assert capture_err(fn ->
Code.eval_string("""
defmodule Sample do
defmacro __using__(_) do
quote do
def hello, do: nil
def hello, do: nil
end
end
end
defmodule UseSample do
use Sample
end
""")
end) =~ "this clause cannot match because a previous clause at line 10 always matches"
after
purge(Sample)
purge(UseSample)
end
test "clause with defaults should be first" do
assert capture_err(fn ->
Code.eval_string ~S"""
+34
View File
@@ -108,4 +108,38 @@ defmodule MapSetTest do
list = MapSet.to_list(MapSet.new(5..120))
assert Enum.sort(list) == Enum.to_list(5..120)
end
test "MapSet v1 compatibility" do
result = 1..5 |> map_set_v1() |> MapSet.new()
assert MapSet.equal?(result, MapSet.new(1..5))
result = MapSet.put(map_set_v1(1..5), 6)
assert MapSet.equal?(result, MapSet.new(1..6))
result = MapSet.union(map_set_v1(1..5), MapSet.new(6..10))
assert MapSet.equal?(result, MapSet.new(1..10))
result = MapSet.intersection(map_set_v1(1..10), MapSet.new(6..15))
assert MapSet.equal?(result, MapSet.new(6..10))
result = MapSet.difference(map_set_v1(1..10), MapSet.new(6..50))
assert MapSet.equal?(result, MapSet.new(1..5))
result = MapSet.delete(map_set_v1(1..10), 1)
assert MapSet.equal?(result, MapSet.new(2..10))
assert MapSet.size(map_set_v1(1..5)) == 5
assert MapSet.to_list(map_set_v1(1..5)) == Enum.to_list(1..5)
assert MapSet.disjoint?(map_set_v1(1..5), MapSet.new(10..15))
refute MapSet.disjoint?(map_set_v1(1..5), MapSet.new(5..10))
assert MapSet.subset?(map_set_v1(3..7), MapSet.new(1..10))
refute MapSet.subset?(map_set_v1(7..12), MapSet.new(1..10))
end
defp map_set_v1(enumerable) do
map = Map.new(enumerable, &{&1, true})
%{__struct__: MapSet, map: map}
end
end
+5
View File
@@ -521,6 +521,11 @@ defmodule RegistryTest do
end
end
test "child_spec/1 uses :name as :id" do
assert %{id: :custom_name} = Registry.child_spec([name: :custom_name])
assert %{id: Registry} = Registry.child_spec([])
end
defp register_task(registry, key, value) do
parent = self()
{:ok, task} =
+5 -1
View File
@@ -80,7 +80,11 @@ defmodule SupervisorTest do
restart: :permanent, shutdown: :infinity) ==
%{id: :foo, start: {:foo, :bar, []}, restart: :permanent, shutdown: :infinity}
assert_raise ArgumentError, ~r"The module Unknown was given as a child", fn ->
assert_raise ArgumentError, ~r"The module SupervisorTest was given as a child.*\nbut it does not implement"m, fn ->
Supervisor.child_spec(SupervisorTest, [])
end
assert_raise ArgumentError, ~r"The module Unknown was given as a child.*but it does not exist"m, fn ->
Supervisor.child_spec(Unknown, [])
end
+1 -1
View File
@@ -53,7 +53,7 @@ quoted_atom_test() ->
oversized_atom_test() ->
OversizedAtom = [$: | string:copies("a", 256)],
{1, "atom length must be less than system limit", ":"} = tokenize_error(OversizedAtom).
{1, "atom length must be less than system limit: ", OversizedAtom} = tokenize_error(OversizedAtom).
op_atom_test() ->
[{atom, {1, 1, 6}, f0_1}] = tokenize(":f0_1").
+40 -25
View File
@@ -109,7 +109,7 @@ defmodule ExUnit.Assertions do
# is not nil nor false. We need to rewrite the if
# to avoid silly warnings though.
return =
no_warning(quote do
suppress_warning(quote do
case right do
x when x in [nil, false] ->
raise ExUnit.AssertionError,
@@ -121,7 +121,7 @@ defmodule ExUnit.Assertions do
end)
match_expr =
no_warning(quote do
suppress_warning(quote do
case right do
unquote(left) ->
unquote(return)
@@ -401,6 +401,40 @@ defmodule ExUnit.Assertions do
quote(do: unquote(left) = received)
end
pattern_finder =
quote do
fn message ->
unquote(suppress_warning(quote do
case message do
unquote(pattern) ->
_ = unquote(vars)
true
_ ->
false
end
end))
end
end
failure_message_hit =
failure_message || quote do
"""
Found message matching #{unquote(binary)} after #{timeout}ms.
This means the message was delivered too close to the timeout value, you may want to either:
1. Give an increased timeout to `assert_receive/2`
2. Increase the default timeout to all `assert_receive` in your
test_helper.exs by setting ExUnit.configure(assert_receive_timeout: ...)
"""
end
failure_message_miss =
failure_message || quote do
"No message matching #{unquote(binary)} after #{timeout}ms."
end
quote do
timeout = unquote(timeout)
@@ -412,29 +446,10 @@ defmodule ExUnit.Assertions do
timeout ->
{:messages, messages} = Process.info(self(), :messages)
pattern_finder = fn message ->
case message do
unquote(pattern) ->
_ = unquote(vars)
true
_ ->
false
end
end
if Enum.any?(messages, pattern_finder) do
flunk(unquote(failure_message) || """
Found message matching #{unquote(binary)} after #{timeout}ms.
This means the message was delivered too close to the timeout value, you may want to either:
1. Give an increased timeout to `assert_receive/2`
2. Increase the default timeout to all `assert_receive` in your
test_helper.exs by setting ExUnit.configure(assert_receive_timeout: ...)
""")
if Enum.any?(messages, unquote(pattern_finder)) do
flunk(unquote(failure_message_hit))
else
failure_message = unquote(failure_message) || "No message matching #{unquote(binary)} after #{timeout}ms."
flunk(failure_message <>
flunk(unquote(failure_message_miss) <>
ExUnit.Assertions.__pins__(unquote(pins)) <>
ExUnit.Assertions.__mailbox__(messages))
end
@@ -515,7 +530,7 @@ defmodule ExUnit.Assertions do
|> elem(1)
end
defp no_warning({name, meta, [expr, [do: clauses]]}) do
defp suppress_warning({name, meta, [expr, [do: clauses]]}) do
clauses = Enum.map clauses, fn {:->, meta, args} ->
{:->, [generated: true] ++ meta, args}
end
+7 -1
View File
@@ -179,6 +179,12 @@ defmodule ExUnit.AssertionsTest do
:hello = assert_receive :hello
end
test "assert receive accepts custom failure message" do
send self(), :hello
assert_receive message, 0, "failure message"
:hello = message
end
test "assert receive with message in mailbox after timeout, but before reading mailbox tells user to increase timeout" do
parent = self()
# This is testing a race condition, so it's not
@@ -297,7 +303,7 @@ defmodule ExUnit.AssertionsTest do
end
end
test "assert received leaks" do
test "assert received binds variables" do
send self(), {:hello, :world}
assert_received {:hello, world}
:world = world
+12
View File
@@ -14,6 +14,9 @@ defmodule IEx.Evaluator do
old_leader = Process.group_leader
Process.group_leader(self(), leader)
evaluator? = !!Process.get(:iex_evaluator)
Process.put(:iex_evaluator, true)
state = loop_state(server, IEx.History.init, opts)
command == :ack && :proc_lib.init_ack(self())
@@ -21,6 +24,15 @@ defmodule IEx.Evaluator do
loop(state)
after
Process.group_leader(self(), old_leader)
# If there was an evaluator, nest failures.
if evaluator? do
send self(), {:done, server}
else
Process.delete(:iex_evaluator)
end
:ok
end
end
+17 -1
View File
@@ -114,7 +114,8 @@ defmodule IEx.Introspection do
defp open_mfa(module, fun, arity) do
with {:module, _} <- Code.ensure_loaded(module),
source when is_list(source) <- module.module_info(:compile)[:source] do
open_abstract_code(module, fun, arity, List.to_string(source))
source = rewrite_elixir_source(module, source)
open_abstract_code(module, fun, arity, source)
else
_ -> :error
end
@@ -158,6 +159,21 @@ defmodule IEx.Introspection do
end
end
defp rewrite_elixir_source(module, source) do
case :application.get_application(module) do
{:ok, app} when app in [:eex, :elixir, :ex_unit, :iex, :logger, :mix] ->
{in_app, [lib_or_src | _]} =
source
|> Path.split()
|> Enum.reverse()
|> Enum.split_while(& &1 not in ["lib", "src"])
Application.app_dir(app, Path.join([lib_or_src | Enum.reverse(in_app)]))
_ ->
List.to_string(source)
end
end
@doc """
Prints documentation.
"""
+13 -13
View File
@@ -123,8 +123,8 @@ defmodule IEx.HelpersTest do
end
describe "open" do
@iex_helpers Path.expand("../../lib/iex/helpers.ex", __DIR__)
@elixir_erl Path.expand("../../../elixir/src/elixir.erl", __DIR__)
@iex_helpers "iex/lib/iex/helpers.ex"
@elixir_erl "elixir/src/elixir.erl"
@editor System.get_env("ELIXIR_EDITOR")
test "opens __FILE__ and __LINE__" do
@@ -137,48 +137,48 @@ defmodule IEx.HelpersTest do
test "opens Elixir module" do
assert capture_iex("open(IEx.Helpers)") |> maybe_trim_quotes() =~
~r/#{@iex_helpers}:1/
~r/#{@iex_helpers}:1$/
end
test "opens function" do
assert capture_iex("open(h)") |> maybe_trim_quotes() =~
~r/#{@iex_helpers}:\d+/
~r/#{@iex_helpers}:\d+$/
end
test "opens function/arity" do
assert capture_iex("open(b/1)") |> maybe_trim_quotes() =~
~r/#{@iex_helpers}:\d+/
~r/#{@iex_helpers}:\d+$/
assert capture_iex("open(h/0)") |> maybe_trim_quotes() =~
~r/#{@iex_helpers}:\d+/
~r/#{@iex_helpers}:\d+$/
end
test "opens module.function" do
assert capture_iex("open(IEx.Helpers.b)") |> maybe_trim_quotes() =~
~r/#{@iex_helpers}:\d+/
~r/#{@iex_helpers}:\d+$/
assert capture_iex("open(IEx.Helpers.h)") |> maybe_trim_quotes() =~
~r/#{@iex_helpers}:\d+/
~r/#{@iex_helpers}:\d+$/
end
test "opens module.function/arity" do
assert capture_iex("open(IEx.Helpers.b/1)") |> maybe_trim_quotes() =~
~r/#{@iex_helpers}:\d+/
~r/#{@iex_helpers}:\d+$/
assert capture_iex("open(IEx.Helpers.h/0)") |> maybe_trim_quotes() =~
~r/#{@iex_helpers}:\d+/
~r/#{@iex_helpers}:\d+$/
end
test "opens Erlang module" do
assert capture_iex("open(:elixir)") |> maybe_trim_quotes() =~
~r/#{@elixir_erl}:\d+/
~r/#{@elixir_erl}:\d+$/
end
test "opens Erlang module.function" do
assert capture_iex("open(:elixir.start)") |> maybe_trim_quotes() =~
~r/#{@elixir_erl}:\d+/
~r/#{@elixir_erl}:\d+$/
end
test "opens Erlang module.function/arity" do
assert capture_iex("open(:elixir.start/2)") |> maybe_trim_quotes() =~
~r/#{@elixir_erl}:\d+/
~r/#{@elixir_erl}:\d+$/
end
test "errors if module is not available" do
+10 -2
View File
@@ -33,7 +33,7 @@ defmodule Mix.Compilers.Test do
case test_files_to_run do
[] when stale ->
Mix.shell.info "No stale tests."
Mix.shell.info "No stale tests"
:noop
[] when test_patterns == [] ->
@@ -45,8 +45,9 @@ defmodule Mix.Compilers.Test do
:noop
test_files ->
task = Task.async(ExUnit, :run, [])
try do
task = Task.async(ExUnit, :run, [])
Kernel.ParallelRequire.files(test_files, parallel_require_callbacks)
ExUnit.Server.cases_loaded()
%{failures: failures} = results = Task.await(task, :infinity)
@@ -56,6 +57,13 @@ defmodule Mix.Compilers.Test do
end
{:ok, results}
catch
kind, reason ->
stack = System.stacktrace
# In case there is an error, shutdown the runner task
# before the error propagates up and trigger links.
Task.shutdown(task)
:erlang.raise(kind, reason, stack)
after
agent_stop(stale_manifest_pid)
end
+5 -3
View File
@@ -214,10 +214,12 @@ defmodule Mix.SCM.Git do
end
end
@rev_command 'git --git-dir=.git config remote.origin.url && git --git-dir=.git rev-parse --verify --quiet HEAD'
defp get_rev_info do
destructure [origin, rev],
:os.cmd('git --git-dir=.git config remote.origin.url && git --git-dir=.git rev-parse --verify --quiet HEAD')
|> IO.iodata_to_binary
:os.cmd(@rev_command)
|> List.to_string()
|> String.split("\n", trim: true)
%{origin: origin, rev: rev}
end
@@ -257,7 +259,7 @@ defmodule Mix.SCM.Git do
:error ->
version =
:os.cmd('git --version')
|> IO.iodata_to_binary
|> List.to_string()
|> parse_version
Mix.State.put(:git_version, version)
+11 -5
View File
@@ -32,13 +32,19 @@ defmodule Mix.Tasks.Local.Hex do
@spec run(OptionParser.argv) :: boolean
def run(argv) do
{opts, _} = OptionParser.parse!(argv, switches: @switches)
force? = Keyword.get(opts, :force, false)
if_missing? = Keyword.get(opts, :if_missing, false)
should_install? =
case {force?, if_missing?} do
{false, true} -> Code.ensure_loaded?(Hex)
_ -> true
if Keyword.get(opts, :if_missing, false) do
not Code.ensure_loaded?(Hex)
else
true
end
argv =
if Keyword.get(opts, :force, false) do
["--force"]
else
[]
end
should_install? && run_install(argv)
+1 -1
View File
@@ -99,7 +99,7 @@ defmodule Mix.Tasks.Profile.Cprof do
{opts, head} = OptionParser.parse_head!(args,
aliases: [r: :require, p: :parallel, e: :eval, c: :config],
strict: @switches)
Mix.Tasks.Run.run(["--no-mixexs" | args], opts, head,
Mix.Tasks.Run.run(["--no-mix-exs" | args], opts, head,
&profile_code(&1, opts),
&profile_code(File.read!(&1), opts))
end
+1 -1
View File
@@ -116,7 +116,7 @@ defmodule Mix.Tasks.Profile.Fprof do
{opts, head} = OptionParser.parse_head!(args,
aliases: [r: :require, p: :parallel, e: :eval, c: :config],
strict: @switches)
Mix.Tasks.Run.run(["--no-mixexs" | args], opts, head,
Mix.Tasks.Run.run(["--no-mix-exs" | args], opts, head,
&profile_code(&1, opts),
&profile_code(File.read!(&1), opts))
end
+6 -4
View File
@@ -38,7 +38,7 @@ defmodule Mix.Tasks.Run do
* `--no-deps-check` - does not check dependencies
* `--no-archives-check` - does not check archives
* `--no-halt` - does not halt the system after running the command
* `--no-mixexs` - allows the command to run even if there is no mix.exs
* `--no-mix-exs` - allows the command to run even if there is no mix.exs
* `--no-start` - does not start applications after compilation
* `--no-elixir-version-check` - does not check the Elixir version from mix.exs
@@ -48,7 +48,7 @@ defmodule Mix.Tasks.Run do
def run(args) do
{opts, head} = OptionParser.parse_head!(args,
aliases: [r: :require, p: :parallel, e: :eval, c: :config],
strict: [parallel: :boolean, require: :keep, eval: :keep, config: :keep, mixexs: :boolean,
strict: [parallel: :boolean, require: :keep, eval: :keep, config: :keep, mix_exs: :boolean,
halt: :boolean, compile: :boolean, deps_check: :boolean, start: :boolean,
archives_check: :boolean, elixir_version_check: :boolean, parallel_require: :keep])
@@ -87,10 +87,12 @@ defmodule Mix.Tasks.Run do
cond do
Mix.Project.get ->
Mix.Task.run "app.start", args
"--no-mixexs" in args ->
"--no-mix-exs" in args ->
:ok
true ->
Mix.Project.get!
Mix.raise "Cannot execute \"mix run\" without a Mix.Project, " <>
"please ensure you are running Mix in a directory with a mix.exs file " <>
"or pass the --no-mix-exs flag"
end
process_load(opts, expr_evaluator)
+2 -2
View File
@@ -40,7 +40,7 @@ defmodule Mix.Tasks.TestTest do
assert_stale_run_output "2 tests, 0 failures"
assert_stale_run_output """
No stale tests.
No stale tests
"""
end
end
@@ -124,7 +124,7 @@ defmodule Mix.Tasks.TestTest do
Port.command(port, "\n")
assert receive_until_match(port, "No stale tests.", []) =~ "Restarting..."
assert receive_until_match(port, "No stale tests", []) =~ "Restarting..."
end
end
+1 -1
View File
@@ -1,6 +1,6 @@
{application, elixir,
[{description, "elixir"},
{vsn, "1.5.1"},
{vsn, "1.5.3"},
{modules, [
elixir
]},