Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
eb5679bfcc | ||
|
|
e38ae98c3a | ||
|
|
71a537b950 | ||
|
|
7087149bfa | ||
|
|
b4b1947a22 | ||
|
|
a33d9116ab | ||
|
|
7d744ebc84 | ||
|
|
3e6c6986f2 | ||
|
|
f167716f53 | ||
|
|
0cdf6d28b1 | ||
|
|
e4c1e3d262 | ||
|
|
3f6313906d | ||
|
|
0089a40a33 | ||
|
|
549745fa5e | ||
|
|
ac41358b5a | ||
|
|
0db2ad6953 | ||
|
|
8ca85ed2a8 | ||
|
|
af64fe7e98 | ||
|
|
28cf40fe03 | ||
|
|
d38a189907 | ||
|
|
2b338092b6 | ||
|
|
557b779bd4 | ||
|
|
ece40480f6 | ||
|
|
a77445c46b | ||
|
|
4d55fa6814 | ||
|
|
b9d6f4f9c1 | ||
|
|
b0cf76c74c | ||
|
|
7841fbb8e0 | ||
|
|
f5773fed42 | ||
|
|
e2b8ab9a0d | ||
|
|
1dc6d10f30 | ||
|
|
e7138fb2c6 | ||
|
|
04ecc05adc |
@@ -116,6 +116,60 @@ Percentage | Module
|
||||
77.19% | Total
|
||||
```
|
||||
|
||||
## v1.7.4 (2018-10-24)
|
||||
|
||||
### 1. Enhancements
|
||||
|
||||
#### Elixir
|
||||
|
||||
* [Kernel] Expand `left..right` at compile time in more cases, which leads to improved performance under different scenarios, especially on `x in left..right` expressions
|
||||
|
||||
#### Mix
|
||||
|
||||
* [mix deps.loadpaths] Add `--no-load-deps` flag. This is useful for Rebar 3 compatibility
|
||||
|
||||
### 2. Bug fixes
|
||||
|
||||
#### Elixir
|
||||
|
||||
* [Calendar] Fix for converting from negative iso days on New Year in a leap year
|
||||
* [Kernel] Ensure `@spec`, `@callback`, `@type` and friends can be read accordingly
|
||||
* [Module] Avoid warnings when using Module.eval_quoted in the middle of existing definitions
|
||||
|
||||
#### Mix
|
||||
|
||||
* [mix archive.build] Unload previous archive versions before building
|
||||
* [mix format] Expand paths so mix format `path\for\windows.ex` works
|
||||
* [mix test] Ensure that `--cover` displays correct coverage in an umbrella app
|
||||
|
||||
## v1.7.3 (2018-08-24)
|
||||
|
||||
### 1. Bug fixes
|
||||
|
||||
#### ExUnit
|
||||
|
||||
* [ExUnit.Assertions] Do not attempt to expand `try/1` as it is a special form
|
||||
|
||||
#### Mix
|
||||
|
||||
* [mix compile.app] Do not include applications with `runtime: false` as a runtime dependency for applications coming from Hex
|
||||
|
||||
## v1.7.2 (2018-08-05)
|
||||
|
||||
### 1. Bug fixes
|
||||
|
||||
#### Elixir
|
||||
|
||||
* [DateTime] Take negative years into account in `DateTime.from_iso8601/1`
|
||||
* [Kernel] Do not emit warnings for repeated docs over different clauses due to false positives
|
||||
|
||||
#### Mix
|
||||
|
||||
* [mix compile] Properly mark top-level dependencies as optional and as runtime. This fixes a bug where Mix attempted to start optional dependencies of a package when those optional dependencies were not available
|
||||
* [mix compile] Avoid deadlock when a config has a timestamp later than current time
|
||||
* [mix help] Show task and alias help when both are available
|
||||
* [mix test] Do not fail suite if there are no test files
|
||||
|
||||
## v1.7.1 (2018-07-26)
|
||||
|
||||
### 1. Bug fixes
|
||||
|
||||
@@ -485,6 +485,14 @@ defmodule DateTime do
|
||||
iex> datetime
|
||||
#DateTime<2015-01-23 21:20:07.123Z>
|
||||
|
||||
iex> {:ok, datetime, 0} = DateTime.from_iso8601("-2015-01-23T23:50:07Z")
|
||||
iex> datetime
|
||||
#DateTime<-2015-01-23 23:50:07Z>
|
||||
|
||||
iex> {:ok, datetime, 9000} = DateTime.from_iso8601("-2015-01-23T23:50:07,123+02:30")
|
||||
iex> datetime
|
||||
#DateTime<-2015-01-23 21:20:07.123Z>
|
||||
|
||||
iex> DateTime.from_iso8601("2015-01-23P23:50:07")
|
||||
{:error, :invalid_format}
|
||||
iex> DateTime.from_iso8601("2015-01-23 23:50:07A")
|
||||
@@ -505,18 +513,28 @@ defmodule DateTime do
|
||||
@doc since: "1.4.0"
|
||||
@spec from_iso8601(String.t(), Calendar.calendar()) ::
|
||||
{:ok, t, Calendar.utc_offset()} | {:error, atom}
|
||||
def from_iso8601(string, calendar \\ Calendar.ISO)
|
||||
|
||||
def from_iso8601(<<?-, rest::binary>>, calendar) do
|
||||
raw_from_iso8601(rest, calendar, true)
|
||||
end
|
||||
|
||||
def from_iso8601(<<rest::binary>>, calendar) do
|
||||
raw_from_iso8601(rest, calendar, false)
|
||||
end
|
||||
|
||||
@sep [?\s, ?T]
|
||||
[match_date, guard_date, read_date] = Calendar.ISO.__match_date__()
|
||||
[match_time, guard_time, read_time] = Calendar.ISO.__match_time__()
|
||||
|
||||
def from_iso8601(string, calendar \\ Calendar.ISO) when is_binary(string) do
|
||||
defp raw_from_iso8601(string, calendar, is_negative_datetime) do
|
||||
with <<unquote(match_date), sep, unquote(match_time), rest::binary>> <- string,
|
||||
true <- unquote(guard_date) and sep in @sep and unquote(guard_time),
|
||||
{microsecond, rest} <- Calendar.ISO.parse_microsecond(rest),
|
||||
{offset, ""} <- Calendar.ISO.parse_offset(rest) do
|
||||
{year, month, day} = unquote(read_date)
|
||||
{hour, minute, second} = unquote(read_time)
|
||||
year = if is_negative_datetime, do: -year, else: year
|
||||
|
||||
cond do
|
||||
not calendar.valid_date?(year, month, day) ->
|
||||
|
||||
@@ -28,6 +28,7 @@ defmodule Calendar.ISO do
|
||||
@seconds_per_hour 60 * 60
|
||||
# Note that this does _not_ handle leap seconds.
|
||||
@seconds_per_day 24 * 60 * 60
|
||||
@last_second_of_the_day @seconds_per_day - 1
|
||||
@microseconds_per_second 1_000_000
|
||||
@parts_per_day @seconds_per_day * @microseconds_per_second
|
||||
|
||||
@@ -207,23 +208,13 @@ defmodule Calendar.ISO do
|
||||
|
||||
# Converts count of days since 0000-01-01 to {year, month, day} tuple.
|
||||
@doc false
|
||||
@doc since: "1.5.0"
|
||||
def date_from_iso_days(days) when days in 0..3_652_424 do
|
||||
def date_from_iso_days(days) when days in -3_652_059..3_652_424 do
|
||||
{year, day_of_year} = days_to_year(days)
|
||||
extra_day = if leap_year?(year), do: 1, else: 0
|
||||
{month, day_in_month} = year_day_to_year_date(extra_day, day_of_year)
|
||||
{year, month, day_in_month + 1}
|
||||
end
|
||||
|
||||
def date_from_iso_days(days) when days in -3_652_059..-1 do
|
||||
{year, day_of_year} = days_to_year(-days)
|
||||
previous_extra_day = if leap_year?(year), do: 1, else: 0
|
||||
extra_day = if leap_year?(year + 1), do: 1, else: 0
|
||||
day_of_year = @days_per_nonleap_year + extra_day - day_of_year
|
||||
{month, day_in_month} = year_day_to_year_date(extra_day, day_of_year)
|
||||
{-year - 1, month, day_in_month + previous_extra_day}
|
||||
end
|
||||
|
||||
defp div_mod(int1, int2) do
|
||||
div = div(int1, int2)
|
||||
rem = int1 - div * int2
|
||||
@@ -499,7 +490,8 @@ defmodule Calendar.ISO do
|
||||
@impl true
|
||||
@spec valid_date?(year, month, day) :: boolean
|
||||
def valid_date?(year, month, day) do
|
||||
month in 1..12 and year in -9999..9999 and day in 1..days_in_month(year, month)
|
||||
month in 1..12 and year in -9999..9999 and
|
||||
(is_integer(day) and day >= 1 and day <= days_in_month(year, month))
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -759,13 +751,30 @@ defmodule Calendar.ISO do
|
||||
if leap_year?(year), do: 1, else: 0
|
||||
end
|
||||
|
||||
defp days_to_year(days) when days < 0 do
|
||||
year_estimate = -div(-days, @days_per_nonleap_year) - 1
|
||||
|
||||
{year, days_before_year} =
|
||||
days_to_year(year_estimate, days, days_to_end_of_epoch(year_estimate))
|
||||
|
||||
leap_year_pad = if leap_year?(year), do: 1, else: 0
|
||||
{year, leap_year_pad + @days_per_nonleap_year + days - days_before_year}
|
||||
end
|
||||
|
||||
defp days_to_year(days) do
|
||||
year = Integer.floor_div(days, @days_per_nonleap_year)
|
||||
{year, days_before_year} = days_to_year(year, days, days_in_previous_years(year))
|
||||
year_estimate = div(days, @days_per_nonleap_year)
|
||||
|
||||
{year, days_before_year} =
|
||||
days_to_year(year_estimate, days, days_in_previous_years(year_estimate))
|
||||
|
||||
{year, days - days_before_year}
|
||||
end
|
||||
|
||||
defp days_to_year(year, days1, days2) when days1 < days2 do
|
||||
defp days_to_year(year, days1, days2) when year < 0 and days1 >= days2 do
|
||||
days_to_year(year + 1, days1, days_to_end_of_epoch(year + 1))
|
||||
end
|
||||
|
||||
defp days_to_year(year, days1, days2) when year >= 0 and days1 < days2 do
|
||||
days_to_year(year - 1, days1, days_in_previous_years(year - 1))
|
||||
end
|
||||
|
||||
@@ -773,6 +782,13 @@ defmodule Calendar.ISO do
|
||||
{year, days2}
|
||||
end
|
||||
|
||||
defp days_to_end_of_epoch(year) when year < 0 do
|
||||
previous_year = year + 1
|
||||
|
||||
div(previous_year, 4) - div(previous_year, 100) + div(previous_year, 400) +
|
||||
previous_year * @days_per_nonleap_year
|
||||
end
|
||||
|
||||
defp days_in_previous_years(0), do: 0
|
||||
|
||||
defp days_in_previous_years(year) do
|
||||
@@ -840,7 +856,7 @@ defmodule Calendar.ISO do
|
||||
{date, time}
|
||||
end
|
||||
|
||||
defp seconds_to_time(seconds) when seconds in 0..(@seconds_per_day - 1) do
|
||||
defp seconds_to_time(seconds) when seconds in 0..@last_second_of_the_day do
|
||||
{hour, rest_seconds} = div_mod(seconds, @seconds_per_hour)
|
||||
{minute, second} = div_mod(rest_seconds, @seconds_per_minute)
|
||||
|
||||
|
||||
@@ -538,7 +538,7 @@ defmodule Inspect.Algebra do
|
||||
["hello", "\n ", "world"]
|
||||
|
||||
"""
|
||||
@spec nest(t, non_neg_integer) :: doc_nest
|
||||
@spec nest(t, non_neg_integer | :cursor | :reset, :always | :break) :: doc_nest
|
||||
def nest(doc, level, mode \\ :always)
|
||||
|
||||
def nest(doc, :cursor, mode) when is_doc(doc) and mode in [:always, :break] do
|
||||
@@ -640,7 +640,7 @@ defmodule Inspect.Algebra do
|
||||
|
||||
"""
|
||||
@doc since: "1.6.0"
|
||||
@spec next_break_fits(t) :: doc_fits
|
||||
@spec next_break_fits(t, :enabled | :disabled) :: doc_fits
|
||||
def next_break_fits(doc, mode \\ @next_break_fits)
|
||||
when is_doc(doc) and mode in [:enabled, :disabled] do
|
||||
doc_fits(doc, mode)
|
||||
@@ -757,7 +757,7 @@ defmodule Inspect.Algebra do
|
||||
["Hello,", "\n", "A", "\n", "B"]
|
||||
|
||||
"""
|
||||
@spec group(t) :: doc_group
|
||||
@spec group(t, :self | :inherit) :: doc_group
|
||||
def group(doc, mode \\ :self) when is_doc(doc) do
|
||||
doc_group(doc, mode)
|
||||
end
|
||||
|
||||
+21
-11
@@ -3027,26 +3027,36 @@ defmodule Kernel do
|
||||
true
|
||||
|
||||
"""
|
||||
defmacro first..last when is_integer(first) and is_integer(last) do
|
||||
defmacro first..last do
|
||||
case bootstrapped?(Macro) do
|
||||
true ->
|
||||
first = Macro.expand(first, __CALLER__)
|
||||
last = Macro.expand(last, __CALLER__)
|
||||
range(__CALLER__.context, first, last)
|
||||
|
||||
false ->
|
||||
range(__CALLER__.context, first, last)
|
||||
end
|
||||
end
|
||||
|
||||
defp range(_context, first, last) when is_integer(first) and is_integer(last) do
|
||||
{:%{}, [], [__struct__: Elixir.Range, first: first, last: last]}
|
||||
end
|
||||
|
||||
defmacro first..last
|
||||
when is_float(first) or is_float(last) or is_atom(first) or is_atom(last) or
|
||||
is_binary(first) or is_binary(last) or is_list(first) or is_list(last) do
|
||||
defp range(_context, first, last)
|
||||
when is_float(first) or is_float(last) or is_atom(first) or is_atom(last) or
|
||||
is_binary(first) or is_binary(last) or is_list(first) or is_list(last) do
|
||||
raise ArgumentError,
|
||||
"ranges (first..last) expect both sides to be integers, " <>
|
||||
"got: #{Macro.to_string({:.., [], [first, last]})}"
|
||||
end
|
||||
|
||||
defmacro first..last do
|
||||
case __CALLER__.context do
|
||||
nil ->
|
||||
quote(do: Elixir.Range.new(unquote(first), unquote(last)))
|
||||
defp range(nil, first, last) do
|
||||
quote(do: Elixir.Range.new(unquote(first), unquote(last)))
|
||||
end
|
||||
|
||||
_ ->
|
||||
{:%{}, [], [__struct__: Elixir.Range, first: first, last: last]}
|
||||
end
|
||||
defp range(_, first, last) do
|
||||
{:%{}, [], [__struct__: Elixir.Range, first: first, last: last]}
|
||||
end
|
||||
|
||||
@doc """
|
||||
|
||||
@@ -71,24 +71,24 @@ defmodule Kernel.Typespec do
|
||||
type_to_signature(expr) == signature
|
||||
end
|
||||
|
||||
:lists.any(finder, get_typespec(bag, :type))
|
||||
:lists.any(finder, get_typespecs(bag, [:type, :opaque, :typep]))
|
||||
end
|
||||
|
||||
def spec_to_callback(module, {name, arity} = signature)
|
||||
when is_atom(module) and is_atom(name) and arity in 0..255 do
|
||||
{_set, bag} = :elixir_module.data_tables(module)
|
||||
|
||||
filter = fn {expr, _} = object ->
|
||||
filter = fn {:spec, expr, pos} ->
|
||||
if spec_to_signature(expr) == signature do
|
||||
:ets.delete_object(bag, {:spec, object})
|
||||
store_typespec(bag, :callback, object)
|
||||
delete_typespec(bag, :spec, expr, pos)
|
||||
store_typespec(bag, :callback, expr, pos)
|
||||
true
|
||||
else
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
:lists.filter(filter, get_typespec(bag, :spec)) != []
|
||||
:lists.filter(filter, get_typespecs(bag, :spec)) != []
|
||||
end
|
||||
|
||||
## Typespec definition and storage
|
||||
@@ -100,7 +100,7 @@ defmodule Kernel.Typespec do
|
||||
"""
|
||||
def deftypespec(:spec, expr, _line, _file, module, pos) do
|
||||
{_set, bag} = :elixir_module.data_tables(module)
|
||||
store_typespec(bag, :spec, {expr, pos})
|
||||
store_typespec(bag, :spec, expr, pos)
|
||||
end
|
||||
|
||||
def deftypespec(kind, expr, line, _file, module, pos)
|
||||
@@ -116,7 +116,7 @@ defmodule Kernel.Typespec do
|
||||
:error
|
||||
end
|
||||
|
||||
store_typespec(bag, kind, {expr, pos})
|
||||
store_typespec(bag, kind, expr, pos)
|
||||
end
|
||||
|
||||
def deftypespec(kind, expr, line, file, module, pos)
|
||||
@@ -143,17 +143,34 @@ defmodule Kernel.Typespec do
|
||||
:error
|
||||
end
|
||||
|
||||
store_typespec(bag, :type, {kind, expr, pos})
|
||||
store_typespec(bag, kind, expr, pos)
|
||||
end
|
||||
|
||||
defp get_typespec(bag, key) do
|
||||
:ets.lookup_element(bag, key, 2)
|
||||
defp get_typespecs(bag, keys) when is_list(keys) do
|
||||
:lists.flatmap(&get_typespecs(bag, &1), keys)
|
||||
end
|
||||
|
||||
defp get_typespecs(bag, key) do
|
||||
:ets.lookup_element(bag, {:accumulate, key}, 2)
|
||||
catch
|
||||
:error, :badarg -> []
|
||||
end
|
||||
|
||||
defp store_typespec(bag, key, value) do
|
||||
:ets.insert(bag, {key, value})
|
||||
defp take_typespecs(bag, keys) when is_list(keys) do
|
||||
:lists.flatmap(&take_typespecs(bag, &1), keys)
|
||||
end
|
||||
|
||||
defp take_typespecs(bag, key) do
|
||||
:lists.map(&elem(&1, 1), :ets.take(bag, {:accumulate, key}))
|
||||
end
|
||||
|
||||
defp store_typespec(bag, key, expr, pos) do
|
||||
:ets.insert(bag, {{:accumulate, key}, {key, expr, pos}})
|
||||
:ok
|
||||
end
|
||||
|
||||
defp delete_typespec(bag, key, expr, pos) do
|
||||
:ets.delete_object(bag, {{:accumulate, key}, {key, expr, pos}})
|
||||
:ok
|
||||
end
|
||||
|
||||
@@ -192,19 +209,15 @@ defmodule Kernel.Typespec do
|
||||
|
||||
@doc false
|
||||
def translate_typespecs_for_module(_set, bag) do
|
||||
types = Enum.map(take_typespec(bag, :type), &translate_type/1)
|
||||
specs = Enum.map(take_typespec(bag, :spec), &translate_spec/1)
|
||||
callbacks = Enum.map(take_typespec(bag, :callback), &translate_spec/1)
|
||||
macrocallbacks = Enum.map(take_typespec(bag, :macrocallback), &translate_spec/1)
|
||||
optional_callbacks = List.flatten(get_typespec(bag, {:accumulate, :optional_callbacks}))
|
||||
types = Enum.map(take_typespecs(bag, [:type, :opaque, :typep]), &translate_type/1)
|
||||
specs = Enum.map(take_typespecs(bag, :spec), &translate_spec/1)
|
||||
callbacks = Enum.map(take_typespecs(bag, :callback), &translate_spec/1)
|
||||
macrocallbacks = Enum.map(take_typespecs(bag, :macrocallback), &translate_spec/1)
|
||||
optional_callbacks = List.flatten(get_typespecs(bag, :optional_callbacks))
|
||||
{types, specs, callbacks, macrocallbacks, optional_callbacks}
|
||||
end
|
||||
|
||||
defp take_typespec(bag, key) do
|
||||
:ets.take(bag, key)
|
||||
end
|
||||
|
||||
defp translate_type({_, {kind, {:::, _, [{name, _, args}, definition]}, pos}})
|
||||
defp translate_type({kind, {:::, _, [{name, _, args}, definition]}, pos})
|
||||
when is_atom(name) and name != ::: do
|
||||
caller = :elixir_locals.get_cached_env(pos)
|
||||
|
||||
@@ -247,7 +260,7 @@ defmodule Kernel.Typespec do
|
||||
{kind, {name, arity}, caller.line, type, export}
|
||||
end
|
||||
|
||||
defp translate_type({_, {_kind, other, pos}}) do
|
||||
defp translate_type({_kind, other, pos}) do
|
||||
caller = :elixir_locals.get_cached_env(pos)
|
||||
type_spec = Macro.to_string(other)
|
||||
compile_error(caller, "invalid type specification: #{type_spec}")
|
||||
@@ -259,12 +272,12 @@ defmodule Kernel.Typespec do
|
||||
|
||||
defp valid_variable_ast?(_), do: false
|
||||
|
||||
defp translate_spec({kind, {{:when, _meta, [spec, guard]}, pos}}) do
|
||||
defp translate_spec({kind, {:when, _meta, [spec, guard]}, pos}) do
|
||||
caller = :elixir_locals.get_cached_env(pos)
|
||||
translate_spec(kind, spec, guard, caller)
|
||||
end
|
||||
|
||||
defp translate_spec({kind, {spec, pos}}) do
|
||||
defp translate_spec({kind, spec, pos}) do
|
||||
caller = :elixir_locals.get_cached_env(pos)
|
||||
translate_spec(kind, spec, [], caller)
|
||||
end
|
||||
|
||||
@@ -1255,6 +1255,12 @@ defmodule Macro do
|
||||
next = :erlang.unique_integer()
|
||||
{:elixir_quote.linify_with_context_counter(0, {receiver, next}, quoted), true}
|
||||
|
||||
{:ok, Kernel, op, [arg]} when op in [:+, :-] ->
|
||||
case expand_once(arg, env) do
|
||||
integer when is_integer(integer) -> {apply(Kernel, op, [integer]), true}
|
||||
_ -> {original, false}
|
||||
end
|
||||
|
||||
{:ok, _receiver, _name, _args} ->
|
||||
{original, false}
|
||||
|
||||
|
||||
@@ -466,7 +466,7 @@ defmodule Module do
|
||||
of the corresponding setting in `Code.compiler_options/1`
|
||||
|
||||
* `@compile {:inline, some_fun: 2, other_fun: 3}` - inlines the given
|
||||
name/arity pairs. Inlining is applied locally, calls from another
|
||||
name/arity pairs. Inlining is applied locally, calls from another
|
||||
module are not affected by this option
|
||||
|
||||
* `@compile {:autoload, false}` - disables automatic loading of
|
||||
@@ -1320,7 +1320,7 @@ defmodule Module do
|
||||
end
|
||||
end
|
||||
|
||||
defp compile_doc(table, line, kind, name, arity, args, body, doc, doc_meta, env, impl) do
|
||||
defp compile_doc(table, line, kind, name, arity, args, _body, doc, doc_meta, env, impl) do
|
||||
key = {doc_key(kind), name, arity}
|
||||
signature = build_signature(args, env)
|
||||
|
||||
@@ -1331,16 +1331,6 @@ defmodule Module do
|
||||
|
||||
[{_, current_line, current_sign, current_doc, current_doc_meta}] ->
|
||||
signature = merge_signatures(current_sign, signature, 1)
|
||||
|
||||
if is_binary(doc) and is_binary(current_doc) and not is_nil(body) do
|
||||
message =
|
||||
"redefining @doc attribute previously set at line #{current_line}. " <>
|
||||
"If you want to redefine a previously specified doc, " <>
|
||||
"use a definition without a body after the original documentation"
|
||||
|
||||
:elixir_errors.warn(line, env.file, message)
|
||||
end
|
||||
|
||||
doc = if is_nil(doc), do: current_doc, else: doc
|
||||
doc = if is_nil(doc) && impl, do: false, else: doc
|
||||
doc_meta = Map.merge(current_doc_meta, doc_meta)
|
||||
@@ -1874,9 +1864,9 @@ defmodule Module do
|
||||
do: {atom, :__on_definition__}
|
||||
|
||||
defp preprocess_attribute(key, _value)
|
||||
when key in [:type, :typep, :export_type, :opaque, :callback, :macrocallback] do
|
||||
when key in [:type, :typep, :export_type, :opaque, :spec, :callback, :macrocallback] do
|
||||
raise ArgumentError,
|
||||
"attributes type, typep, export_type, opaque, callback, and macrocallback" <>
|
||||
"attributes type, typep, export_type, opaque, spec, callback, and macrocallback " <>
|
||||
"must be set directly via the @ notation"
|
||||
end
|
||||
|
||||
|
||||
@@ -564,10 +564,10 @@ defmodule System do
|
||||
iex> System.cmd("echo", ["hello"])
|
||||
{"hello\n", 0}
|
||||
|
||||
iex> System.cmd("echo", ["hello"]), env: [{"MIX_ENV", "test"}]
|
||||
iex> System.cmd("echo", ["hello"], env: [{"MIX_ENV", "test"}])
|
||||
{"hello\n", 0}
|
||||
|
||||
iex> System.cmd("echo", ["hello"]), into: IO.stream(:stdio, :line)
|
||||
iex> System.cmd("echo", ["hello"], into: IO.stream(:stdio, :line))
|
||||
hello
|
||||
{%IO.Stream{}, 0}
|
||||
|
||||
|
||||
@@ -36,7 +36,7 @@ defmodule Tuple do
|
||||
tuple = {:ok, :example}
|
||||
|
||||
# Avoid
|
||||
Tuple.insert_at(tuple, 2, %{}}
|
||||
Tuple.insert_at(tuple, 2, %{})
|
||||
|
||||
# Prefer
|
||||
{:ok, atom} = tuple
|
||||
|
||||
@@ -94,7 +94,9 @@ defmodule Version do
|
||||
@type patch :: non_neg_integer | nil
|
||||
@type pre :: [String.t() | non_neg_integer]
|
||||
@type build :: String.t() | nil
|
||||
@type matchable :: {major :: major, minor :: minor, patch :: patch, pre :: pre}
|
||||
@type matchable ::
|
||||
{major :: major, minor :: minor, patch :: patch, pre :: pre,
|
||||
build_parts :: [String.t()]}
|
||||
@type t :: %__MODULE__{major: major, minor: minor, patch: patch, pre: pre, build: build}
|
||||
|
||||
defmodule Requirement do
|
||||
|
||||
@@ -11,7 +11,7 @@ setup(DataTables) ->
|
||||
ok.
|
||||
|
||||
reset_last({DataSet, _DataBag}) ->
|
||||
ets:insert(DataSet, {?last_def, []});
|
||||
ets:insert(DataSet, {?last_def, none});
|
||||
|
||||
reset_last(Module) when is_atom(Module) ->
|
||||
reset_last(elixir_module:data_tables(Module)).
|
||||
|
||||
@@ -239,6 +239,6 @@ special_form('for', _) -> true;
|
||||
special_form('with', _) -> true;
|
||||
special_form('cond', 1) -> true;
|
||||
special_form('case', 2) -> true;
|
||||
special_form('try', 2) -> true;
|
||||
special_form('try', 1) -> true;
|
||||
special_form('receive', 1) -> true;
|
||||
special_form(_, _) -> false.
|
||||
|
||||
@@ -211,7 +211,7 @@ build(Line, File, Module, Lexical) ->
|
||||
|
||||
%% In the bag table we store:
|
||||
%%
|
||||
%% * {{accumulate, Attribute}, ...}
|
||||
%% * {{accumulate, Attribute}, ...} (includes typespecs)
|
||||
%% * {attributes, ...}
|
||||
%% * {impls, ...}
|
||||
%% * {deprecated, ...}
|
||||
@@ -221,7 +221,6 @@ build(Line, File, Module, Lexical) ->
|
||||
%% * {{clauses, Tuple}, ...} (from elixir_def)
|
||||
%% * {reattach, ...} (from elixir_local)
|
||||
%% * {{local, Tuple}, ...} (from elixir_local)
|
||||
%% * {spec, ...}, {type, ...}, {callback, ...}, {macrocallback, ...}
|
||||
%%
|
||||
DataBag = ets:new(Module, [duplicate_bag, public]),
|
||||
|
||||
@@ -238,6 +237,12 @@ build(Line, File, Module, Lexical) ->
|
||||
{dialyzer, [], accumulate},
|
||||
{external_resource, [], accumulate},
|
||||
{on_definition, [], accumulate},
|
||||
{type, [], accumulate},
|
||||
{opaque, [], accumulate},
|
||||
{typep, [], accumulate},
|
||||
{spec, [], accumulate},
|
||||
{callback, [], accumulate},
|
||||
{macrocallback, [], accumulate},
|
||||
{on_load, [], accumulate},
|
||||
{optional_callbacks, [], accumulate},
|
||||
|
||||
|
||||
@@ -3,4 +3,40 @@ Code.require_file("../test_helper.exs", __DIR__)
|
||||
defmodule Calendar.ISOTest do
|
||||
use ExUnit.Case, async: true
|
||||
doctest Calendar.ISO
|
||||
|
||||
describe "date_from_iso_days" do
|
||||
test "with positive dates" do
|
||||
assert {0, 1, 1} == iso_day_roundtrip(0, 1, 1)
|
||||
assert {0, 12, 31} == iso_day_roundtrip(0, 12, 31)
|
||||
assert {1, 12, 31} == iso_day_roundtrip(1, 12, 31)
|
||||
assert {4, 1, 1} == iso_day_roundtrip(4, 1, 1)
|
||||
assert {4, 12, 31} == iso_day_roundtrip(4, 12, 31)
|
||||
assert {9999, 12, 31} == iso_day_roundtrip(9999, 12, 31)
|
||||
assert {9999, 1, 1} == iso_day_roundtrip(9999, 1, 1)
|
||||
assert {9996, 12, 31} == iso_day_roundtrip(9996, 12, 31)
|
||||
assert {9996, 1, 1} == iso_day_roundtrip(9996, 1, 1)
|
||||
end
|
||||
|
||||
test "with negative dates" do
|
||||
assert {-1, 1, 1} == iso_day_roundtrip(-1, 1, 1)
|
||||
assert {-1, 12, 31} == iso_day_roundtrip(-1, 12, 31)
|
||||
assert {-1, 12, 31} == iso_day_roundtrip(-1, 12, 31)
|
||||
assert {-2, 1, 1} == iso_day_roundtrip(-2, 1, 1)
|
||||
assert {-5, 12, 31} == iso_day_roundtrip(-5, 12, 31)
|
||||
|
||||
assert {-4, 1, 1} == iso_day_roundtrip(-4, 1, 1)
|
||||
assert {-4, 12, 31} == iso_day_roundtrip(-4, 12, 31)
|
||||
|
||||
assert {-9999, 12, 31} == iso_day_roundtrip(-9999, 12, 31)
|
||||
assert {-9996, 12, 31} == iso_day_roundtrip(-9996, 12, 31)
|
||||
|
||||
assert {-9996, 12, 31} == iso_day_roundtrip(-9996, 12, 31)
|
||||
assert {-9996, 1, 1} == iso_day_roundtrip(-9996, 1, 1)
|
||||
end
|
||||
end
|
||||
|
||||
defp iso_day_roundtrip(year, month, day) do
|
||||
iso_days = Calendar.ISO.date_to_iso_days(year, month, day)
|
||||
Calendar.ISO.date_from_iso_days(iso_days)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -293,8 +293,7 @@ defmodule DateTimeTest do
|
||||
end
|
||||
|
||||
test "from_iso8601/1 handles positive and negative offsets" do
|
||||
assert DateTime.from_iso8601("2015-01-24T09:50:07-10:00")
|
||||
|> elem(1) ==
|
||||
assert DateTime.from_iso8601("2015-01-24T09:50:07-10:00") |> elem(1) ==
|
||||
%DateTime{
|
||||
microsecond: {0, 0},
|
||||
month: 1,
|
||||
@@ -309,8 +308,7 @@ defmodule DateTimeTest do
|
||||
second: 7
|
||||
}
|
||||
|
||||
assert DateTime.from_iso8601("2015-01-24T09:50:07+10:00")
|
||||
|> elem(1) ==
|
||||
assert DateTime.from_iso8601("2015-01-24T09:50:07+10:00") |> elem(1) ==
|
||||
%DateTime{
|
||||
microsecond: {0, 0},
|
||||
month: 1,
|
||||
@@ -324,6 +322,98 @@ defmodule DateTimeTest do
|
||||
minute: 50,
|
||||
second: 7
|
||||
}
|
||||
|
||||
assert DateTime.from_iso8601("0000-01-01T01:22:07+10:30") |> elem(1) ==
|
||||
%DateTime{
|
||||
microsecond: {0, 0},
|
||||
month: 12,
|
||||
std_offset: 0,
|
||||
time_zone: "Etc/UTC",
|
||||
utc_offset: 0,
|
||||
year: -1,
|
||||
zone_abbr: "UTC",
|
||||
day: 31,
|
||||
hour: 14,
|
||||
minute: 52,
|
||||
second: 7
|
||||
}
|
||||
end
|
||||
|
||||
test "from_iso8601/1 handles negative dates" do
|
||||
assert DateTime.from_iso8601("-2015-01-24T09:50:07-10:00") |> elem(1) ==
|
||||
%DateTime{
|
||||
microsecond: {0, 0},
|
||||
month: 1,
|
||||
std_offset: 0,
|
||||
time_zone: "Etc/UTC",
|
||||
utc_offset: 0,
|
||||
year: -2015,
|
||||
zone_abbr: "UTC",
|
||||
day: 24,
|
||||
hour: 19,
|
||||
minute: 50,
|
||||
second: 7
|
||||
}
|
||||
|
||||
assert DateTime.from_iso8601("-2015-01-24T09:50:07+10:00") |> elem(1) ==
|
||||
%DateTime{
|
||||
microsecond: {0, 0},
|
||||
month: 1,
|
||||
std_offset: 0,
|
||||
time_zone: "Etc/UTC",
|
||||
utc_offset: 0,
|
||||
year: -2015,
|
||||
zone_abbr: "UTC",
|
||||
day: 23,
|
||||
hour: 23,
|
||||
minute: 50,
|
||||
second: 7
|
||||
}
|
||||
|
||||
assert DateTime.from_iso8601("-0001-01-01T01:22:07+10:30") |> elem(1) ==
|
||||
%DateTime{
|
||||
microsecond: {0, 0},
|
||||
month: 12,
|
||||
std_offset: 0,
|
||||
time_zone: "Etc/UTC",
|
||||
utc_offset: 0,
|
||||
year: -2,
|
||||
zone_abbr: "UTC",
|
||||
day: 31,
|
||||
hour: 14,
|
||||
minute: 52,
|
||||
second: 7
|
||||
}
|
||||
|
||||
assert DateTime.from_iso8601("-0001-01-01T01:22:07-10:30") |> elem(1) ==
|
||||
%DateTime{
|
||||
microsecond: {0, 0},
|
||||
month: 1,
|
||||
std_offset: 0,
|
||||
time_zone: "Etc/UTC",
|
||||
utc_offset: 0,
|
||||
year: -1,
|
||||
zone_abbr: "UTC",
|
||||
day: 1,
|
||||
hour: 11,
|
||||
minute: 52,
|
||||
second: 7
|
||||
}
|
||||
|
||||
assert DateTime.from_iso8601("-0001-12-31T23:22:07-10:30") |> elem(1) ==
|
||||
%DateTime{
|
||||
microsecond: {0, 0},
|
||||
month: 1,
|
||||
std_offset: 0,
|
||||
time_zone: "Etc/UTC",
|
||||
utc_offset: 0,
|
||||
year: 0,
|
||||
zone_abbr: "UTC",
|
||||
day: 1,
|
||||
hour: 9,
|
||||
minute: 52,
|
||||
second: 7
|
||||
}
|
||||
end
|
||||
|
||||
test "from_unix/2" do
|
||||
|
||||
@@ -503,6 +503,22 @@ defmodule Kernel.ErrorsTest do
|
||||
end
|
||||
end
|
||||
|
||||
test "typespec attributes set via Module.put_attribute/4" do
|
||||
message =
|
||||
"attributes type, typep, export_type, opaque, spec, callback, and macrocallback " <>
|
||||
"must be set directly via the @ notation"
|
||||
|
||||
for kind <- [:type, :typep, :opaque, :spec, :callback, :macrocallback] do
|
||||
assert_eval_raise ArgumentError,
|
||||
message,
|
||||
"""
|
||||
defmodule PutTypespecAttribute do
|
||||
Module.put_attribute(__MODULE__, #{inspect(kind)}, {})
|
||||
end
|
||||
"""
|
||||
end
|
||||
end
|
||||
|
||||
test "invalid struct field value" do
|
||||
msg = ~r"invalid value for struct field baz, cannot escape "
|
||||
|
||||
|
||||
@@ -1109,34 +1109,6 @@ defmodule Kernel.WarningTest do
|
||||
purge(Sample)
|
||||
end
|
||||
|
||||
test "duplicated docs across multiple clauses" do
|
||||
output =
|
||||
capture_err(fn ->
|
||||
Code.eval_string("""
|
||||
defmodule Sample do
|
||||
@doc "Something"
|
||||
def foo(a)
|
||||
|
||||
@doc "Something"
|
||||
def foo(1), do: :ok
|
||||
|
||||
@doc "Something"
|
||||
def foo(2), do: :ok
|
||||
|
||||
@doc "Something"
|
||||
def foo(a)
|
||||
end
|
||||
""")
|
||||
end)
|
||||
|
||||
assert output =~ "redefining @doc attribute previously set at line 2"
|
||||
assert output =~ "nofile:5"
|
||||
assert output =~ "nofile:8"
|
||||
refute output =~ "nofile:11"
|
||||
after
|
||||
purge(Sample)
|
||||
end
|
||||
|
||||
test "reserved doc metadata keys" do
|
||||
output =
|
||||
capture_err(fn ->
|
||||
|
||||
@@ -440,9 +440,7 @@ defmodule KernelTest do
|
||||
end
|
||||
end
|
||||
|
||||
test "is optimized" do
|
||||
assert expand_to_string(quote(do: foo in [])) == "Enum.member?([], foo)"
|
||||
|
||||
test "hoists variables" do
|
||||
result = expand_to_string(quote(do: rand() in 1..2))
|
||||
assert result =~ "var = rand()"
|
||||
|
||||
@@ -463,6 +461,16 @@ defmodule KernelTest do
|
||||
assert result =~ ":erlang.orelse(:erlang.\"=:=\"(var, 1), :lists.member(var, var0))"
|
||||
end
|
||||
|
||||
test "is optimized" do
|
||||
assert expand_to_string(quote(do: foo in [])) == "Enum.member?([], foo)"
|
||||
|
||||
assert expand_to_string(quote(do: foo in 0..1)) ==
|
||||
":erlang.andalso(:erlang.is_integer(foo), :erlang.andalso(:erlang.>=(foo, 0), :erlang.\"=<\"(foo, 1)))"
|
||||
|
||||
assert expand_to_string(quote(do: foo in -1..0)) ==
|
||||
":erlang.andalso(:erlang.is_integer(foo), :erlang.andalso(:erlang.>=(foo, -1), :erlang.\"=<\"(foo, 0)))"
|
||||
end
|
||||
|
||||
defp expand_to_string(ast) do
|
||||
ast
|
||||
|> Macro.prewalk(&Macro.expand(&1, __ENV__))
|
||||
|
||||
@@ -85,7 +85,23 @@ defmodule ModuleTest do
|
||||
assert eval_quoted_info() == {ModuleTest, "sample.ex", 13}
|
||||
end
|
||||
|
||||
test "retrieves line from macros" do
|
||||
test "resets last definition information on eval" do
|
||||
# This should not emit any warning
|
||||
defmodule LastDefinition do
|
||||
def foo(0), do: 0
|
||||
|
||||
Module.eval_quoted(
|
||||
__ENV__,
|
||||
quote do
|
||||
def bar, do: :ok
|
||||
end
|
||||
)
|
||||
|
||||
def foo(1), do: 1
|
||||
end
|
||||
end
|
||||
|
||||
test "retrieves line from use callsite" do
|
||||
assert ModuleTest.ToUse.line() == 40
|
||||
end
|
||||
|
||||
|
||||
@@ -747,6 +747,136 @@ defmodule TypespecTest do
|
||||
assert [{:atom, _, :is_subtype}, [{:var, _, :y}, {:var, _, :x}]] = constraint_type
|
||||
end
|
||||
|
||||
test "@type, @opaque, and @typep as module attributes" do
|
||||
defmodule TypeModuleAttributes do
|
||||
@type type1 :: boolean
|
||||
@opaque opaque1 :: boolean
|
||||
@typep typep1 :: boolean
|
||||
|
||||
def type1, do: @type
|
||||
def opaque1, do: @opaque
|
||||
def typep1, do: @typep
|
||||
|
||||
@type type2 :: atom
|
||||
@type type3 :: pid
|
||||
@opaque opaque2 :: atom
|
||||
@opaque opaque3 :: pid
|
||||
@typep typep2 :: atom
|
||||
|
||||
def type2, do: @type
|
||||
def opaque2, do: @opaque
|
||||
def typep2, do: @typep
|
||||
|
||||
# Avoid unused warnings
|
||||
@spec foo(typep1) :: typep2
|
||||
def foo(_x), do: :ok
|
||||
end
|
||||
|
||||
assert [
|
||||
{:type, {:::, _, [{:type1, _, _}, {:boolean, _, _}]},
|
||||
{TypespecTest.TypeModuleAttributes, _}}
|
||||
] = TypeModuleAttributes.type1()
|
||||
|
||||
assert [
|
||||
{:type, {:::, _, [{:type3, _, _}, {:pid, _, _}]},
|
||||
{TypespecTest.TypeModuleAttributes, _}},
|
||||
{:type, {:::, _, [{:type2, _, _}, {:atom, _, _}]},
|
||||
{TypespecTest.TypeModuleAttributes, _}},
|
||||
{:type, {:::, _, [{:type1, _, _}, {:boolean, _, _}]},
|
||||
{TypespecTest.TypeModuleAttributes, _}}
|
||||
] = TypeModuleAttributes.type2()
|
||||
|
||||
assert [
|
||||
{:opaque, {:::, _, [{:opaque1, _, _}, {:boolean, _, _}]},
|
||||
{TypespecTest.TypeModuleAttributes, _}}
|
||||
] = TypeModuleAttributes.opaque1()
|
||||
|
||||
assert [
|
||||
{:opaque, {:::, _, [{:opaque3, _, _}, {:pid, _, _}]},
|
||||
{TypespecTest.TypeModuleAttributes, _}},
|
||||
{:opaque, {:::, _, [{:opaque2, _, _}, {:atom, _, _}]},
|
||||
{TypespecTest.TypeModuleAttributes, _}},
|
||||
{:opaque, {:::, _, [{:opaque1, _, _}, {:boolean, _, _}]},
|
||||
{TypespecTest.TypeModuleAttributes, _}}
|
||||
] = TypeModuleAttributes.opaque2()
|
||||
|
||||
assert [
|
||||
{:typep, {:::, _, [{:typep1, _, _}, {:boolean, _, _}]},
|
||||
{TypespecTest.TypeModuleAttributes, _}}
|
||||
] = TypeModuleAttributes.typep1()
|
||||
|
||||
assert [
|
||||
{:typep, {:::, _, [{:typep2, _, _}, {:atom, _, _}]},
|
||||
{TypespecTest.TypeModuleAttributes, _}},
|
||||
{:typep, {:::, _, [{:typep1, _, _}, {:boolean, _, _}]},
|
||||
{TypespecTest.TypeModuleAttributes, _}}
|
||||
] = TypeModuleAttributes.typep2()
|
||||
after
|
||||
:code.delete(TypeModuleAttributes)
|
||||
:code.purge(TypeModuleAttributes)
|
||||
end
|
||||
|
||||
test "@spec, @callback, and @macrocallback as module attributes" do
|
||||
defmodule SpecModuleAttributes do
|
||||
@callback callback1 :: integer
|
||||
@macrocallback macrocallback1 :: integer
|
||||
|
||||
@spec spec1 :: boolean
|
||||
def spec1, do: @spec
|
||||
|
||||
@callback callback2 :: boolean
|
||||
@macrocallback macrocallback2 :: boolean
|
||||
|
||||
@spec spec2 :: atom
|
||||
def spec2, do: @spec
|
||||
|
||||
@spec spec3 :: pid
|
||||
def spec3, do: :ok
|
||||
def spec4, do: @spec
|
||||
|
||||
def callback, do: @callback
|
||||
def macrocallback, do: @macrocallback
|
||||
end
|
||||
|
||||
assert [
|
||||
{:spec, {:::, _, [{:spec1, _, _}, {:boolean, _, _}]},
|
||||
{TypespecTest.SpecModuleAttributes, _}}
|
||||
] = SpecModuleAttributes.spec1()
|
||||
|
||||
assert [
|
||||
{:spec, {:::, _, [{:spec2, _, _}, {:atom, _, _}]},
|
||||
{TypespecTest.SpecModuleAttributes, _}},
|
||||
{:spec, {:::, _, [{:spec1, _, _}, {:boolean, _, _}]},
|
||||
{TypespecTest.SpecModuleAttributes, _}}
|
||||
] = SpecModuleAttributes.spec2()
|
||||
|
||||
assert [
|
||||
{:spec, {:::, _, [{:spec3, _, _}, {:pid, _, _}]},
|
||||
{TypespecTest.SpecModuleAttributes, _}},
|
||||
{:spec, {:::, _, [{:spec2, _, _}, {:atom, _, _}]},
|
||||
{TypespecTest.SpecModuleAttributes, _}},
|
||||
{:spec, {:::, _, [{:spec1, _, _}, {:boolean, _, _}]},
|
||||
{TypespecTest.SpecModuleAttributes, _}}
|
||||
] = SpecModuleAttributes.spec4()
|
||||
|
||||
assert [
|
||||
{:callback, {:::, _, [{:callback2, _, _}, {:boolean, _, _}]},
|
||||
{TypespecTest.SpecModuleAttributes, _}},
|
||||
{:callback, {:::, _, [{:callback1, _, _}, {:integer, _, _}]},
|
||||
{TypespecTest.SpecModuleAttributes, _}}
|
||||
] = SpecModuleAttributes.callback()
|
||||
|
||||
assert [
|
||||
{:macrocallback, {:::, _, [{:macrocallback2, _, _}, {:boolean, _, _}]},
|
||||
{TypespecTest.SpecModuleAttributes, _}},
|
||||
{:macrocallback, {:::, _, [{:macrocallback1, _, _}, {:integer, _, _}]},
|
||||
{TypespecTest.SpecModuleAttributes, _}}
|
||||
] = SpecModuleAttributes.macrocallback()
|
||||
after
|
||||
:code.delete(SpecModuleAttributes)
|
||||
:code.purge(SpecModuleAttributes)
|
||||
end
|
||||
|
||||
test "@callback(callback)" do
|
||||
bytecode =
|
||||
test_module do
|
||||
|
||||
+19
-3
@@ -124,11 +124,27 @@ defmodule Mix.Dep do
|
||||
defp load_and_cache(config, _top, bottom, _env) do
|
||||
{_, deps} = Mix.ProjectStack.read_cache({:cached_deps, bottom})
|
||||
app = Keyword.fetch!(config, :app)
|
||||
top_level = for dep <- deps, dep.app == app, child <- dep.deps, do: child.app
|
||||
|
||||
seen = populate_seen(MapSet.new(), [app])
|
||||
children = get_deps(deps, tl(Enum.uniq(get_children(deps, seen, [app]))))
|
||||
Enum.map(children, &%{&1 | top_level: &1.app in top_level})
|
||||
|
||||
top_level =
|
||||
for dep <- deps,
|
||||
dep.app == app,
|
||||
child <- dep.deps,
|
||||
do: {child.app, Keyword.get(child.opts, :optional, false)},
|
||||
into: %{}
|
||||
|
||||
Enum.map(children, fn %{app: app, opts: opts} = dep ->
|
||||
# optional only matters at the top level. Any non-top level dependency
|
||||
# that is optional and is still available means it has been fulfilled.
|
||||
case top_level do
|
||||
%{^app => optional} ->
|
||||
%{dep | top_level: true, opts: Keyword.put(opts, :optional, optional)}
|
||||
|
||||
%{} ->
|
||||
%{dep | top_level: false, opts: Keyword.delete(opts, :optional)}
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
defp read_cached_deps(project, env) do
|
||||
|
||||
@@ -99,19 +99,25 @@ defmodule Mix.ProjectStack do
|
||||
|
||||
@spec config_mtime() :: [integer]
|
||||
def config_mtime() do
|
||||
get_and_update(fn state ->
|
||||
get_and_update_in(state.stack, fn
|
||||
[%{config_mtime: nil, config_files: files} = h | t] ->
|
||||
mtime = files |> Enum.map(&Mix.Utils.last_modified/1) |> Enum.max()
|
||||
{mtime, [%{h | config_mtime: mtime} | t]}
|
||||
|
||||
[%{config_mtime: mtime} | _] = stack ->
|
||||
{mtime, stack}
|
||||
|
||||
[] ->
|
||||
{0, []}
|
||||
mtime_or_files =
|
||||
get(fn
|
||||
%{stack: [%{config_mtime: nil, config_files: files} | _]} -> files
|
||||
%{stack: [%{config_mtime: mtime} | _]} -> mtime
|
||||
%{stack: []} -> 0
|
||||
end)
|
||||
end)
|
||||
|
||||
if is_list(mtime_or_files) do
|
||||
mtime = mtime_or_files |> Enum.map(&Mix.Utils.last_modified/1) |> Enum.max()
|
||||
|
||||
cast(fn state ->
|
||||
update_in(state.stack, fn [h | t] -> [%{h | config_mtime: mtime} | t] end)
|
||||
end)
|
||||
|
||||
mtime
|
||||
else
|
||||
mtime = mtime_or_files
|
||||
mtime
|
||||
end
|
||||
end
|
||||
|
||||
@spec config_apps() :: [atom]
|
||||
|
||||
@@ -113,8 +113,15 @@ defmodule Mix.Tasks.Archive.Install do
|
||||
end
|
||||
|
||||
def build(_install_spec, _opts) do
|
||||
src = Mix.Local.name_for(:archive, Mix.Project.config())
|
||||
previous = find_previous_versions(src)
|
||||
|
||||
Enum.each(previous, fn path ->
|
||||
Code.delete_path(Mix.Local.archive_ebin(path))
|
||||
end)
|
||||
|
||||
Mix.Task.run("archive.build", [])
|
||||
Mix.Local.name_for(:archive, Mix.Project.config())
|
||||
src
|
||||
end
|
||||
|
||||
### Private helpers
|
||||
|
||||
@@ -208,7 +208,7 @@ defmodule Mix.Tasks.Compile.App do
|
||||
apps =
|
||||
properties
|
||||
|> Keyword.get(:applications)
|
||||
|> Kernel.||(apps_from_prod_non_optional_deps(properties))
|
||||
|> Kernel.||(apps_from_prod_non_optional_deps(properties, config))
|
||||
|> normalize_apps(extra, config)
|
||||
|
||||
Keyword.put(properties, :applications, apps)
|
||||
@@ -313,17 +313,32 @@ defmodule Mix.Tasks.Compile.App do
|
||||
end)
|
||||
end
|
||||
|
||||
defp apps_from_prod_non_optional_deps(properties) do
|
||||
defp apps_from_prod_non_optional_deps(properties, config) do
|
||||
included_applications = Keyword.get(properties, :included_applications, [])
|
||||
non_runtime_deps = non_runtime_deps(config)
|
||||
|
||||
for %{app: app, opts: opts, top_level: true} <- Mix.Dep.cached(),
|
||||
Keyword.get(opts, :app, true),
|
||||
Keyword.get(opts, :runtime, true),
|
||||
not Keyword.get(opts, :optional, false),
|
||||
not Map.has_key?(non_runtime_deps, app),
|
||||
app not in included_applications,
|
||||
do: app
|
||||
end
|
||||
|
||||
defp non_runtime_deps(config) do
|
||||
for config_dep <- Keyword.get(config, :deps, []),
|
||||
not runtime_dep?(config_dep),
|
||||
do: {elem(config_dep, 0), true},
|
||||
into: %{}
|
||||
end
|
||||
|
||||
defp runtime_dep?({_app, opts}) when is_list(opts), do: runtime_opts?(opts)
|
||||
defp runtime_dep?({_app, _req, opts}) when is_list(opts), do: runtime_opts?(opts)
|
||||
defp runtime_dep?(_), do: true
|
||||
|
||||
defp runtime_opts?(opts) do
|
||||
Keyword.get(opts, :runtime, true) and Keyword.get(opts, :app, true)
|
||||
end
|
||||
|
||||
defp normalize_apps(apps, extra, config) do
|
||||
Enum.uniq([:kernel, :stdlib] ++ language_app(config) ++ extra ++ apps)
|
||||
end
|
||||
|
||||
@@ -145,10 +145,10 @@ defmodule Mix.Tasks.Compile.Protocols do
|
||||
|
||||
case Protocol.consolidate(protocol, impls) do
|
||||
{:ok, binary} ->
|
||||
File.write!(Path.join(output, "#{protocol}.beam"), binary)
|
||||
File.write!(Path.join(output, "#{Atom.to_string(protocol)}.beam"), binary)
|
||||
|
||||
if opts[:verbose] do
|
||||
Mix.shell().info("Consolidated #{inspect(protocol)}")
|
||||
Mix.shell().info("Consolidated #{inspect_protocol(protocol)}")
|
||||
end
|
||||
|
||||
# If we remove a dependency and we have implemented one of its
|
||||
@@ -161,11 +161,17 @@ defmodule Mix.Tasks.Compile.Protocols do
|
||||
remove_consolidated(protocol, output)
|
||||
|
||||
if opts[:verbose] do
|
||||
Mix.shell().info("Unavailable #{inspect(protocol)}")
|
||||
Mix.shell().info("Unavailable #{inspect_protocol(protocol)}")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# We cannot use the inspect protocol while consolidating
|
||||
# since inspect may not be available.
|
||||
defp inspect_protocol(protocol) do
|
||||
Code.Identifier.inspect_as_atom(protocol)
|
||||
end
|
||||
|
||||
defp reload(module) do
|
||||
:code.purge(module)
|
||||
:code.delete(module)
|
||||
@@ -227,6 +233,6 @@ defmodule Mix.Tasks.Compile.Protocols do
|
||||
end
|
||||
|
||||
defp remove_consolidated(protocol, output) do
|
||||
File.rm(Path.join(output, "#{protocol}.beam"))
|
||||
File.rm(Path.join(output, "#{Atom.to_string(protocol)}.beam"))
|
||||
end
|
||||
end
|
||||
|
||||
@@ -93,8 +93,12 @@ defmodule Mix.Tasks.Deps do
|
||||
and override it by setting the `:override` option in a top-level project.
|
||||
|
||||
* `:runtime` - whether the dependency is part of runtime applications.
|
||||
Defaults to `true` which automatically adds the application to the list
|
||||
of apps that are started automatically and included in releases
|
||||
If the `:applications` key is not provided in `def application` in your
|
||||
mix.exs file, Mix will automatically included all dependencies as a runtime
|
||||
application, except if `runtime: false` is given. Defaults to true.
|
||||
|
||||
* `:system_env` - an enumerable of key-value tuples of binaries to be set
|
||||
as environment variables when loading or compiling the dependency
|
||||
|
||||
### Git options (`:git`)
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@ defmodule Mix.Tasks.Deps.Loadpaths do
|
||||
|
||||
* `--no-deps-check` - does not check or compile deps, only load available ones
|
||||
* `--no-compile` - does not compile dependencies
|
||||
* `--no-load-deps` - do not load deps from the code path
|
||||
|
||||
"""
|
||||
|
||||
@@ -26,14 +27,16 @@ defmodule Mix.Tasks.Deps.Loadpaths do
|
||||
deps_check(all, "--no-compile" in args)
|
||||
end
|
||||
|
||||
load_paths =
|
||||
for dep <- all,
|
||||
path <- Mix.Dep.load_paths(dep) do
|
||||
_ = Code.prepend_path(path)
|
||||
path
|
||||
end
|
||||
unless "--no-load-deps" in args do
|
||||
load_paths =
|
||||
for dep <- all,
|
||||
path <- Mix.Dep.load_paths(dep) do
|
||||
_ = Code.prepend_path(path)
|
||||
path
|
||||
end
|
||||
|
||||
prune_deps(load_paths, "--no-deps-check" in args)
|
||||
prune_deps(load_paths, "--no-deps-check" in args)
|
||||
end
|
||||
end
|
||||
|
||||
# If the build is per environment, we should be able to look
|
||||
|
||||
@@ -382,7 +382,7 @@ defmodule Mix.Tasks.Format do
|
||||
end
|
||||
|
||||
defp stdin_or_wildcard("-"), do: [:stdin]
|
||||
defp stdin_or_wildcard(path), do: Path.wildcard(path, match_dot: true)
|
||||
defp stdin_or_wildcard(path), do: path |> Path.expand() |> Path.wildcard(match_dot: true)
|
||||
|
||||
defp read_file(:stdin) do
|
||||
{IO.stream(:stdio, :line) |> Enum.to_list() |> IO.iodata_to_binary(), file: "stdin"}
|
||||
@@ -447,7 +447,7 @@ defmodule Mix.Tasks.Format do
|
||||
end
|
||||
|
||||
defp check!({[{:exit, file, exception, stacktrace} | _], _not_equivalent, _not_formatted}) do
|
||||
Mix.shell().error("mix format failed for file: #{file}")
|
||||
Mix.shell().error("mix format failed for file: #{Path.relative_to_cwd(file)}")
|
||||
reraise exception, stacktrace
|
||||
end
|
||||
|
||||
|
||||
@@ -75,7 +75,6 @@ defmodule Mix.Tasks.Help do
|
||||
aliases = Enum.filter(load_aliases(), fn {name, _} -> String.contains?(name, pattern) end)
|
||||
|
||||
{docs, max} = build_doc_list(modules, aliases)
|
||||
|
||||
display_doc_list(docs, max)
|
||||
end
|
||||
|
||||
@@ -86,25 +85,39 @@ defmodule Mix.Tasks.Help do
|
||||
def run([task]) do
|
||||
loadpaths!()
|
||||
|
||||
{doc, location} = verbose_doc(task)
|
||||
opts = Application.get_env(:mix, :colors)
|
||||
|
||||
if ansi_docs?(opts) do
|
||||
opts = [width: width()] ++ opts
|
||||
IO.ANSI.Docs.print_heading("mix #{task}", opts)
|
||||
IO.ANSI.Docs.print(doc, opts)
|
||||
else
|
||||
IO.puts("# mix #{task}\n")
|
||||
IO.puts(doc)
|
||||
end
|
||||
opts =
|
||||
if ansi_docs?(opts) do
|
||||
[width: width()] ++ opts
|
||||
else
|
||||
opts
|
||||
end
|
||||
|
||||
IO.puts("Location: #{location}")
|
||||
for doc <- verbose_doc(task) do
|
||||
print_doc(task, doc, opts)
|
||||
end
|
||||
end
|
||||
|
||||
def run(_) do
|
||||
Mix.raise("Unexpected arguments, expected \"mix help\" or \"mix help TASK\"")
|
||||
end
|
||||
|
||||
defp print_doc(task, {doc, location, note}, opts) do
|
||||
if ansi_docs?(opts) do
|
||||
opts = [width: width()] ++ opts
|
||||
IO.ANSI.Docs.print_heading("mix #{task}", opts)
|
||||
IO.ANSI.Docs.print(doc, opts)
|
||||
IO.puts("Location: #{location}")
|
||||
note && IO.puts("") && IO.ANSI.Docs.print(note, opts)
|
||||
else
|
||||
IO.puts("# mix #{task}\n")
|
||||
IO.puts(doc)
|
||||
IO.puts("\nLocation: #{location}")
|
||||
note && IO.puts([?\n, note, ?\n, ?\n])
|
||||
end
|
||||
end
|
||||
|
||||
# Loadpaths without checks because tasks may be defined in deps.
|
||||
defp loadpaths! do
|
||||
args = ["--no-elixir-version-check", "--no-deps-check", "--no-archives-check"]
|
||||
@@ -194,14 +207,29 @@ defmodule Mix.Tasks.Help do
|
||||
defp verbose_doc(task) do
|
||||
aliases = load_aliases()
|
||||
|
||||
if Map.has_key?(aliases, task) do
|
||||
task_name = aliases[task]
|
||||
doc = "Alias for " <> inspect(task_name)
|
||||
{doc, "mix.exs"}
|
||||
else
|
||||
module = Mix.Task.get!(task)
|
||||
doc = Mix.Task.moduledoc(module) || "There is no documentation for this task"
|
||||
{doc, where_is_file(module)}
|
||||
has_alias? = Map.has_key?(aliases, task)
|
||||
has_task? = Mix.Task.get(task)
|
||||
|
||||
cond do
|
||||
has_alias? and has_task? ->
|
||||
note = "There is also a task named \"#{task}\". The documentation is shown next."
|
||||
[alias_doc(aliases[task], note), task_doc(task)]
|
||||
|
||||
has_alias? ->
|
||||
[alias_doc(aliases[task], nil)]
|
||||
|
||||
true ->
|
||||
[task_doc(task)]
|
||||
end
|
||||
end
|
||||
|
||||
defp alias_doc(task_name, note) do
|
||||
{"Alias for " <> inspect(task_name), "mix.exs", note}
|
||||
end
|
||||
|
||||
defp task_doc(task) do
|
||||
module = Mix.Task.get!(task)
|
||||
doc = Mix.Task.moduledoc(module) || "There is no documentation for this task"
|
||||
{doc, where_is_file(module), nil}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -6,6 +6,7 @@ defmodule Mix.Tasks.Test do
|
||||
|
||||
def start(compile_path, opts) do
|
||||
Mix.shell().info("Cover compiling modules ...")
|
||||
_ = :cover.stop()
|
||||
_ = :cover.start()
|
||||
|
||||
case :cover.compile_beam_directory(compile_path |> to_charlist) do
|
||||
@@ -407,7 +408,7 @@ defmodule Mix.Tasks.Test do
|
||||
Mix.shell().info("No stale tests")
|
||||
|
||||
files == [] ->
|
||||
raise_or_error_at_exit("There are no tests to run", opts)
|
||||
Mix.shell().info("There are no tests to run")
|
||||
|
||||
true ->
|
||||
message = "Paths given to `mix test` did not match any directory/file: "
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
defmodule Bar do
|
||||
def hello do
|
||||
:world
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,13 @@
|
||||
defmodule Bar.MixProject do
|
||||
use Mix.Project
|
||||
|
||||
def project do
|
||||
[
|
||||
app: :bar,
|
||||
version: "0.1.0",
|
||||
# Choose something besides *_test.exs so that these test files don't
|
||||
# get accidentally swept up into the actual Mix test suite.
|
||||
test_pattern: "*_tests.exs"
|
||||
]
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,7 @@
|
||||
defmodule BarTest do
|
||||
use ExUnit.Case
|
||||
|
||||
test "greets the world" do
|
||||
assert Foo.hello() == :world
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1 @@
|
||||
ExUnit.start()
|
||||
@@ -0,0 +1,5 @@
|
||||
defmodule Foo do
|
||||
def hello do
|
||||
:world
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,13 @@
|
||||
defmodule Foo.MixProject do
|
||||
use Mix.Project
|
||||
|
||||
def project do
|
||||
[
|
||||
app: :foo,
|
||||
version: "0.1.0",
|
||||
# Choose something besides *_test.exs so that these test files don't
|
||||
# get accidentally swept up into the actual Mix test suite.
|
||||
test_pattern: "*_tests.exs"
|
||||
]
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,7 @@
|
||||
defmodule FooTest do
|
||||
use ExUnit.Case
|
||||
|
||||
test "greets the world" do
|
||||
assert Foo.hello() == :world
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1 @@
|
||||
ExUnit.start()
|
||||
@@ -0,0 +1,9 @@
|
||||
defmodule UmbrellaCover.MixProject do
|
||||
use Mix.Project
|
||||
|
||||
def project do
|
||||
[
|
||||
apps_path: "apps"
|
||||
]
|
||||
end
|
||||
end
|
||||
@@ -509,6 +509,51 @@ defmodule Mix.DepTest do
|
||||
end)
|
||||
end
|
||||
|
||||
test "nested deps with optional matching" do
|
||||
Process.put(:custom_deps_git_repo_opts, optional: true)
|
||||
|
||||
# deps_repo brings git_repo but it is optional
|
||||
deps = [
|
||||
{:deps_repo, "0.1.0", path: "custom/deps_repo"},
|
||||
{:git_repo, "0.1.0", git: MixTest.Case.fixture_path("git_repo")}
|
||||
]
|
||||
|
||||
with_deps(deps, fn ->
|
||||
in_fixture("deps_status", fn ->
|
||||
File.mkdir_p!("custom/deps_repo/lib")
|
||||
|
||||
File.write!("custom/deps_repo/lib/a.ex", """
|
||||
# Check that the child dependency is top_level and optional
|
||||
[%Mix.Dep{app: :git_repo, top_level: true, opts: opts}] = Mix.Dep.cached()
|
||||
true = Keyword.fetch!(opts, :optional)
|
||||
""")
|
||||
|
||||
Mix.Tasks.Deps.Get.run([])
|
||||
Mix.Tasks.Deps.Compile.run([])
|
||||
end)
|
||||
end)
|
||||
end
|
||||
|
||||
test "nested deps considers runtime from current app" do
|
||||
Process.put(:custom_deps_git_repo_opts, runtime: false)
|
||||
|
||||
deps = [
|
||||
{:deps_repo, "0.1.0", path: "custom/deps_repo"},
|
||||
{:git_repo, "0.1.0", git: MixTest.Case.fixture_path("git_repo")}
|
||||
]
|
||||
|
||||
with_deps(deps, fn ->
|
||||
in_fixture("deps_status", fn ->
|
||||
Mix.Tasks.Deps.Compile.run([])
|
||||
|
||||
{:ok, [{:application, :deps_repo, opts}]} =
|
||||
:file.consult("_build/dev/lib/deps_repo/ebin/deps_repo.app")
|
||||
|
||||
assert :git_repo not in Keyword.get(opts, :applications)
|
||||
end)
|
||||
end)
|
||||
end
|
||||
|
||||
test "nested deps with overrides" do
|
||||
# deps_repo brings git_repo but it is overriden
|
||||
deps = [
|
||||
|
||||
@@ -183,6 +183,10 @@ defmodule Mix.Tasks.ArchiveTest do
|
||||
send(self(), {:mix_shell_input, :yes?, true})
|
||||
Mix.Tasks.Archive.Uninstall.run(["archive-0.2.0"])
|
||||
refute File.dir?(tmp_path("userhome/.mix/archives/archive-0.2.0/archive-0.2.0/ebin"))
|
||||
|
||||
# Check old paths are unloaded
|
||||
paths = Enum.map(:code.get_path(), &List.to_string/1)
|
||||
refute tmp_path("userhome/.mix/archives/archive-0.1.0/archive-0.1.0/ebin") in paths
|
||||
end)
|
||||
end
|
||||
|
||||
|
||||
@@ -242,6 +242,36 @@ defmodule Mix.Tasks.DepsTest do
|
||||
end)
|
||||
end
|
||||
|
||||
test "does not load or prune builds with --no-load-deps" do
|
||||
Mix.Project.push(SuccessfulDepsApp)
|
||||
|
||||
in_fixture("deps_status", fn ->
|
||||
# Start from scratch!
|
||||
File.rm_rf("_build")
|
||||
|
||||
Mix.Tasks.Deps.Compile.run([])
|
||||
Mix.Tasks.Deps.Loadpaths.run([])
|
||||
assert File.exists?("_build/dev/lib/ok/ebin/ok.app")
|
||||
assert File.exists?("_build/dev/lib/ok/priv/sample")
|
||||
|
||||
Mix.Tasks.Compile.run([])
|
||||
assert to_charlist(Path.expand("_build/dev/lib/ok/ebin/")) in :code.get_path()
|
||||
assert File.exists?("_build/dev/lib/sample/ebin/sample.app")
|
||||
|
||||
# Remove the deps without build_path
|
||||
Mix.ProjectStack.post_config(deps: [])
|
||||
Mix.ProjectStack.clear_cache()
|
||||
Mix.Project.pop()
|
||||
Mix.Project.push(SuccessfulDepsApp)
|
||||
Code.delete_path("_build/dev/lib/ok/ebin")
|
||||
|
||||
Mix.Tasks.Deps.Loadpaths.run(["--no-load-deps"])
|
||||
refute to_charlist(Path.expand("_build/dev/lib/ok/ebin/")) in :code.get_path()
|
||||
assert File.exists?("_build/dev/lib/ok/ebin/ok.app")
|
||||
assert File.exists?("_build/dev/lib/sample/ebin/sample.app")
|
||||
end)
|
||||
end
|
||||
|
||||
## deps.unlock
|
||||
|
||||
test "unlocks all deps", context do
|
||||
|
||||
@@ -136,6 +136,33 @@ defmodule Mix.Tasks.HelpTest do
|
||||
end)
|
||||
end
|
||||
|
||||
defmodule ShadowedAliases do
|
||||
def project do
|
||||
[aliases: [compile: "compile"]]
|
||||
end
|
||||
end
|
||||
|
||||
test "help TASK && ALIAS", context do
|
||||
Mix.Project.push(ShadowedAliases)
|
||||
|
||||
in_tmp(context.test, fn ->
|
||||
output =
|
||||
capture_io(fn ->
|
||||
Mix.Tasks.Help.run(["compile"])
|
||||
end)
|
||||
|
||||
assert output =~ "# mix compile\n\n"
|
||||
assert output =~ "Alias for \"compile\"\n"
|
||||
assert output =~ ~r/^Location: mix.exs/m
|
||||
|
||||
assert output =~
|
||||
"\nThere is also a task named \"compile\". The documentation is shown next.\n"
|
||||
|
||||
assert output =~ "## Command line options"
|
||||
assert output =~ ~r/^Location:/m
|
||||
end)
|
||||
end
|
||||
|
||||
test "help --search PATTERN", context do
|
||||
in_tmp(context.test, fn ->
|
||||
Mix.Tasks.Help.run(["--search", "deps"])
|
||||
|
||||
@@ -61,6 +61,12 @@ defmodule Mix.Tasks.LoadconfigTest do
|
||||
Mix.Task.run("loadconfig", [config])
|
||||
assert config in Mix.Project.config_files()
|
||||
assert Mix.Project.config_mtime() > mtime
|
||||
|
||||
# Touching it should not have any deadlocks
|
||||
File.touch!(config, {{2030, 1, 1}, {0, 0, 0}})
|
||||
Mix.Task.run("loadconfig", [config])
|
||||
assert config in Mix.Project.config_files()
|
||||
assert Mix.Project.config_mtime() > mtime
|
||||
end)
|
||||
end
|
||||
|
||||
|
||||
@@ -123,6 +123,27 @@ defmodule Mix.Tasks.TestTest do
|
||||
end)
|
||||
end
|
||||
|
||||
test "--cover: in an umbrella application, reports on the coverage of each app's modules exactly once" do
|
||||
in_fixture("umbrella_cover", fn ->
|
||||
output = mix(["test", "--cover"])
|
||||
|
||||
occurrences_of_bar_coverage =
|
||||
output
|
||||
|> String.split("100.00% | Bar")
|
||||
|> length()
|
||||
|> (&(&1 - 1)).()
|
||||
|
||||
occurrences_of_foo_coverage =
|
||||
output
|
||||
|> String.split("100.00% | Foo")
|
||||
|> length()
|
||||
|> (&(&1 - 1)).()
|
||||
|
||||
assert occurrences_of_bar_coverage == 1
|
||||
assert occurrences_of_foo_coverage == 1
|
||||
end)
|
||||
end
|
||||
|
||||
test "--failed: loads only files with failures and runs just the failures" do
|
||||
in_fixture("test_failed", fn ->
|
||||
loading_only_passing_test_msg = "loading OnlyPassingTest"
|
||||
|
||||
Reference in New Issue
Block a user