Compare commits

...
14 Commits
20 changed files with 312 additions and 52 deletions
+26
View File
@@ -90,6 +90,24 @@ 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.3 (2021-09-05)
### 1. Bug fixes
#### Elixir
* [Code] Make sure that bindings in the default context returned by `Code.eval_*` functions are not returned as tagged tuples
* [Kernel] Do not crash when handling ambiguity errors
* [Range] Still match on old range patterns throughout the stdlib
#### IEx
* [IEx.Autocomplete] Do not error autocompletion with module attribute
#### Mix
* [Mix] Rename inconsistent `:exit_code` option to `:exit_status` on `Mix.raise/2`
## v1.12.2 (2021-07-01)
### 1. Bug fixes
@@ -277,6 +295,14 @@ Elixir v1.12 has the additional of many functions across the standard library. T
* [mix compile] The `:xref` compiler is deprecated and it has no effect. Please remove it from your mix.exs file.
### 5. Backwards incompatible changes
A backward incompatible change was found on Elixir v1.12.0 after the release. This was not planned and it is documented below:
#### ExUnit
* [ExUnit.Formatter] `{:suite_finished, load_time, run_time}` message has been updated to `{:suite_finished, %{load: load_time, run: run_time, async: async_time}}`
## v1.11
The CHANGELOG for v1.11 releases can be found [in the v1.11 branch](https://github.com/elixir-lang/elixir/blob/v1.11/CHANGELOG.md).
+1 -1
View File
@@ -1 +1 @@
1.12.2
1.12.3
+1 -1
View File
@@ -14,7 +14,7 @@ Usage: $(basename "$0") [elixir switches] [compiler switches] [.ex files]
--no-docs Does not attach documentation to compiled modules
--profile time Profile the time to compile modules
--verbose Prints compilation status
--warnings-as-errors Treats warnings as errors and return non-zero exit code
--warnings-as-errors Treats warnings as errors and return non-zero exit status
Options given after -- are passed down to the executed code.
Options can be passed to the Erlang runtime using \$ELIXIR_ERL_OPTIONS.
+1 -1
View File
@@ -23,7 +23,7 @@ echo --no-debug-info Does not attach debug info to compiled modules
echo --no-docs Does not attach documentation to compiled modules
echo --profile time Profile the time to compile modules
echo --verbose Prints compilation status
echo --warnings-as-errors Treats warnings as errors and returns non-zero exit code
echo --warnings-as-errors Treats warnings as errors and returns non-zero exit status
echo.
echo ** Options given after -- are passed down to the executed code
echo ** Options can be passed to the Erlang runtime using ELIXIR_ERL_OPTIONS
+84 -24
View File
@@ -26,13 +26,15 @@ defmodule Date.Range do
defstruct [:first, :last, :first_in_iso_days, :last_in_iso_days, :step]
defimpl Enumerable do
def member?(%{first: %{calendar: calendar}} = range, %Date{calendar: calendar} = date) do
%{
first_in_iso_days: first_days,
last_in_iso_days: last_days,
step: step
} = range
def member?(
%Date.Range{
first: %{calendar: calendar},
first_in_iso_days: first_days,
last_in_iso_days: last_days,
step: step
} = range,
%Date{calendar: calendar} = date
) do
{days, _} = Date.to_iso_days(date)
cond do
@@ -47,24 +49,43 @@ defmodule Date.Range do
end
end
def member?(_, _) do
def member?(%Date.Range{step: _}, _) do
{:ok, false}
end
# TODO: Remove me on v2.0
def member?(
%{__struct__: Date.Range, first_in_iso_days: first_days, last_in_iso_days: last_days} =
date_range,
date
) do
step = if first_days <= last_days, do: 1, else: -1
member?(Map.put(date_range, :step, step), date)
end
def count(range) do
{:ok, size(range)}
end
def slice(range) do
%{
first_in_iso_days: first,
first: %{calendar: calendar},
step: step
} = range
def slice(
%Date.Range{
first_in_iso_days: first,
first: %{calendar: calendar},
step: step
} = range
) do
{:ok, size(range), &slice(first + &1 * step, step, &2, calendar)}
end
# TODO: Remove me on v2.0
def slice(
%{__struct__: Date.Range, first_in_iso_days: first_days, last_in_iso_days: last_days} =
date_range
) do
step = if first_days <= last_days, do: 1, else: -1
slice(Map.put(date_range, :step, step))
end
defp slice(current, _step, 1, calendar) do
[date_from_iso_days(current, calendar)]
end
@@ -76,17 +97,30 @@ defmodule Date.Range do
]
end
def reduce(range, acc, fun) do
%{
first_in_iso_days: first_days,
last_in_iso_days: last_days,
first: %{calendar: calendar},
step: step
} = range
def reduce(
%Date.Range{
first_in_iso_days: first_days,
last_in_iso_days: last_days,
first: %{calendar: calendar},
step: step
},
acc,
fun
) do
reduce(first_days, last_days, acc, fun, step, calendar)
end
# TODO: Remove me on v2.0
def reduce(
%{__struct__: Date.Range, first_in_iso_days: first_days, last_in_iso_days: last_days} =
date_range,
acc,
fun
) do
step = if first_days <= last_days, do: 1, else: -1
reduce(Map.put(date_range, :step, step), acc, fun)
end
defp reduce(_first_days, _last_days, {:halt, acc}, _fun, _step, _calendar) do
{:halted, acc}
end
@@ -135,6 +169,15 @@ defmodule Date.Range do
defp size(%Date.Range{first_in_iso_days: first_days, last_in_iso_days: last_days, step: step}),
do: abs(div(last_days - first_days, step)) + 1
# TODO: Remove me on v2.0
defp size(
%{__struct__: Date.Range, first_in_iso_days: first_days, last_in_iso_days: last_days} =
date_range
) do
step = if first_days <= last_days, do: 1, else: -1
size(Map.put(date_range, :step, step))
end
defp empty?(%Date.Range{
first_in_iso_days: first_days,
last_in_iso_days: last_days,
@@ -151,10 +194,21 @@ defmodule Date.Range do
when step < 0 and first_days < last_days,
do: true
defp empty?(%Date.Range{}), do: false
defp empty?(%Date.Range{step: _}), do: false
# TODO: Remove me on v2.0
defp empty?(
%{__struct__: Date.Range, first_in_iso_days: first_days, last_in_iso_days: last_days} =
date_range
) do
step = if first_days <= last_days, do: 1, else: -1
empty?(Map.put(date_range, :step, step))
end
end
defimpl Inspect do
import Kernel, except: [inspect: 2]
def inspect(%Date.Range{first: first, last: last, step: 1}, _) do
"#DateRange<" <> inspect(first) <> ", " <> inspect(last) <> ">"
end
@@ -162,5 +216,11 @@ defmodule Date.Range do
def inspect(%Date.Range{first: first, last: last, step: step}, _) do
"#DateRange<" <> inspect(first) <> ", " <> inspect(last) <> ", #{step}>"
end
# TODO: Remove me on v2.0
def inspect(%{__struct__: Date.Range, first: first, last: last} = date_range, opts) do
step = if first <= last, do: 1, else: -1
inspect(Map.put(date_range, :step, step), opts)
end
end
end
+6
View File
@@ -2661,6 +2661,12 @@ defmodule Enum do
end
end
# TODO: Remove me on v2.0
def slice(enumerable, %{__struct__: Range, first: first, last: last} = index_range) do
step = if first <= last, do: 1, else: -1
slice(enumerable, Map.put(index_range, :step, step))
end
defp slice_range(enumerable, first, last) when last >= first and last >= 0 and first >= 0 do
slice_any(enumerable, first, last - first + 1)
end
+32
View File
@@ -185,6 +185,12 @@ defmodule Range do
def size(first..last//step) when step < 0 and first < last, do: 0
def size(first..last//step), do: abs(div(last - first, step)) + 1
# TODO: Remove me on v2.0
def size(%{__struct__: Range, first: first, last: last} = range) do
step = if first <= last, do: 1, else: -1
size(Map.put(range, :step, step))
end
@doc """
Checks if two ranges are disjoint.
@@ -273,6 +279,12 @@ defimpl Enumerable, for: Range do
reduce(first, last, acc, fun, step)
end
# TODO: Remove me on v2.0
def reduce(%{__struct__: Range, first: first, last: last} = range, acc, fun) do
step = if first <= last, do: 1, else: -1
reduce(Map.put(range, :step, step), acc, fun)
end
defp reduce(_first, _last, {:halt, acc}, _fun, _step) do
{:halted, acc}
end
@@ -304,6 +316,13 @@ defimpl Enumerable, for: Range do
end
end
# TODO: Remove me on v2.0
def member?(%{__struct__: Range, first: first, last: last} = range, value)
when is_integer(value) do
step = if first <= last, do: 1, else: -1
member?(Map.put(range, :step, step), value)
end
def member?(_, _value) do
{:ok, false}
end
@@ -316,12 +335,19 @@ defimpl Enumerable, for: Range do
{:ok, Range.size(range), &slice(first + &1 * step, step, &2)}
end
# TODO: Remove me on v2.0
def slice(%{__struct__: Range, first: first, last: last} = range) do
step = if first <= last, do: 1, else: -1
slice(Map.put(range, :step, step))
end
defp slice(current, _step, 1), do: [current]
defp slice(current, step, remaining), do: [current | slice(current + step, step, remaining - 1)]
end
defimpl Inspect, for: Range do
import Inspect.Algebra
import Kernel, except: [inspect: 2]
def inspect(first..last//1, opts) do
concat([to_doc(first, opts), "..", to_doc(last, opts)])
@@ -330,4 +356,10 @@ defimpl Inspect, for: Range do
def inspect(first..last//step, opts) do
concat([to_doc(first, opts), "..", to_doc(last, opts), "//", to_doc(step, opts)])
end
# TODO: Remove me on v2.0
def inspect(%{__struct__: Range, first: first, last: last} = range, opts) do
step = if first <= last, do: 1, else: -1
inspect(Map.put(range, :step, step), opts)
end
end
+6
View File
@@ -2098,6 +2098,12 @@ defmodule String do
end
end
# TODO: Remove me on v2.0
def slice(string, %{__struct__: Range, first: first, last: last} = range) do
step = if first <= last, do: 1, else: -1
slice(string, Map.put(range, :step, step))
end
defp slice_range("", _, _), do: ""
defp slice_range(string, first, -1) when first >= 0 do
+1 -1
View File
@@ -252,7 +252,7 @@ eval_forms(Tree, RawBinding, OE) ->
case Erl of
{atom, _, Atom} ->
{Atom, Binding, NewE};
{Atom, RawBinding, NewE};
_ ->
Exprs =
+6 -10
View File
@@ -1161,23 +1161,19 @@ format_error({invalid_alias, Expr}) ->
"it at runtime. If instead you wanted to invoke a function or access a field, "
"wrap the function or field name in double quotes",
io_lib:format(Message, ['Elixir.Macro':to_string(Expr)]);
format_error({op_ambiguity, Name, {Op, _, [Arg]}}) ->
format_error({op_ambiguity, Name, Arg}) ->
NameString = atom_to_binary(Name, utf8),
OpString = atom_to_binary(Op, utf8),
ArgString = 'Elixir.Macro':to_string(Arg),
Message =
"\"~ts ~ts~ts\" looks like a function call but there is a variable named \"~ts\".\n"
"\"~ts ~ts\" looks like a function call but there is a variable named \"~ts\". "
"If you want to perform a function call, use parentheses:\n"
"\n"
" ~ts(~ts~ts)\n"
" ~ts(~ts)\n"
"\n"
"If you want to perform an operation on the variable ~ts, use even spaces instead:\n"
"\n"
" ~ts ~ts ~ts",
io_lib:format(Message, [NameString, OpString, ArgString, NameString,
NameString, OpString, ArgString, NameString,
NameString, OpString, ArgString]);
"If you want to perform an operation on the variable ~ts, use spaces "
"around the unary operator",
io_lib:format(Message, [NameString, ArgString, NameString, NameString, ArgString, NameString]);
format_error({invalid_clauses, Name}) ->
Message =
"the function \"~ts\" cannot handle clauses with the -> operator because it is not a macro. "
@@ -141,4 +141,71 @@ defmodule Date.RangeTest do
Date.range(~D[2000-01-01], ~D[2000-01-31], step)
end
end
describe "old date ranges" do
test "inspect" do
asc = %{
__struct__: Date.Range,
first: ~D[2021-07-14],
first_in_iso_days: 738_350,
last: ~D[2021-07-17],
last_in_iso_days: 738_353
}
desc = %{
__struct__: Date.Range,
first: ~D[2021-07-17],
first_in_iso_days: 738_353,
last: ~D[2021-07-14],
last_in_iso_days: 738_350
}
assert inspect(asc) == "#DateRange<~D[2021-07-14], ~D[2021-07-17]>"
assert inspect(desc) == "#DateRange<~D[2021-07-17], ~D[2021-07-14], -1>"
end
test "enumerable" do
asc = %{
__struct__: Date.Range,
first: ~D[2021-07-14],
first_in_iso_days: 738_350,
last: ~D[2021-07-17],
last_in_iso_days: 738_353
}
desc = %{
__struct__: Date.Range,
first: ~D[2021-07-17],
first_in_iso_days: 738_353,
last: ~D[2021-07-14],
last_in_iso_days: 738_350
}
# member? implementations tests also empty?
assert Enumerable.member?(asc, ~D[2021-07-15])
assert {:ok, 4, _} = Enumerable.slice(asc)
assert Enum.reduce(asc, [], fn x, acc -> [x | acc] end) == [
~D[2021-07-17],
~D[2021-07-16],
~D[2021-07-15],
~D[2021-07-14]
]
assert Enum.count(asc) == 4
# member? implementations tests also empty?
assert Enumerable.member?(desc, ~D[2021-07-15])
assert {:ok, 4, _} = Enumerable.slice(desc)
assert Enum.reduce(desc, [], fn x, acc -> [x | acc] end) == [
~D[2021-07-14],
~D[2021-07-15],
~D[2021-07-16],
~D[2021-07-17]
]
assert Enum.count(desc) == 4
end
end
end
+4
View File
@@ -167,6 +167,10 @@ defmodule CodeTest do
assert Code.eval_string("two = 1 + 1") == {2, [two: 2]}
end
test "keeps bindings on optimized evals" do
assert Code.eval_string("import Enum", x: 1) == {Enum, [x: 1]}
end
test "supports a %Macro.Env{} struct as the third argument" do
assert {3, _} = Code.eval_string("a + b", [a: 1, b: 2], __ENV__)
end
@@ -2538,10 +2538,10 @@ defmodule Kernel.ExpansionTest do
describe "op ambiguity" do
test "raises when a call is ambiguous" do
# We use string_to_quoted! here to avoid the formatter adding parentheses
message = ~r["a -1" looks like a function call but there is a variable named "a"]
assert_raise CompileError, message, fn ->
# We use string_to_quoted! here to avoid the formatter adding parentheses to "a -1".
code =
Code.string_to_quoted!("""
a = 1
@@ -2550,6 +2550,18 @@ defmodule Kernel.ExpansionTest do
expand(code)
end
message = ~r["a -1\.\.a \+ 1" looks like a function call but there is a variable named "a"]
assert_raise CompileError, message, fn ->
code =
Code.string_to_quoted!("""
a = 1
a -1 .. a + 1
""")
expand(code)
end
end
end
+45
View File
@@ -94,4 +94,49 @@ defmodule RangeTest do
assert Range.disjoint?(1..1, 1..1) == false
end
end
describe "old ranges" do
test "inspect" do
asc = %{__struct__: Range, first: 1, last: 3}
desc = %{__struct__: Range, first: 3, last: 1}
assert inspect(asc) == "1..3"
assert inspect(desc) == "3..1//-1"
end
test "enum" do
asc = %{__struct__: Range, first: 1, last: 3}
desc = %{__struct__: Range, first: 3, last: 1}
assert Enum.to_list(asc) == [1, 2, 3]
assert Enum.member?(asc, 2)
assert Enum.count(asc) == 3
assert Enum.drop(asc, 1) == [2, 3]
assert Enum.slice([1, 2, 3, 4, 5, 6], asc) == [2, 3, 4]
# testing private Enum.aggregate
assert Enum.max(asc) == 3
assert Enum.sum(asc) == 6
assert Enum.min_max(asc) == {1, 3}
assert Enum.reduce(asc, 0, fn a, b -> a + b end) == 6
assert Enum.to_list(desc) == [3, 2, 1]
assert Enum.member?(desc, 2)
assert Enum.count(desc) == 3
assert Enum.drop(desc, 1) == [2, 1]
assert Enum.slice([1, 2, 3, 4, 5, 6], desc) == []
# testing private Enum.aggregate
assert Enum.max(desc) == 3
assert Enum.sum(desc) == 6
assert Enum.min_max(desc) == {1, 3}
assert Enum.reduce(desc, 0, fn a, b -> a + b end) == 6
end
test "string" do
asc = %{__struct__: Range, first: 1, last: 3}
desc = %{__struct__: Range, first: 3, last: 1}
assert String.slice("elixir", asc) == "lix"
assert String.slice("elixir", desc) == ""
end
end
end
+4
View File
@@ -163,6 +163,10 @@ defmodule IEx.Autocomplete do
{:ok, List.to_atom(var)}
end
defp expand_dot_path({:module_attribute, _}, _shell) do
:error
end
defp expand_dot_path({:dot, parent, call}, shell) do
case expand_dot_path(parent, shell) do
{:ok, %{} = map} -> Map.fetch(map, List.to_atom(call))
+4 -3
View File
@@ -161,6 +161,7 @@ defmodule IEx.AutocompleteTest do
assert expand('Xyz') == {:no, '', []}
assert expand('x.Foo') == {:no, '', []}
assert expand('x.Foo.get_by') == {:no, '', []}
assert expand('@foo.bar') == {:no, '', []}
end
test "Elixir root submodule completion" do
@@ -221,21 +222,21 @@ defmodule IEx.AutocompleteTest do
assert expand('map.f') == {:no, '', []}
end
test "autocompletion off a bound variable only works for modules and maps" do
test "bound variables for modules and maps" do
eval("num = 5; map = %{nested: %{num: 23}}")
assert expand('num.print') == {:no, '', []}
assert expand('map.nested.num.f') == {:no, '', []}
assert expand('map.nested.num.key.f') == {:no, '', []}
end
test "autocompletion using access syntax does is not supported" do
test "access syntax is not supported" do
eval("map = %{nested: %{deeply: %{num: 23}}}")
assert expand('map[:nested][:deeply].n') == {:no, '', []}
assert expand('map[:nested].deeply.n') == {:no, '', []}
assert expand('map.nested.[:deeply].n') == {:no, '', []}
end
test "autocompletion off of unbound variables is not supported" do
test "unbound variables is not supported" do
eval("num = 5")
assert expand('other_var.f') == {:no, '', []}
assert expand('a.b.c.d') == {:no, '', []}
+7 -6
View File
@@ -433,11 +433,11 @@ defmodule Mix do
end
@doc """
Raises a Mix error that is nicely formatted, defaulting to exit code `1`.
Raises a Mix error that is nicely formatted, defaulting to exit status `1`.
"""
@spec raise(binary) :: no_return
def raise(message) do
__MODULE__.raise(message, exit_code: 1)
__MODULE__.raise(message, exit_status: 1)
end
@doc """
@@ -445,13 +445,14 @@ defmodule Mix do
## Options
* `:exit_code` - defines exit code value, defaults to `1`
* `:exit_status` - defines exit status, defaults to `1`
"""
@doc since: "1.12.0"
@spec raise(binary, exit_code: non_neg_integer()) :: no_return
@doc since: "1.12.3"
@spec raise(binary, exit_status: non_neg_integer()) :: no_return
def raise(message, opts) when is_binary(message) and is_list(opts) do
Kernel.raise(Mix.Error, mix: Keyword.get(opts, :exit_code, 1), message: message)
status = opts[:exit_status] || opts[:exit_code] || 1
Kernel.raise(Mix.Error, mix: status, message: message)
end
@doc """
+1 -1
View File
@@ -52,7 +52,7 @@ defmodule Mix.Tasks.Compile.Elixir do
* `--debug-info` (`--no-debug-info`) - attaches (or not) debug info to compiled modules
* `--ignore-module-conflict` - does not emit warnings if a module was previously defined
* `--warnings-as-errors` - treats warnings in the current project as errors and
return a non-zero exit code
return a non-zero exit status
* `--long-compilation-threshold N` - sets the "long compilation" threshold
(in seconds) to `N` (see the docs for `Kernel.ParallelCompiler.compile/2`)
* `--profile` - if set to `time`, outputs timing information of compilation steps
+1 -1
View File
@@ -174,7 +174,7 @@ defmodule Mix.Tasks.Test do
Note that in trace mode test timeouts will be ignored as timeout is set to `:infinity`
* `--warnings-as-errors` - (since v1.12.0) treats warnings as errors and returns a non-zero
exit code. This option only applies to test files. To treat warnings as errors during
exit status. This option only applies to test files. To treat warnings as errors during
compilation and during tests, run:
MIX_ENV=test mix do compile --warnings-as-errors, test --warnings-as-errors
+2 -2
View File
@@ -20,7 +20,7 @@ defmodule Mix.CLITest do
end
@tag :unix
test "Mix.raise/2 can set exit code", %{tmp_dir: tmp_dir} do
test "Mix.raise/2 can set exit status", %{tmp_dir: tmp_dir} do
File.cd!(tmp_dir, fn ->
File.mkdir_p!("lib")
@@ -34,7 +34,7 @@ defmodule Mix.CLITest do
defp aliases do
[
custom: &error(&1, exit_code: 99),
custom: &error(&1, exit_status: 99),
]
end