Skip to content

Commit 9c0fa3c

Browse files
authored
Warn when matching on 0.0 and generate erl AST for +/-0.0 (#12949)
1 parent 1ecdd2d commit 9c0fa3c

File tree

5 files changed

+56
-3
lines changed

5 files changed

+56
-3
lines changed

lib/elixir/src/elixir_erl.erl

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,14 @@ elixir_to_erl(Tree, Ann) when is_atom(Tree) ->
7474
{atom, Ann, Tree};
7575
elixir_to_erl(Tree, Ann) when is_integer(Tree) ->
7676
{integer, Ann, Tree};
77+
elixir_to_erl(Tree, Ann) when is_float(Tree), Tree == 0.0 ->
78+
% 0.0 needs to be rewritten as the AST for +0.0 in matches
79+
Op =
80+
case <<Tree/float>> of
81+
<<1:1,_:63>> -> '-';
82+
_ -> '+'
83+
end,
84+
{op, Ann, Op, {float, Ann, 0.0}};
7785
elixir_to_erl(Tree, Ann) when is_float(Tree) ->
7886
{float, Ann, Tree};
7987
elixir_to_erl(Tree, Ann) when is_binary(Tree) ->

lib/elixir/src/elixir_expand.erl

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -456,6 +456,10 @@ expand(Pid, S, E) when is_pid(Pid) ->
456456
{Pid, E}
457457
end;
458458

459+
expand(Zero, S, #{context := match} = E) when is_float(Zero), Zero == 0.0 ->
460+
elixir_errors:file_warn([], E, ?MODULE, invalid_match_on_zero_float),
461+
{Zero, S, E};
462+
459463
expand(Other, S, E) when is_number(Other); is_atom(Other); is_binary(Other) ->
460464
{Other, S, E};
461465

@@ -1163,6 +1167,8 @@ guard_context(_) -> "guards".
11631167
format_error({remote_nullary_no_parens, Expr}) ->
11641168
String = 'Elixir.String':replace_suffix('Elixir.Macro':to_string(Expr), <<"()">>, <<>>),
11651169
io_lib:format("parentheses are required for function calls with no arguments, got: ~ts", [String]);
1170+
format_error(invalid_match_on_zero_float) ->
1171+
"pattern matching on 0.0 is equivalent to matching only on +0.0 from Erlang/OTP 27+. Instead you must match on +0.0 or -0.0";
11661172
format_error({useless_literal, Term}) ->
11671173
io_lib:format("code block contains unused literal ~ts "
11681174
"(remove the literal or assign it to _ to avoid warnings)",

lib/elixir/test/elixir/kernel/expansion_test.exs

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -468,6 +468,16 @@ defmodule Kernel.ExpansionTest do
468468
end
469469
end
470470

471+
describe "floats" do
472+
test "cannot be 0.0 inside match" do
473+
assert capture_io(:stderr, fn -> expand(quote(do: 0.0 = 0.0)) end) =~
474+
"pattern matching on 0.0 is equivalent to matching only on +0.0 from Erlang/OTP 27+"
475+
476+
assert {:=, [], [+0.0, +0.0]} = expand(quote(do: +0.0 = 0.0))
477+
assert {:=, [], [-0.0, +0.0]} = expand(quote(do: -0.0 = 0.0))
478+
end
479+
end
480+
471481
describe "tuples" do
472482
test "expanded as arguments" do
473483
assert expand(quote(do: {after_expansion = 1, a})) == quote(do: {after_expansion = 1, a()})
@@ -715,8 +725,11 @@ defmodule Kernel.ExpansionTest do
715725
expand(quote(do: [1] ++ 2 ++ [3] = [1, 2, 3]))
716726
end)
717727

718-
assert {:=, _, [-1, {{:., _, [:erlang, :-]}, _, [1]}]} = expand(quote(do: -1 = -1))
719-
assert {:=, _, [1, {{:., _, [:erlang, :+]}, _, [1]}]} = expand(quote(do: +1 = +1))
728+
assert {:=, _, [-1, {{:., _, [:erlang, :-]}, _, [1]}]} =
729+
expand(quote(do: -1 = -1))
730+
731+
assert {:=, _, [1, {{:., _, [:erlang, :+]}, _, [1]}]} =
732+
expand(quote(do: +1 = +1))
720733

721734
assert {:=, _, [[{:|, _, [1, [{:|, _, [2, 3]}]]}], [1, 2, 3]]} =
722735
expand(quote(do: [1] ++ [2] ++ 3 = [1, 2, 3]))

lib/elixir/test/elixir/module/types/pattern_test.exs

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,20 @@ defmodule Module.Types.PatternTest do
1515
end
1616
end
1717

18+
defmacrop quoted_pattern_with_diagnostics(patterns) do
19+
{ast, diagnostics} = Code.with_diagnostics(fn -> expand_head(patterns, true) end)
20+
21+
quote do
22+
{patterns, true} = unquote(Macro.escape(ast))
23+
24+
result =
25+
Pattern.of_pattern(patterns, new_stack(), new_context())
26+
|> lift_result()
27+
28+
{result, unquote(Macro.escape(diagnostics))}
29+
end
30+
end
31+
1832
defmacrop quoted_head(patterns, guards \\ []) do
1933
quote do
2034
{patterns, guards} = unquote(Macro.escape(expand_head(patterns, guards)))
@@ -76,8 +90,14 @@ defmodule Module.Types.PatternTest do
7690
assert quoted_pattern(false) == {:ok, {:atom, false}}
7791
assert quoted_pattern(:foo) == {:ok, {:atom, :foo}}
7892
assert quoted_pattern(0) == {:ok, :integer}
79-
assert quoted_pattern(0.0) == {:ok, :float}
93+
assert quoted_pattern(+0.0) == {:ok, :float}
94+
assert quoted_pattern(-0.0) == {:ok, :float}
8095
assert quoted_pattern("foo") == {:ok, :binary}
96+
97+
assert {{:ok, :float}, [diagnostic]} = quoted_pattern_with_diagnostics(0.0)
98+
99+
assert diagnostic.message =~
100+
"pattern matching on 0.0 is equivalent to matching only on +0.0"
81101
end
82102

83103
test "list" do

lib/elixir/test/erlang/control_test.erl

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,12 @@ cond_line_test() ->
1212
{clause, 3, _, _, _}]
1313
} = to_erl("cond do\n 1 -> :ok\n 2 -> :ok\nend").
1414

15+
float_match_test() ->
16+
{'case', _, _,
17+
[{clause, _, [{op, _, '+', {float, _, 0.0}}], [], [{atom, _, pos}]},
18+
{clause, _, [{op, _, '-', {float, _, 0.0}}], [], [{atom, _, neg}]}]
19+
} = to_erl("case X do\n +0.0 -> :pos\n -0.0 -> :neg\nend").
20+
1521
% Optimized
1622

1723
optimized_if_test() ->

0 commit comments

Comments
 (0)