Compare commits

...
24 Commits
Author SHA1 Message Date
Schnittchen a1fb6c0d0e Use System.os_time as reference in Mix.Utils.last_modified_and_size/1 (#8502) 2018-12-11 12:51:54 +01:00
Ivan Mironov 4f4aad2cd4 Fix direct Logger.log call in case of compile_time_purge_matching logger option (#8391) 2018-11-20 17:43:27 +01:00
Alan Walton d0f5236c8e Fix misleading doc string (#8355)
naive_datetime_to_iso_days takes a datetime without time zone
2018-11-02 16:57:05 +01:00
Alan Walton 2b95813d98 Fix copy/paste error in doc string (#8356)
Doc string for split/3 seems to have been copied from contains?/1, with references left behind to true and false return values.
2018-11-02 14:16:21 +01:00
José Valim eb5679bfcc Release v1.7.4 2018-10-24 20:11:49 +02:00
Fernando Tapia Rico e38ae98c3a Allow typespec attrs to be used as module attrs (#8327)
Port of: https://github.com/elixir-lang/elixir/commit/c190f09d5cbb1e79b510ea4332c65a7717aa9eb8
2018-10-24 13:05:53 +02:00
Eric Meadows-Jönsson 71a537b950 Add --no-load-deps flag to deps.loadpaths task (#8256) 2018-10-09 22:44:58 +02:00
José Valim 7087149bfa Run the formatter 2018-10-09 20:56:25 +02:00
José Valim b4b1947a22 Ensure eval_quoted properly resets last definition, closes #8261
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2018-10-09 20:49:41 +02:00
Po Chen a33d9116ab Expand paths so mix format path\for\windows.ex works (#8147) 2018-10-09 13:32:43 +02:00
Gabe Jackson 7d744ebc84 Ensure that mix --cover displays correct coverage in an umbrella app (#8176)
`:cover.start/0` initializes the internal database where coverage data
on all compiled modules is stored. Prior to this change, the database
wasn't cleared out between sub-apps in an umbrella app. When
`:cover.analyse/2` is called, it runs against the entire contents of the
internal database. In an umbrella app, the coverage report for App1
would show App1, the report for App2 would show App1 and App2, App3
would have App1, App2, and App3, and so on.

Closes #8086
2018-10-09 13:32:23 +02:00
Paul Wilson 3e6c6986f2 Fix for converting from negative iso days on New Year in a leap year (#8145) 2018-10-09 13:31:49 +02:00
Eric Meadows-Jönsson f167716f53 Unload previous archive versions before building (#8253)
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2018-10-07 17:03:58 +02:00
José Valim 0cdf6d28b1 Consider that Macro may have not been bootstrapped 2018-09-27 14:50:59 +02:00
José Valim e4c1e3d262 Format kernel accordingly 2018-09-27 14:18:51 +02:00
José Valim 3f6313906d Avoid range creation in Calendar.ISO.valid_date?/3 2018-09-27 13:08:52 +02:00
Saša Jurić 0089a40a33 Fix typespec (#8207) 2018-09-20 11:58:46 +02:00
Tobiasz Małecki 549745fa5e Inspect.Algebra typespecs improvement (#8047)
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2018-09-20 11:07:15 +02:00
Mike Schutte ac41358b5a Fix small curly typo in tuple docs (#8162) 2018-09-03 16:54:56 +02:00
As-har Dweedar 0db2ad6953 Fix warning for unused variable (#8158) 2018-09-01 20:42:48 +02:00
José Valim 8ca85ed2a8 Release v1.7.3 2018-08-24 20:46:27 +02:00
José Valim af64fe7e98 Consider :runtime and :app from mix.exs (#8109)
Since those values are not stored on Hex, we should not
expect them to be available on Mix.Dep.cached. Instead
we traverse the deps in `mix.exs` to lift this information.

Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2018-08-24 20:13:26 +02:00
Fernando Tapia Rico 28cf40fe03 Fix arity of try/1 in :elixir_import.special_form (#8079)
Fixes #8078

Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2018-08-14 12:57:12 +02:00
José Valim d38a189907 Do not use protocols while consolidating
This avoids a race condition in tests.

Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2018-08-06 07:47:22 +02:00
44 changed files with 569 additions and 145 deletions
+38
View File
@@ -116,6 +116,44 @@ 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
+1 -1
View File
@@ -1 +1 @@
1.7.2
1.7.4
+1 -1
View File
@@ -170,7 +170,7 @@ defmodule Calendar do
@callback time_to_string(hour, minute, second, microsecond) :: String.t()
@doc """
Converts the given datetime (with time zone) into the `t:iso_days/0` format.
Converts the given datetime (without time zone) into the `t:iso_days/0` format.
"""
@callback naive_datetime_to_iso_days(year, month, day, hour, minute, second, microsecond) ::
iso_days
+32 -16
View File
@@ -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)
+3 -3
View File
@@ -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
View File
@@ -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 """
+38 -25
View File
@@ -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
+6
View File
@@ -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}
+4 -4
View File
@@ -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
+2 -2
View File
@@ -389,13 +389,13 @@ defmodule String do
Note this function can split within or across grapheme boundaries.
For example, take the grapheme "é" which is made of the characters
"e" and the acute accent. The following returns `true`:
"e" and the acute accent. The following returns the acute accent separately:
iex> String.split(String.normalize("é", :nfd), "e")
["", "́"]
However, if "é" is represented by the single character "e with acute"
accent, then it will return `false`:
accent, then it will return "e with acute":
iex> String.split(String.normalize("é", :nfc), "e")
["é"]
+1 -1
View File
@@ -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
+3 -1
View File
@@ -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
+1 -1
View File
@@ -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)).
+1 -1
View File
@@ -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.
+7 -2
View File
@@ -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 "
+11 -3
View File
@@ -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__))
+17 -1
View File
@@ -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
+130
View File
@@ -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
+1 -1
View File
@@ -789,7 +789,7 @@ defmodule Logger do
metadata = Keyword.merge(caller, metadata)
{metadata, metadata}
else
{metadata,
{[],
quote do
Keyword.merge(unquote(caller), unquote(metadata))
end}
+15 -5
View File
@@ -252,11 +252,13 @@ defmodule LoggerTest do
test "remove unused calls at compile time based on matching metadata" do
Logger.configure(
compile_time_application: :sample_app,
compile_time_purge_matching: [
[module: LoggerTest.PurgeMatching, function: "two_filters/0"],
[function: "one_filter/0"],
[custom: true],
[function: "level_filter/0", level_lower_than: :info]
[function: "level_filter/0", level_lower_than: :warn],
[application: :sample_app, level_lower_than: :info]
]
)
@@ -274,12 +276,16 @@ defmodule LoggerTest do
end
def level_filter do
Logger.debug("debug_filter")
Logger.info("info_filter")
Logger.warn("warn_filter")
end
def works do
Logger.debug("works")
Logger.info("works")
end
def log(level, metadata \\ []) do
Logger.log(level, "ok", metadata)
end
end
@@ -287,9 +293,13 @@ defmodule LoggerTest do
assert capture_log(fn -> assert PurgeMatching.one_filter() == :ok end) == ""
assert capture_log(fn -> assert PurgeMatching.two_filters() == :ok end) == ""
assert capture_log(fn -> assert PurgeMatching.custom_filters() == :ok end) == ""
assert capture_log(fn -> assert PurgeMatching.level_filter() == :ok end) =~ "info_filter"
refute capture_log(fn -> assert PurgeMatching.level_filter() == :ok end) =~ "debug_filter"
assert capture_log(fn -> assert PurgeMatching.level_filter() == :ok end) =~ "warn_filter"
refute capture_log(fn -> assert PurgeMatching.level_filter() == :ok end) =~ "info_filter"
capture_log(fn -> assert PurgeMatching.log(:info) == :ok end)
capture_log(fn -> assert PurgeMatching.log(:debug) == :ok end)
after
Logger.configure(compile_time_application: nil)
Logger.configure(compile_time_purge_matching: [])
end
+7 -15
View File
@@ -117,11 +117,6 @@ defmodule Mix.Dep do
end
end
# optional and runtime only matter at the top level.
# Any non-top level dependency that is optional and
# is still available means it has been fulfilled.
@child_keep_opts [:optional, :runtime]
defp load_and_cache(_config, top, top, env) do
converge(env: env)
end
@@ -136,21 +131,18 @@ defmodule Mix.Dep do
for dep <- deps,
dep.app == app,
child <- dep.deps,
do: {child.app, Keyword.take(child.opts, @child_keep_opts)},
do: {child.app, Keyword.get(child.opts, :optional, false)},
into: %{}
Enum.map(children, fn %{app: app} = dep ->
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 => child_opts} ->
opts =
dep.opts
|> Keyword.drop(@child_keep_opts)
|> Keyword.merge(child_opts)
%{dep | top_level: true, opts: opts}
%{^app => optional} ->
%{dep | top_level: true, opts: Keyword.put(opts, :optional, optional)}
%{} ->
%{dep | top_level: false}
%{dep | top_level: false, opts: Keyword.delete(opts, :optional)}
end
end)
end
+8 -1
View File
@@ -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
+19 -4
View File
@@ -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
+10 -4
View File
@@ -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
+6 -2
View File
@@ -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`)
+10 -7
View File
@@ -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
+2 -2
View File
@@ -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
+1
View File
@@ -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
+1 -1
View File
@@ -176,7 +176,7 @@ defmodule Mix.Utils do
(1970-01-01 00:00:00).
"""
def last_modified_and_size(path) do
now = System.system_time(:second)
now = System.os_time(:second)
case :elixir_utils.read_posix_mtime_and_size(path) do
{:ok, mtime, size} when mtime > now ->
@@ -0,0 +1,5 @@
defmodule Bar do
def hello do
:world
end
end
+13
View File
@@ -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
+13
View File
@@ -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()
+9
View File
@@ -0,0 +1,9 @@
defmodule UmbrellaCover.MixProject do
use Mix.Project
def project do
[
apps_path: "apps"
]
end
end
+4 -30
View File
@@ -534,7 +534,7 @@ defmodule Mix.DepTest do
end)
end
test "nested deps with runtime override on parent" do
test "nested deps considers runtime from current app" do
Process.put(:custom_deps_git_repo_opts, runtime: false)
deps = [
@@ -544,38 +544,12 @@ defmodule Mix.DepTest do
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()
false = Keyword.fetch!(opts, :runtime)
""")
Mix.Tasks.Deps.Get.run([])
Mix.Tasks.Deps.Compile.run([])
end)
end)
end
test "nested deps with runtime override on child" do
deps = [
{:deps_repo, "0.1.0", path: "custom/deps_repo"},
{:git_repo, "0.1.0", git: MixTest.Case.fixture_path("git_repo"), runtime: false}
]
{:ok, [{:application, :deps_repo, opts}]} =
:file.consult("_build/dev/lib/deps_repo/ebin/deps_repo.app")
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()
false = Keyword.has_key?(opts, :runtime)
""")
Mix.Tasks.Deps.Get.run([])
Mix.Tasks.Deps.Compile.run([])
assert :git_repo not in Keyword.get(opts, :applications)
end)
end)
end
+4
View File
@@ -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
+30
View File
@@ -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
+21
View File
@@ -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"