From d8a281ef735be327dceace003b36e14616023126 Mon Sep 17 00:00:00 2001
From: Micha White <botahamec@outlook.com>
Date: Sat, 21 May 2022 17:02:00 -0400
Subject: [PATCH] Added an unused_rounding lint

---
 CHANGELOG.md                             |  1 +
 clippy_lints/src/lib.register_lints.rs   |  1 +
 clippy_lints/src/lib.register_nursery.rs |  1 +
 clippy_lints/src/lib.rs                  |  2 +
 clippy_lints/src/unused_rounding.rs      | 69 ++++++++++++++++++++++++
 tests/ui/unused_rounding.fixed           |  8 +++
 tests/ui/unused_rounding.rs              |  8 +++
 tests/ui/unused_rounding.stderr          | 22 ++++++++
 8 files changed, 112 insertions(+)
 create mode 100644 clippy_lints/src/unused_rounding.rs
 create mode 100644 tests/ui/unused_rounding.fixed
 create mode 100644 tests/ui/unused_rounding.rs
 create mode 100644 tests/ui/unused_rounding.stderr

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 0f6295a45f3..137fd793e75 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -3821,6 +3821,7 @@ Released 2018-09-13
 [`unused_collect`]: https://rust-lang.github.io/rust-clippy/master/index.html#unused_collect
 [`unused_io_amount`]: https://rust-lang.github.io/rust-clippy/master/index.html#unused_io_amount
 [`unused_label`]: https://rust-lang.github.io/rust-clippy/master/index.html#unused_label
+[`unused_rounding`]: https://rust-lang.github.io/rust-clippy/master/index.html#unused_rounding
 [`unused_self`]: https://rust-lang.github.io/rust-clippy/master/index.html#unused_self
 [`unused_unit`]: https://rust-lang.github.io/rust-clippy/master/index.html#unused_unit
 [`unusual_byte_groupings`]: https://rust-lang.github.io/rust-clippy/master/index.html#unusual_byte_groupings
diff --git a/clippy_lints/src/lib.register_lints.rs b/clippy_lints/src/lib.register_lints.rs
index 570d736518b..02171a0909e 100644
--- a/clippy_lints/src/lib.register_lints.rs
+++ b/clippy_lints/src/lib.register_lints.rs
@@ -546,6 +546,7 @@ store.register_lints(&[
     unsafe_removed_from_name::UNSAFE_REMOVED_FROM_NAME,
     unused_async::UNUSED_ASYNC,
     unused_io_amount::UNUSED_IO_AMOUNT,
+    unused_rounding::UNUSED_ROUNDING,
     unused_self::UNUSED_SELF,
     unused_unit::UNUSED_UNIT,
     unwrap::PANICKING_UNWRAP,
diff --git a/clippy_lints/src/lib.register_nursery.rs b/clippy_lints/src/lib.register_nursery.rs
index ec187563b3f..10808af363d 100644
--- a/clippy_lints/src/lib.register_nursery.rs
+++ b/clippy_lints/src/lib.register_nursery.rs
@@ -32,5 +32,6 @@ store.register_group(true, "clippy::nursery", Some("clippy_nursery"), vec![
     LintId::of(trait_bounds::TYPE_REPETITION_IN_BOUNDS),
     LintId::of(transmute::TRANSMUTE_UNDEFINED_REPR),
     LintId::of(transmute::USELESS_TRANSMUTE),
+    LintId::of(unused_rounding::UNUSED_ROUNDING),
     LintId::of(use_self::USE_SELF),
 ])
diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs
index 5f636e5114b..09435b06d0a 100644
--- a/clippy_lints/src/lib.rs
+++ b/clippy_lints/src/lib.rs
@@ -402,6 +402,7 @@ mod unnested_or_patterns;
 mod unsafe_removed_from_name;
 mod unused_async;
 mod unused_io_amount;
+mod unused_rounding;
 mod unused_self;
 mod unused_unit;
 mod unwrap;
@@ -906,6 +907,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
     store.register_late_pass(|| Box::new(rc_clone_in_vec_init::RcCloneInVecInit));
     store.register_early_pass(|| Box::new(duplicate_mod::DuplicateMod::default()));
     store.register_late_pass(|| Box::new(get_first::GetFirst));
+    store.register_early_pass(|| Box::new(unused_rounding::UnusedRounding));
     // add lints here, do not remove this comment, it's used in `new_lint`
 }
 
diff --git a/clippy_lints/src/unused_rounding.rs b/clippy_lints/src/unused_rounding.rs
new file mode 100644
index 00000000000..7706a338eb2
--- /dev/null
+++ b/clippy_lints/src/unused_rounding.rs
@@ -0,0 +1,69 @@
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use rustc_ast::ast::{Expr, ExprKind, LitFloatType, LitKind};
+use rustc_errors::Applicability;
+use rustc_lint::{EarlyContext, EarlyLintPass};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+
+declare_clippy_lint! {
+    /// ### What it does
+    ///
+    /// Detects cases where a whole-number literal float is being rounded, using
+    /// the `floor`, `ceil`, or `round` methods.
+    ///
+    /// ### Why is this bad?
+    ///
+    /// This is unnecessary and confusing to the reader. Doing this is probably a mistake.
+    ///
+    /// ### Example
+    /// ```rust
+    /// let x = 1f32.ceil();
+    /// ```
+    /// Use instead:
+    /// ```rust
+    /// let x = 1f32;
+    /// ```
+    #[clippy::version = "1.62.0"]
+    pub UNUSED_ROUNDING,
+    nursery,
+    "Rounding a whole number literal, which is useless"
+}
+declare_lint_pass!(UnusedRounding => [UNUSED_ROUNDING]);
+
+fn is_useless_rounding(expr: &Expr) -> Option<(&str, String)> {
+    if let ExprKind::MethodCall(name_ident, args, _) = &expr.kind
+        && let method_name = name_ident.ident.name.as_str()
+        && (method_name == "ceil" || method_name == "round" || method_name == "floor")
+        && !args.is_empty()
+        && let ExprKind::Lit(spanned) = &args[0].kind
+        && let LitKind::Float(symbol, ty) = spanned.kind {
+            let f = symbol.as_str().parse::<f64>().unwrap();
+            let f_str = symbol.to_string() + if let LitFloatType::Suffixed(ty) = ty {
+                ty.name_str()
+            } else {
+                ""
+            };
+            if f.fract() < f64::EPSILON {
+                Some((method_name, f_str))
+            } else {
+                None
+            }
+        } else {
+            None
+        }
+}
+
+impl EarlyLintPass for UnusedRounding {
+    fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &Expr) {
+        if let Some((method_name, float)) = is_useless_rounding(expr) {
+            span_lint_and_sugg(
+                cx,
+                UNUSED_ROUNDING,
+                expr.span,
+                &format!("used the `{}` method with a whole number float", method_name),
+                &format!("remove the `{}` method call", method_name),
+                float,
+                Applicability::MachineApplicable,
+            );
+        }
+    }
+}
diff --git a/tests/ui/unused_rounding.fixed b/tests/ui/unused_rounding.fixed
new file mode 100644
index 00000000000..a1cb80413d2
--- /dev/null
+++ b/tests/ui/unused_rounding.fixed
@@ -0,0 +1,8 @@
+// run-rustfix
+#![warn(clippy::unused_rounding)]
+
+fn main() {
+    let _ = 1f32;
+    let _ = 1.0f64;
+    let _ = 1.00f32;
+}
diff --git a/tests/ui/unused_rounding.rs b/tests/ui/unused_rounding.rs
new file mode 100644
index 00000000000..90af8f19dd9
--- /dev/null
+++ b/tests/ui/unused_rounding.rs
@@ -0,0 +1,8 @@
+// run-rustfix
+#![warn(clippy::unused_rounding)]
+
+fn main() {
+    let _ = 1f32.ceil();
+    let _ = 1.0f64.floor();
+    let _ = 1.00f32.round();
+}
diff --git a/tests/ui/unused_rounding.stderr b/tests/ui/unused_rounding.stderr
new file mode 100644
index 00000000000..6cfb02e0402
--- /dev/null
+++ b/tests/ui/unused_rounding.stderr
@@ -0,0 +1,22 @@
+error: used the `ceil` method with a whole number float
+  --> $DIR/unused_rounding.rs:5:13
+   |
+LL |     let _ = 1f32.ceil();
+   |             ^^^^^^^^^^^ help: remove the `ceil` method call: `1f32`
+   |
+   = note: `-D clippy::unused-rounding` implied by `-D warnings`
+
+error: used the `floor` method with a whole number float
+  --> $DIR/unused_rounding.rs:6:13
+   |
+LL |     let _ = 1.0f64.floor();
+   |             ^^^^^^^^^^^^^^ help: remove the `floor` method call: `1.0f64`
+
+error: used the `round` method with a whole number float
+  --> $DIR/unused_rounding.rs:7:13
+   |
+LL |     let _ = 1.00f32.round();
+   |             ^^^^^^^^^^^^^^^ help: remove the `round` method call: `1.00f32`
+
+error: aborting due to 3 previous errors
+