Compare commits

...
35 Commits
Author SHA1 Message Date
José Valim f045705ac5 Release v1.4.0 2017-01-05 00:09:35 +01:00
José Valim 2b4f573092 Cleanup exit messages on Task.async_stream (#5612) 2017-01-05 00:04:20 +01:00
Michal Muskala 7cf4369157 Issue warning instead of error for unhandled messages
While the change to start logging unhandled messages from handle_info/2 is a
good one, the choice to issue "error" level events was a poor one. Code that
worked perfectly fine on previous versions suddenly started issuing errors,
which is unexpected. A "warn" level log seems to be a better choice, at least
initially. The log level can be revisited in the future.

Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2017-01-01 18:25:35 +01:00
Gal Tsubery 4135ab1c0d Fix ceil function with zero as input (#5594)
Zero has special encoding as IEEE754 floating point number with a 0
value for exponent. This encoding requires special handling in
current Float.round implementation.

Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2016-12-27 10:47:09 +01:00
José Valim 89f7dbc64d Improve reading in error message
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2016-12-26 01:05:35 +01:00
Povilas Skruibis ec5afe2226 Fix system_test (#5528)
Git since v2.11 no longer defaults to 7-hexdigits for short object
abbreviation.

See https://github.com/git/git/commit/e6c587c733b4634030b353f4024794b08bc86892

Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2016-12-22 23:36:22 +01:00
Aleksei Magusev 345ce9c592 Improve style of examples in Kernel.SpecialForms 2016-12-20 11:13:29 +01:00
José Valim 9c734ac6ee last_day_of_month -> days_in_month 2016-12-19 11:33:25 +01:00
José Valim 9388198ca0 Ensure autocomplete does not raise on struct keys, closes #5576
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2016-12-19 10:08:02 +01:00
Kelvin Stinghen 6ce366c7d4 Better behavior for the default test task (#5561)
Before, if the task didn't find a test_helper.exs file, it failed. Now, if
no test_helper.exs is found, a warning is emitted.

Beside this, some messages on empty states were improved.
2016-12-16 15:38:09 +01:00
José Valim 10f9b78b4f Make sure put_attribute still returns a quoted expression
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2016-12-09 20:39:20 +01:00
José Valim eb8c734b96 Move extract_files to proper test file
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2016-12-09 11:47:15 +01:00
José Valim c55cf5ac90 Do not traverse filesystem on empty paths
See #5551

Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2016-12-09 11:26:20 +01:00
José Valim d28cc754b6 Use non-deprecated calls in Access.key/2 2016-12-07 11:55:20 +01:00
José Valim 1819addc75 Also mention Access.key! 2016-12-07 11:00:16 +01:00
José Valim 21e649a1f2 Add deprecation and warning of Access.key/1 behaviour, closes #5548 2016-12-07 10:53:44 +01:00
Andrea Leopardi c599956de1 Soft-deprecate (instead of hard-) Stream.uniq/2 in 1.4
The hard-deprecation should be done in version 1.5.
2016-12-07 10:36:45 +01:00
Myron Marston ca7f09a8b1 Mention new IO.inspect/2 :label option in changelog (#5547) 2016-12-07 10:31:12 +01:00
Eric Entin d32ad190ea Update CHANGELOG.md
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2016-12-06 21:50:07 +01:00
José Valim 4635631a56 Do not interpolate in defprotocol docs
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2016-12-06 17:46:46 +01:00
José Valim 2edf718f84 Do not promote Blank protocol in docs
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2016-12-06 17:37:42 +01:00
José Valim 16a14c1f6d Release v1.4.0-rc.1 2016-12-05 21:51:41 +01:00
Andrea Leopardi cfa6cf220e Use :extra_applications instead of :applications in escript.build (#5534) 2016-12-05 13:47:54 +01:00
José Valim 5e1b7d9470 Fix expansion_test
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2016-12-03 22:56:12 +01:00
José Valim 38baa729da Annotate the context for variables as zero-arity funs in quotes
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2016-12-03 22:42:35 +01:00
Andrea Leopardi 5fe53f7b7a Hard-deprecate the Set module (#5513)
It will be removed in Elixir 2.0.
2016-12-01 10:30:50 +01:00
José Valim 276b0d3c72 Ensure comprehensions with guards and filters keep proper ordering, closes #5509
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2016-11-30 20:00:38 +01:00
Henrik Nyh abb8817a0a inflect -> infer (#5503) 2016-11-30 09:36:07 +01:00
José Valim 5309843d2b Unify testing of evaluator related functionality
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2016-11-29 17:52:33 +01:00
Myron Marston 1a2c2dcda6 Improve IEx autocomplete to support navigating map atom keys (#5488)
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2016-11-29 17:52:27 +01:00
José Valim 03b6c07215 Include proper line numbers on unused module attribute warning, closes #5490
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2016-11-29 11:15:51 +01:00
José Valim d10b9d5b29 Improve docs for archives
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2016-11-29 09:59:50 +01:00
José Valim e38be075c3 Add DateTime.from_iso8601/1
Thanks to Lau Taarnskov for suggestions and code review.

Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2016-11-29 00:15:27 +01:00
José Valim 28824e8171 Ensure child task cannot link after parent unlinks
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2016-11-28 15:07:29 +01:00
José Valim 3edc407713 Update CHANGELOG 2016-11-28 12:28:57 +01:00
49 changed files with 852 additions and 384 deletions
+35 -7
View File
@@ -1,6 +1,6 @@
# Changelog for Elixir v1.4
Elixir v1.4 brings new features, enhancements and bug fixes into Elixir. The most notable changes are the addition of the `Registry` module and the `Task.async_stream/3` and `Task.async_stream/5` which aids developers in writing concurrent software. Those two features and a couple other improvements are described in detail below followed by the complete list of changes.
Elixir v1.4 brings new features, enhancements and bug fixes into Elixir. The most notable changes are the addition of the `Registry` module and the `Task.async_stream/3` and `Task.async_stream/5` which aid developers in writing concurrent software. Those two features and a couple other improvements are described in detail below followed by the complete list of changes.
## Registry
@@ -55,9 +55,9 @@ While the snippet above works fine in many occasions, for large collections it w
The `Task.async_stream` functions are also lazy, allowing developers to partially consume the stream until a condition is reached. Furthermore, `Task.Supervisor.async_stream/4` and `Task.Supervisor.async_stream/6` can be used to ensure the concurrent tasks are spawned under a given supervisor.
## Application inflection
## Application inference
Mix v1.4 now automatically inflects the list of applications that are required on runtime from your dependencies list.
Mix v1.4 now automatically infers the list of applications that are required on runtime from your dependencies list.
In previous Mix versions, most of your dependencies had to be added both to your dependencies list and applications list. Here is how a `mix.exs` would look like:
@@ -72,7 +72,7 @@ In previous Mix versions, most of your dependencies had to be added both to your
This was error prone as many developers would not list their dependencies in their applications list.
Mix v1.4 now automatically inflects your applications list as long as you leave the `:applications` key empty. The `mix.exs` above can be rewritten to:
Mix v1.4 now automatically infers your applications list as long as you leave the `:applications` key empty. The `mix.exs` above can be rewritten to:
def application do
[extra_applications: [:logger]]
@@ -91,7 +91,25 @@ Finally, if there is a dependency you don't want to include in the application r
We hope this feature provides a more streamlined workflow for developers who are building releases for their Elixir projects.
## v1.4.0-rc.0 (2016-11-28)
## Mix install from SCM
Mix v1.4 can now install escripts and archives from both Git and Hex, providing you with even more options for distributing Elixir code.
This makes it possible to distribute CLI applications written in Elixir by publishing a package which builds an escript to Hex. [`ex_doc`](https://hex.pm/packages/ex_doc) has been updated to serve as an example of how to use this new functionality.
Simply running:
mix escript.install hex ex_doc
will fetch `ex_doc` and its dependencies, build them, and then install `ex_doc` to `~/.mix/escripts` (by default). After adding `~/.mix/escripts` to your `PATH`, running `ex_doc` is as simple as:
ex_doc
You can now also install archives from Hex in this way. Since they are fetched and built on the user's machine, they do not have the same limitations as pre-built archives. However, keep in mind archives run alongside every Mix project, which may lead to conflicts. For this reason, escripts is the preferred format.
It is also possible to install escripts and archives by providing a Git/GitHub repo. See `mix help escript.install` and `mix help archive.install` for more details.
## v1.4.0 (2017-01-05)
### 1. Enhancements
@@ -105,10 +123,11 @@ We hope this feature provides a more streamlined workflow for developers who are
* [Enum] Add `min/2`, `max/2`, `min_max/2`, `min_by/3`, `max_by/3`, and `min_max_by/3` that allow a function specifying the default value when the enumerable is empty
* [Enum] Introduce `Enum.zip/1` to zip multiple entries at once
* [Float] Introduce `Float.ratio/1` that returns a tuple with the numerator and denominator as integers to retrieve the given float
* [GenServer] Log error on default `handle_info/2` implementation
* [GenServer] Log warn on default `handle_info/2` implementation
* [Inspect] Support syntax coloring via the `:syntax_color` option
* [Integer] `Integer.digits/2` now accepts negative integers
* [Integer] Add `Integer.mod/2` and `Integer.floor_div/2`
* [IO] Add `:label` option to `IO.inspect/2` to help distinguish multiple `IO.inspect/2` calls.
* [Kernel] Recognize merge conflict markers in source and provide a readable error message
* [Kernel] Warn on unused module attributes
* [Kernel] Improve compiler message on unexpected end of line
@@ -135,6 +154,7 @@ We hope this feature provides a more streamlined workflow for developers who are
* [IEx.Autocomplete] Stop appending a trailing dot when autocompleting modules in IEx
* [IEx.Autocomplete] Support autocompletion for structs
* [IEx.Autocomplete] Improve IEx autocomplete to support navigating map atom keys
* [IEx.Helpers] `c/1` now compiles in memory by default to avoid common issue where `.beam` files remain at projects root directory
* [IEx.Helpers] Add info about protocols in `i/1`
* [IEx.Server] Support interrupting IEx evaluation through the Ctrl+G prompt
@@ -142,12 +162,14 @@ We hope this feature provides a more streamlined workflow for developers who are
#### Mix
* [mix archive] Compress archive files built by `mix archive` as they are now unzipped during installation
* [mix compile] Automatically inflect the list of applications for Mix projects
* [mix archive] Install from SCM
* [mix compile] Automatically infer the list of applications for Mix projects
* [mix cmd] Add the ability to specify one or more apps in `mix cmd`
* [mix deps] Warn if there are non-applications in the `apps` directory for umbrella projects
* [mix deps] Add warning for invalid paths on `mix deps.clean`
* [mix deps] Add `Mix.Project.apps_paths` that returns the paths to children applications in umbrella projects
* [mix deps] Add `MIX_REBAR` environment variable for overriding local rebar
* [mix escript] Install from SCM
* [mix new] Check directory existence in `mix new` and ask how to proceed if one exists
* [mix new] Applications built with the `--sup` flag now have an individual module to work as application callback
* [mix test] Add `--formatter` option to `mix test`
@@ -157,9 +179,12 @@ We hope this feature provides a more streamlined workflow for developers who are
#### Elixir
* [Access] Do not accept nils in `Access.key/1` and `Access.key/2` in favor of explicit default values (or Access.key!/1 if you expect the key to always be available)
* [Float] Avoid multiple roundings in `Float.ceil/2`, `Float.floor/2` and `Float.round/2`
* [Kernel] Don't crash in `macro_exported?/3` when dealing with Erlang modules
* [Kernel] Ensure locals calls are rewritten when calling a local function or macro from inside a module
* [Kernel] Annotate the context for variables as zero-arity funs in quotes
* [Kernel.SpecialForms] Ensure comprehensions with guards and filters keep proper ordering,
* [Kernel.SpecialForms] Produce meaningful warning when with's else clauses have no effect
* [Macro] Wrap fn calls in parens in `Macro.to_string/2`
* [Macro] Do not print aliases as keys inside keyword lists in `Macro.to_string/2`
@@ -167,6 +192,7 @@ We hope this feature provides a more streamlined workflow for developers who are
* [Stream] Ensure `Stream.take/2` does not consume next element on `:suspend`
* [String] Fix infinite recursion in `String.replace_leading/3` and `String.replace_trailing/3` when given an empty string
* [Task] Fix `Task.shutdown/1,2` infinite block when task has no monitor
* [Task] Ensure task cannot link after parents unlinks
#### ExUnit
@@ -197,12 +223,14 @@ We hope this feature provides a more streamlined workflow for developers who are
#### Elixir
* [Access] `Access.key/1` is deprecated due to erratic behaviour for missing keys, please use Access.key/2 instead with proper default values
* [Behaviour] The `Behaviour` module is deprecated. Callbacks may now be defined directly via the `@callback` attribute
* [Enum] Deprecate `Enum.uniq/2` in favor of `Enum.uniq_by/2`
* [Float] `Float.to_char_list/2` and `Float.to_string/2` are deprecated (use the :erlang functions if such conversions are desired)
* [Kernel] Deprecate support for making private functions overridable. Overridable functions must always be public as they must be contracts
* [Kernel] Warn if variable is used as a function call
* [OptionParser] Deprecate aliases with multiple letters, such as `-abc`
* [Set] Deprecate the `Set` module
* [Stream] Deprecate `Stream.uniq/2` in favor of `Stream.uniq_by/2`
#### IEx
+1 -1
View File
@@ -1,7 +1,7 @@
REBAR ?= "$(CURDIR)/rebar"
PREFIX ?= /usr/local
SHARE_PREFIX ?= $(PREFIX)/share
CANONICAL := master/
CANONICAL :=
ELIXIRC := bin/elixirc --verbose --ignore-module-conflict
ERLC := erlc -I lib/elixir/include
ERL := erl -I lib/elixir/include -noshell -pa lib/elixir/ebin
+1 -1
View File
@@ -1 +1 @@
1.4.0-rc.0
1.4.0
+29 -7
View File
@@ -358,6 +358,28 @@ defmodule Access do
## Accessors
@doc false
def key(key) do
IO.warn "Access.key/1 is deprecated due to erratic behaviour for missing keys, " <>
"please use Access.key/2 instead with proper default values " <>
"(or Access.key!/1 if you expect the key to always be available)"
fn
:get, data, next ->
next.(Map.get(to_map(data), key))
:get_and_update, data, next ->
value = Map.get(to_map(data), key)
case next.(value) do
{get, update} -> {get, Map.put(data, key, update)}
:pop -> {value, Map.delete(data, key)}
end
end
end
defp to_map(nil), do: %{}
defp to_map(%{} = map), do: map
defp to_map(data), do: raise "Access.key/1 expected a map/struct or nil, got: #{inspect data}"
@doc """
Returns a function that accesses the given key in a map/struct.
@@ -367,13 +389,13 @@ defmodule Access do
The returned function uses the default value if the key does not exist.
This can be used to specify defaults and safely traverse missing keys:
iex> get_in(%{}, [Access.key(:user, %{}), Access.key(:name)])
iex> get_in(%{}, [Access.key(:user, %{}), Access.key(:name, nil)])
nil
Such is also useful when using update functions, allowing us to introduce
values as we traverse the data-structure for updates:
iex> put_in(%{}, [Access.key(:user, %{}), Access.key(:name)], "Mary")
iex> put_in(%{}, [Access.key(:user, %{}), Access.key(:name, nil)], "Mary")
%{user: %{name: "Mary"}}
## Examples
@@ -381,23 +403,23 @@ defmodule Access do
iex> map = %{user: %{name: "john"}}
iex> get_in(map, [Access.key(:unknown, %{}), Access.key(:name, "john")])
"john"
iex> get_and_update_in(map, [Access.key(:user), Access.key(:name)], fn
iex> get_and_update_in(map, [Access.key!(:user), Access.key!(:name)], fn
...> prev -> {prev, String.upcase(prev)}
...> end)
{"john", %{user: %{name: "JOHN"}}}
iex> pop_in(map, [Access.key(:user), Access.key(:name)])
iex> pop_in(map, [Access.key!(:user), Access.key!(:name)])
{"john", %{user: %{}}}
An error is raised if the accessed structure is not a map or a struct:
iex> get_in(nil, [Access.key(:foo)])
iex> get_in(nil, [Access.key(:foo, nil)])
** (BadMapError) expected a map, got: nil
iex> get_in([], [Access.key(:foo)])
iex> get_in([], [Access.key(:foo, nil)])
** (BadMapError) expected a map, got: []
"""
def key(key, default \\ nil) do
def key(key, default) do
fn
:get, data, next ->
next.(Map.get(data, key, default))
+102 -6
View File
@@ -66,9 +66,9 @@ defmodule Calendar do
time_zone: time_zone, zone_abbr: zone_abbr, utc_offset: utc_offset, std_offset: std_offset}
@doc """
Returns the last day of the month for the given year-month pair.
Returns how many days there are in the given year-month.
"""
@callback last_day_of_month(year, month) :: day
@callback days_in_month(year, month) :: day
@doc """
Returns true if the given year is a leap year.
@@ -90,7 +90,7 @@ defmodule Calendar do
@callback date_to_string(year, month, day) :: String.t
@doc """
Coverts the date time (without time zone) into a string according to the calendar.
Converts the date time (without time zone) into a string according to the calendar.
"""
@callback naive_datetime_to_string(year, month, day, hour, minute, second, microsecond) :: String.t
@@ -175,6 +175,24 @@ defmodule Date do
calendar.leap_year?(year)
end
@doc """
Returns the number of days in the given date month.
## Examples
iex> Date.days_in_month(~D[1900-01-13])
31
iex> Date.days_in_month(~D[1900-02-09])
28
iex> Date.days_in_month(~N[2000-02-20 01:23:45])
29
"""
@spec days_in_month(Calendar.date) :: Calendar.day
def days_in_month(%{calendar: calendar, year: year, month: month}) do
calendar.days_in_month(year, month)
end
@doc """
Builds a new ISO date.
@@ -1598,9 +1616,7 @@ defmodule DateTime do
WARNING: the ISO 8601 datetime format does not contain the time zone nor
its abbreviation, which means information is lost when converting to such
format. This is also why this module does not provide a `from_iso8601/1`
function, as it is impossible to build a proper `DateTime` from only the
information in the ISO 8601 string.
format.
### Examples
@@ -1630,6 +1646,86 @@ defmodule DateTime do
time_zone, zone_abbr, utc_offset, std_offset)
end
@doc """
Parses the extended "Date and time of day" format described by
[ISO 8601:2004](https://en.wikipedia.org/wiki/ISO_8601).
Since ISO8601 does not include the proper time zone, the given
string will be converted to UTC and its offset in seconds will be
returned as part of this function. Therefore offset information
must be present in the string.
As specified in the standard, the separator "T" may be omitted if
desired as there is no ambiguity within this function.
Time representations with reduced accuracy are not supported.
## Examples
iex> DateTime.from_iso8601("2015-01-23T23:50:07Z")
{:ok, %DateTime{calendar: Calendar.ISO, day: 23, hour: 23, microsecond: {0, 0}, minute: 50, month: 1, second: 7, std_offset: 0,
time_zone: "Etc/UTC", utc_offset: 0, year: 2015, zone_abbr: "UTC"}, 0}
iex> DateTime.from_iso8601("2015-01-23T23:50:07.123+02:30")
{:ok, %DateTime{calendar: Calendar.ISO, day: 23, hour: 21, microsecond: {123000, 3}, minute: 20, month: 1, second: 7, std_offset: 0,
time_zone: "Etc/UTC", utc_offset: 0, year: 2015, zone_abbr: "UTC"}, 9000}
iex> DateTime.from_iso8601("2015-01-23P23:50:07")
{:error, :invalid_format}
iex> DateTime.from_iso8601("2015-01-23 23:50:07A")
{:error, :invalid_format}
iex> DateTime.from_iso8601("2015-01-23T23:50:07")
{:error, :missing_offset}
iex> DateTime.from_iso8601("2015-01-23 23:50:61")
{:error, :invalid_time}
iex> DateTime.from_iso8601("2015-01-32 23:50:07")
{:error, :invalid_date}
iex> DateTime.from_iso8601("2015-01-23T23:50:07.123-00:00")
{:error, :invalid_format}
iex> DateTime.from_iso8601("2015-01-23T23:50:07.123-00:60")
{:error, :invalid_format}
"""
@spec from_iso8601(String.t) :: {:ok, t, Calendar.utc_offset} | {:error, atom}
def from_iso8601(<<year::4-bytes, ?-, month::2-bytes, ?-, day::2-bytes, sep,
hour::2-bytes, ?:, min::2-bytes, ?:, sec::2-bytes, rest::binary>>) when sep in [?\s, ?T] do
with {year, ""} <- Integer.parse(year),
{month, ""} <- Integer.parse(month),
{day, ""} <- Integer.parse(day),
{hour, ""} <- Integer.parse(hour),
{min, ""} <- Integer.parse(min),
{sec, ""} <- Integer.parse(sec),
{microsec, rest} <- Calendar.ISO.parse_microsecond(rest),
{:ok, date} <- Calendar.ISO.date(year, month, day),
{:ok, time} <- Time.new(hour, min, sec, microsec),
{:ok, offset} <- parse_offset(rest) do
%{year: year, month: month, day: day} = date
%{hour: hour, minute: minute, second: second, microsecond: microsecond} = time
erl = {{year, month, day}, {hour, minute, second}}
seconds = :calendar.datetime_to_gregorian_seconds(erl)
{{year, month, day}, {hour, minute, second}} =
:calendar.gregorian_seconds_to_datetime(seconds - offset)
{:ok, %DateTime{year: year, month: month, day: day,
hour: hour, minute: minute, second: second, microsecond: microsecond,
std_offset: 0, utc_offset: 0, zone_abbr: "UTC", time_zone: "Etc/UTC"}, offset}
else
{:error, reason} -> {:error, reason}
_ -> {:error, :invalid_format}
end
end
def from_iso8601(_) do
{:error, :invalid_format}
end
defp parse_offset(rest) do
case Calendar.ISO.parse_offset(rest) do
{offset, ""} when is_integer(offset) -> {:ok, offset}
{nil, ""} -> {:error, :missing_offset}
_ -> {:error, :invalid_format}
end
end
@doc """
Converts the given datetime to a string according to its calendar.
+12 -12
View File
@@ -17,32 +17,32 @@ defmodule Calendar.ISO do
@type day :: 1..31
@doc """
Returns the last day of the month for the given year.
Returns how many days there are in the given year-month.
## Examples
iex> Calendar.ISO.last_day_of_month(1900, 1)
iex> Calendar.ISO.days_in_month(1900, 1)
31
iex> Calendar.ISO.last_day_of_month(1900, 2)
iex> Calendar.ISO.days_in_month(1900, 2)
28
iex> Calendar.ISO.last_day_of_month(2000, 2)
iex> Calendar.ISO.days_in_month(2000, 2)
29
iex> Calendar.ISO.last_day_of_month(2001, 2)
iex> Calendar.ISO.days_in_month(2001, 2)
28
iex> Calendar.ISO.last_day_of_month(2004, 2)
iex> Calendar.ISO.days_in_month(2004, 2)
29
iex> Calendar.ISO.last_day_of_month(2004, 4)
iex> Calendar.ISO.days_in_month(2004, 4)
30
"""
@spec last_day_of_month(year, month) :: 28..31
def last_day_of_month(year, month)
@spec days_in_month(year, month) :: 28..31
def days_in_month(year, month)
def last_day_of_month(year, 2) do
def days_in_month(year, 2) do
if leap_year?(year), do: 29, else: 28
end
def last_day_of_month(_, month) when month in [4, 6, 9, 11], do: 30
def last_day_of_month(_, month) when month in 1..12, do: 31
def days_in_month(_, month) when month in [4, 6, 9, 11], do: 30
def days_in_month(_, month) when month in 1..12, do: 31
@doc """
Returns if the given year is a leap year.
+1 -1
View File
@@ -647,7 +647,7 @@ defmodule UndefinedFunctionError do
def message(%{reason: :"function not exported", module: module, function: function, arity: arity, exports: exports}) do
suffix =
if macro_exported?(module, function, arity) do
" but #{inspect(module)} defines a macro with the same name and arity." <>
". However there is a macro with the same name and arity." <>
" Be sure to require #{inspect(module)} if you intend to invoke this macro"
else
did_you_mean(module, function, arity, exports)
+2 -1
View File
@@ -203,7 +203,8 @@ defmodule Float do
count = count - exp + 1023
cond do
count <= 0 -> # There is no decimal precision
count <= 0 or # There is no decimal precision
(0 == exp and <<0::52>> == significant) -> #zero or minus zero
float
count >= 104 -> # Precision beyond 15 digits
+1 -1
View File
@@ -568,7 +568,7 @@ defmodule GenServer do
{_, []} -> self()
{_, name} -> name
end
:error_logger.error_msg('~p ~p received unexpected message in handle_info/2: ~p~n',
:error_logger.warning_msg('~p ~p received unexpected message in handle_info/2: ~p~n',
[__MODULE__, proc, msg])
{:noreply, state}
end
+71 -64
View File
@@ -2399,7 +2399,11 @@ defmodule Kernel do
# @attribute(value)
defp do_at([arg], meta, name, function?, env) do
read? = :lists.keymember(:context, 1, meta)
line =
case :lists.keymember(:context, 1, meta) do
true -> nil
false -> env.line
end
cond do
function? ->
@@ -2413,11 +2417,11 @@ defmodule Kernel do
{stack, _} = :elixir_quote.escape(env_stacktrace(env), false)
arg = {env.line, arg}
quote do: Module.put_attribute(__MODULE__, unquote(name), unquote(arg),
unquote(stack), unquote(read?))
unquote(stack), unquote(line))
true ->
quote do: Module.put_attribute(__MODULE__, unquote(name), unquote(arg),
nil, unquote(read?))
nil, unquote(line))
end
end
@@ -3704,7 +3708,7 @@ defmodule Kernel do
end
end
@doc """
@doc ~S"""
Defines a protocol.
A protocol specifies an API that should be defined by its
@@ -3712,43 +3716,46 @@ defmodule Kernel do
## Examples
In Elixir, only `false` and `nil` are considered falsy values.
Everything else evaluates to `true` in `if/2` clauses. Depending
on the application, it may be important to specify a `blank?`
protocol that returns a boolean for other data types that should
be considered "blank". For instance, an empty list or an empty
binary could be considered blank.
In Elixir, we have two verbs for checking how many items there
are in a data structure: `length` and `size`. `length` means the
information must be computed. For example, `length(list)` needs to
traverse the whole list to calculate its length. On the other hand,
`tuple_size(tuple)` and `byte_size(binary)` do not depend on the
tuple and binary size as the size information is precomputed in
the data structure.
Such protocol could be implemented as follows:
Although Elixir includes specific functions such as `tuple_size`,
`binary_size` and `map_size`, sometimes we want to be able to
retrieve the size of a data structure regardless of its type.
In Elixir we can write polymorphic code, i.e. code that works
with different shapes/types, by using protocols. A size protocol
could be implemented as follows:
defprotocol Blank do
@doc "Returns `true` if `data` is considered blank/empty"
def blank?(data)
defprotocol Size do
@doc "Calculates the size (and not the length!) of a data structure"
def size(data)
end
Now that the protocol is defined it can be implemented. It needs to be
implemented for each Elixir type; for example:
Now that the protocol can be implemented for every data structure
the protocol may have a compliant implementation for:
# Integers are never blank
defimpl Blank, for: Integer do
def blank?(number), do: false
defimpl Size, for: Binary do
def size(binary), do: byte_size(binary)
end
# The only blank list is the empty one
defimpl Blank, for: List do
def blank?([]), do: true
def blank?(_), do: false
defimpl Size, for: Map do
def size(map), do: map_size(map)
end
# The only blank atoms are "false" and "nil"
defimpl Blank, for: Atom do
def blank?(false), do: true
def blank?(nil), do: true
def blank?(_), do: false
defimpl Size, for: Tuple do
def size(tuple), do: tuple_size(tuple)
end
The implementation of the `Blank` protocol would need to be defined for all
Elixir types. The available types are:
Notice we didn't implement it for lists as we don't have the
`size` information on lists, rather its value needs to be
computed with `length`.
It is possible to implement protocols for all Elixir types:
* Structs (see below)
* `Tuple`
@@ -3768,11 +3775,11 @@ defmodule Kernel do
The real benefit of protocols comes when mixed with structs.
For instance, Elixir ships with many data types implemented as
structs, like `MapSet`. We can implement the `Blank` protocol
structs, like `MapSet`. We can implement the `Size` protocol
for those types as well:
defimpl Blank, for: MapSet do
def blank?(enum_like), do: Enum.empty?(enum_like)
defimpl Size, for: MapSet do
def size(map_set), do: MapSet.size(map_set)
end
When implementing a protocol for a struct, the `:for` option can
@@ -3782,54 +3789,54 @@ defmodule Kernel do
defmodule User do
defstruct [:email, :name]
defimpl Blank do
def blank?(%User{}), do: false
defimpl Size do
def size(%User{}), do: 2 # two fields
end
end
If a protocol is not found for a given type, it will fallback to
`Any`. Protocols that are implemented for maps don't work by default
on structs; look at `defstruct/1` for more information about deriving
If a protocol implementation is not found for a given type,
invoking the protocol will raise unless it is configured to
fallback to `Any`. Conveniences for building implementations
on top of existing ones are also available, look at `defstruct/1`
for more information about deriving
protocols.
## Fallback to any
In some cases, it may be convenient to provide a default
implementation for all types. This can be achieved by
setting the `@fallback_to_any` attribute to `true` in the protocol
implementation for all types. This can be achieved by setting
the `@fallback_to_any` attribute to `true` in the protocol
definition:
defprotocol Blank do
defprotocol Size do
@fallback_to_any true
def blank?(data)
def size(data)
end
The `Blank` protocol can now be implemented for `Any`:
The `Size` protocol can now be implemented for `Any`:
defimpl Blank, for: Any do
def blank?(_), do: true
defimpl Size, for: Any do
def size(_), do: 0
end
One may wonder why such behaviour (fallback to any) is not the default one.
It is two-fold: first, the majority of protocols cannot
implement an action in a generic way for all types; in fact,
providing a default implementation may be harmful, because users
may rely on the default implementation instead of providing a
specialized one.
Second, falling back to `Any` adds an extra lookup to all types,
which is unnecessary overhead unless an implementation for `Any` is
required.
Although the implementation above is arguably not a reasonable
one. For example, it makes no sense to say a PID or an Integer
have a size of 0. That's one of the reasons why `@fallback_to_any`
is an opt-in behaviour. For the majority of protocols, raising
an error when a protocol is not implemented is the proper behaviour.
## Types
Defining a protocol automatically defines a type named `t`, which
can be used as follows:
@spec present?(Blank.t) :: boolean
def present?(blank) do
not Blank.blank?(blank)
@spec print_size(Size.t) :: :ok
def print_size(data) do
IO.puts(case Size.size(data) do
0 -> "data has no items"
1 -> "data has one item"
n -> "data has #{n} items"
end)
end
The `@spec` above expresses that all types allowed to implement the
@@ -3868,12 +3875,12 @@ defmodule Kernel do
all implementations are known up-front, Elixir provides a feature
called protocol consolidation. For this reason, all protocols are
compiled with `debug_info` set to `true`, regardless of the option
set by `elixirc` compiler. The debug info though may be removed
after consolidation.
set by `elixirc` compiler. The debug info though may be removed after
consolidation.
For more information on how to apply protocol consolidation to
a given project, please check the functions in the `Protocol`
module or the `mix compile.protocols` task.
Protocol consolidation is applied by default to all Mix projects.
For applying consolidation manually, please check the functions in
the `Protocol` module or the `mix compile.protocols` task.
"""
defmacro defprotocol(name, do_block)
+3 -3
View File
@@ -222,7 +222,7 @@ defmodule Kernel.SpecialForms do
iex> x = 1
iex> <<x::8>> == <<x::size(8)>>
true
iex> <<x::8 * 4>> == <<x::size(8)-unit(4)>>
iex> <<x::8*4>> == <<x::size(8)-unit(4)>>
true
This syntax reflects the fact the effective size is given by
@@ -1241,7 +1241,7 @@ defmodule Kernel.SpecialForms do
[2, 4, 6, 8]
# A comprehension with two generators
iex> for x <- [1, 2], y <- [2, 3], do: x*y
iex> for x <- [1, 2], y <- [2, 3], do: x * y
[2, 3, 4, 6]
Filters can also be given:
@@ -1263,7 +1263,7 @@ defmodule Kernel.SpecialForms do
need to organize bitstring streams:
iex> pixels = <<213, 45, 132, 64, 76, 32, 76, 0, 0, 234, 32, 15>>
iex> for <<r::8, g::8, b::8 <- pixels >>, do: {r, g, b}
iex> for <<r::8, g::8, b::8 <- pixels>>, do: {r, g, b}
[{213, 45, 132}, {64, 76, 32}, {76, 0, 0}, {234, 32, 15}]
Variable assignments inside the comprehension, be it in generators,
+16 -17
View File
@@ -843,8 +843,6 @@ defmodule Module do
@doc """
Puts a module attribute with key and value in the given module.
It returns the value of the attribute after normalization.
## Examples
defmodule MyModule do
@@ -852,9 +850,9 @@ defmodule Module do
end
"""
@spec put_attribute(module, key :: atom, value :: term) :: term
@spec put_attribute(module, key :: atom, value :: term) :: :ok
def put_attribute(module, key, value) do
put_attribute(module, key, value, nil, false)
put_attribute(module, key, value, nil, nil)
end
@doc """
@@ -910,7 +908,7 @@ defmodule Module do
table = data_table_for(module)
case :ets.take(table, key) do
[{_, value, _accumulated? = true, _}] ->
:ets.insert(table, {key, [], true, true})
:ets.insert(table, {key, [], true, nil})
value
[{_, value, _, _}] ->
value
@@ -920,9 +918,10 @@ defmodule Module do
end
@doc """
Registers an attribute. By registering an attribute, a developer
is able to customize how Elixir will store and accumulate the
attribute values.
Registers an attribute.
By registering an attribute, a developer is able to customize
how Elixir will store and accumulate the attribute values.
## Options
@@ -960,7 +959,7 @@ defmodule Module do
end
if Keyword.get(opts, :accumulate) do
:ets.insert_new(table, {new, [], _accumulated? = true, _read? = true}) ||
:ets.insert_new(table, {new, [], _accumulated? = true, _unread_line = nil}) ||
:ets.update_element(table, new, {3, true})
end
@@ -1031,7 +1030,7 @@ defmodule Module do
[] -> [value]
end
:ets.insert(table, {key, new, true, true})
:ets.insert(table, {key, new, true, nil})
end
@doc false
@@ -1043,7 +1042,7 @@ defmodule Module do
case :ets.lookup(table, key) do
[{^key, val, _, _}] ->
:ets.update_element(table, key, {4, true})
:ets.update_element(table, key, {4, nil})
val
[] when is_list(stack) ->
# TODO: Consider raising instead of warning on v2.0 as it usually cascades
@@ -1058,25 +1057,25 @@ defmodule Module do
@doc false
# Used internally by Kernel's @.
# This function is private and must be used only internally.
def put_attribute(module, key, value, stack, read?) when is_atom(key) do
def put_attribute(module, key, value, stack, unread_line) when is_atom(key) do
assert_not_compiled!(:put_attribute, module)
table = data_table_for(module)
value = preprocess_attribute(key, value)
case :ets.lookup(table, key) do
[{^key, {line, <<_::binary>>}, accumulated?, _read?}]
[{^key, {line, <<_::binary>>}, accumulated?, _unread_line}]
when key in [:doc, :typedoc, :moduledoc] and is_list(stack) ->
IO.warn "redefining @#{key} attribute previously set at line #{line}", stack
:ets.insert(table, {key, value, accumulated?, read?})
:ets.insert(table, {key, value, accumulated?, unread_line})
[{^key, current, _accumulated? = true, _read?}] ->
:ets.insert(table, {key, [value | current], true, read?})
:ets.insert(table, {key, [value | current], true, unread_line})
_ ->
:ets.insert(table, {key, value, false, read?})
:ets.insert(table, {key, value, false, unread_line})
end
value
:ok
end
## Helpers
+3 -1
View File
@@ -9,7 +9,9 @@ defmodule Set do
@type values :: [ value ]
@type t :: map
# TODO: Deprecate every function by 1.4
# TODO: Remove by 2.0
# (hard-deprecated in elixir_dispatch)
defmacrop target(set) do
quote do
case unquote(set) do
-1
View File
@@ -902,7 +902,6 @@ defmodule Stream do
@doc false
def uniq(enum, fun) do
IO.warn "Stream.uniq/2 is deprecated, use Stream.uniq_by/2 instead"
uniq_by(enum, fun)
end
+4 -2
View File
@@ -287,8 +287,10 @@ defmodule Task do
concurrently on each item in `enumerable`.
Each item will be appended to the given `args` and processed by its
own task. The tasks will be linked to the current process similar to
`async/3`.
own task. The tasks will be linked to an intermediate process that is
then linked to the current process. This means a failure in a task
terminates the current process and a failure in the current process
terminates all tasks.
When streamed, each task will emit `{:ok, val}` upon successful
completion or `{:exit, val}` if the caller is trapping exits. Results
+80 -67
View File
@@ -10,29 +10,21 @@ defmodule Task.Supervised do
{:ok, :proc_lib.spawn_link(__MODULE__, :noreply, [info, fun])}
end
def start_link(caller, link, info, fun) do
{:ok, spawn_link(caller, link, info, fun)}
def start_link(caller, monitor, info, fun) do
{:ok, spawn_link(caller, monitor, info, fun)}
end
def spawn_link(caller, link \\ :nolink, info, fun) do
:proc_lib.spawn_link(__MODULE__, :reply, [caller, link, info, fun])
def spawn_link(caller, monitor \\ :nomonitor, info, fun) do
:proc_lib.spawn_link(__MODULE__, :reply, [caller, monitor, info, fun])
end
def reply(caller, link, info, mfa) do
def reply(caller, monitor, info, mfa) do
initial_call(mfa)
case link do
:link ->
try do
Process.link(caller)
catch
:error, :noproc ->
exit({:shutdown, :noproc})
end
reply(caller, nil, @ref_timeout, info, mfa)
case monitor do
:monitor ->
mref = Process.monitor(caller)
reply(caller, mref, @ref_timeout, info, mfa)
:nolink ->
:nomonitor ->
reply(caller, nil, :infinity, info, mfa)
end
end
@@ -157,37 +149,41 @@ defmodule Task.Supervised do
parent = self()
# Start a process responsible for translating down messages.
{monitor_pid, monitor_ref} = spawn_monitor(fn -> stream_monitor(parent) end)
{:trap_exit, trap_exit} =
Process.info(self(), :trap_exit)
{monitor_pid, monitor_ref} =
Process.spawn(fn -> stream_monitor(parent, mfa, spawn, trap_exit) end, [:link, :monitor])
send(monitor_pid, {parent, monitor_ref})
stream_reduce(acc, max_concurrency, 0, 0, %{}, next,
reducer, mfa, spawn, monitor_pid, monitor_ref, timeout)
reducer, monitor_pid, monitor_ref, timeout)
end
defp stream_reduce({:halt, acc}, _max, _spawned, _delivered, waiting, next,
_reducer, _mfa, _spawn, monitor_pid, monitor_ref, timeout) do
defp stream_reduce({:halt, acc}, _max, _spawned, _delivered, _waiting, next,
_reducer, monitor_pid, monitor_ref, timeout) do
stream_close(monitor_pid, monitor_ref, timeout)
is_function(next) && next.({:halt, []})
stream_close(waiting, monitor_pid, monitor_ref, timeout)
{:halted, acc}
end
defp stream_reduce({:suspend, acc}, max, spawned, delivered, waiting, next,
reducer, mfa, spawn, monitor_pid, monitor_ref, timeout) do
reducer, monitor_pid, monitor_ref, timeout) do
{:suspended, acc, &stream_reduce(&1, max, spawned, delivered, waiting, next,
reducer, mfa, spawn, monitor_pid, monitor_ref, timeout)}
reducer, monitor_pid, monitor_ref, timeout)}
end
# All spawned, all delivered, next is done.
defp stream_reduce({:cont, acc}, _max, spawned, delivered, waiting, next,
_reducer, _mfa, _spawn, monitor_pid, monitor_ref, timeout)
defp stream_reduce({:cont, acc}, _max, spawned, delivered, _waiting, next,
_reducer, monitor_pid, monitor_ref, timeout)
when spawned == delivered and next == :done do
stream_close(waiting, monitor_pid, monitor_ref, timeout)
stream_close(monitor_pid, monitor_ref, timeout)
{:done, acc}
end
# No more tasks to spawn because max == 0 or next is done.
# No more tasks to spawned because max == 0 or next is done.
defp stream_reduce({:cont, acc}, max, spawned, delivered, waiting, next,
reducer, mfa, spawn, monitor_pid, monitor_ref, timeout)
reducer, monitor_pid, monitor_ref, timeout)
when max == 0
when next == :done do
receive do
@@ -195,7 +191,7 @@ defmodule Task.Supervised do
%{^position => {pid, :running}} = waiting
waiting = Map.put(waiting, position, {pid, {:ok, value}})
stream_reduce({:cont, acc}, max, spawned, delivered, waiting, next,
reducer, mfa, spawn, monitor_pid, monitor_ref, timeout)
reducer, monitor_pid, monitor_ref, timeout)
{:down, {^monitor_ref, position}, reason} ->
waiting =
case waiting do
@@ -203,53 +199,53 @@ defmodule Task.Supervised do
%{^position => {_, :running}} -> Map.put(waiting, position, {nil, {:exit, reason}})
end
stream_deliver({:cont, acc}, max + 1, spawned, delivered, waiting, next,
reducer, mfa, spawn, monitor_pid, monitor_ref, timeout)
reducer, monitor_pid, monitor_ref, timeout)
{:DOWN, ^monitor_ref, _, ^monitor_pid, reason} ->
stream_cleanup_inbox(monitor_ref)
stream_cleanup_inbox(monitor_pid, monitor_ref)
exit({reason, {__MODULE__, :stream, [timeout]}})
after
timeout ->
stream_close(waiting, monitor_pid, monitor_ref, timeout)
stream_close(monitor_pid, monitor_ref, timeout)
exit({:timeout, {__MODULE__, :stream, [timeout]}})
end
end
defp stream_reduce({:cont, acc}, max, spawned, delivered, waiting, next,
reducer, mfa, spawn, monitor_pid, monitor_ref, timeout) do
reducer, monitor_pid, monitor_ref, timeout) do
try do
next.({:cont, []})
catch
kind, reason ->
stacktrace = System.stacktrace
stream_close(waiting, monitor_pid, monitor_ref, timeout)
stream_close(monitor_pid, monitor_ref, timeout)
:erlang.raise(kind, reason, stacktrace)
else
{:suspended, [value], next} ->
waiting = stream_spawn(value, spawned, waiting, mfa, spawn, monitor_pid, monitor_ref)
waiting = stream_spawn(value, spawned, waiting, monitor_pid, monitor_ref, timeout)
stream_reduce({:cont, acc}, max - 1, spawned + 1, delivered, waiting, next,
reducer, mfa, spawn, monitor_pid, monitor_ref, timeout)
reducer, monitor_pid, monitor_ref, timeout)
{_, [value]} ->
waiting = stream_spawn(value, spawned, waiting, mfa, spawn, monitor_pid, monitor_ref)
waiting = stream_spawn(value, spawned, waiting, monitor_pid, monitor_ref, timeout)
stream_reduce({:cont, acc}, max - 1, spawned + 1, delivered, waiting, :done,
reducer, mfa, spawn, monitor_pid, monitor_ref, timeout)
reducer, monitor_pid, monitor_ref, timeout)
{_, []} ->
stream_reduce({:cont, acc}, max, spawned, delivered, waiting, :done,
reducer, mfa, spawn, monitor_pid, monitor_ref, timeout)
reducer, monitor_pid, monitor_ref, timeout)
end
end
defp stream_deliver({:suspend, acc}, max, spawned, delivered, waiting, next,
reducer, mfa, spawn, monitor_pid, monitor_ref, timeout) do
reducer, monitor_pid, monitor_ref, timeout) do
{:suspended, acc, &stream_deliver(&1, max, spawned, delivered, waiting, next,
reducer, mfa, spawn, monitor_pid, monitor_ref, timeout)}
reducer, monitor_pid, monitor_ref, timeout)}
end
defp stream_deliver({:halt, acc}, max, spawned, delivered, waiting, next,
reducer, mfa, spawn, monitor_pid, monitor_ref, timeout) do
reducer, monitor_pid, monitor_ref, timeout) do
stream_reduce({:halt, acc}, max, spawned, delivered, waiting, next,
reducer, mfa, spawn, monitor_pid, monitor_ref, timeout)
reducer, monitor_pid, monitor_ref, timeout)
end
defp stream_deliver({:cont, acc}, max, spawned, delivered, waiting, next,
reducer, mfa, spawn, monitor_pid, monitor_ref, timeout) do
reducer, monitor_pid, monitor_ref, timeout) do
case waiting do
%{^delivered => {nil, reply}} ->
try do
@@ -258,38 +254,44 @@ defmodule Task.Supervised do
kind, reason ->
stacktrace = System.stacktrace
is_function(next) && next.({:halt, []})
stream_close(waiting, monitor_pid, monitor_ref, timeout)
stream_close(monitor_pid, monitor_ref, timeout)
:erlang.raise(kind, reason, stacktrace)
else
pair ->
stream_deliver(pair, max, spawned, delivered + 1, Map.delete(waiting, delivered), next,
reducer, mfa, spawn, monitor_pid, monitor_ref, timeout)
reducer, monitor_pid, monitor_ref, timeout)
end
%{} ->
stream_reduce({:cont, acc}, max, spawned, delivered, waiting, next,
reducer, mfa, spawn, monitor_pid, monitor_ref, timeout)
reducer, monitor_pid, monitor_ref, timeout)
end
end
defp stream_close(waiting, monitor_pid, monitor_ref, timeout) do
for {_, {pid, _}} <- waiting do
pid && Process.unlink(pid)
end
send(monitor_pid, {:down, monitor_ref})
defp stream_close(monitor_pid, monitor_ref, timeout) do
send(monitor_pid, {:stop, monitor_ref})
receive do
{:DOWN, ^monitor_ref, _, _, {:shutdown, ^monitor_ref}} ->
{:DOWN, ^monitor_ref, _, _, :normal} ->
stream_cleanup_inbox(monitor_pid, monitor_ref)
:ok
{:DOWN, ^monitor_ref, _, _, reason} ->
stream_cleanup_inbox(monitor_pid, monitor_ref)
exit({reason, {__MODULE__, :stream, [timeout]}})
end
stream_cleanup_inbox(monitor_ref)
end
defp stream_cleanup_inbox(monitor_pid, monitor_ref) do
receive do
{:EXIT, ^monitor_pid, _} -> stream_cleanup_inbox(monitor_ref)
after
0 -> stream_cleanup_inbox(monitor_ref)
end
end
defp stream_cleanup_inbox(monitor_ref) do
receive do
{{^monitor_ref, _}, _} ->
stream_cleanup_inbox(monitor_ref)
{:DOWN, {^monitor_ref, _}, _} ->
{:down, {^monitor_ref, _}, _} ->
stream_cleanup_inbox(monitor_ref)
after
0 ->
@@ -300,38 +302,47 @@ defmodule Task.Supervised do
defp stream_mfa({mod, fun, args}, arg), do: {mod, fun, [arg | args]}
defp stream_mfa(fun, arg), do: {:erlang, :apply, [fun, [arg]]}
defp stream_spawn(value, spawned, waiting, mfa, spawn, monitor_pid, monitor_ref) do
owner = self()
{type, pid} = spawn.(owner, stream_mfa(mfa, value))
send(monitor_pid, {:up, owner, monitor_ref, spawned, type, pid})
defp stream_spawn(value, spawned, waiting, monitor_pid, monitor_ref, timeout) do
send(monitor_pid, {:spawn, spawned, value})
receive do
{:spawned, {^monitor_ref, ^spawned}, pid} ->
send(pid, {self(), {monitor_ref, spawned}})
Map.put(waiting, spawned, {pid, :running})
{:DOWN, ^monitor_ref, _, ^monitor_pid, reason} ->
stream_cleanup_inbox(monitor_pid, monitor_ref)
exit({reason, {__MODULE__, :stream, [timeout]}})
end
end
defp stream_monitor(parent_pid) do
defp stream_monitor(parent_pid, mfa, spawn, trap_exit) do
Process.flag(:trap_exit, trap_exit)
parent_ref = Process.monitor(parent_pid)
receive do
{^parent_pid, monitor_ref} ->
stream_monitor(parent_pid, parent_ref, monitor_ref, %{})
stream_monitor(parent_pid, parent_ref, mfa, spawn, monitor_ref, %{})
{:DOWN, ^parent_ref, _, _, reason} ->
exit(reason)
end
end
defp stream_monitor(parent_pid, parent_ref, monitor_ref, counters) do
defp stream_monitor(parent_pid, parent_ref, mfa, spawn, monitor_ref, counters) do
receive do
{:up, owner, ^monitor_ref, counter, type, pid} ->
{:spawn, counter, value} ->
{type, pid} = spawn.(parent_pid, stream_mfa(mfa, value))
ref = Process.monitor(pid)
send(pid, {owner, {monitor_ref, counter}})
send(parent_pid, {:spawned, {monitor_ref, counter}, pid})
counters = Map.put(counters, ref, {counter, type, pid})
stream_monitor(parent_pid, parent_ref, monitor_ref, counters)
{:down, ^monitor_ref} ->
stream_monitor(parent_pid, parent_ref, mfa, spawn, monitor_ref, counters)
{:stop, ^monitor_ref} ->
Process.flag(:trap_exit, true)
for {ref, {_counter, _, pid}} <- counters do
Process.exit(pid, :kill)
receive do
{:DOWN, ^ref, _, _, _} -> :ok
end
end
exit({:shutdown, monitor_ref})
exit(:normal)
{:DOWN, ^parent_ref, _, _, reason} ->
for {_ref, {_counter, :link, pid}} <- counters do
Process.exit(pid, reason)
@@ -340,7 +351,9 @@ defmodule Task.Supervised do
{:DOWN, ref, _, _, reason} ->
{{counter, _, _}, counters} = Map.pop(counters, ref)
send(parent_pid, {:down, {monitor_ref, counter}, reason})
stream_monitor(parent_pid, parent_ref, monitor_ref, counters)
stream_monitor(parent_pid, parent_ref, mfa, spawn, monitor_ref, counters)
{:EXIT, _, _} ->
stream_monitor(parent_pid, parent_ref, mfa, spawn, monitor_ref, counters)
end
end
end
+8 -8
View File
@@ -106,7 +106,7 @@ defmodule Task.Supervisor do
"""
@spec async_nolink(Supervisor.supervisor, module, atom, [term]) :: Task.t
def async_nolink(supervisor, module, fun, args) do
do_async(supervisor, :monitor, module, fun, args)
do_async(supervisor, :nolink, module, fun, args)
end
@doc """
@@ -181,7 +181,7 @@ defmodule Task.Supervisor do
Enumerable.t
def async_stream_nolink(supervisor, enumerable, module, function, args, options \\ [])
when is_atom(module) and is_atom(function) and is_list(args) do
build_stream(supervisor, :monitor, enumerable, {module, function, args}, options)
build_stream(supervisor, :nolink, enumerable, {module, function, args}, options)
end
@doc """
@@ -197,7 +197,7 @@ defmodule Task.Supervisor do
@spec async_stream_nolink(Supervisor.supervisor, Enumerable.t, (term -> term), Keyword.t) ::
Enumerable.t
def async_stream_nolink(supervisor, enumerable, fun, options \\ []) when is_function(fun, 1) do
build_stream(supervisor, :monitor, enumerable, fun, options)
build_stream(supervisor, :nolink, enumerable, fun, options)
end
@doc """
@@ -250,7 +250,7 @@ defmodule Task.Supervisor do
defp do_async(supervisor, link_type, module, fun, args) do
owner = self()
args = [owner, link_type, get_info(owner), {module, fun, args}]
args = [owner, :monitor, get_info(owner), {module, fun, args}]
{:ok, pid} = Supervisor.start_child(supervisor, args)
if link_type == :link, do: Process.link(pid)
ref = Process.monitor(pid)
@@ -258,12 +258,12 @@ defmodule Task.Supervisor do
%Task{pid: pid, ref: ref, owner: owner}
end
defp build_stream(supervisor, type, enumerable, fun, options) do
defp build_stream(supervisor, link_type, enumerable, fun, options) do
&Task.Supervised.stream(enumerable, &1, &2, fun, options, fn owner, mfa ->
args = [owner, type, get_info(owner), mfa]
args = [owner, :monitor, get_info(owner), mfa]
{:ok, pid} = Supervisor.start_child(supervisor, args)
if type == :link, do: Process.link(pid)
{type, pid}
if link_type == :link, do: Process.link(pid)
{link_type, pid}
end)
end
end
+3
View File
@@ -353,5 +353,8 @@ deprecation('Elixir.HashSet', _, _) ->
"use the MapSet module instead";
deprecation('Elixir.Dict', _, _) ->
"use the Map module for working with maps or the Keyword module for working with keyword lists";
deprecation('Elixir.Set', _, _) ->
"use the MapSet module for working with sets";
deprecation(_, _, _) ->
false.
+3 -1
View File
@@ -114,7 +114,9 @@ translate_gen(_Meta, Left, Right, T, S) ->
SF = SL#elixir_scope{extra=S#elixir_scope.extra, extra_guards=nil},
{TT, {TFilters, TS}} = translate_filters(T, SF),
Guards = ExtraGuards ++ translate_guards(TLeftGuards) ++ TFilters,
%% The list of guards is kept in reverse order
Guards = TFilters ++ translate_guards(TLeftGuards) ++ ExtraGuards,
{TLeft, TRight, Guards, TT, TS}.
translate_guards([]) ->
+22 -22
View File
@@ -85,7 +85,7 @@ do_compile(Line, Module, Block, Vars, E) ->
Forms3 = attributes_form(Line, File, Data, PersistedAttrs, Forms2),
elixir_locals:ensure_no_import_conflict(Line, File, Module, All),
warn_unused_attributes(Line, File, Data, PersistedAttrs),
warn_unused_attributes(File, Data, PersistedAttrs),
Location = {elixir_utils:characters_to_list(elixir_utils:relative_to_cwd(File)), Line},
@@ -152,26 +152,26 @@ build(Line, File, Module, Docs, Lexical) ->
end,
ets:insert(Data, [
% {Key, Value, Accumulate?, Read?}
{after_compile, [], true, true},
{before_compile, [], true, true},
{behaviour, [], true, true},
{compile, [], true, true},
{derive, [], true, true},
{dialyzer, [], true, true},
{external_resource, [], true, true},
{moduledoc, nil, false, true},
{on_definition, OnDefinition, true, true},
{on_load, [], true, true},
% {Key, Value, Accumulate?, UnreadLine}
{after_compile, [], true, nil},
{before_compile, [], true, nil},
{behaviour, [], true, nil},
{compile, [], true, nil},
{derive, [], true, nil},
{dialyzer, [], true, nil},
{external_resource, [], true, nil},
{moduledoc, nil, false, nil},
{on_definition, OnDefinition, true, nil},
{on_load, [], true, nil},
% Types
{callback, [], true, true},
{opaque, [], true, true},
{optional_callbacks, [], true, true},
{macrocallback, [], true, true},
{spec, [], true, true},
{type, [], true, true},
{typep, [], true, true}
{callback, [], true, nil},
{opaque, [], true, nil},
{optional_callbacks, [], true, nil},
{macrocallback, [], true, nil},
{spec, [], true, nil},
{type, [], true, nil},
{typep, [], true, nil}
]),
Persisted = [behaviour, on_load, compile, external_resource, dialyzer, vsn],
@@ -425,11 +425,11 @@ check_module_availability(Line, File, Module) ->
ok
end.
warn_unused_attributes(Line, File, Data, PersistedAttrs) ->
warn_unused_attributes(File, Data, PersistedAttrs) ->
ReservedAttrs = [after_compile, before_compile, moduledoc, on_definition | PersistedAttrs],
Keys = ets:select(Data, [{{'$1', '_', '_', false}, [{is_atom, '$1'}], ['$1']}]),
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 <- Keys, not lists:member(Key, ReservedAttrs)].
[Key, Line] <- Keys, not lists:member(Key, ReservedAttrs)].
% __INFO__
+32 -23
View File
@@ -189,12 +189,15 @@ do_quote({'__aliases__', Meta, [H | T]} = Alias, #elixir_quote{aliases_hygiene=t
Aliases when is_list(Aliases) -> false
end,
AliasMeta = keystore(alias, keydelete(counter, Meta), Annotation),
do_quote_tuple({'__aliases__', AliasMeta, [H | T]}, Q, E);
do_quote_tuple('__aliases__', AliasMeta, [H | T], Q, E);
%% Vars
do_quote({Left, Meta, nil}, #elixir_quote{vars_hygiene=true, imports_hygiene=true} = Q, E) when is_atom(Left) ->
do_quote_import(Left, Meta, Q#elixir_quote.context, Q, E);
do_quote({Left, Meta, nil}, #elixir_quote{vars_hygiene=true} = Q, E) when is_atom(Left) ->
do_quote_tuple({Left, Meta, Q#elixir_quote.context}, Q, E);
do_quote_tuple(Left, Meta, Q#elixir_quote.context, Q, E);
%% Unquote
@@ -211,24 +214,7 @@ do_quote({'&', Meta, [{'/', _, [{F, _, C}, A]}] = Args},
do_quote_fa('&', Meta, Args, F, A, Q, E);
do_quote({Name, Meta, ArgsOrAtom}, #elixir_quote{imports_hygiene=true} = Q, E) when is_atom(Name) ->
Arity = case is_atom(ArgsOrAtom) of
true -> 0;
false -> length(ArgsOrAtom)
end,
NewMeta = case (keyfind(import, Meta) == false) andalso
elixir_dispatch:find_import(Meta, Name, Arity, E) of
false ->
case (Arity == 1) andalso keyfind(ambiguous_op, Meta) of
{ambiguous_op, nil} -> keystore(ambiguous_op, Meta, Q#elixir_quote.context);
_ -> Meta
end;
Receiver ->
keystore(import, keystore(context, Meta, Q#elixir_quote.context), Receiver)
end,
Annotated = annotate({Name, NewMeta, ArgsOrAtom}, Q#elixir_quote.context),
do_quote_tuple(Annotated, Q, E);
do_quote_import(Name, Meta, ArgsOrAtom, Q, E);
do_quote({_, _, _} = Tuple, #elixir_quote{escape=false} = Q, E) ->
Annotated = annotate(Tuple, Q#elixir_quote.context),
@@ -300,6 +286,26 @@ bad_escape(Arg) ->
"The supported values are: lists, tuples, maps, atoms, numbers, bitstrings, ",
"PIDs and remote functions in the format &Mod.fun/arity">>).
do_quote_import(Name, Meta, ArgsOrAtom, #elixir_quote{imports_hygiene=true} = Q, E) ->
Arity = case is_atom(ArgsOrAtom) of
true -> 0;
false -> length(ArgsOrAtom)
end,
NewMeta = case (keyfind(import, Meta) == false) andalso
elixir_dispatch:find_import(Meta, Name, Arity, E) of
false ->
case (Arity == 1) andalso keyfind(ambiguous_op, Meta) of
{ambiguous_op, nil} -> keystore(ambiguous_op, Meta, Q#elixir_quote.context);
_ -> Meta
end;
Receiver ->
keystore(import, keystore(context, Meta, Q#elixir_quote.context), Receiver)
end,
Annotated = annotate({Name, NewMeta, ArgsOrAtom}, Q#elixir_quote.context),
do_quote_tuple(Annotated, Q, E).
do_quote_call(Left, Meta, Expr, Args, Q, E) ->
All = [meta(Meta, Q), Left, {unquote, Meta, [Expr]}, Args,
Q#elixir_quote.context],
@@ -313,13 +319,16 @@ do_quote_fa(Target, Meta, Args, F, A, Q, E) ->
false -> Meta;
Receiver -> keystore(import_fa, Meta, {Receiver, Q#elixir_quote.context})
end,
do_quote_tuple({Target, NewMeta, Args}, Q, E).
do_quote_tuple(Target, NewMeta, Args, Q, E).
do_quote_tuple({Left, Meta, Right}, Q, E) ->
do_quote_tuple(Left, Meta, Right, Q, E).
% In a def unquote(name)(args) expression name will be an atom literal,
% thus location: :keep will not have enough information to generate the proper file/line annotation.
% This alters metadata to force Elixir to show the file to which the definition is added
% instead of the file where definition is quoted (i.e. we behave the opposite to location: :keep).
do_quote_tuple({Left, Meta, [{{unquote, _, _}, _, _}, _] = Right}, Q, E) when ?defs(Left) ->
do_quote_tuple(Left, Meta, [{{unquote, _, _}, _, _}, _] = Right, Q, E) when ?defs(Left) ->
{TLeft, LQ} = do_quote(Left, Q, E),
{[Head, Body], RQ} = do_quote(Right, LQ, E),
{'{}', [], [HLeft, HMeta, HRight]} = Head,
@@ -327,7 +336,7 @@ do_quote_tuple({Left, Meta, [{{unquote, _, _}, _, _}, _] = Right}, Q, E) when ?d
NewHead = {'{}', [], [HLeft, NewMeta, HRight]},
{{'{}', [], [TLeft, meta(Meta, Q), [NewHead, Body]]}, RQ};
do_quote_tuple({Left, Meta, Right}, Q, E) ->
do_quote_tuple(Left, Meta, Right, Q, E) ->
{TLeft, LQ} = do_quote(Left, Q, E),
{TRight, RQ} = do_quote(Right, LQ, E),
{{'{}', [], [TLeft, meta(Meta, Q), TRight]}, RQ}.
+2 -1
View File
@@ -386,8 +386,9 @@ defmodule ExceptionTest do
end
test "UndefinedFunctionError when the mfa is a macro but require wasn't called" do
_ = Code.ensure_loaded(Integer)
assert %UndefinedFunctionError{module: Integer, function: :is_odd, arity: 1} |> message ==
"function Integer.is_odd/1 is undefined or private but Integer defines " <>
"function Integer.is_odd/1 is undefined or private. However there is " <>
"a macro with the same name and arity. Be sure to require Integer if " <>
"you intend to invoke this macro"
end
+3
View File
@@ -80,6 +80,7 @@ defmodule FloatTest do
assert Float.ceil(0.32453e-10) === 1.0
assert Float.ceil(-0.32453e-10) === 0.0
assert Float.ceil(1.32453e-10) === 1.0
assert Float.ceil(0.0) === 0.0
end
test "ceil with precision" do
@@ -94,6 +95,8 @@ defmodule FloatTest do
assert Float.ceil(12.32453e-20, 2) === 0.01
assert Float.ceil(-12.32453e-20, 2) === 0.0
assert Float.ceil(0.0, 2) === 0.0
end
test "round" do
@@ -3,6 +3,8 @@ Code.require_file "test_helper.exs", __DIR__
defmodule GenServerTest do
use ExUnit.Case, async: true
import ExUnit.CaptureLog
defmodule Stack do
use GenServer
@@ -161,4 +163,13 @@ defmodule GenServerTest do
{:ok, _} = GenServer.start(Stack, [], name: :stack_for_stop)
assert GenServer.stop(:stack_for_stop, :normal) == :ok
end
test "handle_info/2 warning" do
{:ok, pid} = GenServer.start(Stack, [])
log = capture_log(fn ->
send(pid, :foo)
GenServer.stop(pid)
end)
assert log =~ "[warn] GenServerTest.Stack #{inspect pid} received unexpected message in handle_info/2: :foo"
end
end
@@ -51,6 +51,12 @@ defmodule Kernel.ComprehensionTest do
assert for(x when x == 3 when x == 7 <- 1..10, do: x) == [3, 7]
end
test "for comprehensions with guards and filters" do
assert for({var, _} when is_atom(var) <- [{:foo, 1}, {2, :bar}],
var = Atom.to_string(var),
do: var) == ["foo"]
end
test "for comprehensions with map key matching" do
maps = [%{x: 1}, %{y: 2}, %{x: 3}]
assert for(%{x: v} <- maps, do: v * 2) == [2, 6]
@@ -153,7 +153,7 @@ defmodule Kernel.ExpansionTest do
end
test "locals: in guards" do
assert expand(quote(do: fn pid when :erlang.==(pid, self) -> pid end)) ==
assert expand_and_clean(quote(do: fn pid when :erlang.==(pid, self) -> pid end), [:import, :context]) ==
quote(do: fn pid when :erlang.==(pid, :erlang.self()) -> pid end)
end
@@ -337,22 +337,22 @@ defmodule Kernel.ExpansionTest do
## Cond
test "cond: expands each clause" do
assert expand_and_clean(quote do: (cond do x = 1 -> x; _ -> x end)) ==
assert expand(quote do: (cond do x = 1 -> x; _ -> x end)) ==
quote do: (cond do x = 1 -> x; _ -> x() end)
end
test "cond: does not share lexical scope between clauses" do
assert expand_and_clean(quote do: (cond do 1 -> import List; 2 -> flatten([1, 2, 3]) end)) ==
assert expand(quote do: (cond do 1 -> import List; 2 -> flatten([1, 2, 3]) end)) ==
quote do: (cond do 1 -> import :"Elixir.List", []; 2 -> flatten([1, 2, 3]) end)
end
test "cond: does not leaks vars on head" do
assert expand_and_clean(quote do: (cond do x = 1 -> x; y = 2 -> y end; :erlang.+(x, y))) ==
assert expand(quote do: (cond do x = 1 -> x; y = 2 -> y end; :erlang.+(x, y))) ==
quote do: (cond do x = 1 -> x; y = 2 -> y end; :erlang.+(x(), y()))
end
test "cond: leaks vars" do
assert expand_and_clean(quote do: (cond do 1 -> x = 1; 2 -> y = 2 end; :erlang.+(x, y))) ==
assert expand(quote do: (cond do 1 -> x = 1; 2 -> y = 2 end; :erlang.+(x, y))) ==
quote do: (cond do 1 -> x = 1; 2 -> y = 2 end; :erlang.+(x, y))
end
@@ -365,27 +365,27 @@ defmodule Kernel.ExpansionTest do
## Case
test "case: expands each clause" do
assert expand_and_clean(quote do: (case w do x -> x; _ -> x end)) ==
assert expand(quote do: (case w do x -> x; _ -> x end)) ==
quote do: (case w() do x -> x; _ -> x() end)
end
test "case: does not share lexical scope between clauses" do
assert expand_and_clean(quote do: (case w do 1 -> import List; 2 -> flatten([1, 2, 3]) end)) ==
assert expand(quote do: (case w do 1 -> import List; 2 -> flatten([1, 2, 3]) end)) ==
quote do: (case w() do 1 -> import :"Elixir.List", []; 2 -> flatten([1, 2, 3]) end)
end
test "case: expands guards" do
assert expand_and_clean(quote do: (case w do x when x when __ENV__.context -> true end)) ==
assert expand(quote do: (case w do x when x when __ENV__.context -> true end)) ==
quote do: (case w() do x when x when :guard -> true end)
end
test "case: does not leaks vars on head" do
assert expand_and_clean(quote do: (case w do x -> x; y -> y end; :erlang.+(x, y))) ==
assert expand(quote do: (case w do x -> x; y -> y end; :erlang.+(x, y))) ==
quote do: (case w() do x -> x; y -> y end; :erlang.+(x(), y()))
end
test "case: leaks vars" do
assert expand_and_clean(quote do: (case w do x -> x = x; y -> y = y end; :erlang.+(x, y))) ==
assert expand(quote do: (case w do x -> x = x; y -> y = y end; :erlang.+(x, y))) ==
quote do: (case w() do x -> x = x; y -> y = y end; :erlang.+(x, y))
end
@@ -398,32 +398,32 @@ defmodule Kernel.ExpansionTest do
## Receive
test "receive: expands each clause" do
assert expand_and_clean(quote do: (receive do x -> x; _ -> x end)) ==
assert expand(quote do: (receive do x -> x; _ -> x end)) ==
quote do: (receive do x -> x; _ -> x() end)
end
test "receive: does not share lexical scope between clauses" do
assert expand_and_clean(quote do: (receive do 1 -> import List; 2 -> flatten([1, 2, 3]) end)) ==
assert expand(quote do: (receive do 1 -> import List; 2 -> flatten([1, 2, 3]) end)) ==
quote do: (receive do 1 -> import :"Elixir.List", []; 2 -> flatten([1, 2, 3]) end)
end
test "receive: expands guards" do
assert expand_and_clean(quote do: (receive do x when x when __ENV__.context -> true end)) ==
assert expand(quote do: (receive do x when x when __ENV__.context -> true end)) ==
quote do: (receive do x when x when :guard -> true end)
end
test "receive: does not leaks clause vars" do
assert expand_and_clean(quote do: (receive do x -> x; y -> y end; :erlang.+(x, y))) ==
assert expand(quote do: (receive do x -> x; y -> y end; :erlang.+(x, y))) ==
quote do: (receive do x -> x; y -> y end; :erlang.+(x(), y()))
end
test "receive: leaks vars" do
assert expand_and_clean(quote do: (receive do x -> x = x; y -> y = y end; :erlang.+(x, y))) ==
assert expand(quote do: (receive do x -> x = x; y -> y = y end; :erlang.+(x, y))) ==
quote do: (receive do x -> x = x; y -> y = y end; :erlang.+(x, y))
end
test "receive: leaks vars on after" do
assert expand_and_clean(quote do: (receive do x -> x = x after y -> y; w = y end; :erlang.+(x, w))) ==
assert expand(quote do: (receive do x -> x = x after y -> y; w = y end; :erlang.+(x, w))) ==
quote do: (receive do x -> x = x after y() -> y(); w = y() end; :erlang.+(x, w))
end
@@ -540,8 +540,8 @@ defmodule Kernel.ExpansionTest do
13
end
defp expand_and_clean(expr) do
cleaner = &Keyword.drop(&1, [:export])
defp expand_and_clean(expr, vars) do
cleaner = &Keyword.drop(&1, vars)
expr
|> expand_env(__ENV__)
|> elem(0)
@@ -434,4 +434,18 @@ defmodule Kernel.QuoteTest.ImportsHygieneTest do
test "explicitly overridden imports" do
assert with_length() == 5
end
defmodule BinaryUtils do
defmacro int32 do
quote do
integer-size(32)
end
end
end
test "checks the context also for variables to zero-arity functions" do
import BinaryUtils
{:int32, meta, __MODULE__} = quote do: int32
assert meta[:import] == BinaryUtils
end
end
@@ -685,12 +685,11 @@ defmodule Kernel.WarningTest do
Code.eval_string """
defmodule Sample do
@at "Something"
Module.put_attribute(__MODULE__, :put_attribute, "Something")
end
"""
end)
assert content =~ "module attribute @at was set but never used"
assert content =~ "module attribute @put_attribute was set but never used"
assert content =~ "nofile:2"
after
purge Sample
end
+1 -1
View File
@@ -60,7 +60,7 @@ defmodule ModuleTest do
test "module attributes returns value" do
in_module do
assert (@return [:foo, :bar]) == [:foo, :bar]
assert (@return [:foo, :bar]) == :ok
_ = @return
end
end
+1 -1
View File
@@ -13,7 +13,7 @@ defmodule SystemTest do
assert is_binary build_info[:version]
if build_info[:revision] != "" do
assert String.length(build_info[:revision]) == 7
assert String.length(build_info[:revision]) >= 7
end
version_file = Path.join([__DIR__, "../../../..", "VERSION"]) |> Path.expand
+1
View File
@@ -492,6 +492,7 @@ defmodule TaskTest do
Process.flag(:trap_exit, true)
assert 1..4 |> Task.async_stream(&exit/1, @opts) |> Enum.to_list ==
[exit: 1, exit: 2, exit: 3, exit: 4]
refute_received {:EXIT, _, _}
end
test "shuts down unused tasks" do
+106 -31
View File
@@ -1,20 +1,22 @@
defmodule IEx.Autocomplete do
@moduledoc false
def expand('') do
def expand(expr, server \\ IEx.Server)
def expand('', _server) do
expand_import("")
end
def expand([h | t]=expr) do
def expand([h | t]=expr, server) do
cond do
h === ?. and t != [] ->
expand_dot(reduce(t))
expand_dot(reduce(t), server)
h === ?: and t == [] ->
expand_erlang_modules()
identifier?(h) ->
expand_expr(reduce(expr))
expand_expr(reduce(expr), server)
(h == ?/) and t != [] and identifier?(hd(t)) ->
expand_expr(reduce(t))
expand_expr(reduce(t), server)
h in '([{' ->
expand('')
true ->
@@ -26,31 +28,33 @@ defmodule IEx.Autocomplete do
(h in ?a..?z) or (h in ?A..?Z) or (h in ?0..?9) or h in [?_, ??, ?!]
end
defp expand_dot(expr) do
defp expand_dot(expr, server) do
case Code.string_to_quoted expr do
{:ok, atom} when is_atom(atom) ->
expand_call(atom, "")
expand_call(atom, "", server)
{:ok, {:__aliases__, _, list}} ->
expand_elixir_modules(list, "")
expand_elixir_modules(list, "", server)
{:ok, {_, _, _} = ast_node} ->
expand_call(ast_node, "", server)
_ ->
no()
end
end
defp expand_expr(expr) do
defp expand_expr(expr, server) do
case Code.string_to_quoted expr do
{:ok, atom} when is_atom(atom) ->
expand_erlang_modules(Atom.to_string(atom))
{:ok, {atom, _, nil}} when is_atom(atom) ->
expand_import(Atom.to_string(atom))
{:ok, {:__aliases__, _, [root]}} ->
expand_elixir_modules([], Atom.to_string(root))
expand_elixir_modules([], Atom.to_string(root), server)
{:ok, {:__aliases__, _, [h | _] = list}} when is_atom(h) ->
hint = Atom.to_string(List.last(list))
list = Enum.take(list, length(list) - 1)
expand_elixir_modules(list, hint)
{:ok, {{:., _, [mod, fun]}, _, []}} when is_atom(fun) ->
expand_call(mod, Atom.to_string(fun))
expand_elixir_modules(list, hint, server)
{:ok, {{:., _, [ast_node, fun]}, _, []}} when is_atom(fun) ->
expand_call(ast_node, Atom.to_string(fun), server)
_ ->
no()
end
@@ -105,21 +109,37 @@ defmodule IEx.Autocomplete do
## Expand calls
# :atom.fun
defp expand_call(mod, hint) when is_atom(mod) do
defp expand_call(mod, hint, _server) when is_atom(mod) do
expand_require(mod, hint)
end
# Elixir.fun
defp expand_call({:__aliases__, _, list}, hint) do
expand_alias(list)
defp expand_call({:__aliases__, _, list}, hint, server) do
expand_alias(list, server)
|> normalize_module
|> expand_require(hint)
end
defp expand_call(_, _) do
# variable.fun_or_key
defp expand_call({_, _, _} = ast_node, hint, server) do
case value_from_binding(ast_node, server) do
{:ok, mod} when is_atom(mod) -> expand_call(mod, hint, server)
{:ok, map} when is_map(map) -> expand_map_field_access(map, hint)
_otherwise -> no()
end
end
defp expand_call(_, _, _) do
no()
end
defp expand_map_field_access(map, hint) do
case match_map_fields(map, hint) do
[%{kind: :map_key, name: name, value_is_map: false}] when name == hint -> no()
map_fields when is_list(map_fields) -> format_expansion(map_fields, hint)
end
end
defp expand_require(mod, hint) do
format_expansion match_module_funs(mod, hint), hint
end
@@ -145,26 +165,27 @@ defmodule IEx.Autocomplete do
## Elixir modules
defp expand_elixir_modules([], hint) do
expand_elixir_modules(Elixir, hint, match_aliases(hint))
defp expand_elixir_modules([], hint, server) do
aliases = match_aliases(hint, server)
expand_elixir_modules_from_aliases(Elixir, hint, aliases)
end
defp expand_elixir_modules(list, hint) do
expand_alias(list)
defp expand_elixir_modules(list, hint, server) do
expand_alias(list, server)
|> normalize_module
|> expand_elixir_modules(hint, [])
|> expand_elixir_modules_from_aliases(hint, [])
end
defp expand_elixir_modules(mod, hint, aliases) do
defp expand_elixir_modules_from_aliases(mod, hint, aliases) do
aliases
|> Kernel.++(match_elixir_modules(mod, hint))
|> Kernel.++(match_module_funs(mod, hint))
|> format_expansion(hint)
end
defp expand_alias([name | rest] = list) do
defp expand_alias([name | rest] = list, server) do
module = Module.concat(Elixir, name)
Enum.find_value env_aliases(), list, fn {alias, mod} ->
Enum.find_value aliases_from_env(server), list, fn {alias, mod} ->
if alias === module do
case Atom.to_string(mod) do
"Elixir." <> mod ->
@@ -176,12 +197,8 @@ defmodule IEx.Autocomplete do
end
end
defp env_aliases do
Application.get_env(:iex, :autocomplete_server).current_env.aliases
end
defp match_aliases(hint) do
for {alias, _mod} <- env_aliases(),
defp match_aliases(hint, server) do
for {alias, _mod} <- aliases_from_env(server),
[name] = Module.split(alias),
starts_with?(name, hint) do
%{kind: :module, type: :alias, name: name}
@@ -269,6 +286,14 @@ defmodule IEx.Autocomplete do
end
end
defp match_map_fields(map, hint) do
for {key, value} <- Map.to_list(map),
is_atom(key),
key = Atom.to_string(key),
String.starts_with?(key, hint),
do: %{kind: :map_key, name: key, value_is_map: is_map(value)}
end
defp get_module_funs(mod) do
docs = Code.get_docs(mod, :docs) || []
module_info_funs(mod) |> Enum.reject(&hidden_fun?(&1, docs))
@@ -316,6 +341,10 @@ defmodule IEx.Autocomplete do
for a <- :lists.sort(arities), do: "#{name}/#{a}"
end
defp to_entries(%{kind: :map_key, name: name}) do
[name]
end
defp to_uniq_entries(%{kind: :module}) do
[]
end
@@ -324,6 +353,10 @@ defmodule IEx.Autocomplete do
to_entries(fun)
end
defp to_uniq_entries(%{kind: :map_key}) do
[]
end
defp to_hint(%{kind: :module, name: name}, hint) when name == hint do
format_hint(name, name) <> "."
end
@@ -336,8 +369,50 @@ defmodule IEx.Autocomplete do
format_hint(name, hint)
end
defp to_hint(%{kind: :map_key, name: name, value_is_map: true}, hint) when name == hint do
format_hint(name, hint) <> "."
end
defp to_hint(%{kind: :map_key, name: name}, hint) do
format_hint(name, hint)
end
defp format_hint(name, hint) do
hint_size = byte_size(hint)
:binary.part(name, hint_size, byte_size(name) - hint_size)
end
defp aliases_from_env(server) do
with evaluator when is_pid(evaluator) <- server.evaluator,
{:ok, aliases} <- IEx.Evaluator.value_from_env(evaluator, :aliases) do
aliases
else
_ -> []
end
end
defp value_from_binding(ast_node, server) do
with evaluator when is_pid(evaluator) <- server.evaluator(),
{var, map_key_path} <- extract_from_ast(ast_node, []) do
IEx.Evaluator.value_from_binding(evaluator, var, map_key_path)
else
_ -> :error
end
end
defp extract_from_ast(var_name, acc) when is_atom(var_name) do
{var_name, acc}
end
defp extract_from_ast({var_name, _, nil}, acc) when is_atom(var_name) do
{var_name, acc}
end
defp extract_from_ast({{:., _, [ast_node, fun]}, _, []}, acc) when is_atom(fun) do
extract_from_ast(ast_node, [fun | acc])
end
defp extract_from_ast(_ast_node, _acc) do
:error
end
end
+46 -2
View File
@@ -24,20 +24,64 @@ defmodule IEx.Evaluator do
end
end
@doc """
Gets a value out of the binding, using the provided
variable name and map key path.
"""
@spec value_from_binding(pid, atom, [atom]) :: {:ok, any} | :error
def value_from_binding(evaluator, var_name, map_key_path) do
ref = make_ref()
send evaluator, {:value_from_binding, ref, self(), var_name, map_key_path}
receive do
{^ref, result} -> result
after
5000 -> :error
end
end
@doc """
Returns the current session environment if a session exists.
"""
@spec value_from_env(pid, atom) :: {:ok, term} | :error
def value_from_env(evaluator, key) do
ref = make_ref()
send evaluator, {:value_from_env, ref, self(), key}
receive do
{^ref, result} -> result
after
5000 -> :error
end
end
defp loop(server, history, state) do
receive do
{:eval, ^server, code, iex_state} ->
{result, history, state} = eval(code, iex_state, history, state)
send server, {:evaled, self(), result}
loop(server, history, state)
{:peek_env, receiver} ->
send receiver, {:peek_env, state.env}
{:value_from_env, ref, receiver, key} ->
send receiver, {ref, Map.fetch(state.env, key)}
loop(server, history, state)
{:value_from_binding, ref, receiver, var_name, map_key_path} ->
value = traverse_binding(state.binding, var_name, map_key_path)
send receiver, {ref, value}
loop(server, history, state)
{:done, ^server} ->
:ok
end
end
defp traverse_binding(binding, var_name, map_key_path) do
accumulator = Keyword.fetch(binding, var_name)
Enum.reduce map_key_path, accumulator, fn
key, {:ok, map} when is_map(map) -> Map.fetch(map, key)
_key, _acc -> :error
end
end
defp loop_state(opts) do
env =
if env = opts[:env] do
+13 -16
View File
@@ -43,19 +43,15 @@ defmodule IEx.Server do
end
@doc """
Returns the current session environment if a session exists.
Returns the PID of the IEx evaluator process if it exists.
"""
@spec current_env :: Macro.Env.t
def current_env() do
case IEx.Server.whereis() do
nil -> %Macro.Env{}
server ->
send(server, {:peek_env, self()})
receive do
{:peek_env, %Macro.Env{} = env} -> env
after
5000 -> %Macro.Env{}
end
@spec evaluator :: pid | nil
def evaluator() do
case IEx.Server.local do
nil -> nil
pid ->
{:dictionary, dictionary} = Process.info(pid, :dictionary)
dictionary[:evaluator]
end
end
@@ -144,7 +140,11 @@ defmodule IEx.Server do
loop(run_state(opts), evaluator, Process.monitor(evaluator))
end
defp start_evaluator(opts) do
@doc """
Starst an evaluator using the provided options.
"""
@spec start_evaluator(Keyword.t) :: pid
def start_evaluator(opts) do
self_pid = self()
self_leader = Process.group_leader
evaluator = opts[:evaluator] ||
@@ -192,9 +192,6 @@ defmodule IEx.Server do
exit_loop(evaluator, evaluator_ref)
{:input, ^input, {:error, :terminated}} ->
exit_loop(evaluator, evaluator_ref)
{:peek_env, receiver} ->
send evaluator, {:peek_env, receiver}
wait_input(state, evaluator, evaluator_ref, input)
msg ->
handle_take_over(msg, evaluator, evaluator_ref, input, fn ->
wait_input(state, evaluator, evaluator_ref, input)
-1
View File
@@ -11,7 +11,6 @@ defmodule IEx.Mixfile do
[registered: [IEx.Supervisor, IEx.Config],
mod: {IEx.App, []},
env: [
autocomplete_server: IEx.Server,
colors: [],
inspect: [pretty: true],
history_size: 20,
+92 -18
View File
@@ -3,8 +3,29 @@ Code.require_file "../test_helper.exs", __DIR__
defmodule IEx.AutocompleteTest do
use ExUnit.Case, async: true
setup context do
previous_line = context[:previous_line]
if previous_line do
ExUnit.CaptureIO.capture_io(fn ->
evaluator = IEx.Server.start_evaluator([])
Process.put(:evaluator, evaluator)
send evaluator, {:eval, self(), previous_line <> "\n", %IEx.State{}}
assert_receive {:evaled, _, _}
end)
end
:ok
end
defmodule MyServer do
def evaluator do
Process.get(:evaluator)
end
end
def expand(expr) do
IEx.Autocomplete.expand(Enum.reverse expr)
IEx.Autocomplete.expand(Enum.reverse(expr), MyServer)
end
test "Erlang module completion" do
@@ -93,6 +114,62 @@ defmodule IEx.AutocompleteTest do
assert expand('String.printable?/') == {:yes, '', ['printable?/1']}
end
@tag previous_line: "mod = String"
test "function completion using a variable bound to a module" do
assert expand('mod.print') == {:yes, 'able?', []}
end
@tag previous_line: "map = %{foo: 1, bar_1: 23, bar_2: 14}"
test "map atom key completion is supported" do
assert expand('map.f') == {:yes, 'oo', []}
assert expand('map.b') == {:yes, 'ar_', []}
assert expand('map.bar_') == {:yes, '', ['bar_1', 'bar_2']}
assert expand('map.c') == {:no, '', []}
assert expand('map.') == {:yes, '', ['bar_1', 'bar_2', 'foo']}
assert expand('map.foo') == {:no, '', []}
end
@tag previous_line: "map = %{nested: %{deeply: %{foo: 1, bar_1: 23, bar_2: 14, mod: String, num: 1}}}"
test "nested map atom key completion is supported" do
assert expand('map.nested.deeply.f') == {:yes, 'oo', []}
assert expand('map.nested.deeply.b') == {:yes, 'ar_', []}
assert expand('map.nested.deeply.bar_') == {:yes, '', ['bar_1', 'bar_2']}
assert expand('map.nested.deeply.') == {:yes, '', ['bar_1', 'bar_2', 'foo', 'mod', 'num']}
assert expand('map.nested.deeply.mod.print') == {:yes, 'able?', []}
assert expand('map.nested') == {:yes, '.', []}
assert expand('map.nested.deeply') == {:yes, '.', []}
assert expand('map.nested.deeply.foo') == {:no, '', []}
assert expand('map.nested.deeply.c') == {:no, '', []}
assert expand('map.a.b.c.f') == {:no, '', []}
end
@tag previous_line: ~s(map = %{"foo" => 1})
test "map string key completion is not supported" do
assert expand('map.f') == {:no, '', []}
end
@tag previous_line: "num = 5; map = %{nested: %{num: 23}}"
test "autocompletion off a bound variable only works for modules and maps" do
assert expand('num.print') == {:no, '', []}
assert expand('map.nested.num.f') == {:no, '', []}
assert expand('map.nested.num.key.f') == {:no, '', []}
end
@tag previous_line: "map = %{nested: %{deeply: %{num: 23}}}"
test "autocompletion using access syntax does is not supported" do
assert expand('map[:nested][:deeply].n') == {:no, '', []}
assert expand('map[:nested].deeply.n') == {:no, '', []}
assert expand('map.nested.[:deeply].n') == {:no, '', []}
end
@tag previous_line: "num = 5"
test "autocompletion off of unbound variables is not supported" do
assert expand('other_var.f') == {:no, '', []}
assert expand('a.b.c.d') == {:no, '', []}
end
test "macro completion" do
{:yes, '', list} = expand('Kernel.is_')
assert is_list(list)
@@ -136,23 +213,15 @@ defmodule IEx.AutocompleteTest do
assert expand('IEx.AutocompleteTest.SublevelTest.') == {:yes, 'LevelA', []}
end
defmodule MyServer do
def current_env do
%Macro.Env{aliases: [{MyList, List}, {EList, :lists}]}
end
end
@tag previous_line: "alias List, as: MyList"
test "complete aliases of Elixir modules" do
Application.put_env(:iex, :autocomplete_server, MyServer)
assert expand('MyL') == {:yes, 'ist', []}
assert expand('MyList') == {:yes, '.', []}
assert expand('MyList.to_integer') == {:yes, [], ['to_integer/1', 'to_integer/2']}
end
@tag previous_line: "alias :lists, as: EList"
test "complete aliases of Erlang modules" do
Application.put_env(:iex, :autocomplete_server, MyServer)
assert expand('EL') == {:yes, 'ist', []}
assert expand('EList') == {:yes, '.', []}
assert expand('EList.map') == {:yes, [], ['map/2', 'mapfoldl/3', 'mapfoldr/3']}
@@ -160,21 +229,21 @@ defmodule IEx.AutocompleteTest do
test "completion for functions added when compiled module is reloaded" do
{:module, _, bytecode, _} =
defmodule Elixir.Sample do
defmodule Sample do
def foo(), do: 0
end
File.write!("Elixir.Sample.beam", bytecode)
File.write!("Elixir.IEx.AutocompleteTest.Sample.beam", bytecode)
assert Code.get_docs(Sample, :docs)
assert expand('Sample.foo') == {:yes, '', ['foo/0']}
assert expand('IEx.AutocompleteTest.Sample.foo') == {:yes, '', ['foo/0']}
Code.compiler_options(ignore_module_conflict: true)
defmodule Elixir.Sample do
defmodule Sample do
def foo(), do: 0
def foobar(), do: 0
end
assert expand('Sample.foo') == {:yes, '', ['foo/0', 'foobar/0']}
assert expand('IEx.AutocompleteTest.Sample.foo') == {:yes, '', ['foo/0', 'foobar/0']}
after
File.rm("Elixir.Sample.beam")
File.rm("Elixir.IEx.AutocompleteTest.Sample.beam")
Code.compiler_options(ignore_module_conflict: false)
:code.purge(Sample)
:code.delete(Sample)
@@ -184,7 +253,12 @@ defmodule IEx.AutocompleteTest do
defstruct [:my_val]
end
test "completion for structs" do
test "completion for struct names" do
assert expand('%IEx.AutocompleteTest.MyStr') == {:yes, 'uct', []}
end
@tag previous_line: "struct = %IEx.AutocompleteTest.MyStruct{}"
test "completion for struct keys" do
assert expand('struct.my') == {:yes, '_val', []}
end
end
+33 -19
View File
@@ -150,6 +150,21 @@ defmodule Logger.TranslatorTest do
"""s
end
test "translates Task async_stream crashes with neighbour" do
fun = fn -> Task.async_stream([:oops], :erlang, :error, []) |> Enum.to_list() end
{:ok, pid} = Task.start(__MODULE__, :task, [self(), fun])
assert capture_log(:debug, fn ->
ref = Process.monitor(pid)
send(pid, :go)
receive do: ({:DOWN, ^ref, _, _, _} -> :ok)
end) =~ ~r"""
Neighbours:
#{inspect pid}
Initial Call: Logger\.TranslatorTest\.task/2
"""
end
test "translates Task undef module crash" do
assert capture_log(fn ->
{:ok, pid} = Task.start(:module_does_not_exist, :undef, [])
@@ -244,24 +259,6 @@ defmodule Logger.TranslatorTest do
"""
end
test "translates :proc_lib crashes" do
{:ok, pid} = Task.start_link(__MODULE__, :task, [self()])
assert capture_log(:info, fn ->
ref = Process.monitor(pid)
send(pid, :message)
send(pid, :go)
receive do: ({:DOWN, ^ref, _, _, _} -> :ok)
end) =~ ~r"""
\[error\] Process #PID<\d+\.\d+\.\d+> terminating
\*\* \(exit\) an exception was raised:
\*\* \(RuntimeError\) oops
.*
Initial Call: Logger.TranslatorTest.task/1
Ancestors: \[#PID<\d+\.\d+\.\d+>\]
"""s
end
test "translates Process crashes" do
assert capture_log(:info, fn ->
{_, ref} = spawn_monitor(fn() -> raise "oops" end)
@@ -275,6 +272,24 @@ defmodule Logger.TranslatorTest do
"""
end
test "translates :proc_lib crashes" do
{:ok, pid} = Task.start_link(__MODULE__, :task, [self()])
assert capture_log(:info, fn ->
ref = Process.monitor(pid)
send(pid, :go)
receive do: ({:DOWN, ^ref, _, _, _} -> :ok)
end) =~ ~r"""
\[error\] Process #PID<\d+\.\d+\.\d+> terminating
\*\* \(exit\) an exception was raised:
\*\* \(RuntimeError\) oops
.*
Initial Call: Logger.TranslatorTest.task/1
Ancestors: \[#PID<\d+\.\d+\.\d+>\]
"""s
end
test "translates :proc_lib crashes with name" do
{:ok, pid} = Task.start_link(__MODULE__, :task,
[self(), fn() ->
@@ -648,5 +663,4 @@ defmodule Logger.TranslatorTest do
:proc_lib.init_ack({:ok, self()})
receive do: ({:EXIT, _, _} -> exit(:stop))
end
end
+4
View File
@@ -36,6 +36,10 @@ defmodule Mix.Compilers.Test do
Mix.shell.info "No stale tests."
:noop
[] when test_patterns == [] ->
Mix.shell.info "There are no tests to run"
:noop
[] ->
Mix.shell.error "Test patterns did not match any file: " <> Enum.join(test_patterns, ", ")
:noop
+14 -4
View File
@@ -7,14 +7,24 @@ defmodule Mix.Tasks.Archive.Build do
Builds an archive according to the specification of the
[Erlang Archive Format](http://www.erlang.org/doc/man/code.html).
Archives are meant to contain small projects, usually installed
locally. Archives may be installed into a Mix environment by
running `mix archive.install`. Once installed, the archive is
available to all Mix projects. For this reason, the functionality
behind archives is limited. For instance, archives do not include
dependencies, as those would conflict with any dependency in a
Mix project after the archive is installed. In general, we recommend
the usage of archives to be limited for extensions of Mix, such
as custom SCMs, package managers, etc. For general scripts to be
installed into machines, please see `mix escript.build`.
The archive will be created in the current directory (which is
expected to be the project root), unless an argument `-o` is
provided with the file name.
Archives are meant to contain small projects, usually installed
locally. By default, this command archives the current project
but the `-i` and `-o` options can be used to archive any directory.
For example, `mix archive.build` with no options translates to:
By default, this command archives the current project but the `-i`
option can be used to archive any directory. For example,
`mix archive.build` with no options translates to:
mix archive.build -i _build/ENV/lib/APP -o APP-VERSION.ez
+6 -5
View File
@@ -6,14 +6,15 @@ defmodule Mix.Tasks.Archive.Install do
@moduledoc """
Installs an archive locally.
If no argument is supplied but there is an archive in the project's root directory
(created with `mix archive.build`), then the archive will be installed
locally. For example:
If no argument is supplied but there is an archive in the project's
root directory (created with `mix archive.build`), then the archive
will be installed locally. For example:
mix do archive.build, archive.install
If an argument is provided, it should be a local path or a URL to a prebuilt archive,
a git repository, a github repository, or a hex package.
If an argument is provided, it should be a local path or a URL to a
prebuilt archive, a git repository, a github repository, or a hex
package.
mix archive.install archive.ez
mix archive.install path/to/archive.ez
+1 -1
View File
@@ -18,7 +18,7 @@ defmodule Mix.Tasks.Compile.App do
function in your `mix.exs` with the following options:
* `:applications` - all applications your application depends
on at runtime. By default, this list is automatically inflected
on at runtime. By default, this list is automatically inferred
from your dependencies. Any extra Erlang/Elixir dependency
must be specified in `:extra_applications`. Mix and other tools
use the application list in order to start your dependencies
+5 -2
View File
@@ -197,10 +197,13 @@ defmodule Mix.Tasks.Escript.Build do
extra_apps =
if function_exported?(mod, :application, 0) do
mod.application[:applications]
app = mod.application()
Keyword.get(app, :applications, []) ++ Keyword.get(app, :extra_applications, [])
else
[]
end
Enum.filter(extra_apps || [], &(&1 in [:eex, :ex_unit, :mix, :iex, :logger]))
Enum.filter(extra_apps, &(&1 in [:eex, :ex_unit, :mix, :iex, :logger]))
end
defp app_files(app) do
+11 -3
View File
@@ -122,8 +122,8 @@ defmodule Mix.Tasks.Test do
## Configuration
* `:test_paths` - list of paths containing test files, defaults to
`["test"]`. It is expected all test paths to contain a `test_helper.exs`
file.
`["test"]` if the `test` directory exists, otherwise it defaults to `[]`.
It is expected all test paths to contain a `test_helper.exs` file.
* `:test_pattern` - a pattern to load test files, defaults to `*_test.exs`.
@@ -225,7 +225,7 @@ defmodule Mix.Tasks.Test do
ex_unit_opts = ex_unit_opts(opts)
ExUnit.configure(ex_unit_opts)
test_paths = project[:test_paths] || ["test"]
test_paths = project[:test_paths] || default_test_paths()
Enum.each(test_paths, &require_test_helper(&1))
ExUnit.configure(merge_helper_opts(ex_unit_opts))
@@ -365,4 +365,12 @@ defmodule Mix.Tasks.Test do
Mix.raise "Cannot run tests because test helper file #{inspect file} does not exist"
end
end
defp default_test_paths do
if File.dir?("test") do
["test"]
else
[]
end
end
end
+5 -1
View File
@@ -138,7 +138,11 @@ defmodule Mix.Utils do
def extract_files(paths, pattern) do
Enum.flat_map(paths, fn path ->
if File.regular?(path), do: [path], else: Path.wildcard("#{path}/**/#{pattern}")
case :elixir_utils.read_file_type(path) do
{:ok, :directory} -> Path.wildcard("#{path}/**/#{pattern}")
{:ok, :regular} -> [path]
_ -> []
end
end) |> Enum.uniq
end
+1 -1
View File
@@ -85,7 +85,7 @@ defmodule Mix.Tasks.Compile.AppTest do
end
end
test "automatically inflects applications" do
test "automatically infers applications" do
Mix.Project.push CustomDeps
in_fixture "no_mixfile", fn ->
+1 -1
View File
@@ -47,7 +47,7 @@ defmodule Mix.Tasks.EscriptTest do
end
def application do
[applications: []]
[applications: [], extra_applications: [:crypto]]
end
end
+13 -1
View File
@@ -108,6 +108,14 @@ defmodule Mix.Tasks.TestTest do
end
end
test "logs test absence for a project with no test paths" do
in_fixture "test_stale", fn ->
File.rm_rf! "test"
assert_run_output "There are no tests to run"
end
end
test "--listen-on-stdin: runs tests after input" do
in_fixture "test_stale", fn ->
port = mix_port(~w[test --stale --listen-on-stdin])
@@ -190,6 +198,10 @@ defmodule Mix.Tasks.TestTest do
end
defp assert_stale_run_output(opts \\ [], expected) do
assert mix(~w[test --stale] ++ opts) =~ expected
assert_run_output(["--stale" | opts], expected)
end
defp assert_run_output(opts \\ [], expected) do
assert mix(["test" | opts]) =~ expected
end
end
+5
View File
@@ -33,6 +33,11 @@ defmodule Mix.UtilsTest do
assert Path.basename(hd(files)) == "local.sample.ex"
end
test "extract files with empty string returns empty list" do
assert Mix.Utils.extract_files([""], ".ex") == []
end
test "extract stale" do
time = {{2030, 1, 1}, {0, 0, 0}}
assert Mix.Utils.extract_stale([__ENV__.file], [time]) == []
+1 -1
View File
@@ -1,6 +1,6 @@
{application, elixir,
[{description, "elixir"},
{vsn, "1.4.0-rc.0"},
{vsn, "1.4.0"},
{modules, [
elixir
]},