about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG.md2
-rw-r--r--clippy_lints/src/declared_lints.rs2
-rw-r--r--clippy_lints/src/lib.rs2
-rw-r--r--clippy_lints/src/manual_float_methods.rs106
-rw-r--r--clippy_utils/src/consts.rs11
-rw-r--r--tests/ui/manual_float_methods.fixed37
-rw-r--r--tests/ui/manual_float_methods.rs37
-rw-r--r--tests/ui/manual_float_methods.stderr42
8 files changed, 239 insertions, 0 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index e2004c2931d..59719080e44 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4941,6 +4941,8 @@ Released 2018-09-13
 [`manual_flatten`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_flatten
 [`manual_instant_elapsed`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_instant_elapsed
 [`manual_is_ascii_check`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_is_ascii_check
+[`manual_is_finite`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_is_finite
+[`manual_is_infinite`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_is_infinite
 [`manual_let_else`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_let_else
 [`manual_main_separator_str`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_main_separator_str
 [`manual_map`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_map
diff --git a/clippy_lints/src/declared_lints.rs b/clippy_lints/src/declared_lints.rs
index ca97db04079..f8b8b94dd29 100644
--- a/clippy_lints/src/declared_lints.rs
+++ b/clippy_lints/src/declared_lints.rs
@@ -273,6 +273,8 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
     crate::manual_async_fn::MANUAL_ASYNC_FN_INFO,
     crate::manual_bits::MANUAL_BITS_INFO,
     crate::manual_clamp::MANUAL_CLAMP_INFO,
+    crate::manual_float_methods::MANUAL_IS_FINITE_INFO,
+    crate::manual_float_methods::MANUAL_IS_INFINITE_INFO,
     crate::manual_is_ascii_check::MANUAL_IS_ASCII_CHECK_INFO,
     crate::manual_let_else::MANUAL_LET_ELSE_INFO,
     crate::manual_main_separator_str::MANUAL_MAIN_SEPARATOR_STR_INFO,
diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs
index 00d46025caa..9abcbe01176 100644
--- a/clippy_lints/src/lib.rs
+++ b/clippy_lints/src/lib.rs
@@ -184,6 +184,7 @@ mod manual_assert;
 mod manual_async_fn;
 mod manual_bits;
 mod manual_clamp;
+mod manual_float_methods;
 mod manual_is_ascii_check;
 mod manual_let_else;
 mod manual_main_separator_str;
@@ -1073,6 +1074,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
     store.register_late_pass(|_| Box::new(manual_range_patterns::ManualRangePatterns));
     store.register_early_pass(|| Box::new(visibility::Visibility));
     store.register_late_pass(move |_| Box::new(tuple_array_conversions::TupleArrayConversions { msrv: msrv() }));
+    store.register_late_pass(|_| Box::new(manual_float_methods::ManualFloatMethods));
     // add lints here, do not remove this comment, it's used in `new_lint`
 }
 
diff --git a/clippy_lints/src/manual_float_methods.rs b/clippy_lints/src/manual_float_methods.rs
new file mode 100644
index 00000000000..abf1eb788dd
--- /dev/null
+++ b/clippy_lints/src/manual_float_methods.rs
@@ -0,0 +1,106 @@
+use clippy_utils::{
+    consts::constant, diagnostics::span_lint_and_sugg, is_from_proc_macro, path_to_local, source::snippet_opt,
+};
+use rustc_errors::Applicability;
+use rustc_hir::{BinOpKind, Expr, ExprKind};
+use rustc_lint::{LateContext, LateLintPass, LintContext};
+use rustc_middle::lint::in_external_macro;
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+
+declare_clippy_lint! {
+    /// ### What it does
+    /// Checks for `x == <float>::INFINITY || x == <float>::NEG_INFINITY`.
+    ///
+    /// ### Why is this bad?
+    /// This should use the dedicated method instead, `is_infinite`.
+    ///
+    /// ### Example
+    /// ```rust
+    /// # let x = 1.0f32;
+    /// if x == f32::INFINITY || x == f32::NEG_INFINITY {}
+    /// ```
+    /// Use instead:
+    /// ```rust
+    /// # let x = 1.0f32;
+    /// if x.is_infinite() {}
+    /// ```
+    #[clippy::version = "1.72.0"]
+    pub MANUAL_IS_INFINITE,
+    style,
+    "use dedicated method to check if a float is infinite"
+}
+declare_clippy_lint! {
+    /// ### What it does
+    /// Checks for `x != <float>::INFINITY && x != <float>::NEG_INFINITY`.
+    ///
+    /// ### Why is this bad?
+    /// This should use the dedicated method instead, `is_finite`.
+    ///
+    /// ### Example
+    /// ```rust
+    /// # let x = 1.0f32;
+    /// if x != f32::INFINITY && x != f32::NEG_INFINITY {}
+    /// ```
+    /// Use instead:
+    /// ```rust
+    /// # let x = 1.0f32;
+    /// if x.is_finite() {}
+    /// ```
+    #[clippy::version = "1.72.0"]
+    pub MANUAL_IS_FINITE,
+    style,
+    "use dedicated method to check if a float is finite"
+}
+declare_lint_pass!(ManualFloatMethods => [MANUAL_IS_INFINITE, MANUAL_IS_FINITE]);
+
+impl<'tcx> LateLintPass<'tcx> for ManualFloatMethods {
+    fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) {
+        if !in_external_macro(cx.sess(), expr.span)
+            && let ExprKind::Binary(kind, lhs, rhs) = expr.kind
+            && let ExprKind::Binary(lhs_kind, lhs_lhs, lhs_rhs) = lhs.kind
+            && let ExprKind::Binary(rhs_kind, rhs_lhs, rhs_rhs) = rhs.kind
+            && let (operands, consts) = [lhs_lhs, lhs_rhs, rhs_lhs, rhs_rhs]
+                .into_iter()
+                .partition::<Vec<&Expr<'_>>, _>(|i| path_to_local(i).is_some())
+            && let [first, second] = &*operands
+            && let Some([const_1, const_2]) = consts
+                .into_iter()
+                .map(|i| constant(cx, cx.typeck_results(), i).and_then(|c| c.to_bits()))
+                .collect::<Option<Vec<_>>>()
+                .as_deref()
+            && path_to_local(first).is_some_and(|f| path_to_local(second).is_some_and(|s| f == s))
+            && (is_infinity(*const_1) && is_neg_infinity(*const_2)
+                || is_neg_infinity(*const_1) && is_infinity(*const_2))
+            && let Some(local_snippet) = snippet_opt(cx, first.span)
+            && !is_from_proc_macro(cx, expr)
+        {
+            let (msg, lint, sugg_fn) = match (kind.node, lhs_kind.node, rhs_kind.node) {
+                (BinOpKind::Or, BinOpKind::Eq, BinOpKind::Eq) => {
+                    ("manually checking if a float is infinite", MANUAL_IS_INFINITE, "is_infinite")
+                },
+                (BinOpKind::And, BinOpKind::Ne, BinOpKind::Ne) => {
+                    ("manually checking if a float is finite", MANUAL_IS_FINITE, "is_finite")
+                },
+                _ => return,
+            };
+
+            span_lint_and_sugg(
+                cx,
+                lint,
+                expr.span,
+                msg,
+                "try",
+                format!("{local_snippet}.{sugg_fn}()"),
+                Applicability::MachineApplicable,
+            );
+        }
+    }
+}
+
+fn is_infinity(bits: u128) -> bool {
+    bits == 0x7f80_0000 || bits == 0x7ff0_0000_0000_0000
+}
+
+fn is_neg_infinity(bits: u128) -> bool {
+    bits == 0xff80_0000 || bits == 0xfff0_0000_0000_0000
+}
diff --git a/clippy_utils/src/consts.rs b/clippy_utils/src/consts.rs
index 87d85d742ce..b9f8eefeb8e 100644
--- a/clippy_utils/src/consts.rs
+++ b/clippy_utils/src/consts.rs
@@ -190,6 +190,17 @@ impl<'tcx> Constant<'tcx> {
         }
     }
 
+    /// Returns the bit representation if `self` is a bool, integer, or float.
+    pub fn to_bits(&self) -> Option<u128> {
+        match self {
+            Constant::Int(int) => Some(*int),
+            Constant::F32(float) => Some(u128::from(float.to_bits())),
+            Constant::F64(float) => Some(u128::from(float.to_bits())),
+            Constant::Bool(bool) => Some(u128::from(*bool)),
+            _ => None,
+        }
+    }
+
     /// Returns the integer value or `None` if `self` or `val_type` is not integer type.
     pub fn int_value(&self, cx: &LateContext<'_>, val_type: Ty<'_>) -> Option<FullInt> {
         if let Constant::Int(const_int) = *self {
diff --git a/tests/ui/manual_float_methods.fixed b/tests/ui/manual_float_methods.fixed
new file mode 100644
index 00000000000..87a23b5a75b
--- /dev/null
+++ b/tests/ui/manual_float_methods.fixed
@@ -0,0 +1,37 @@
+//@run-rustfix
+//@aux-build:proc_macros.rs:proc-macro
+#![allow(clippy::needless_if, unused)]
+#![warn(clippy::manual_is_infinite, clippy::manual_is_finite)]
+
+#[macro_use]
+extern crate proc_macros;
+
+const INFINITE: f32 = f32::INFINITY;
+const NEG_INFINITE: f32 = f32::NEG_INFINITY;
+
+fn main() {
+    let x = 1.0f32;
+    if x.is_infinite() {}
+    if x.is_finite() {}
+    if x.is_infinite() {}
+    if x.is_finite() {}
+    let x = 1.0f64;
+    if x.is_infinite() {}
+    if x.is_finite() {}
+    // Don't lint
+    if x.is_infinite() {}
+    if x.is_finite() {}
+    // If they're doing it this way, they probably know what they're doing
+    if x.abs() < f64::INFINITY {}
+    external! {
+        let x = 1.0;
+        if x == f32::INFINITY || x == f32::NEG_INFINITY {}
+        if x != f32::INFINITY && x != f32::NEG_INFINITY {}
+    }
+    with_span! {
+        span
+        let x = 1.0;
+        if x == f32::INFINITY || x == f32::NEG_INFINITY {}
+        if x != f32::INFINITY && x != f32::NEG_INFINITY {}
+    }
+}
diff --git a/tests/ui/manual_float_methods.rs b/tests/ui/manual_float_methods.rs
new file mode 100644
index 00000000000..9255bf93418
--- /dev/null
+++ b/tests/ui/manual_float_methods.rs
@@ -0,0 +1,37 @@
+//@run-rustfix
+//@aux-build:proc_macros.rs:proc-macro
+#![allow(clippy::needless_if, unused)]
+#![warn(clippy::manual_is_infinite, clippy::manual_is_finite)]
+
+#[macro_use]
+extern crate proc_macros;
+
+const INFINITE: f32 = f32::INFINITY;
+const NEG_INFINITE: f32 = f32::NEG_INFINITY;
+
+fn main() {
+    let x = 1.0f32;
+    if x == f32::INFINITY || x == f32::NEG_INFINITY {}
+    if x != f32::INFINITY && x != f32::NEG_INFINITY {}
+    if x == INFINITE || x == NEG_INFINITE {}
+    if x != INFINITE && x != NEG_INFINITE {}
+    let x = 1.0f64;
+    if x == f64::INFINITY || x == f64::NEG_INFINITY {}
+    if x != f64::INFINITY && x != f64::NEG_INFINITY {}
+    // Don't lint
+    if x.is_infinite() {}
+    if x.is_finite() {}
+    // If they're doing it this way, they probably know what they're doing
+    if x.abs() < f64::INFINITY {}
+    external! {
+        let x = 1.0;
+        if x == f32::INFINITY || x == f32::NEG_INFINITY {}
+        if x != f32::INFINITY && x != f32::NEG_INFINITY {}
+    }
+    with_span! {
+        span
+        let x = 1.0;
+        if x == f32::INFINITY || x == f32::NEG_INFINITY {}
+        if x != f32::INFINITY && x != f32::NEG_INFINITY {}
+    }
+}
diff --git a/tests/ui/manual_float_methods.stderr b/tests/ui/manual_float_methods.stderr
new file mode 100644
index 00000000000..00227593a9e
--- /dev/null
+++ b/tests/ui/manual_float_methods.stderr
@@ -0,0 +1,42 @@
+error: manually checking if a float is infinite
+  --> $DIR/manual_float_methods.rs:14:8
+   |
+LL |     if x == f32::INFINITY || x == f32::NEG_INFINITY {}
+   |        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `x.is_infinite()`
+   |
+   = note: `-D clippy::manual-is-infinite` implied by `-D warnings`
+
+error: manually checking if a float is finite
+  --> $DIR/manual_float_methods.rs:15:8
+   |
+LL |     if x != f32::INFINITY && x != f32::NEG_INFINITY {}
+   |        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `x.is_finite()`
+   |
+   = note: `-D clippy::manual-is-finite` implied by `-D warnings`
+
+error: manually checking if a float is infinite
+  --> $DIR/manual_float_methods.rs:16:8
+   |
+LL |     if x == INFINITE || x == NEG_INFINITE {}
+   |        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `x.is_infinite()`
+
+error: manually checking if a float is finite
+  --> $DIR/manual_float_methods.rs:17:8
+   |
+LL |     if x != INFINITE && x != NEG_INFINITE {}
+   |        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `x.is_finite()`
+
+error: manually checking if a float is infinite
+  --> $DIR/manual_float_methods.rs:19:8
+   |
+LL |     if x == f64::INFINITY || x == f64::NEG_INFINITY {}
+   |        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `x.is_infinite()`
+
+error: manually checking if a float is finite
+  --> $DIR/manual_float_methods.rs:20:8
+   |
+LL |     if x != f64::INFINITY && x != f64::NEG_INFINITY {}
+   |        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `x.is_finite()`
+
+error: aborting due to 6 previous errors
+