Compare commits

...
25 Commits
Author SHA1 Message Date
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
36 changed files with 400 additions and 144 deletions
+32 -1
View File
@@ -156,6 +156,36 @@ 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.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 +201,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 +326,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.2
+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).
+6 -5
View File
@@ -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
+16 -1
View File
@@ -832,13 +832,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
+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
}
+49 -41
View File
@@ -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) ->
+23 -16
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
@@ -186,6 +187,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 +202,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 +279,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 +297,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 +331,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 -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.
+16
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
@@ -228,6 +237,13 @@ defmodule DateTimeTest do
assert DateTime.from_unix(253402300799) == {: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
@@ -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 """
+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").
+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.2"},
{modules, [
elixir
]},