Skip to content

Commit 8e261c0

Browse files
committed
Auto merge of #11049 - Centri3:manual_is_infinite, r=blyxyas,xFrednet
New lints [`manual_is_infinite`] and [`manual_is_finite`] Closes #9665 changelog: New lints [`manual_is_infinite`] and [`manual_is_finite`] [#11049](#11049)
2 parents e5db198 + 41438c2 commit 8e261c0

File tree

8 files changed

+318
-2
lines changed

8 files changed

+318
-2
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4915,6 +4915,8 @@ Released 2018-09-13
49154915
[`manual_flatten`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_flatten
49164916
[`manual_instant_elapsed`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_instant_elapsed
49174917
[`manual_is_ascii_check`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_is_ascii_check
4918+
[`manual_is_finite`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_is_finite
4919+
[`manual_is_infinite`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_is_infinite
49184920
[`manual_let_else`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_let_else
49194921
[`manual_main_separator_str`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_main_separator_str
49204922
[`manual_map`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_map

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
A collection of lints to catch common mistakes and improve your [Rust](https://github.com/rust-lang/rust) code.
77

8-
[There are over 600 lints included in this crate!](https://rust-lang.github.io/rust-clippy/master/index.html)
8+
[There are over 650 lints included in this crate!](https://rust-lang.github.io/rust-clippy/master/index.html)
99

1010
Lints are divided into categories, each with a default [lint level](https://doc.rust-lang.org/rustc/lints/levels.html).
1111
You can choose how much Clippy is supposed to ~~annoy~~ help you by changing the lint level by category.

book/src/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
A collection of lints to catch common mistakes and improve your
77
[Rust](https://github.com/rust-lang/rust) code.
88

9-
[There are over 600 lints included in this crate!](https://rust-lang.github.io/rust-clippy/master/index.html)
9+
[There are over 650 lints included in this crate!](https://rust-lang.github.io/rust-clippy/master/index.html)
1010

1111
Lints are divided into categories, each with a default [lint
1212
level](https://doc.rust-lang.org/rustc/lints/levels.html). You can choose how

clippy_lints/src/declared_lints.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -274,6 +274,8 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
274274
crate::manual_async_fn::MANUAL_ASYNC_FN_INFO,
275275
crate::manual_bits::MANUAL_BITS_INFO,
276276
crate::manual_clamp::MANUAL_CLAMP_INFO,
277+
crate::manual_float_methods::MANUAL_IS_FINITE_INFO,
278+
crate::manual_float_methods::MANUAL_IS_INFINITE_INFO,
277279
crate::manual_is_ascii_check::MANUAL_IS_ASCII_CHECK_INFO,
278280
crate::manual_let_else::MANUAL_LET_ELSE_INFO,
279281
crate::manual_main_separator_str::MANUAL_MAIN_SEPARATOR_STR_INFO,

clippy_lints/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,7 @@ mod manual_assert;
184184
mod manual_async_fn;
185185
mod manual_bits;
186186
mod manual_clamp;
187+
mod manual_float_methods;
187188
mod manual_is_ascii_check;
188189
mod manual_let_else;
189190
mod manual_main_separator_str;
@@ -1073,6 +1074,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
10731074
store.register_late_pass(|_| Box::new(manual_range_patterns::ManualRangePatterns));
10741075
store.register_early_pass(|| Box::new(visibility::Visibility));
10751076
store.register_late_pass(move |_| Box::new(tuple_array_conversions::TupleArrayConversions { msrv: msrv() }));
1077+
store.register_late_pass(|_| Box::new(manual_float_methods::ManualFloatMethods));
10761078
// add lints here, do not remove this comment, it's used in `new_lint`
10771079
}
10781080

Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
use clippy_utils::{
2+
consts::{constant, Constant},
3+
diagnostics::span_lint_and_then,
4+
is_from_proc_macro, path_to_local,
5+
source::snippet_opt,
6+
};
7+
use rustc_errors::Applicability;
8+
use rustc_hir::{BinOpKind, Expr, ExprKind};
9+
use rustc_lint::{LateContext, LateLintPass, Lint, LintContext};
10+
use rustc_middle::lint::in_external_macro;
11+
use rustc_session::{declare_lint_pass, declare_tool_lint};
12+
13+
declare_clippy_lint! {
14+
/// ### What it does
15+
/// Checks for manual `is_infinite` reimplementations
16+
/// (i.e., `x == <float>::INFINITY || x == <float>::NEG_INFINITY`).
17+
///
18+
/// ### Why is this bad?
19+
/// The method `is_infinite` is shorter and more readable.
20+
///
21+
/// ### Example
22+
/// ```rust
23+
/// # let x = 1.0f32;
24+
/// if x == f32::INFINITY || x == f32::NEG_INFINITY {}
25+
/// ```
26+
/// Use instead:
27+
/// ```rust
28+
/// # let x = 1.0f32;
29+
/// if x.is_infinite() {}
30+
/// ```
31+
#[clippy::version = "1.72.0"]
32+
pub MANUAL_IS_INFINITE,
33+
style,
34+
"use dedicated method to check if a float is infinite"
35+
}
36+
declare_clippy_lint! {
37+
/// ### What it does
38+
/// Checks for manual `is_finite` reimplementations
39+
/// (i.e., `x != <float>::INFINITY && x != <float>::NEG_INFINITY`).
40+
///
41+
/// ### Why is this bad?
42+
/// The method `is_finite` is shorter and more readable.
43+
///
44+
/// ### Example
45+
/// ```rust
46+
/// # let x = 1.0f32;
47+
/// if x != f32::INFINITY && x != f32::NEG_INFINITY {}
48+
/// if x.abs() < f32::INFINITY {}
49+
/// ```
50+
/// Use instead:
51+
/// ```rust
52+
/// # let x = 1.0f32;
53+
/// if x.is_finite() {}
54+
/// if x.is_finite() {}
55+
/// ```
56+
#[clippy::version = "1.72.0"]
57+
pub MANUAL_IS_FINITE,
58+
style,
59+
"use dedicated method to check if a float is finite"
60+
}
61+
declare_lint_pass!(ManualFloatMethods => [MANUAL_IS_INFINITE, MANUAL_IS_FINITE]);
62+
63+
#[derive(Clone, Copy)]
64+
enum Variant {
65+
ManualIsInfinite,
66+
ManualIsFinite,
67+
}
68+
69+
impl Variant {
70+
pub fn lint(self) -> &'static Lint {
71+
match self {
72+
Self::ManualIsInfinite => MANUAL_IS_INFINITE,
73+
Self::ManualIsFinite => MANUAL_IS_FINITE,
74+
}
75+
}
76+
77+
pub fn msg(self) -> &'static str {
78+
match self {
79+
Self::ManualIsInfinite => "manually checking if a float is infinite",
80+
Self::ManualIsFinite => "manually checking if a float is finite",
81+
}
82+
}
83+
}
84+
85+
impl<'tcx> LateLintPass<'tcx> for ManualFloatMethods {
86+
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) {
87+
if !in_external_macro(cx.sess(), expr.span)
88+
&& (!cx.param_env.is_const() || cx.tcx.features().active(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+
&& !is_from_proc_macro(cx, expr)
109+
&& let Some(local_snippet) = snippet_opt(cx, first.span)
110+
{
111+
let variant = match (kind.node, lhs_kind.node, rhs_kind.node) {
112+
(BinOpKind::Or, BinOpKind::Eq, BinOpKind::Eq) => Variant::ManualIsInfinite,
113+
(BinOpKind::And, BinOpKind::Ne, BinOpKind::Ne) => Variant::ManualIsFinite,
114+
_ => return,
115+
};
116+
117+
span_lint_and_then(
118+
cx,
119+
variant.lint(),
120+
expr.span,
121+
variant.msg(),
122+
|diag| {
123+
match variant {
124+
Variant::ManualIsInfinite => {
125+
diag.span_suggestion(
126+
expr.span,
127+
"use the dedicated method instead",
128+
format!("{local_snippet}.is_infinite()"),
129+
Applicability::MachineApplicable,
130+
);
131+
},
132+
Variant::ManualIsFinite => {
133+
// TODO: There's probably some better way to do this, i.e., create
134+
// multiple suggestions with notes between each of them
135+
diag.span_suggestion_verbose(
136+
expr.span,
137+
"use the dedicated method instead",
138+
format!("{local_snippet}.is_finite()"),
139+
Applicability::MaybeIncorrect,
140+
)
141+
.span_suggestion_verbose(
142+
expr.span,
143+
"this will alter how it handles NaN; if that is a problem, use instead",
144+
format!("{local_snippet}.is_finite() || {local_snippet}.is_nan()"),
145+
Applicability::MaybeIncorrect,
146+
)
147+
.span_suggestion_verbose(
148+
expr.span,
149+
"or, for conciseness",
150+
format!("!{local_snippet}.is_infinite()"),
151+
Applicability::MaybeIncorrect,
152+
);
153+
},
154+
}
155+
},
156+
);
157+
}
158+
}
159+
}
160+
161+
fn is_infinity(constant: &Constant<'_>) -> bool {
162+
match constant {
163+
Constant::F32(float) => *float == f32::INFINITY,
164+
Constant::F64(float) => *float == f64::INFINITY,
165+
_ => false,
166+
}
167+
}
168+
169+
fn is_neg_infinity(constant: &Constant<'_>) -> bool {
170+
match constant {
171+
Constant::F32(float) => *float == f32::NEG_INFINITY,
172+
Constant::F64(float) => *float == f64::NEG_INFINITY,
173+
_ => false,
174+
}
175+
}

tests/ui/manual_float_methods.rs

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
//@aux-build:proc_macros.rs:proc-macro
2+
#![allow(clippy::needless_if, unused)]
3+
#![warn(clippy::manual_is_infinite, clippy::manual_is_finite)]
4+
#![feature(inline_const)]
5+
6+
#[macro_use]
7+
extern crate proc_macros;
8+
9+
const INFINITE: f32 = f32::INFINITY;
10+
const NEG_INFINITE: f32 = f32::NEG_INFINITY;
11+
12+
fn fn_test() -> f64 {
13+
f64::NEG_INFINITY
14+
}
15+
16+
fn fn_test_not_inf() -> f64 {
17+
112.0
18+
}
19+
20+
fn main() {
21+
let x = 1.0f32;
22+
if x == f32::INFINITY || x == f32::NEG_INFINITY {}
23+
if x != f32::INFINITY && x != f32::NEG_INFINITY {}
24+
if x == INFINITE || x == NEG_INFINITE {}
25+
if x != INFINITE && x != NEG_INFINITE {}
26+
let x = 1.0f64;
27+
if x == f64::INFINITY || x == f64::NEG_INFINITY {}
28+
if x != f64::INFINITY && x != f64::NEG_INFINITY {}
29+
// Don't lint
30+
if x.is_infinite() {}
31+
if x.is_finite() {}
32+
if x.abs() < f64::INFINITY {}
33+
if f64::INFINITY > x.abs() {}
34+
if f64::abs(x) < f64::INFINITY {}
35+
if f64::INFINITY > f64::abs(x) {}
36+
// Is not evaluated by `clippy_utils::constant`
37+
if x != f64::INFINITY && x != fn_test() {}
38+
// Not -inf
39+
if x != f64::INFINITY && x != fn_test_not_inf() {}
40+
const X: f64 = 1.0f64;
41+
// Will be linted if `const_float_classify` is enabled
42+
if const { X == f64::INFINITY || X == f64::NEG_INFINITY } {}
43+
if const { X != f64::INFINITY && X != f64::NEG_INFINITY } {}
44+
external! {
45+
let x = 1.0;
46+
if x == f32::INFINITY || x == f32::NEG_INFINITY {}
47+
if x != f32::INFINITY && x != f32::NEG_INFINITY {}
48+
}
49+
with_span! {
50+
span
51+
let x = 1.0;
52+
if x == f32::INFINITY || x == f32::NEG_INFINITY {}
53+
if x != f32::INFINITY && x != f32::NEG_INFINITY {}
54+
}
55+
}

tests/ui/manual_float_methods.stderr

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
error: manually checking if a float is infinite
2+
--> $DIR/manual_float_methods.rs:22:8
3+
|
4+
LL | if x == f32::INFINITY || x == f32::NEG_INFINITY {}
5+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the dedicated method instead: `x.is_infinite()`
6+
|
7+
= note: `-D clippy::manual-is-infinite` implied by `-D warnings`
8+
9+
error: manually checking if a float is finite
10+
--> $DIR/manual_float_methods.rs:23:8
11+
|
12+
LL | if x != f32::INFINITY && x != f32::NEG_INFINITY {}
13+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
14+
|
15+
= note: `-D clippy::manual-is-finite` implied by `-D warnings`
16+
help: use the dedicated method instead
17+
|
18+
LL | if x.is_finite() {}
19+
| ~~~~~~~~~~~~~
20+
help: this will alter how it handles NaN; if that is a problem, use instead
21+
|
22+
LL | if x.is_finite() || x.is_nan() {}
23+
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~
24+
help: or, for conciseness
25+
|
26+
LL | if !x.is_infinite() {}
27+
| ~~~~~~~~~~~~~~~~
28+
29+
error: manually checking if a float is infinite
30+
--> $DIR/manual_float_methods.rs:24:8
31+
|
32+
LL | if x == INFINITE || x == NEG_INFINITE {}
33+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the dedicated method instead: `x.is_infinite()`
34+
35+
error: manually checking if a float is finite
36+
--> $DIR/manual_float_methods.rs:25:8
37+
|
38+
LL | if x != INFINITE && x != NEG_INFINITE {}
39+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
40+
|
41+
help: use the dedicated method instead
42+
|
43+
LL | if x.is_finite() {}
44+
| ~~~~~~~~~~~~~
45+
help: this will alter how it handles NaN; if that is a problem, use instead
46+
|
47+
LL | if x.is_finite() || x.is_nan() {}
48+
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~
49+
help: or, for conciseness
50+
|
51+
LL | if !x.is_infinite() {}
52+
| ~~~~~~~~~~~~~~~~
53+
54+
error: manually checking if a float is infinite
55+
--> $DIR/manual_float_methods.rs:27:8
56+
|
57+
LL | if x == f64::INFINITY || x == f64::NEG_INFINITY {}
58+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the dedicated method instead: `x.is_infinite()`
59+
60+
error: manually checking if a float is finite
61+
--> $DIR/manual_float_methods.rs:28:8
62+
|
63+
LL | if x != f64::INFINITY && x != f64::NEG_INFINITY {}
64+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
65+
|
66+
help: use the dedicated method instead
67+
|
68+
LL | if x.is_finite() {}
69+
| ~~~~~~~~~~~~~
70+
help: this will alter how it handles NaN; if that is a problem, use instead
71+
|
72+
LL | if x.is_finite() || x.is_nan() {}
73+
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~
74+
help: or, for conciseness
75+
|
76+
LL | if !x.is_infinite() {}
77+
| ~~~~~~~~~~~~~~~~
78+
79+
error: aborting due to 6 previous errors
80+

0 commit comments

Comments
 (0)