Compare commits

...
33 Commits
Author SHA1 Message Date
José Valim 58ca6524f8 Release v1.12.2 2021-07-01 18:15:34 +02:00
José Valim afcefb3e21 Run formatter 2021-06-26 09:33:24 +02:00
José Valim fb5aabf05a Remove warning and improve coverage on Enum.into/2 tests 2021-06-26 09:31:02 +02:00
José Valim d9a81101dd Optimize Enum.into and Map.new 2021-06-26 09:26:45 +02:00
José Valim 8fca53ad31 Ensure deprecated macros emit warnings 2021-06-23 22:15:43 +02:00
José Valim 76dfedb2e3 Compile most recently changed files first 2021-06-22 20:07:38 +02:00
Christopher Keele 8bec593f5d Ensure unconstrained rebar deps generate valid mix specifications (#11063) 2021-06-15 10:25:33 +02:00
TORIFUKUKaiou 03859fb92e Add @doc since: "1.12.0" and comments (#11046) 2021-06-08 09:07:35 +02:00
José Valim 53e49f33f1 Update CHANGELOG 2021-06-08 09:03:06 +02:00
Marc Riera c0bdd6101f Improve docs for sigil_r (#11051) 2021-06-08 08:59:24 +02:00
José Valim e7cb210712 Do not add compile time deps on args to Application.compile_env/2, closes #11052 2021-06-08 08:59:21 +02:00
TORIFUKUKaiou 4a4657e413 Add @doc since: "1.12.0" (#11039) 2021-06-05 22:21:21 +02:00
José Valim fcada17819 Align profiling times for better readability
Before we would write:

    lib/foo.ex compiled in 10ms (plus 100ms waiting)

However, because the file name is variable, it was
hard to see which file took the longest. Now we will
write:

        10ms compiling +    100ms waiting for lib/foo.ex

By right aligning all times, we can easily look in
the first row and see which file took the longest.
We can also provide a summary option in the future,
if necessary.
2021-06-05 22:21:15 +02:00
José Valim 6be8bddc02 Also annotate temp var for remote call in map.foo as generated
Closes #11030.
2021-06-01 15:04:23 +02:00
José Valim 9feda914dc Simplify reading of apps from deps 2021-06-01 11:53:12 +02:00
José Valim 62e1d71377 Avoid central bottleneck when loading apps if we know their source 2021-06-01 10:24:18 +02:00
José Valim d6ed6d18ef Remove duplication across branches 2021-06-01 09:37:47 +02:00
José Valim e024a12c50 Load apps concurrently 2021-06-01 09:36:25 +02:00
José Valim 42807d288e Release v1.12.1 2021-05-28 17:34:14 +02:00
José Valim 3b8d7fa15d Update CHANGELOG 2021-05-28 11:41:32 +02:00
José Valim d1d5e04319 Revert "Run all available tests if there are no pending --failed tests"
This reverts commit ae48325991.
2021-05-27 15:53:28 +02:00
José Valim ba1fd1abb8 Improve error message on invalid config 2021-05-26 19:14:28 +02:00
Wojtek Mach 8fd6c8f845 Ensure Mix.install/2 can be called after errors with deps (#11011) 2021-05-26 19:14:04 +02:00
Wojtek Mach 95dcbe9fe0 Mix.install/2: Expand paths for path deps (#11012) 2021-05-26 19:14:04 +02:00
José Valim 98a79dc839 Fix spec for which_children, closes #11005 2021-05-25 09:46:59 +02:00
José Valim 29444793b7 Fix formatter integration test 2021-05-22 21:24:58 +02:00
José Valim ff3674ddef Format multiline inside interpolation on first run, closes #10996 2021-05-22 20:46:05 +02:00
José Valim 0460017c5c Revert keeping of underscores between digits in camelize, closes #10995 2021-05-21 14:11:13 +02:00
José Valim f3b870857a Split on \r\n and \n accordingly during unicode compilation, closes #10991 2021-05-20 12:12:55 +02:00
José Valim faa2f35d8e Fix bootstrapping issue 2021-05-19 23:23:55 +02:00
José Valim 3339168330 Silence httpc warnings 2021-05-19 23:13:56 +02:00
José Valim 1c3ee4ed72 Remove unecessary copying of priv/include for Rebar 3 deps 2021-05-19 19:30:40 +02:00
José Valim 926455ca24 Add date to CHANGELOG 2021-05-19 14:36:49 +02:00
36 changed files with 408 additions and 162 deletions
+41 -1
View File
@@ -90,7 +90,47 @@ As of Elixir v1.12, implicitly decreasing ranges are soft-deprecated and warning
Elixir v1.12 has the additional of many functions across the standard library. The `Enum` module received additions such as `Enum.count_until/2`, `Enum.product/1`, `Enum.zip_with/2`, and more. The `Integer` module now includes `Integer.pow/2` and `Integer.extended_gcd/2`. Finally, the `Kernel` module got two new functions, `Kernel.then/2` and `Kernel.tap/2`, which are specially useful in `|>` pipelines.
## v1.12.0
## v1.12.2 (2021-07-01)
### 1. Bug fixes
#### Elixir
* [Kernel] Ensure deprecated macros emit warnings
#### Mix
* [mix deps] Ensure unconstrained rebar deps generate valid mix specifications
### 2. Enhancements
#### Elixir
* [elixirc] Change the output of `--profile time` to make it easier to detect outliers
* [Application] Do not add compile time deps on args to `Application.compile_env/2` and `Application.compile_env!/2`
* [Enum] Optimize `Enum.into/3` and `Map.new/2`
#### Mix
* [mix compile] Compile most recently changed files first
* [mix compile, mix run, mix test] Speed up the time taken to load dependencies. This should make the usage of Mix inside projects quite more responsive
## v1.12.1 (2021-05-28)
### 1. Bug fixes
#### Elixir
* [Code] Make sure `Code.format_string!/2` formats multiline expression inside interpolation on the first run
* [Macro] Revert keeping of underscores between digits in camelize
#### Mix
* [Mix] Make sure `Mix.install/2` expand paths for deps
* [mix deps.get] Silence false positives on `httpc` warnings
* [mix test] Do not run the whole suite when there are no --failed tests as it won't behave as expected inside umbrellas
## v1.12.0 (2021-05-19)
### 1. Enhancements
+1 -1
View File
@@ -1 +1 @@
1.12.0
1.12.2
+13
View File
@@ -507,11 +507,22 @@ defmodule Application do
raise "Application.compile_env/3 cannot be called inside functions, only in the module body"
end
key_or_path = expand_key_or_path(key_or_path, __CALLER__)
quote do
Application.__compile_env__(unquote(app), unquote(key_or_path), unquote(default), __ENV__)
end
end
defp expand_key_or_path({:__aliases__, _, _} = alias, env),
do: Macro.expand(alias, %{env | function: {:__info__, 1}})
defp expand_key_or_path(list, env) when is_list(list),
do: Enum.map(list, &expand_key_or_path(&1, env))
defp expand_key_or_path(other, _env),
do: other
@doc false
def __compile_env__(app, key_or_path, default, env) do
case fetch_compile_env(app, key_or_path, env) do
@@ -533,6 +544,8 @@ defmodule Application do
raise "Application.compile_env!/2 cannot be called inside functions, only in the module body"
end
key_or_path = expand_key_or_path(key_or_path, __CALLER__)
quote do
Application.__compile_env__!(unquote(app), unquote(key_or_path), __ENV__)
end
+1
View File
@@ -1550,6 +1550,7 @@ defmodule Code do
@doc """
Same as `ensure_loaded/1` but raises if the module cannot be loaded.
"""
@doc since: "1.12.0"
@spec ensure_loaded!(module) :: module
def ensure_loaded!(module) do
case ensure_loaded(module) do
+29 -25
View File
@@ -257,6 +257,7 @@ defmodule Code.Formatter do
force_do_end_blocks: force_do_end_blocks,
locals_without_parens: locals_without_parens,
operand_nesting: 2,
skip_eol: false,
comments: comments
}
end
@@ -528,7 +529,7 @@ defmodule Code.Formatter do
end
defp quoted_to_algebra({:fn, meta, [_ | _] = clauses}, _context, state) do
anon_fun_to_algebra(clauses, line(meta), closing_line(meta), state, eol?(meta))
anon_fun_to_algebra(clauses, line(meta), closing_line(meta), state, eol?(meta, state))
end
defp quoted_to_algebra({fun, meta, args}, context, state) when is_atom(fun) and is_list(args) do
@@ -777,7 +778,7 @@ defmodule Code.Formatter do
concat(concat(group(left), op_string), group(right))
true ->
eol? = eol?(meta)
eol? = eol?(meta, state)
next_break_fits? =
op in @next_break_fits_operators and next_break_fits?(right_arg, state) and not eol?
@@ -919,7 +920,7 @@ defmodule Code.Formatter do
{docs, comments?, state} =
quoted_to_algebra_with_comments(operands, acc, min_line, max_line, state, fun)
if comments? or eol?(meta) do
if comments? or eol?(meta, state) do
{docs |> Enum.reduce(&line(&2, &1)) |> force_unfit(), state}
else
{docs |> Enum.reduce(&glue(&2, &1)), state}
@@ -1155,7 +1156,7 @@ defmodule Code.Formatter do
end
args = if keyword?, do: left ++ right, else: left ++ [right]
many_eol? = match?([_, _ | _], args) and eol?(meta)
many_eol? = match?([_, _ | _], args) and eol?(meta, state)
no_generators? = no_generators?(args)
to_algebra_fun = &quoted_to_algebra(&1, context, &2)
@@ -1346,8 +1347,7 @@ defmodule Code.Formatter do
defp list_interpolation_to_algebra([entry | entries], escape, state, acc, last) do
{{:., _, [Kernel, :to_string]}, _meta, [quoted]} = entry
{doc, state} = block_to_algebra(quoted, @max_line, @min_line, state)
doc = surround("\#{", doc, "}") |> interpolation_to_string()
{doc, state} = interpolation_to_string(quoted, state)
list_interpolation_to_algebra(entries, escape, state, concat(acc, doc), last)
end
@@ -1363,8 +1363,7 @@ defmodule Code.Formatter do
defp interpolation_to_algebra([entry | entries], escape, state, acc, last) do
{:"::", _, [{{:., _, [Kernel, :to_string]}, _meta, [quoted]}, {:binary, _, _}]} = entry
{doc, state} = block_to_algebra(quoted, @max_line, @min_line, state)
doc = surround("\#{", doc, "}") |> interpolation_to_string()
{doc, state} = interpolation_to_string(quoted, state)
interpolation_to_algebra(entries, escape, state, concat(acc, doc), last)
end
@@ -1372,6 +1371,12 @@ defmodule Code.Formatter do
{concat(acc, last), state}
end
defp interpolation_to_string(quoted, %{skip_eol: skip_eol} = state) do
{doc, state} = block_to_algebra(quoted, @max_line, @min_line, %{state | skip_eol: true})
doc = interpolation_to_string(surround("\#{", doc, "}"))
{doc, %{state | skip_eol: skip_eol}}
end
defp interpolation_to_string(doc) do
[head | tail] =
doc
@@ -1421,7 +1426,7 @@ defmodule Code.Formatter do
defp bitstring_to_algebra(meta, args, state) do
last = length(args) - 1
join = if eol?(meta), do: :line, else: :flex_break
join = if eol?(meta, state), do: :line, else: :flex_break
to_algebra_fun = &bitstring_segment_to_algebra(&1, &2, last)
{args_doc, join, state} =
@@ -1489,7 +1494,7 @@ defmodule Code.Formatter do
## Literals
defp list_to_algebra(meta, args, state) do
join = if eol?(meta), do: :line, else: :break
join = if eol?(meta, state), do: :line, else: :break
fun = &quoted_to_algebra(&1, :parens_arg, &2)
{args_doc, _join, state} =
@@ -1499,7 +1504,7 @@ defmodule Code.Formatter do
end
defp map_to_algebra(meta, name_doc, [{:|, _, [left, right]}], state) do
join = if eol?(meta), do: :line, else: :break
join = if eol?(meta, state), do: :line, else: :break
fun = &quoted_to_algebra(&1, :parens_arg, &2)
{left_doc, state} = fun.(left, state)
@@ -1516,7 +1521,7 @@ defmodule Code.Formatter do
end
defp map_to_algebra(meta, name_doc, args, state) do
join = if eol?(meta), do: :line, else: :break
join = if eol?(meta, state), do: :line, else: :break
fun = &quoted_to_algebra(&1, :parens_arg, &2)
{args_doc, _join, state} =
@@ -1527,7 +1532,7 @@ defmodule Code.Formatter do
end
defp tuple_to_algebra(meta, args, join, state) do
join = if eol?(meta), do: :line, else: join
join = if eol?(meta, state), do: :line, else: join
fun = &quoted_to_algebra(&1, :parens_arg, &2)
{args_doc, join, state} =
@@ -1709,7 +1714,7 @@ defmodule Code.Formatter do
|> glue(body_doc)
|> nest(2)
|> glue("end")
|> maybe_force_clauses(clauses)
|> maybe_force_clauses(clauses, state)
|> group()
{doc, state}
@@ -1743,7 +1748,7 @@ defmodule Code.Formatter do
|> glue(body_doc)
|> nest(2)
|> glue("end")
|> maybe_force_clauses(clauses)
|> maybe_force_clauses(clauses, state)
|> group()
{doc, state}
@@ -1771,7 +1776,7 @@ defmodule Code.Formatter do
"(() -> "
|> concat(nest(body_doc, :cursor))
|> concat(")")
|> maybe_force_clauses(clauses)
|> maybe_force_clauses(clauses, state)
|> group()
{doc, state}
@@ -1792,7 +1797,7 @@ defmodule Code.Formatter do
|> group()
|> concat(break() |> concat(body_doc) |> nest(2))
|> wrap_in_parens()
|> maybe_force_clauses(clauses)
|> maybe_force_clauses(clauses, state)
|> group()
{doc, state}
@@ -1811,8 +1816,8 @@ defmodule Code.Formatter do
## Clauses
defp maybe_force_clauses(doc, clauses) do
if Enum.any?(clauses, fn {:->, meta, _} -> eol?(meta) end) do
defp maybe_force_clauses(doc, clauses, state) do
if Enum.any?(clauses, fn {:->, meta, _} -> eol?(meta, state) end) do
force_unfit(doc)
else
doc
@@ -1835,7 +1840,7 @@ defmodule Code.Formatter do
{doc_acc, state_acc}
end)
{clauses_doc |> maybe_force_clauses([clause | clauses]) |> group(), state}
{clauses_doc |> maybe_force_clauses([clause | clauses], state) |> group(), state}
end
defp clauses_to_algebra(other, min_line, max_line, state) do
@@ -2176,8 +2181,8 @@ defmodule Code.Formatter do
false
end
defp eol_or_comments?(meta, %{comments: comments}) do
eol?(meta) or
defp eol_or_comments?(meta, %{comments: comments} = state) do
eol?(meta, state) or
(
min_line = line(meta)
max_line = closing_line(meta)
@@ -2264,9 +2269,8 @@ defmodule Code.Formatter do
defp keyword_key?(_),
do: false
defp eol?(meta) do
Keyword.get(meta, :newlines, 0) > 0
end
defp eol?(_meta, %{skip_eol: true}), do: false
defp eol?(meta, _state), do: Keyword.get(meta, :newlines, 0) > 0
defp meta?(meta, key) do
is_list(meta[key])
+1 -1
View File
@@ -419,7 +419,7 @@ defmodule DynamicSupervisor do
@doc since: "1.6.0"
@spec which_children(Supervisor.supervisor()) :: [
# module() | :dynamic here because :supervisor.modules() is not exported
{:undefined, pid | :restarting, :worker | :supervisor, module() | :dynamic}
{:undefined, pid | :restarting, :worker | :supervisor, [module()] | :dynamic}
]
def which_children(supervisor) do
call(supervisor, :which_children)
+44 -15
View File
@@ -1421,28 +1421,35 @@ defmodule Enum do
into_protocol(enumerable, collectable)
end
def into(%{} = enumerable, %{} = collectable) do
Map.merge(collectable, enumerable)
end
def into(enumerable, %{} = collectable) when is_list(enumerable) do
Map.merge(collectable, :maps.from_list(enumerable))
end
def into(enumerable, %{} = collectable) do
reduce(enumerable, collectable, fn {key, val}, acc ->
Map.put(acc, key, val)
end)
if map_size(collectable) == 0 do
into_map(enumerable)
else
into_map(enumerable, collectable)
end
end
def into(enumerable, collectable) do
into_protocol(enumerable, collectable)
end
defp into_map(%{} = enumerable), do: enumerable
defp into_map(enumerable) when is_list(enumerable), do: :maps.from_list(enumerable)
defp into_map(enumerable), do: enumerable |> Enum.to_list() |> :maps.from_list()
defp into_map(%{} = enumerable, collectable),
do: Map.merge(collectable, enumerable)
defp into_map(enumerable, collectable) when is_list(enumerable),
do: Map.merge(collectable, :maps.from_list(enumerable))
defp into_map(enumerable, collectable),
do: Enum.reduce(enumerable, collectable, fn {key, val}, acc -> Map.put(acc, key, val) end)
defp into_protocol(enumerable, collectable) do
{initial, fun} = Collectable.into(collectable)
into(enumerable, initial, fun, fn entry, acc ->
into_protocol(enumerable, initial, fun, fn entry, acc ->
fun.(acc, {:cont, entry})
end)
end
@@ -1461,20 +1468,42 @@ defmodule Enum do
"""
@spec into(Enumerable.t(), Collectable.t(), (term -> term)) :: Collectable.t()
def into(enumerable, collectable, transform) when is_list(collectable) do
collectable ++ map(enumerable, transform)
end
def into(%_{} = enumerable, collectable, transform) do
into_protocol(enumerable, collectable, transform)
end
def into(enumerable, %_{} = collectable, transform) do
into_protocol(enumerable, collectable, transform)
end
def into(enumerable, %{} = collectable, transform) do
if map_size(collectable) == 0 do
enumerable |> Enum.map(transform) |> :maps.from_list()
else
Enum.reduce(enumerable, collectable, fn entry, acc ->
{key, val} = transform.(entry)
Map.put(acc, key, val)
end)
end
end
def into(enumerable, collectable, transform) do
into_protocol(enumerable, collectable, transform)
end
defp into_protocol(enumerable, collectable, transform) do
{initial, fun} = Collectable.into(collectable)
into(enumerable, initial, fun, fn entry, acc ->
into_protocol(enumerable, initial, fun, fn entry, acc ->
fun.(acc, {:cont, transform.(entry)})
end)
end
defp into(enumerable, initial, fun, callback) do
defp into_protocol(enumerable, initial, fun, callback) do
try do
reduce(enumerable, initial, callback)
catch
+8
View File
@@ -516,6 +516,7 @@ defmodule IO do
IO.stream(:stdio, :line)
"""
@doc since: "1.12.0"
def stream, do: stream(:stdio, :line)
@doc """
@@ -534,6 +535,9 @@ defmodule IO do
Note that an IO stream has side effects and every time
you go over the stream you may get different results.
`stream/1` has been introduced in Elixir v1.12.0,
while `stream/2` has been available since v1.0.0.
## Examples
Here is an example on how we mimic an echo server
@@ -557,6 +561,7 @@ defmodule IO do
IO.binstream(:stdio, :line)
"""
@doc since: "1.12.0"
def binstream, do: binstream(:stdio, :line)
@doc """
@@ -574,6 +579,9 @@ defmodule IO do
Finally, do not use this function on IO devices in Unicode
mode as it will return the wrong result.
`binstream/1` has been introduced in Elixir v1.12.0,
while `binstream/2` has been available since v1.0.0.
"""
@spec binstream(device, :line | pos_integer) :: Enumerable.t()
def binstream(device \\ :stdio, line_or_bytes)
+5 -2
View File
@@ -5554,7 +5554,7 @@ defmodule Kernel do
quote(do: List.to_charlist(unquote(unescape_list_tokens(pieces))))
end
@doc """
@doc ~S"""
Handles the sigil `~r` for regular expressions.
It returns a regular expression pattern, unescaping characters and replacing
@@ -5564,12 +5564,15 @@ defmodule Kernel do
## Examples
iex> Regex.match?(~r(foo), "foo")
iex> Regex.match?(~r/foo/, "foo")
true
iex> Regex.match?(~r/a#{:b}c/, "abc")
true
While the `~r` sigil allows parens and brackets to be used as delimiters,
it is preferred to use `"` or `/` to avoid escaping conflicts with reserved
regex characters.
"""
defmacro sigil_r(term, modifiers)
+11 -4
View File
@@ -623,13 +623,13 @@ defmodule Kernel.ParallelCompiler do
data = maybe_warn_long_compilation(data, state)
if state.profile != :none do
compiling = System.convert_time_unit(data.compiling, :native, :millisecond)
waiting = System.convert_time_unit(data.waiting, :native, :millisecond)
extra = if waiting > 0, do: " (plus #{waiting}ms waiting)", else: ""
compiling = to_padded_ms(data.compiling)
waiting = to_padded_ms(data.waiting)
relative = Path.relative_to_cwd(data.file)
IO.puts(
:stderr,
"[profile] #{Path.relative_to_cwd(data.file)} compiled in #{compiling}ms" <> extra
"[profile] #{compiling}ms compiling + #{waiting}ms waiting for #{relative}"
)
end
@@ -640,6 +640,13 @@ defmodule Kernel.ParallelCompiler do
end)
end
defp to_padded_ms(time) do
time
|> System.convert_time_unit(:native, :millisecond)
|> Integer.to_string()
|> String.pad_leading(6, " ")
end
defp discard_down(pid) do
receive do
{:DOWN, _, :process, ^pid, _} -> :ok
-3
View File
@@ -1735,9 +1735,6 @@ defmodule Macro do
defp do_camelize(<<?_, h, t::binary>>) when h >= ?a and h <= ?z,
do: <<to_upper_char(h)>> <> do_camelize(t)
defp do_camelize(<<p, ?_, h, t::binary>>) when p >= ?0 and p <= ?9 and h >= ?0 and h <= ?9,
do: <<p, ?_, h>> <> do_camelize(t)
defp do_camelize(<<?_, h, t::binary>>) when h >= ?0 and h <= ?9, do: <<h>> <> do_camelize(t)
defp do_camelize(<<?_>>), do: <<>>
defp do_camelize(<<?/, t::binary>>), do: <<?.>> <> camelize(t)
+3
View File
@@ -105,7 +105,9 @@ defmodule Macro.Env do
vars: vars
}
# Define the __struct__ callbacks by hand for bootstrap reasons.
# TODO: Remove :vars field on v2.0
@doc false
def __struct__ do
%{
__struct__: __MODULE__,
@@ -130,6 +132,7 @@ defmodule Macro.Env do
}
end
@doc false
def __struct__(kv) do
Enum.reduce(kv, __struct__(), fn {k, v}, acc -> :maps.update(k, v, acc) end)
end
+1 -11
View File
@@ -216,20 +216,10 @@ defmodule Map do
@spec new(Enumerable.t(), (term -> {key, value})) :: map
def new(enumerable, transform) when is_function(transform, 1) do
enumerable
|> Enum.to_list()
|> new_transform(transform, [])
end
defp new_transform([], _fun, acc) do
acc
|> :lists.reverse()
|> Enum.map(transform)
|> :maps.from_list()
end
defp new_transform([element | rest], fun, acc) do
new_transform(rest, fun, [fun.(element) | acc])
end
@doc """
Returns whether the given `key` exists in the given `map`.
+1 -1
View File
@@ -930,7 +930,7 @@ defmodule Supervisor do
"""
@spec which_children(supervisor) :: [
# inlining module() | :dynamic here because :supervisor.modules() is not exported
{term() | :undefined, child | :restarting, :worker | :supervisor, module() | :dynamic}
{term() | :undefined, child | :restarting, :worker | :supervisor, [module()] | :dynamic}
]
def which_children(supervisor) do
call(supervisor, :which_children)
+11 -9
View File
@@ -77,7 +77,7 @@ require_function(Meta, Receiver, Name, Arity, E) ->
end.
remote_function(Meta, Receiver, Name, Arity, E) ->
check_deprecated(Meta, Receiver, Name, Arity, E),
check_deprecated(Meta, function, Receiver, Name, Arity, E),
case elixir_rewrite:inline(Receiver, Name, Arity) of
{AR, AN} -> {remote, AR, AN, Arity};
@@ -161,7 +161,7 @@ do_expand_import(Meta, {Name, Arity} = Tuple, Args, Module, E, Result) ->
elixir_locals:record_import(Tuple, Receiver, Module, ?key(E, function)),
{ok, Receiver, Name, Args};
{macro, Receiver} ->
check_deprecated(Meta, Receiver, Name, Arity, E),
check_deprecated(Meta, macro, Receiver, Name, Arity, E),
elixir_env:trace({imported_macro, Meta, Receiver, Name, Arity}, E),
elixir_locals:record_import(Tuple, Receiver, Module, ?key(E, function)),
{ok, Receiver, expand_macro_named(Meta, Receiver, Name, Arity, Args, E)};
@@ -180,17 +180,18 @@ do_expand_import(Meta, {Name, Arity} = Tuple, Args, Module, E, Result) ->
end.
expand_require(Meta, Receiver, {Name, Arity} = Tuple, Args, E) ->
check_deprecated(Meta, Receiver, Name, Arity, E),
Required = (Receiver == ?key(E, module)) orelse required(Meta) orelse is_element(Receiver, ?key(E, requires)),
case is_element(Tuple, get_macros(Receiver, Required)) of
true when Required ->
check_deprecated(Meta, macro, Receiver, Name, Arity, E),
elixir_env:trace({remote_macro, Meta, Receiver, Name, Arity}, E),
{ok, Receiver, expand_macro_named(Meta, Receiver, Name, Arity, Args, E)};
true ->
Info = {unrequired_module, {Receiver, Name, length(Args)}},
elixir_errors:form_error(Meta, E, ?MODULE, Info);
false ->
check_deprecated(Meta, function, Receiver, Name, Arity, E),
error
end.
@@ -361,12 +362,13 @@ elixir_imported_macros() ->
error:undef -> []
end.
check_deprecated(_, erlang, _, _, _) ->
ok;
check_deprecated(_, ?kernel, _, _, _) ->
ok;
check_deprecated(Meta, Receiver, Name, Arity, E) ->
case (?key(E, function) == nil) andalso get_deprecations(Receiver) of
check_deprecated(_, _, erlang, _, _, _) -> ok;
check_deprecated(_, _, elixir_def, _, _, _) -> ok;
check_deprecated(_, _, elixir_module, _, _, _) -> ok;
check_deprecated(_, _, ?kernel, _, _, _) -> ok;
check_deprecated(Meta, Kind, Receiver, Name, Arity, E) ->
%% Any compile time behaviour cannot be verified by the runtime group pass.
case ((?key(E, function) == nil) or (Kind == macro)) andalso get_deprecations(Receiver) of
[_ | _] = Deprecations ->
case lists:keyfind({Name, Arity}, 1, Deprecations) of
{_, Message} ->
+2 -2
View File
@@ -223,9 +223,9 @@ translate({{'.', _, [Left, Right]}, Meta, []}, S) when is_tuple(Left), is_atom(R
Ann = ?ann(Meta),
TRight = {atom, Ann, Right},
{Var, SV} = elixir_erl_var:build('_', SL),
TVar = {var, Ann, Var},
Generated = erl_anno:set_generated(true, Ann),
{Var, SV} = elixir_erl_var:build('_', SL),
TVar = {var, Generated, Var},
TError = {tuple, Ann, [{atom, Ann, badkey}, TRight, TVar]},
{{'case', Generated, TLeft, [
@@ -548,6 +548,21 @@ defmodule Code.Formatter.IntegrationTest do
"""
end
test "multiline expression inside interpolation" do
bad = ~S"""
Logger.info("Example: #{
inspect(%{
a: 1,
b: 2
})
}")
"""
assert_format bad, ~S"""
Logger.info("Example: #{inspect(%{a: 1, b: 2})}")
"""
end
test "comment inside operator with when" do
bad = """
raise function(x) ::
+3 -1
View File
@@ -396,6 +396,8 @@ defmodule EnumTest do
test "into/2" do
assert Enum.into([a: 1, b: 2], %{}) == %{a: 1, b: 2}
assert Enum.into([a: 1, b: 2], %{c: 3}) == %{a: 1, b: 2, c: 3}
assert Enum.into(MapSet.new(a: 1, b: 2), %{}) == %{a: 1, b: 2}
assert Enum.into(MapSet.new(a: 1, b: 2), %{c: 3}) == %{a: 1, b: 2, c: 3}
assert Enum.into(%{a: 1, b: 2}, []) == [a: 1, b: 2]
assert Enum.into(1..3, []) == [1, 2, 3]
assert Enum.into(["H", "i"], "") == "Hi"
@@ -405,7 +407,7 @@ defmodule EnumTest do
assert Enum.into([1, 2, 3], [], fn x -> x * 2 end) == [2, 4, 6]
assert Enum.into([1, 2, 3], "numbers: ", &to_string/1) == "numbers: 123"
assert_raise FunctionClauseError, fn ->
assert_raise MatchError, fn ->
Enum.into([2, 3], %{a: 1}, & &1)
end
end
@@ -202,5 +202,20 @@ defmodule Kernel.LexicalTrackerTest do
assert URI in exports
refute URI in runtime
end
test "compile_env! does not add a compile dependency" do
{{compile, exports, runtime, _}, _binding} =
Code.eval_string("""
defmodule Kernel.LexicalTrackerTest.CompileEnvStruct do
Application.compile_env(:elixir, URI)
Application.compile_env(:elixir, [:foo, URI, :bar])
Kernel.LexicalTracker.references(__ENV__.lexical_tracker)
end |> elem(3)
""")
refute URI in compile
refute URI in exports
assert URI in runtime
end
end
end
@@ -43,7 +43,9 @@ defmodule Kernel.ParallelCompilerTest do
assert HelloWorld in modules
end)
assert profile =~ ~r"\[profile\] .*tmp/profile_time/bar.ex compiled in \d+ms"
assert profile =~
~r"\[profile\] [\s\d]{6}ms compiling \+ 0ms waiting for .*tmp/profile_time/bar.ex"
assert profile =~ ~r"\[profile\] Finished compilation cycle of 1 modules in \d+ms"
assert profile =~ ~r"\[profile\] Finished group pass check of 1 modules in \d+ms"
after
+2 -1
View File
@@ -1036,7 +1036,8 @@ defmodule MacroTest do
assert Macro.camelize("foo__bar") == "FooBar"
assert Macro.camelize("foo/bar") == "Foo.Bar"
assert Macro.camelize("Foo.Bar") == "Foo.Bar"
assert Macro.camelize("foo1_0") == "Foo1_0"
assert Macro.camelize("foo1_0") == "Foo10"
assert Macro.camelize("foo_123_4_567") == "Foo1234567"
assert Macro.camelize("FOO_BAR") == "FOO_BAR"
assert Macro.camelize("FOO.BAR") == "FOO.BAR"
assert Macro.camelize("") == ""
@@ -663,6 +663,31 @@ defmodule Module.Types.IntegrationTest do
assert_warnings(files, warning)
end
test "reports macro" do
files = %{
"a.ex" => """
defmodule A do
@deprecated "oops"
defmacro a, do: :ok
end
""",
"b.ex" => """
defmodule B do
require A
def b, do: A.a()
end
"""
}
warning = """
warning: A.a/0 is deprecated. oops
b.ex:3: B.b/0
"""
assert_warnings(files, warning)
end
end
defp assert_warnings(files, expected) when is_binary(expected) do
+2 -2
View File
@@ -138,7 +138,7 @@ defmodule String.Casing do
codes =
special_path
|> File.read!()
|> String.split("\n", trim: true)
|> String.split(["\r\n", "\n"], trim: true)
|> Enum.reduce(codes, fn
"", acc ->
acc
@@ -325,7 +325,7 @@ defmodule String.Break do
whitespace =
prop_path
|> File.read!()
|> String.split("\n")
|> String.split(["\r\n", "\n"])
|> Enum.reduce([], fn line, acc ->
case :binary.split(line, ";") do
[<<first::4-bytes, "..", last::4-bytes, _::binary>>, <<" White_Space", _::binary>>] ->
+1 -1
View File
@@ -47,7 +47,7 @@ defmodule String.Tokenizer do
{start, continue, patterns} =
prop_path
|> File.read!()
|> String.split("\n", trim: true)
|> String.split(["\r\n", "\n"], trim: true)
|> Enum.reduce({start, continue, []}, fn line, acc ->
[codepoints | category] = :binary.split(line, ";")
+23 -4
View File
@@ -518,8 +518,17 @@ defmodule Mix do
deps =
Enum.map(deps, fn
dep when is_atom(dep) -> {dep, ">= 0.0.0"}
dep -> dep
dep when is_atom(dep) ->
{dep, ">= 0.0.0"}
{app, opts} when is_atom(app) and is_list(opts) ->
{app, maybe_expand_path_dep(opts)}
{app, requirement, opts} when is_atom(app) and is_binary(requirement) and is_list(opts) ->
{app, requirement, maybe_expand_path_dep(opts)}
other ->
other
end)
force? = !!opts[:force]
@@ -567,9 +576,10 @@ defmodule Mix do
:ok = Mix.Local.append_archives()
:ok = Mix.ProjectStack.push(__MODULE__.InstallProject, config, "nofile")
build_dir = Path.join(dir, "_build")
try do
run_deps? = not File.dir?(Path.join(dir, "_build"))
run_deps? = not File.dir?(build_dir)
File.mkdir_p!(dir)
File.cd!(dir, fn ->
@@ -577,7 +587,8 @@ defmodule Mix do
Mix.Task.rerun("deps.get")
end
Mix.Task.run("compile")
Mix.Task.rerun("deps.loadpaths")
Mix.Task.rerun("compile")
end)
for app <- Mix.Project.deps_apps() do
@@ -590,4 +601,12 @@ defmodule Mix do
Mix.ProjectStack.pop()
end
end
defp maybe_expand_path_dep(opts) do
if Keyword.has_key?(opts, :path) do
Keyword.update!(opts, :path, &Path.expand/1)
else
opts
end
end
end
+9
View File
@@ -226,6 +226,15 @@ defmodule Mix.Compilers.Elixir do
dest
)
# Now sort the files so the ones changed more recently come first.
# We do an optimized version of sort_by since we don't care about
# stable sorting.
changed =
changed
|> Enum.map(&{-elem(Map.fetch!(sources_stats, &1), 0), &1})
|> Enum.sort()
|> Enum.map(&elem(&1, 1))
{modules, exports, changed, sources_stats}
end
+1 -1
View File
@@ -123,7 +123,7 @@ defmodule Mix.Rebar do
# Translate a Rebar dependency declaration to a Mix declaration
# From http://www.rebar3.org/docs/dependencies#section-declaring-dependencies
defp parse_dep(app) when is_atom(app) do
{app, nil, override: true}
{app, override: true}
end
defp parse_dep({app, req}) when is_list(req) do
+32 -4
View File
@@ -418,13 +418,41 @@ defmodule Mix.Release do
:ok
{:error, reason} ->
{:error,
"Could not read configuration file. It likely has invalid configuration terms " <>
"such as functions, references, and pids. Please make sure your configuration " <>
"is made of numbers, atoms, strings, maps, tuples and lists. Reason: #{inspect(reason)}"}
invalid =
for {app, kv} <- sys_config,
{key, value} <- kv,
not valid_config?(value),
do: """
Application: #{inspect(app)}
Key: #{inspect(key)}
Value: #{inspect(value)}
"""
message =
case invalid do
[] ->
"Could not read configuration file. Reason: #{inspect(reason)}"
_ ->
"Could not read configuration file. It has invalid configuration terms " <>
"such as functions, references, and pids. Please make sure your configuration " <>
"is made of numbers, atoms, strings, maps, tuples and lists. The following entries " <>
"are wrong:\n#{Enum.join(invalid)}"
end
{:error, message}
end
end
defp valid_config?(n) when is_number(n), do: true
defp valid_config?(a) when is_atom(a), do: true
defp valid_config?(b) when is_binary(b), do: true
defp valid_config?(l) when is_list(l), do: Enum.all?(l, &valid_config?/1)
defp valid_config?(m) when is_map(m), do: Enum.all?(m, &valid_config?/1)
defp valid_config?(t) when is_tuple(t), do: Enum.all?(Tuple.to_list(t), &valid_config?/1)
defp valid_config?(_), do: false
defp merge_provider_config(%{config_providers: []}, sys_config, _), do: {sys_config, false}
defp merge_provider_config(release, sys_config, config_path) do
+65 -32
View File
@@ -14,6 +14,7 @@ defmodule Mix.Tasks.Compile.All do
Mix.Project.get!()
config = Mix.Project.config()
validate_compile_env? = "--no-validate-compile-env" not in args
lib_path = Path.join(Mix.Project.build_path(config), "lib")
# Make sure Mix.Dep is cached to avoid loading dependencies
# during compilation. It is likely this will be invoked anyway,
@@ -21,7 +22,7 @@ defmodule Mix.Tasks.Compile.All do
Mix.Dep.cached()
unless "--no-app-loading" in args do
load_apps(config, validate_compile_env?)
load_apps(config, lib_path, validate_compile_env?)
end
result =
@@ -39,8 +40,9 @@ defmodule Mix.Tasks.Compile.All do
end)
end
app = config[:app]
_ = Code.prepend_path(Mix.Project.compile_path())
load_app(config[:app], validate_compile_env?)
load_app(app, lib_path, validate_compile_env?)
result
end
@@ -93,44 +95,63 @@ defmodule Mix.Tasks.Compile.All do
## App loading helpers
defp load_apps(config, validate_compile_env?) do
defp load_apps(config, lib_path, validate_compile_env?) do
{runtime, optional} = Mix.Tasks.Compile.App.project_apps(config)
parent = self()
opts = [ordered: false, timeout: :infinity]
deps = for dep <- Mix.Dep.cached(), into: %{}, do: {dep.app, lib_path}
%{}
|> load_apps(runtime, validate_compile_env?)
|> load_apps(optional, validate_compile_env?)
stream_apps(runtime ++ optional, deps)
|> Task.async_stream(&load_stream_app(&1, parent, validate_compile_env?), opts)
|> Stream.run()
end
defp load_stream_app({app, lib_path}, parent, validate_compile_env?) do
children =
case load_app(app, lib_path, validate_compile_env?) do
:ok ->
Application.spec(app, :applications) ++ Application.spec(app, :included_applications)
:error ->
[]
end
send(parent, {:done, app, children})
:ok
end
defp load_apps(seen, apps, validate_compile_env?) do
Enum.reduce(apps, seen, fn app, seen ->
if Map.has_key?(seen, app) do
seen
else
seen = Map.put(seen, app, true)
case load_app(app, validate_compile_env?) do
:ok ->
seen
|> load_apps(Application.spec(app, :applications), validate_compile_env?)
|> load_apps(Application.spec(app, :included_applications), validate_compile_env?)
:error ->
seen
end
end
end)
defp stream_apps(initial, deps) do
Stream.unfold({initial, %{}, %{}, deps}, &stream_app/1)
end
defp load_app(app, validate_compile_env?) do
# We already processed this app, skip it.
defp stream_app({[app | apps], seen, done, deps}) when is_map_key(seen, app) do
stream_app({apps, seen, done, deps})
end
# We haven't processed this app, emit it.
defp stream_app({[app | apps], seen, done, deps}) do
{{app, deps[app]}, {apps, Map.put(seen, app, true), done, deps}}
end
# We have processed all apps and all seen have been done.
defp stream_app({[], seen, done, _deps}) when map_size(seen) == map_size(done) do
nil
end
# We have processed all apps but there is work being done.
defp stream_app({[], seen, done, deps}) do
receive do
{:done, app, children} -> stream_app({children, seen, Map.put(done, app, true), deps})
end
end
defp load_app(app, lib_path, validate_compile_env?) do
if Application.spec(app, :vsn) do
:ok
else
name = Atom.to_charlist(app) ++ '.app'
with [_ | _] = path <- :code.where_is_file(name),
{:ok, {:application, _, properties} = application_data} <- consult_app_file(path),
with {:ok, bin} <- read_app(app, lib_path),
{:ok, {:application, _, properties} = application_data} <- consult_app_file(bin),
:ok <- :application.load(application_data) do
if compile_env = validate_compile_env? && properties[:compile_env] do
Config.Provider.validate_compile_env(compile_env, false)
@@ -143,10 +164,22 @@ defmodule Mix.Tasks.Compile.All do
end
end
defp consult_app_file(path) do
# The app didn't come from a dep, go through the slow path (code/erl_prim_loader)
defp read_app(app, nil) do
name = Atom.to_charlist(app) ++ '.app'
with [_ | _] = path <- :code.where_is_file(name),
{:ok, bin, _full_name} <- :erl_prim_loader.get_file(path),
do: {:ok, bin}
end
defp read_app(app, lib_path) do
File.read("#{lib_path}/#{app}/ebin/#{app}.app")
end
defp consult_app_file(bin) do
# The path could be located in an .ez archive, so we use the prim loader.
with {:ok, bin, _full_name} <- :erl_prim_loader.get_file(path),
{:ok, tokens, _} <- :erl_scan.string(String.to_charlist(bin)) do
with {:ok, tokens, _} <- :erl_scan.string(String.to_charlist(bin)) do
:erl_parse.parse_term(tokens)
end
end
-14
View File
@@ -206,20 +206,6 @@ defmodule Mix.Tasks.Deps.Compile do
File.mkdir_p!(dep_path)
File.write!(config_path, rebar_config(dep))
# We need to copy/symlink include/priv in case it is
# needed for compiling the dependency itself. Later on
# we will build the whole structure and also symlink/copy
# ebin.
#
# Note both configurations are necessary when using the
# bare compiler with compiler output dir, otherwise includes
# and priv files can't be found. For example, without those
# options, we were unable to compile certifi, which uses
# both parse transforms and reads from priv at compilation.
Mix.Utils.symlink_or_copy(Path.join(opts[:dest], "include"), Path.join(dep_path, "include"))
Mix.Utils.symlink_or_copy(Path.join(opts[:dest], "priv"), Path.join(dep_path, "priv"))
do_command(dep, config, cmd, false, env)
end
+2 -9
View File
@@ -116,8 +116,7 @@ defmodule Mix.Tasks.Test do
* `--export-coverage` - the name of the file to export coverage results to.
Only has an effect when used with `--cover`
* `--failed` - runs only tests that failed the last time they ran.
If there are no pending --failed tests, `mix test` will run all available tests
* `--failed` - runs only tests that failed the last time they ran
* `--force` - forces compilation regardless of modification times
@@ -702,13 +701,7 @@ defmodule Mix.Tasks.Test do
end
{allowed_files, failed_ids} = ExUnit.Filters.failure_info(manifest_file)
if MapSet.size(failed_ids) == 0 do
Mix.shell().info("No pending --failed tests, re-running all available tests...")
{opts, nil}
else
{Keyword.put(opts, :only_test_ids, failed_ids), allowed_files}
end
{Keyword.put(opts, :only_test_ids, failed_ids), allowed_files}
else
{opts, nil}
end
+19 -9
View File
@@ -1,6 +1,9 @@
defmodule Mix.Utils do
@moduledoc false
# For bootstrapping purposes
@compile {:no_warn_undefined, Logger}
@doc """
Gets the Mix home.
@@ -638,17 +641,24 @@ defmodule Mix.Utils do
# If a proxy environment variable was supplied add a proxy to httpc.
http_options = [relaxed: true] ++ proxy_config(path)
case httpc_request(request, http_options) do
{:error, {:failed_connect, [{:to_address, _}, {inet, _, reason}]}}
when inet in [:inet, :inet6] and reason in [:ehostunreach, :enetunreach] ->
:httpc.set_options([ipfamily: fallback(inet)], :mix)
request |> httpc_request(http_options) |> httpc_response()
# Silence the warning from OTP as we verify the contents
level = Logger.level()
Logger.configure(level: :error)
response ->
httpc_response(response)
try do
case httpc_request(request, http_options) do
{:error, {:failed_connect, [{:to_address, _}, {inet, _, reason}]}}
when inet in [:inet, :inet6] and reason in [:ehostunreach, :enetunreach] ->
:httpc.set_options([ipfamily: fallback(inet)], :mix)
request |> httpc_request(http_options) |> httpc_response()
response ->
httpc_response(response)
end
after
Logger.configure(level: level)
:inets.stop(:httpc, :mix)
end
after
:inets.stop(:httpc, :mix)
end
defp fallback(:inet), do: :inet6
+2
View File
@@ -112,6 +112,8 @@ defmodule Mix.RebarTest do
assert parse_dep({:git_rebar, '', {:git, @git_rebar_charlist, {:ref, '64691eb'}}}) ==
{:git_rebar, ~r"", override: true, git: @git_rebar_string, ref: "64691eb"}
assert parse_dep(:git_rebar) == {:git_rebar, override: true}
end
end
+1 -1
View File
@@ -584,7 +584,7 @@ defmodule Mix.ReleaseTest do
test "errors on bad config" do
assert {:error, "Could not read configuration file." <> _} =
make_sys_config(release([]), [foo: self()], "unused/runtime/path")
make_sys_config(release([]), [foo: [bar: self()]], "unused/runtime/path")
end
end
+2 -7
View File
@@ -214,13 +214,8 @@ defmodule Mix.Tasks.TestTest do
System.put_env("PASS_FAILING_TESTS", "true")
assert mix(["test", "--failed"]) =~ "2 tests, 0 failures"
# All tests should be run if we try it again with no failing tests.
# This prevents `mix test --failed` from passing in cases where
# `mix test` had a compilation error before having failing tests.
# It also provides a better workflow as you can always run with --failed.
output = mix(["test", "--failed"])
assert output =~ "No pending --failed tests, re-running all available tests..."
assert output =~ "4 tests, 0 failures"
# Nothing should get run if we try it again since everything is passing.
assert mix(["test", "--failed"]) =~ "There are no tests to run"
# `--failed` and `--stale` cannot be combined
output = mix(["test", "--failed", "--stale"])
+14
View File
@@ -85,6 +85,20 @@ defmodule MixTest do
end
end
test "install after errors", %{tmp_dir: tmp_dir} do
assert_raise Mix.Error, "Can't continue due to errors on dependencies", fn ->
Mix.install([
{:bad, path: Path.join(tmp_dir, "bad")}
])
end
Mix.install([
{:install_test, path: Path.join(tmp_dir, "install_test")}
])
assert apply(InstallTest, :hello, []) == :world
end
test "consolidate_protocols: false", %{tmp_dir: tmp_dir} do
Mix.install(
[