]> git.lizzy.rs Git - rust.git/blob - src/tools/clippy/clippy_lints/src/operators/float_cmp.rs
Rollup merge of #103305 - c410-f3r:moar-errors, r=petrochenkov
[rust.git] / src / tools / clippy / clippy_lints / src / operators / float_cmp.rs
1 use clippy_utils::consts::{constant, Constant};
2 use clippy_utils::diagnostics::span_lint_and_then;
3 use clippy_utils::get_item_name;
4 use clippy_utils::sugg::Sugg;
5 use if_chain::if_chain;
6 use rustc_errors::Applicability;
7 use rustc_hir::{BinOpKind, Expr, ExprKind, UnOp};
8 use rustc_lint::LateContext;
9 use rustc_middle::ty;
10
11 use super::{FLOAT_CMP, FLOAT_CMP_CONST};
12
13 pub(crate) fn check<'tcx>(
14     cx: &LateContext<'tcx>,
15     expr: &'tcx Expr<'_>,
16     op: BinOpKind,
17     left: &'tcx Expr<'_>,
18     right: &'tcx Expr<'_>,
19 ) {
20     if (op == BinOpKind::Eq || op == BinOpKind::Ne) && (is_float(cx, left) || is_float(cx, right)) {
21         if is_allowed(cx, left) || is_allowed(cx, right) {
22             return;
23         }
24
25         // Allow comparing the results of signum()
26         if is_signum(cx, left) && is_signum(cx, right) {
27             return;
28         }
29
30         if let Some(name) = get_item_name(cx, expr) {
31             let name = name.as_str();
32             if name == "eq" || name == "ne" || name == "is_nan" || name.starts_with("eq_") || name.ends_with("_eq") {
33                 return;
34             }
35         }
36         let is_comparing_arrays = is_array(cx, left) || is_array(cx, right);
37         let (lint, msg) = get_lint_and_message(
38             is_named_constant(cx, left) || is_named_constant(cx, right),
39             is_comparing_arrays,
40         );
41         span_lint_and_then(cx, lint, expr.span, msg, |diag| {
42             let lhs = Sugg::hir(cx, left, "..");
43             let rhs = Sugg::hir(cx, right, "..");
44
45             if !is_comparing_arrays {
46                 diag.span_suggestion(
47                     expr.span,
48                     "consider comparing them within some margin of error",
49                     format!(
50                         "({}).abs() {} error_margin",
51                         lhs - rhs,
52                         if op == BinOpKind::Eq { '<' } else { '>' }
53                     ),
54                     Applicability::HasPlaceholders, // snippet
55                 );
56             }
57             diag.note("`f32::EPSILON` and `f64::EPSILON` are available for the `error_margin`");
58         });
59     }
60 }
61
62 fn get_lint_and_message(
63     is_comparing_constants: bool,
64     is_comparing_arrays: bool,
65 ) -> (&'static rustc_lint::Lint, &'static str) {
66     if is_comparing_constants {
67         (
68             FLOAT_CMP_CONST,
69             if is_comparing_arrays {
70                 "strict comparison of `f32` or `f64` constant arrays"
71             } else {
72                 "strict comparison of `f32` or `f64` constant"
73             },
74         )
75     } else {
76         (
77             FLOAT_CMP,
78             if is_comparing_arrays {
79                 "strict comparison of `f32` or `f64` arrays"
80             } else {
81                 "strict comparison of `f32` or `f64`"
82             },
83         )
84     }
85 }
86
87 fn is_named_constant<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> bool {
88     if let Some((_, res)) = constant(cx, cx.typeck_results(), expr) {
89         res
90     } else {
91         false
92     }
93 }
94
95 fn is_allowed<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> bool {
96     match constant(cx, cx.typeck_results(), expr) {
97         Some((Constant::F32(f), _)) => f == 0.0 || f.is_infinite(),
98         Some((Constant::F64(f), _)) => f == 0.0 || f.is_infinite(),
99         Some((Constant::Vec(vec), _)) => vec.iter().all(|f| match f {
100             Constant::F32(f) => *f == 0.0 || (*f).is_infinite(),
101             Constant::F64(f) => *f == 0.0 || (*f).is_infinite(),
102             _ => false,
103         }),
104         _ => false,
105     }
106 }
107
108 // Return true if `expr` is the result of `signum()` invoked on a float value.
109 fn is_signum(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
110     // The negation of a signum is still a signum
111     if let ExprKind::Unary(UnOp::Neg, child_expr) = expr.kind {
112         return is_signum(cx, child_expr);
113     }
114
115     if_chain! {
116         if let ExprKind::MethodCall(method_name, self_arg, ..) = expr.kind;
117         if sym!(signum) == method_name.ident.name;
118         // Check that the receiver of the signum() is a float (expressions[0] is the receiver of
119         // the method call)
120         then {
121             return is_float(cx, self_arg);
122         }
123     }
124     false
125 }
126
127 fn is_float(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
128     let value = &cx.typeck_results().expr_ty(expr).peel_refs().kind();
129
130     if let ty::Array(arr_ty, _) = value {
131         return matches!(arr_ty.kind(), ty::Float(_));
132     };
133
134     matches!(value, ty::Float(_))
135 }
136
137 fn is_array(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
138     matches!(&cx.typeck_results().expr_ty(expr).peel_refs().kind(), ty::Array(_, _))
139 }