From 67a4acfeb8e95d6c86bff5349267b4b68aee87da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Sat, 12 Aug 2023 10:32:39 +0200 Subject: [PATCH] Warn when matching on naked 0.0 for Erlang/OTP 27 --- lib/elixir/src/elixir.hrl | 2 +- lib/elixir/src/elixir_erl.erl | 7 +++++++ lib/elixir/src/elixir_expand.erl | 14 +++++++++++++- lib/elixir/test/elixir/kernel/expansion_test.exs | 10 ++++++++++ lib/elixir/test/erlang/control_test.erl | 6 ++++++ 5 files changed, 37 insertions(+), 2 deletions(-) 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 dd5d5129d2d..3ea94254953 100644 --- a/lib/elixir/src/elixir_expand.erl +++ b/lib/elixir/src/elixir_expand.erl @@ -451,6 +451,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}; @@ -860,7 +864,7 @@ expand_remote(Receiver, DotMeta, Right, Meta, Args, S, SL, #{context := Context} is_atom(Receiver) andalso elixir_env:trace({remote_function, Meta, Receiver, Right, length(Args)}, 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} -> @@ -875,6 +879,12 @@ expand_remote(Receiver, DotMeta, Right, Meta, 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}) @@ -1159,6 +1169,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({useless_literal, Term}) -> io_lib:format("code block contains unused literal ~ts " "(remove the literal or assign it to _ to avoid warnings)", 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() ->