diff --git a/lib/elixir/src/elixir.hrl b/lib/elixir/src/elixir.hrl index 3199ea2ebea..5d4389850eb 100644 --- a/lib/elixir/src/elixir.hrl +++ b/lib/elixir/src/elixir.hrl @@ -15,7 +15,7 @@ }). -record(elixir_erl, { - context=nil, %% can be match, guards or nil + context=nil, %% can be match, guard or nil extra=nil, %% extra information about the context, like pin_guard and map_key caller=false, %% when true, it means caller was invoked var_names=#{}, %% maps of defined variables and their alias diff --git a/lib/elixir/src/elixir_erl.erl b/lib/elixir/src/elixir_erl.erl index 41a519bf0a4..99aed8085e5 100644 --- a/lib/elixir/src/elixir_erl.erl +++ b/lib/elixir/src/elixir_erl.erl @@ -74,6 +74,13 @@ elixir_to_erl(Tree, Ann) when is_atom(Tree) -> {atom, Ann, Tree}; elixir_to_erl(Tree, Ann) when is_integer(Tree) -> {integer, Ann, Tree}; +elixir_to_erl(Tree, Ann) when is_float(Tree), Tree == 0.0 -> + Op = + case <> of + <<1:1,_:63>> -> '-'; + _ -> '+' + end, + {op, Ann, Op, {float, Ann, 0.0}}; elixir_to_erl(Tree, Ann) when is_float(Tree) -> {float, Ann, Tree}; elixir_to_erl(Tree, Ann) when is_binary(Tree) -> diff --git a/lib/elixir/src/elixir_expand.erl b/lib/elixir/src/elixir_expand.erl index 62b1f7c5583..904d85db631 100644 --- a/lib/elixir/src/elixir_expand.erl +++ b/lib/elixir/src/elixir_expand.erl @@ -456,6 +456,10 @@ expand(Pid, S, E) when is_pid(Pid) -> {Pid, E} end; +expand(Float, S, #{context := match} = E) when is_float(Float), Float == 0.0 -> + elixir_errors:file_warn([], E, ?MODULE, invalid_match_on_zero_float), + {Float, S, E}; + expand(Other, S, E) when is_number(Other); is_atom(Other); is_binary(Other) -> {Other, S, E}; @@ -861,7 +865,7 @@ expand_remote(Receiver, DotMeta, Right, Meta, NoParens, Args, S, SL, #{context : true -> AttachedDotMeta = attach_context_module(Receiver, DotMeta, E), - {EArgs, {SA, _}, EA} = mapfold(fun expand_arg/3, {SL, S}, E, Args), + {EArgs, {SA, _}, EA} = expand_remote_args(Receiver, Right, Args, {SL, S}, E), case rewrite(Context, Receiver, AttachedDotMeta, Right, Meta, EArgs, S) of {ok, Rewritten} -> @@ -876,6 +880,12 @@ expand_remote(Receiver, DotMeta, Right, Meta, _NoParens, Args, _, _, E) -> Call = {{'.', DotMeta, [Receiver, Right]}, Meta, Args}, file_error(Meta, E, ?MODULE, {invalid_call, Call}). +%% 0.0 inside a match warns. It must be either +0.0 or -0.0. +%% Therefore we want to avoid the warning here. +expand_remote_args(erlang, '+', [Arg], PairS, E) when is_number(Arg) -> {[Arg], PairS, E}; +expand_remote_args(erlang, '-', [Arg], PairS, E) when is_number(Arg) -> {[Arg], PairS, E}; +expand_remote_args(_Remote, _Fun, Args, PairS, E) -> mapfold(fun expand_arg/3, PairS, E, Args). + attach_context_module(_Receiver, Meta, #{function := nil}) -> Meta; attach_context_module(Receiver, Meta, #{context_modules := [Ctx | _], module := Mod}) @@ -1160,6 +1170,8 @@ assert_no_underscore_clause_in_cond(_Other, _E) -> guard_context(#elixir_ex{prematch={_, _, {bitsize, _}}}) -> "bitstring size specifier"; guard_context(_) -> "guards". +format_error(invalid_match_on_zero_float) -> + "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"; format_error({remote_nullary_no_parens, Expr}) -> String = 'Elixir.String':replace_suffix('Elixir.Macro':to_string(Expr), <<"()">>, <<>>), io_lib:format("parentheses are required for function calls with no arguments, got: ~ts", [String]); diff --git a/lib/elixir/test/elixir/kernel/expansion_test.exs b/lib/elixir/test/elixir/kernel/expansion_test.exs index f7042df3a78..82d472afb97 100644 --- a/lib/elixir/test/elixir/kernel/expansion_test.exs +++ b/lib/elixir/test/elixir/kernel/expansion_test.exs @@ -468,6 +468,16 @@ defmodule Kernel.ExpansionTest do end end + describe "floats" do + test "cannot be 0.0 inside match" do + assert capture_io(:stderr, fn -> expand(quote(do: 0.0 = 0.0)) end) =~ + "pattern matching on 0.0 is equivalent to matching only on +0.0 from Erlang/OTP 27+" + + assert {:=, [], [+0.0, +0.0]} = expand(quote(do: +0.0 = 0.0)) + assert {:=, [], [-0.0, +0.0]} = expand(quote(do: -0.0 = 0.0)) + end + end + describe "tuples" do test "expanded as arguments" do assert expand(quote(do: {after_expansion = 1, a})) == quote(do: {after_expansion = 1, a()}) diff --git a/lib/elixir/test/erlang/control_test.erl b/lib/elixir/test/erlang/control_test.erl index fbe853bd2d3..7f3337f7880 100644 --- a/lib/elixir/test/erlang/control_test.erl +++ b/lib/elixir/test/erlang/control_test.erl @@ -12,6 +12,12 @@ cond_line_test() -> {clause, 3, _, _, _}] } = to_erl("cond do\n 1 -> :ok\n 2 -> :ok\nend"). +float_match_test() -> + {'case', _, _, + [{clause, _, [{op, _, '+', {float, _, 0.0}}], [], [{atom, _, pos}]}, + {clause, _, [{op, _, '-', {float, _, 0.0}}], [], [{atom, _, neg}]}] + } = to_erl("case X do\n +0.0 -> :pos\n -0.0 -> :neg\nend"). + % Optimized optimized_if_test() ->