]> git.proxmox.com Git - rustc.git/blob - src/tools/clippy/clippy_lints/src/manual_float_methods.rs
New upstream version 1.76.0+dfsg1
[rustc.git] / src / tools / clippy / clippy_lints / src / manual_float_methods.rs
1 use clippy_utils::consts::{constant, Constant};
2 use clippy_utils::diagnostics::span_lint_and_then;
3 use clippy_utils::source::snippet_opt;
4 use clippy_utils::{is_from_proc_macro, path_to_local};
5 use rustc_errors::Applicability;
6 use rustc_hir::{BinOpKind, Constness, Expr, ExprKind};
7 use rustc_lint::{LateContext, LateLintPass, Lint, LintContext};
8 use rustc_middle::lint::in_external_macro;
9 use rustc_session::declare_lint_pass;
10
11 declare_clippy_lint! {
12 /// ### What it does
13 /// Checks for manual `is_infinite` reimplementations
14 /// (i.e., `x == <float>::INFINITY || x == <float>::NEG_INFINITY`).
15 ///
16 /// ### Why is this bad?
17 /// The method `is_infinite` is shorter and more readable.
18 ///
19 /// ### Example
20 /// ```no_run
21 /// # let x = 1.0f32;
22 /// if x == f32::INFINITY || x == f32::NEG_INFINITY {}
23 /// ```
24 /// Use instead:
25 /// ```no_run
26 /// # let x = 1.0f32;
27 /// if x.is_infinite() {}
28 /// ```
29 #[clippy::version = "1.73.0"]
30 pub MANUAL_IS_INFINITE,
31 style,
32 "use dedicated method to check if a float is infinite"
33 }
34 declare_clippy_lint! {
35 /// ### What it does
36 /// Checks for manual `is_finite` reimplementations
37 /// (i.e., `x != <float>::INFINITY && x != <float>::NEG_INFINITY`).
38 ///
39 /// ### Why is this bad?
40 /// The method `is_finite` is shorter and more readable.
41 ///
42 /// ### Example
43 /// ```no_run
44 /// # let x = 1.0f32;
45 /// if x != f32::INFINITY && x != f32::NEG_INFINITY {}
46 /// if x.abs() < f32::INFINITY {}
47 /// ```
48 /// Use instead:
49 /// ```no_run
50 /// # let x = 1.0f32;
51 /// if x.is_finite() {}
52 /// if x.is_finite() {}
53 /// ```
54 #[clippy::version = "1.73.0"]
55 pub MANUAL_IS_FINITE,
56 style,
57 "use dedicated method to check if a float is finite"
58 }
59 declare_lint_pass!(ManualFloatMethods => [MANUAL_IS_INFINITE, MANUAL_IS_FINITE]);
60
61 #[derive(Clone, Copy)]
62 enum Variant {
63 ManualIsInfinite,
64 ManualIsFinite,
65 }
66
67 impl Variant {
68 pub fn lint(self) -> &'static Lint {
69 match self {
70 Self::ManualIsInfinite => MANUAL_IS_INFINITE,
71 Self::ManualIsFinite => MANUAL_IS_FINITE,
72 }
73 }
74
75 pub fn msg(self) -> &'static str {
76 match self {
77 Self::ManualIsInfinite => "manually checking if a float is infinite",
78 Self::ManualIsFinite => "manually checking if a float is finite",
79 }
80 }
81 }
82
83 impl<'tcx> LateLintPass<'tcx> for ManualFloatMethods {
84 fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) {
85 if !in_external_macro(cx.sess(), expr.span)
86 && (
87 matches!(cx.tcx.constness(cx.tcx.hir().enclosing_body_owner(expr.hir_id)), Constness::NotConst)
88 || cx.tcx.features().declared(sym!(const_float_classify))
89 ) && let ExprKind::Binary(kind, lhs, rhs) = expr.kind
90 && let ExprKind::Binary(lhs_kind, lhs_lhs, lhs_rhs) = lhs.kind
91 && let ExprKind::Binary(rhs_kind, rhs_lhs, rhs_rhs) = rhs.kind
92 // Checking all possible scenarios using a function would be a hopeless task, as we have
93 // 16 possible alignments of constants/operands. For now, let's use `partition`.
94 && let (operands, constants) = [lhs_lhs, lhs_rhs, rhs_lhs, rhs_rhs]
95 .into_iter()
96 .partition::<Vec<&Expr<'_>>, _>(|i| path_to_local(i).is_some())
97 && let [first, second] = &*operands
98 && let Some([const_1, const_2]) = constants
99 .into_iter()
100 .map(|i| constant(cx, cx.typeck_results(), i))
101 .collect::<Option<Vec<_>>>()
102 .as_deref()
103 && path_to_local(first).is_some_and(|f| path_to_local(second).is_some_and(|s| f == s))
104 // The actual infinity check, we also allow `NEG_INFINITY` before` INFINITY` just in
105 // case somebody does that for some reason
106 && (is_infinity(const_1) && is_neg_infinity(const_2)
107 || is_neg_infinity(const_1) && is_infinity(const_2))
108 && let Some(local_snippet) = snippet_opt(cx, first.span)
109 {
110 let variant = match (kind.node, lhs_kind.node, rhs_kind.node) {
111 (BinOpKind::Or, BinOpKind::Eq, BinOpKind::Eq) => Variant::ManualIsInfinite,
112 (BinOpKind::And, BinOpKind::Ne, BinOpKind::Ne) => Variant::ManualIsFinite,
113 _ => return,
114 };
115 if is_from_proc_macro(cx, expr) {
116 return;
117 }
118
119 span_lint_and_then(cx, variant.lint(), expr.span, variant.msg(), |diag| {
120 match variant {
121 Variant::ManualIsInfinite => {
122 diag.span_suggestion(
123 expr.span,
124 "use the dedicated method instead",
125 format!("{local_snippet}.is_infinite()"),
126 Applicability::MachineApplicable,
127 );
128 },
129 Variant::ManualIsFinite => {
130 // TODO: There's probably some better way to do this, i.e., create
131 // multiple suggestions with notes between each of them
132 diag.span_suggestion_verbose(
133 expr.span,
134 "use the dedicated method instead",
135 format!("{local_snippet}.is_finite()"),
136 Applicability::MaybeIncorrect,
137 )
138 .span_suggestion_verbose(
139 expr.span,
140 "this will alter how it handles NaN; if that is a problem, use instead",
141 format!("{local_snippet}.is_finite() || {local_snippet}.is_nan()"),
142 Applicability::MaybeIncorrect,
143 )
144 .span_suggestion_verbose(
145 expr.span,
146 "or, for conciseness",
147 format!("!{local_snippet}.is_infinite()"),
148 Applicability::MaybeIncorrect,
149 );
150 },
151 }
152 });
153 }
154 }
155 }
156
157 fn is_infinity(constant: &Constant<'_>) -> bool {
158 match constant {
159 Constant::F32(float) => *float == f32::INFINITY,
160 Constant::F64(float) => *float == f64::INFINITY,
161 _ => false,
162 }
163 }
164
165 fn is_neg_infinity(constant: &Constant<'_>) -> bool {
166 match constant {
167 Constant::F32(float) => *float == f32::NEG_INFINITY,
168 Constant::F64(float) => *float == f64::NEG_INFINITY,
169 _ => false,
170 }
171 }