diff --git a/CHANGELOG.md b/CHANGELOG.md
index 41a86e8ce510..c79345890df4 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5621,6 +5621,7 @@ Released 2018-09-13
 [`manual_find_map`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_find_map
 [`manual_flatten`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_flatten
 [`manual_hash_one`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_hash_one
+[`manual_ilog2`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_ilog2
 [`manual_inspect`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_inspect
 [`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
diff --git a/clippy_config/src/msrvs.rs b/clippy_config/src/msrvs.rs
index e30df3d32341..8045e8919168 100644
--- a/clippy_config/src/msrvs.rs
+++ b/clippy_config/src/msrvs.rs
@@ -27,6 +27,7 @@ msrv_aliases! {
     1,71,0 { TUPLE_ARRAY_CONVERSIONS, BUILD_HASHER_HASH_ONE }
     1,70,0 { OPTION_RESULT_IS_VARIANT_AND, BINARY_HEAP_RETAIN }
     1,68,0 { PATH_MAIN_SEPARATOR_STR }
+    1,67,0 { MANUAL_ILOG2 }
     1,65,0 { LET_ELSE, POINTER_CAST_CONSTNESS }
     1,63,0 { CLONE_INTO }
     1,62,0 { BOOL_THEN_SOME, DEFAULT_ENUM_ATTRIBUTE, CONST_EXTERN_C_FN }
diff --git a/clippy_lints/src/declared_lints.rs b/clippy_lints/src/declared_lints.rs
index 2b229d2fe6ac..34a7fc572fcd 100644
--- a/clippy_lints/src/declared_lints.rs
+++ b/clippy_lints/src/declared_lints.rs
@@ -305,6 +305,7 @@ pub static LINTS: &[&crate::LintInfo] = &[
     crate::manual_float_methods::MANUAL_IS_FINITE_INFO,
     crate::manual_float_methods::MANUAL_IS_INFINITE_INFO,
     crate::manual_hash_one::MANUAL_HASH_ONE_INFO,
+    crate::manual_ilog2::MANUAL_ILOG2_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,
diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs
index 1d41f568f378..e9160d813767 100644
--- a/clippy_lints/src/lib.rs
+++ b/clippy_lints/src/lib.rs
@@ -206,6 +206,7 @@ mod manual_clamp;
 mod manual_div_ceil;
 mod manual_float_methods;
 mod manual_hash_one;
+mod manual_ilog2;
 mod manual_is_ascii_check;
 mod manual_is_power_of_two;
 mod manual_let_else;
@@ -944,5 +945,6 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) {
     store.register_late_pass(|_| Box::new(manual_is_power_of_two::ManualIsPowerOfTwo));
     store.register_late_pass(|_| Box::new(non_zero_suggestions::NonZeroSuggestions));
     store.register_late_pass(move |_| Box::new(unused_trait_names::UnusedTraitNames::new(conf)));
+    store.register_late_pass(move |_| Box::new(manual_ilog2::ManualIlog2::new(conf)));
     // add lints here, do not remove this comment, it's used in `new_lint`
 }
diff --git a/clippy_lints/src/manual_ilog2.rs b/clippy_lints/src/manual_ilog2.rs
new file mode 100644
index 000000000000..1c57e462d62b
--- /dev/null
+++ b/clippy_lints/src/manual_ilog2.rs
@@ -0,0 +1,102 @@
+use clippy_config::msrvs::{self, Msrv};
+use clippy_config::Conf;
+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;
+use rustc_session::impl_lint_pass;
+
+declare_clippy_lint! {
+    /// ### What it does
+    /// Checks for expressions like `31 - x.leading_zeros()` or `x.ilog(2)` which are manual
+    /// reimplementations of `x.ilog2()`
+    /// ### Why is this bad?
+    /// Manual reimplementations of `ilog2` increase code complexity for little benefit.
+    /// ### Example
+    /// ```no_run
+    /// let x: u32 = 5;
+    /// let log = 31 - x.leading_zeros();
+    /// ```
+    /// Use instead:
+    /// ```no_run
+    /// let x: u32 = 5;
+    /// let log = x.ilog2();
+    /// ```
+    #[clippy::version = "1.82.0"]
+    pub MANUAL_ILOG2,
+    complexity,
+    "manually reimplementing `ilog2`"
+}
+
+pub struct ManualIlog2 {
+    msrv: Msrv,
+}
+
+impl ManualIlog2 {
+    #[must_use]
+    pub fn new(conf: &Conf) -> Self {
+        Self {
+            msrv: conf.msrv.clone(),
+        }
+    }
+}
+
+impl_lint_pass!(ManualIlog2 => [MANUAL_ILOG2]);
+
+impl LateLintPass<'_> for ManualIlog2 {
+    fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
+        if !self.msrv.meets(msrvs::MANUAL_ILOG2) {
+            return;
+        }
+        let mut applicability = Applicability::MachineApplicable;
+
+        if let ExprKind::Binary(op, left, right) = expr.kind
+            && BinOpKind::Sub == op.node
+            && let ExprKind::Lit(lit) = left.kind
+            && let LitKind::Int(Pu128(val), _) = lit.node
+            && let ExprKind::MethodCall(method_name, reciever, _, _) = right.kind
+            && method_name.ident.as_str() == "leading_zeros"
+        {
+            let type_ = cx.typeck_results().expr_ty(reciever);
+            let Some(bit_width) = (match type_.kind() {
+                ty::Int(itype) => itype.bit_width(),
+                ty::Uint(itype) => itype.bit_width(),
+                _ => return,
+            }) else {
+                return;
+            };
+            if val == u128::from(bit_width) - 1 {
+                suggest_change(cx, reciever, expr, &mut applicability);
+            }
+        }
+
+        if let ExprKind::MethodCall(method_name, reciever, args, _) = expr.kind
+            && method_name.ident.as_str() == "ilog"
+            && args.len() == 1
+            && let ExprKind::Lit(lit) = args[0].kind
+            && let LitKind::Int(Pu128(2), _) = lit.node
+            && cx.typeck_results().expr_ty(reciever).is_integral()
+        {
+            suggest_change(cx, reciever, expr, &mut applicability);
+        }
+    }
+
+    extract_msrv_attr!(LateContext);
+}
+
+fn suggest_change(cx: &LateContext<'_>, reciever: &Expr<'_>, full_expr: &Expr<'_>, applicability: &mut Applicability) {
+    let sugg = snippet_with_applicability(cx, reciever.span, "..", applicability);
+    span_lint_and_sugg(
+        cx,
+        MANUAL_ILOG2,
+        full_expr.span,
+        "manually reimplementing `ilog2`",
+        "consider using .ilog2()",
+        format!("{sugg}.ilog2()"),
+        *applicability,
+    );
+}
diff --git a/tests/ui/manual_ilog2.fixed b/tests/ui/manual_ilog2.fixed
new file mode 100644
index 000000000000..865009db8d79
--- /dev/null
+++ b/tests/ui/manual_ilog2.fixed
@@ -0,0 +1,12 @@
+#![warn(clippy::manual_ilog2)]
+
+fn main() {
+    let a: u32 = 5;
+    let _ = a.ilog2();
+    let _ = a.ilog2();
+
+    let b: u64 = 543534;
+    let _ = b.ilog2();
+
+    let _ = 64 - b.leading_zeros(); // No lint
+}
diff --git a/tests/ui/manual_ilog2.rs b/tests/ui/manual_ilog2.rs
new file mode 100644
index 000000000000..c72b1ba66f0b
--- /dev/null
+++ b/tests/ui/manual_ilog2.rs
@@ -0,0 +1,12 @@
+#![warn(clippy::manual_ilog2)]
+
+fn main() {
+    let a: u32 = 5;
+    let _ = 31 - a.leading_zeros();
+    let _ = a.ilog(2);
+
+    let b: u64 = 543534;
+    let _ = 63 - b.leading_zeros();
+
+    let _ = 64 - b.leading_zeros(); // No lint because manual ilog2 is BIT_WIDTH - 1 - x.leading_zeros()
+}
diff --git a/tests/ui/manual_ilog2.stderr b/tests/ui/manual_ilog2.stderr
new file mode 100644
index 000000000000..88abb3ce9efd
--- /dev/null
+++ b/tests/ui/manual_ilog2.stderr
@@ -0,0 +1,23 @@
+error: manually reimplementing `ilog2`
+  --> tests/ui/manual_ilog2.rs:5:13
+   |
+LL |     let _ = 31 - a.leading_zeros();
+   |             ^^^^^^^^^^^^^^^^^^^^^^ help: consider using .ilog2(): `a.ilog2()`
+   |
+   = note: `-D clippy::manual-ilog2` implied by `-D warnings`
+   = help: to override `-D warnings` add `#[allow(clippy::manual_ilog2)]`
+
+error: manually reimplementing `ilog2`
+  --> tests/ui/manual_ilog2.rs:6:13
+   |
+LL |     let _ = a.ilog(2);
+   |             ^^^^^^^^^ help: consider using .ilog2(): `a.ilog2()`
+
+error: manually reimplementing `ilog2`
+  --> tests/ui/manual_ilog2.rs:9:13
+   |
+LL |     let _ = 63 - b.leading_zeros();
+   |             ^^^^^^^^^^^^^^^^^^^^^^ help: consider using .ilog2(): `b.ilog2()`
+
+error: aborting due to 3 previous errors
+