From 23c9595405312a691e6266c3ec6882f243ed0fa6 Mon Sep 17 00:00:00 2001 From: Wojtek Mach Date: Fri, 12 Feb 2021 12:23:58 +0100 Subject: [PATCH 1/5] Update Erlang warnings translation (#10694) Since https://github.com/erlang/otp/commit/8ecc648df22dbfe9dd68a3e2b968ba2b6a2ca7fe instead of `{eval_failure, Reason}` we'd get `{eval_failure, Call, Reason}` --- lib/elixir/src/elixir_erl_compiler.erl | 11 +++++++++++ lib/elixir/test/elixir/kernel/warning_test.exs | 13 +++++++++++-- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/lib/elixir/src/elixir_erl_compiler.erl b/lib/elixir/src/elixir_erl_compiler.erl index 3affb18e8f6..7010b59fd9f 100644 --- a/lib/elixir/src/elixir_erl_compiler.erl +++ b/lib/elixir/src/elixir_erl_compiler.erl @@ -132,6 +132,17 @@ custom_format(sys_core_fold, nomatch_guard) -> "this check/guard will always yield the same result"; %% Handle literal eval failures +custom_format(sys_core_fold, {eval_failure, {Mod, Name, Arity}, Error}) -> + #{'__struct__' := Struct} = 'Elixir.Exception':normalize(error, Error), + {ExMod, ExName, ExArgs} = elixir_rewrite:erl_to_ex(Mod, Name, lists:duplicate(Arity, nil)), + Call = 'Elixir.Exception':format_mfa(ExMod, ExName, length(ExArgs)), + Trimmed = case Call of + <<"Kernel.", Rest/binary>> -> Rest; + _ -> Call + end, + ["the call to ", Trimmed, " will fail with ", elixir_aliases:inspect(Struct)]; + +%% TODO: remove when we require OTP 24 custom_format(sys_core_fold, {eval_failure, Error}) -> #{'__struct__' := Struct} = 'Elixir.Exception':normalize(error, Error), ["this expression will fail with ", elixir_aliases:inspect(Struct)]; diff --git a/lib/elixir/test/elixir/kernel/warning_test.exs b/lib/elixir/test/elixir/kernel/warning_test.exs index 842676b26f4..15a984a5409 100644 --- a/lib/elixir/test/elixir/kernel/warning_test.exs +++ b/lib/elixir/test/elixir/kernel/warning_test.exs @@ -931,6 +931,15 @@ defmodule Kernel.WarningTest do purge(Sample) end + # TODO: Simplify when we require OTP 24 + if System.otp_release() >= "24" do + @argument_error_message "the call to :erlang.atom_to_binary/2" + @arithmetic_error_message "the call to +/2" + else + @argument_error_message "this expression" + @arithmetic_error_message "this expression" + end + test "eval failure warning" do assert capture_err(fn -> Code.eval_string(""" @@ -938,7 +947,7 @@ defmodule Kernel.WarningTest do def foo, do: Atom.to_string "abc" end """) - end) =~ ~r"this expression will fail with ArgumentError\n.*nofile:2" + end) =~ "#{@argument_error_message} will fail with ArgumentError\n nofile:2" assert capture_err(fn -> Code.eval_string(""" @@ -946,7 +955,7 @@ defmodule Kernel.WarningTest do def foo, do: 1 + nil end """) - end) =~ ~r"this expression will fail with ArithmeticError\n.*nofile:2" + end) =~ "#{@arithmetic_error_message} will fail with ArithmeticError\n nofile:2" after purge([Sample1, Sample2]) end From 2974c747f53ff071ab73de3918cfff73afe28784 Mon Sep 17 00:00:00 2001 From: Wojtek Mach Date: Tue, 16 Feb 2021 13:15:04 +0100 Subject: [PATCH 2/5] Fix translating erl compiler errors to diagnostics on OTP 24 (#10719) The compiler previously emitted just `line` and now emits `{line, column}`. The diagnostic struct accepts as position either: nil line {start_line, start_col, end_line, end_col} so we could have used the `column` to create the 4-tuple but we don't have enough information to do that correctly, we don't know where the line ends. --- lib/mix/lib/mix/compilers/erlang.ex | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/lib/mix/lib/mix/compilers/erlang.ex b/lib/mix/lib/mix/compilers/erlang.ex index 81862727fba..f34f0bde37a 100644 --- a/lib/mix/lib/mix/compilers/erlang.ex +++ b/lib/mix/lib/mix/compilers/erlang.ex @@ -271,7 +271,18 @@ defmodule Mix.Compilers.Erlang do defp to_diagnostics(warnings_or_errors, severity) do for {file, issues} <- warnings_or_errors, {line, module, data} <- issues do - position = if is_integer(line) and line >= 1, do: line + position = + case line do + # TODO: remove when we require OTP 24 + line when is_integer(line) and line >= 1 -> + line + + {line, _column} when is_integer(line) and line >= 1 -> + line + + _ -> + nil + end %Mix.Task.Compiler.Diagnostic{ file: Path.absname(file), From 16fa1efda3bf918a4317943c620ec911ca4da009 Mon Sep 17 00:00:00 2001 From: Wojtek Mach Date: Tue, 16 Feb 2021 15:09:09 +0100 Subject: [PATCH 3/5] Fix remaining warning translations for OTP 24 (#10720) Yecc error message changed so we need to loosen up our assertion: OTP 23 iex> iex Erlang/OTP 23 [erts-11.1.7] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] Interactive Elixir (1.11.3) - press Ctrl+C to exit (type h() ENTER for help) iex(1)> :yecc.file('f.yrl') f.yrl:1: syntax error before: '.' :error OTP 24 iex> iex Erlang/OTP 24 [DEVELOPMENT] [erts-11.1.7] [source-802d2c5083] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] Interactive Elixir (1.12.0-dev) - press Ctrl+C to exit (type h() ENTER for help) iex(1)> :yecc.file('f.yrl') f.yrl:1:5: syntax error before: . :error --- lib/mix/lib/mix/compilers/erlang.ex | 20 +++++++------------- lib/mix/test/mix/tasks/compile.yecc_test.exs | 4 +++- 2 files changed, 10 insertions(+), 14 deletions(-) diff --git a/lib/mix/lib/mix/compilers/erlang.ex b/lib/mix/lib/mix/compilers/erlang.ex index f34f0bde37a..7b5e86b9eee 100644 --- a/lib/mix/lib/mix/compilers/erlang.ex +++ b/lib/mix/lib/mix/compilers/erlang.ex @@ -271,18 +271,7 @@ defmodule Mix.Compilers.Erlang do defp to_diagnostics(warnings_or_errors, severity) do for {file, issues} <- warnings_or_errors, {line, module, data} <- issues do - position = - case line do - # TODO: remove when we require OTP 24 - line when is_integer(line) and line >= 1 -> - line - - {line, _column} when is_integer(line) and line >= 1 -> - line - - _ -> - nil - end + position = line(line) %Mix.Task.Compiler.Diagnostic{ file: Path.absname(file), @@ -299,7 +288,12 @@ defmodule Mix.Compilers.Erlang do for {_, warnings} <- entries, {file, issues} <- warnings, {line, module, message} <- issues do - IO.puts("#{file}:#{line}: Warning: #{module.format_error(message)}") + IO.puts("#{file}:#{line(line)}: Warning: #{module.format_error(message)}") end end + + defp line({line, _column}) when is_integer(line) and line >= 1, do: line + # TODO: remove when we require OTP 24 + defp line(line) when is_integer(line) and line >= 1, do: line + defp line(_), do: nil end diff --git a/lib/mix/test/mix/tasks/compile.yecc_test.exs b/lib/mix/test/mix/tasks/compile.yecc_test.exs index 323a5f44f95..29e6e5dc40b 100644 --- a/lib/mix/test/mix/tasks/compile.yecc_test.exs +++ b/lib/mix/test/mix/tasks/compile.yecc_test.exs @@ -23,10 +23,12 @@ defmodule Mix.Tasks.Compile.YeccTest do assert %Mix.Task.Compiler.Diagnostic{ compiler_name: "yecc", file: ^file, - message: "syntax error before: '.'", + message: message, position: 1, severity: :error } = diagnostic + + assert message =~ "syntax error before: " end) assert File.regular?("src/test_ok.erl") From 594098a0795e2f4c7dd004b895471f7d420efc42 Mon Sep 17 00:00:00 2001 From: Wojtek Mach Date: Sat, 20 Feb 2021 11:24:58 +0100 Subject: [PATCH 4/5] Support 16bit floats in bitstrings (#10740) On OTP 24: iex> <> = <<60, 0>> iex> x 1.0 iex> <> <<60, 0>> Before OTP 24 we'd get errors or wouldn't match: iex> <<1.0::float-16>> ** (ArgumentError) argument error while evaluating iex at line 1 <> = <<60, 0>> ** (MatchError) no match of right hand side value: <<60, 0>> iex> (fn <> -> x; _ -> :nomatch end).(<<60, 0>>) :nomatch --- lib/elixir/lib/kernel/special_forms.ex | 4 +- lib/elixir/src/elixir_bitstring.erl | 22 +++++++-- .../test/elixir/kernel/expansion_test.exs | 46 ++++++++++++++++--- 3 files changed, 60 insertions(+), 12 deletions(-) diff --git a/lib/elixir/lib/kernel/special_forms.ex b/lib/elixir/lib/kernel/special_forms.ex index d81919f2c9c..aa148583f5e 100644 --- a/lib/elixir/lib/kernel/special_forms.ex +++ b/lib/elixir/lib/kernel/special_forms.ex @@ -233,9 +233,9 @@ defmodule Kernel.SpecialForms do Sizes for types are a bit more nuanced. The default size for integers is 8. - For floats, it is 64. For floats, `size * unit` must result in 32 or 64, + For floats, it is 64. For floats, `size * unit` must result in 16, 32, or 64, corresponding to [IEEE 754](https://en.wikipedia.org/wiki/IEEE_floating_point) - binary32 and binary64, respectively. + binary16, binary32, and binary64, respectively. For binaries, the default is the size of the binary. Only the last binary in a match can use the default size. All others must have their size specified diff --git a/lib/elixir/src/elixir_bitstring.erl b/lib/elixir/src/elixir_bitstring.erl index 3b653453440..25f88ef307e 100644 --- a/lib/elixir/src/elixir_bitstring.erl +++ b/lib/elixir/src/elixir_bitstring.erl @@ -319,8 +319,13 @@ build_spec(Meta, _Size, Unit, Type, _Endianness, Sign, Spec, E) when Type == bin build_spec(Meta, Size, Unit, Type, Endianness, Sign, Spec, E) when Type == integer; Type == float -> NumberSize = number_size(Size, Unit), if - Type == float, is_integer(NumberSize), NumberSize /= 32, NumberSize /= 64 -> - form_error(Meta, E, ?MODULE, {bittype_float_size, NumberSize}); + Type == float, is_integer(NumberSize) -> + case valid_float_size(NumberSize) of + true -> + add_spec(Type, add_spec(Endianness, add_spec(Sign, Spec))); + false -> + form_error(Meta, E, ?MODULE, {bittype_float_size, NumberSize}) + end; Size == default, Unit /= default -> form_error(Meta, E, ?MODULE, bittype_unit); true -> @@ -331,6 +336,12 @@ number_size(Size, default) when is_integer(Size) -> Size; number_size(Size, Unit) when is_integer(Size) -> Size * Unit; number_size(Size, _) -> Size. +%% TODO: Simplify when we require OTP 24 +valid_float_size(16) -> erlang:system_info(otp_release) >= "24"; +valid_float_size(32) -> true; +valid_float_size(64) -> true; +valid_float_size(_) -> false. + add_spec(default, Spec) -> Spec; add_spec(Key, Spec) -> [{Key, [], []} | Spec]. @@ -372,7 +383,12 @@ format_error(bittype_signed) -> format_error(bittype_unit) -> "integer and float types require a size specifier if the unit specifier is given"; format_error({bittype_float_size, Other}) -> - io_lib:format("float requires size*unit to be 32 or 64 (default), got: ~p", [Other]); + Message = + case erlang:system_info(otp_release) >= "24" of + true -> "16, 32, or 64"; + false -> "32 or 64" + end, + io_lib:format("float requires size*unit to be ~s (default), got: ~p", [Message, Other]); format_error({invalid_literal, Literal}) -> io_lib:format("invalid literal ~ts in <<>>", ['Elixir.Macro':to_string(Literal)]); format_error({undefined_bittype, Expr}) -> diff --git a/lib/elixir/test/elixir/kernel/expansion_test.exs b/lib/elixir/test/elixir/kernel/expansion_test.exs index 3fbffc1a159..0acba1d2ac7 100644 --- a/lib/elixir/test/elixir/kernel/expansion_test.exs +++ b/lib/elixir/test/elixir/kernel/expansion_test.exs @@ -2387,17 +2387,49 @@ defmodule Kernel.ExpansionTest do end end - test "raises for invalid size * unit for floats" do - message = ~r"float requires size\*unit to be 32 or 64 \(default\), got: 128" + # TODO: Simplify when we require OTP 24 + if System.otp_release() >= "24" do + test "16-bit floats" do + import Kernel, except: [-: 2] - assert_raise CompileError, message, fn -> - expand(quote(do: <<12.3::32*4>>)) + assert expand(quote(do: <<12.3::float-16>>)) |> clean_meta([:alignment]) == + quote(do: <<12.3::float()-size(16)>>) end - message = ~r"float requires size\*unit to be 32 or 64 \(default\), got: 256" + test "raises for invalid size * unit for floats" do + message = ~r"float requires size\*unit to be 16, 32, or 64 \(default\), got: 128" - assert_raise CompileError, message, fn -> - expand(quote(do: <<12.3::256>>)) + assert_raise CompileError, message, fn -> + expand(quote(do: <<12.3::32*4>>)) + end + + message = ~r"float requires size\*unit to be 16, 32, or 64 \(default\), got: 256" + + assert_raise CompileError, message, fn -> + expand(quote(do: <<12.3::256>>)) + end + end + else + test "16-bit floats" do + message = ~r"float requires size\*unit to be 32 or 64 \(default\), got: 16" + + assert_raise CompileError, message, fn -> + expand(quote(do: <<12.3::16>>)) + end + end + + test "raises for invalid size * unit for floats" do + message = ~r"float requires size\*unit to be 32 or 64 \(default\), got: 128" + + assert_raise CompileError, message, fn -> + expand(quote(do: <<12.3::32*4>>)) + end + + message = ~r"float requires size\*unit to be 32 or 64 \(default\), got: 256" + + assert_raise CompileError, message, fn -> + expand(quote(do: <<12.3::256>>)) + end end end From 0dd7f4fc8984cb760c22f7a5134ab70a7f0f5ebc Mon Sep 17 00:00:00 2001 From: Wojtek Mach Date: Sat, 20 Feb 2021 13:46:47 +0100 Subject: [PATCH 5/5] Handle :compile.file/2 returning :error on OTP 24 (#10742) Before OTP 24 when given invalid args, :compile.file/2 would return: iex(1)> :compile.file('a.erl', [{:d, 'foo', 'bar'}, :report]) {:error, :badarg} On OTP 24: iex(1)> :compile.file('a.erl', [{:d, 'foo', 'bar'}, :report]) *** Internal compiler error *** exception error: bad argument in function io_lib:format/2 called as io_lib:format("badly formed '~s'",[{"foo","bar"}]) in call from sys_messages:list_errors/3 (sys_messages.erl, line 53) in call from lists:foreach/2 (lists.erl, line 1342) in call from compile:comp_ret_err/1 (compile.erl, line 546) in call from compile:'-internal_fun/2-anonymous-0-'/2 (compile.erl, line 229) in call from compile:'-do_compile/2-anonymous-0-'/1 (compile.erl, line 219) :error --- lib/mix/lib/mix/tasks/compile.erlang.ex | 5 +++-- lib/mix/test/mix/tasks/compile.erlang_test.exs | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/mix/lib/mix/tasks/compile.erlang.ex b/lib/mix/lib/mix/tasks/compile.erlang.ex index 16b43f9af5f..12262fe926f 100644 --- a/lib/mix/lib/mix/tasks/compile.erlang.ex +++ b/lib/mix/lib/mix/tasks/compile.erlang.ex @@ -100,9 +100,10 @@ defmodule Mix.Tasks.Compile.Erlang do file = Erlang.to_erl_file(Path.rootname(input, ".erl")) case :compile.file(file, erlc_options) do - {:error, :badarg} -> + # TODO: Don't handle {:error, :badarg} when we require OTP 24 + error when error == :error or error == {:error, :badarg} -> message = - "Compiling Erlang #{inspect(file)} failed with ArgumentError, probably because of invalid :erlc_options" + "Compiling Erlang file #{inspect(file)} failed, probably because of invalid :erlc_options" Mix.raise(message) diff --git a/lib/mix/test/mix/tasks/compile.erlang_test.exs b/lib/mix/test/mix/tasks/compile.erlang_test.exs index 414642ebada..316636c02b7 100644 --- a/lib/mix/test/mix/tasks/compile.erlang_test.exs +++ b/lib/mix/test/mix/tasks/compile.erlang_test.exs @@ -14,7 +14,7 @@ defmodule Mix.Tasks.Compile.ErlangTest do @tag erlc_options: [{:d, 'foo', 'bar'}] test "raises on invalid erlc_options" do in_fixture("compile_erlang", fn -> - assert_raise Mix.Error, ~r"failed with ArgumentError", fn -> + assert_raise Mix.Error, ~r"Compiling Erlang file '.*' failed", fn -> capture_io(fn -> Mix.Tasks.Compile.Erlang.run([]) end)