Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
eb5679bfcc | ||
|
|
e38ae98c3a | ||
|
|
71a537b950 | ||
|
|
7087149bfa | ||
|
|
b4b1947a22 | ||
|
|
a33d9116ab | ||
|
|
7d744ebc84 | ||
|
|
3e6c6986f2 | ||
|
|
f167716f53 | ||
|
|
0cdf6d28b1 | ||
|
|
e4c1e3d262 | ||
|
|
3f6313906d | ||
|
|
0089a40a33 | ||
|
|
549745fa5e | ||
|
|
ac41358b5a | ||
|
|
0db2ad6953 |
@@ -116,6 +116,32 @@ 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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -1864,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
|
||||
|
||||
|
||||
@@ -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)).
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 "
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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