Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions lib/elixir/lib/kernel/special_forms.ex
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
22 changes: 19 additions & 3 deletions lib/elixir/src/elixir_bitstring.erl
Original file line number Diff line number Diff line change
Expand Up @@ -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 ->
Expand All @@ -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].

Expand Down Expand Up @@ -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}) ->
Expand Down
11 changes: 11 additions & 0 deletions lib/elixir/src/elixir_erl_compiler.erl
Original file line number Diff line number Diff line change
Expand Up @@ -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)];
Expand Down
46 changes: 39 additions & 7 deletions lib/elixir/test/elixir/kernel/expansion_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
13 changes: 11 additions & 2 deletions lib/elixir/test/elixir/kernel/warning_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -931,22 +931,31 @@ 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("""
defmodule Sample1 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("""
defmodule Sample2 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
Expand Down
9 changes: 7 additions & 2 deletions lib/mix/lib/mix/compilers/erlang.ex
Original file line number Diff line number Diff line change
Expand Up @@ -271,7 +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 = if is_integer(line) and line >= 1, do: line
position = line(line)

%Mix.Task.Compiler.Diagnostic{
file: Path.absname(file),
Expand All @@ -288,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
5 changes: 3 additions & 2 deletions lib/mix/lib/mix/tasks/compile.erlang.ex
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
2 changes: 1 addition & 1 deletion lib/mix/test/mix/tasks/compile.erlang_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
4 changes: 3 additions & 1 deletion lib/mix/test/mix/tasks/compile.yecc_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down