about summary refs log tree commit diff
diff options
context:
space:
mode:
authorSour1emon <isaac.bess@outlook.com>2024-08-31 16:30:03 -0700
committerSour1emon <isaac.bess@outlook.com>2024-09-05 18:38:06 -0700
commitd7996da9dae165c7d6e700f8b734392db30f9697 (patch)
tree29828a9d371bf3f392c74854addb14b20bd7a88b
parent9e9042a11074c83a2c40b4e8ce29f9e2bb610d34 (diff)
downloadrust-d7996da9dae165c7d6e700f8b734392db30f9697.tar.gz
rust-d7996da9dae165c7d6e700f8b734392db30f9697.zip
Add `manual_is_power_of_two`
-rw-r--r--CHANGELOG.md1
-rw-r--r--clippy_lints/src/declared_lints.rs1
-rw-r--r--clippy_lints/src/lib.rs2
-rw-r--r--clippy_lints/src/manual_is_power_of_two.rs88
-rw-r--r--tests/ui/manual_is_power_of_two.fixed14
-rw-r--r--tests/ui/manual_is_power_of_two.rs14
-rw-r--r--tests/ui/manual_is_power_of_two.stderr17
7 files changed, 137 insertions, 0 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 44a569d1ab5..f1de51c936e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5626,6 +5626,7 @@ Released 2018-09-13
 [`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_is_power_of_two`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_is_power_of_two
 [`manual_is_variant_and`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_is_variant_and
 [`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
diff --git a/clippy_lints/src/declared_lints.rs b/clippy_lints/src/declared_lints.rs
index 4804399ef7d..6f468f01b2f 100644
--- a/clippy_lints/src/declared_lints.rs
+++ b/clippy_lints/src/declared_lints.rs
@@ -306,6 +306,7 @@ pub static LINTS: &[&crate::LintInfo] = &[
     crate::manual_float_methods::MANUAL_IS_INFINITE_INFO,
     crate::manual_hash_one::MANUAL_HASH_ONE_INFO,
     crate::manual_is_ascii_check::MANUAL_IS_ASCII_CHECK_INFO,
+    crate::manual_is_power_of_two::MANUAL_IS_POWER_OF_TWO_INFO,
     crate::manual_let_else::MANUAL_LET_ELSE_INFO,
     crate::manual_main_separator_str::MANUAL_MAIN_SEPARATOR_STR_INFO,
     crate::manual_non_exhaustive::MANUAL_NON_EXHAUSTIVE_INFO,
diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs
index 9514ba8c315..bc16a3b0c01 100644
--- a/clippy_lints/src/lib.rs
+++ b/clippy_lints/src/lib.rs
@@ -207,6 +207,7 @@ mod manual_div_ceil;
 mod manual_float_methods;
 mod manual_hash_one;
 mod manual_is_ascii_check;
+mod manual_is_power_of_two;
 mod manual_let_else;
 mod manual_main_separator_str;
 mod manual_non_exhaustive;
@@ -938,5 +939,6 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) {
     store.register_late_pass(|_| Box::new(zombie_processes::ZombieProcesses));
     store.register_late_pass(|_| Box::new(pointers_in_nomem_asm_block::PointersInNomemAsmBlock));
     store.register_late_pass(move |_| Box::new(manual_div_ceil::ManualDivCeil::new(conf)));
+    store.register_late_pass(|_| Box::new(manual_is_power_of_two::ManualIsPowerOfTwo));
     // add lints here, do not remove this comment, it's used in `new_lint`
 }
diff --git a/clippy_lints/src/manual_is_power_of_two.rs b/clippy_lints/src/manual_is_power_of_two.rs
new file mode 100644
index 00000000000..a65401fc571
--- /dev/null
+++ b/clippy_lints/src/manual_is_power_of_two.rs
@@ -0,0 +1,88 @@
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::source::snippet_with_applicability;
+use rustc_ast::LitKind;
+use rustc_data_structures::packed::Pu128;
+use rustc_errors::Applicability;
+use rustc_hir::{BinOpKind, Expr, ExprKind};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_middle::ty::Uint;
+use rustc_session::declare_lint_pass;
+
+declare_clippy_lint! {
+    /// ### What it does
+    /// Checks for expressions like `x.count_ones() == 1` or `x & (x - 1) == 0` which are manual
+    /// reimplementations of `x.is_power_of_two()``
+    /// ### Why is this bad?
+    /// It's simpler and clearer
+    /// ### Example
+    /// ```no_run
+    /// let x: u32 = 1;
+    /// let result = x.count_ones() == 1;
+    /// ```
+    /// Use instead:
+    /// ```no_run
+    /// let x: u32 = 1;
+    /// let result = x.is_power_of_two();
+    /// ```
+    #[clippy::version = "1.82.0"]
+    pub MANUAL_IS_POWER_OF_TWO,
+    complexity,
+    "manually reimplementing `is_power_of_two`"
+}
+
+declare_lint_pass!(ManualIsPowerOfTwo => [MANUAL_IS_POWER_OF_TWO]);
+
+impl LateLintPass<'_> for ManualIsPowerOfTwo {
+    fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
+        let mut applicability = Applicability::MachineApplicable;
+
+        // x.count_ones() == 1
+        if let ExprKind::Binary(op, left, right) = expr.kind
+            && BinOpKind::Eq == op.node
+            && let ExprKind::MethodCall(method_name, reciever, _, _) = left.kind
+            && method_name.ident.as_str() == "count_ones"
+            && let ExprKind::Lit(lit) = right.kind
+            && let LitKind::Int(Pu128(1), _) = lit.node
+            && let &Uint(_) = cx.typeck_results().expr_ty(reciever).kind()
+        {
+            let snippet = snippet_with_applicability(cx, reciever.span, "..", &mut applicability);
+            let sugg = format!("{snippet}.is_power_of_two()");
+            span_lint_and_sugg(
+                cx,
+                MANUAL_IS_POWER_OF_TWO,
+                expr.span,
+                "manually reimplementing `is_power_of_two`",
+                "consider using `.is_power_of_two()`",
+                sugg,
+                applicability,
+            );
+        }
+
+        // x & (x - 1) == 0
+        if let ExprKind::Binary(op, left, right) = expr.kind
+            && BinOpKind::Eq == op.node
+            && let ExprKind::Binary(op1, left1, right1) = left.kind
+            && BinOpKind::BitAnd == op1.node
+            && let ExprKind::Binary(op2, left2, right2) = right1.kind
+            && BinOpKind::Sub == op2.node
+            && left1.span.eq_ctxt(left2.span)
+            && let &Uint(_) = cx.typeck_results().expr_ty(left1).kind()
+            && let ExprKind::Lit(lit) = right2.kind
+            && let LitKind::Int(Pu128(1), _) = lit.node
+            && let ExprKind::Lit(lit1) = right.kind
+            && let LitKind::Int(Pu128(0), _) = lit1.node
+        {
+            let snippet = snippet_with_applicability(cx, left1.span, "..", &mut applicability);
+            let sugg = format!("{snippet}.is_power_of_two()");
+            span_lint_and_sugg(
+                cx,
+                MANUAL_IS_POWER_OF_TWO,
+                expr.span,
+                "manually reimplementing `is_power_of_two`",
+                "consider using `.is_power_of_two()`",
+                sugg,
+                applicability,
+            );
+        }
+    }
+}
diff --git a/tests/ui/manual_is_power_of_two.fixed b/tests/ui/manual_is_power_of_two.fixed
new file mode 100644
index 00000000000..beee2eaf665
--- /dev/null
+++ b/tests/ui/manual_is_power_of_two.fixed
@@ -0,0 +1,14 @@
+#![warn(clippy::manual_is_power_of_two)]
+
+fn main() {
+    let a = 16_u64;
+
+    let _ = a.is_power_of_two();
+    let _ = a.is_power_of_two();
+
+    let b = 4_i64;
+
+    // is_power_of_two only works for unsigned integers
+    let _ = b.count_ones() == 1;
+    let _ = b & (b - 1) == 0;
+}
diff --git a/tests/ui/manual_is_power_of_two.rs b/tests/ui/manual_is_power_of_two.rs
new file mode 100644
index 00000000000..0810b4c28da
--- /dev/null
+++ b/tests/ui/manual_is_power_of_two.rs
@@ -0,0 +1,14 @@
+#![warn(clippy::manual_is_power_of_two)]
+
+fn main() {
+    let a = 16_u64;
+
+    let _ = a.count_ones() == 1;
+    let _ = a & (a - 1) == 0;
+
+    let b = 4_i64;
+
+    // is_power_of_two only works for unsigned integers
+    let _ = b.count_ones() == 1;
+    let _ = b & (b - 1) == 0;
+}
diff --git a/tests/ui/manual_is_power_of_two.stderr b/tests/ui/manual_is_power_of_two.stderr
new file mode 100644
index 00000000000..c7dfe6b11b9
--- /dev/null
+++ b/tests/ui/manual_is_power_of_two.stderr
@@ -0,0 +1,17 @@
+error: manually reimplementing `is_power_of_two`
+  --> tests/ui/manual_is_power_of_two.rs:6:13
+   |
+LL |     let _ = a.count_ones() == 1;
+   |             ^^^^^^^^^^^^^^^^^^^ help: consider using `.is_power_of_two()`: `a.is_power_of_two()`
+   |
+   = note: `-D clippy::manual-is-power-of-two` implied by `-D warnings`
+   = help: to override `-D warnings` add `#[allow(clippy::manual_is_power_of_two)]`
+
+error: manually reimplementing `is_power_of_two`
+  --> tests/ui/manual_is_power_of_two.rs:7:13
+   |
+LL |     let _ = a & (a - 1) == 0;
+   |             ^^^^^^^^^^^^^^^^ help: consider using `.is_power_of_two()`: `a.is_power_of_two()`
+
+error: aborting due to 2 previous errors
+