[clang-tools-extra] [clang-tidy] New checker: modernize.use-std-bit to detect std::has_one_bit idiom (PR #185435)

via cfe-commits cfe-commits at lists.llvm.org
Mon Mar 9 08:21:51 PDT 2026


https://github.com/serge-sans-paille updated https://github.com/llvm/llvm-project/pull/185435

>From 309550019fdac9a0d283350ef180dd457a9f0df4 Mon Sep 17 00:00:00 2001
From: serge-sans-paille <sguelton at mozilla.com>
Date: Mon, 9 Mar 2026 09:52:19 +0100
Subject: [PATCH 1/2] [clang-tidy] New checker: modernize.use-std-bit to detect
 std::has_one_bit idiom

>From https://graphics.stanford.edu/~seander/bithacks.html#DetermineIfPowerOf2
---
 .../clang-tidy/modernize/CMakeLists.txt       |  1 +
 .../modernize/ModernizeTidyModule.cpp         |  2 +
 .../clang-tidy/modernize/UseStdBitCheck.cpp   | 84 +++++++++++++++++++
 .../clang-tidy/modernize/UseStdBitCheck.h     | 43 ++++++++++
 clang-tools-extra/docs/ReleaseNotes.rst       |  5 ++
 .../docs/clang-tidy/checks/list.rst           |  1 +
 .../checkers/modernize/use-std-bit.cpp        | 50 +++++++++++
 7 files changed, 186 insertions(+)
 create mode 100644 clang-tools-extra/clang-tidy/modernize/UseStdBitCheck.cpp
 create mode 100644 clang-tools-extra/clang-tidy/modernize/UseStdBitCheck.h
 create mode 100644 clang-tools-extra/test/clang-tidy/checkers/modernize/use-std-bit.cpp

diff --git a/clang-tools-extra/clang-tidy/modernize/CMakeLists.txt b/clang-tools-extra/clang-tidy/modernize/CMakeLists.txt
index cc4cc7a02b594..2c5c44db587fe 100644
--- a/clang-tools-extra/clang-tidy/modernize/CMakeLists.txt
+++ b/clang-tools-extra/clang-tidy/modernize/CMakeLists.txt
@@ -47,6 +47,7 @@ add_clang_library(clangTidyModernizeModule STATIC
   UseRangesCheck.cpp
   UseScopedLockCheck.cpp
   UseStartsEndsWithCheck.cpp
+  UseStdBitCheck.cpp
   UseStdFormatCheck.cpp
   UseStdNumbersCheck.cpp
   UseStdPrintCheck.cpp
diff --git a/clang-tools-extra/clang-tidy/modernize/ModernizeTidyModule.cpp b/clang-tools-extra/clang-tidy/modernize/ModernizeTidyModule.cpp
index fcb860d8c5298..cc13da7535bcb 100644
--- a/clang-tools-extra/clang-tidy/modernize/ModernizeTidyModule.cpp
+++ b/clang-tools-extra/clang-tidy/modernize/ModernizeTidyModule.cpp
@@ -47,6 +47,7 @@
 #include "UseRangesCheck.h"
 #include "UseScopedLockCheck.h"
 #include "UseStartsEndsWithCheck.h"
+#include "UseStdBitCheck.h"
 #include "UseStdFormatCheck.h"
 #include "UseStdNumbersCheck.h"
 #include "UseStdPrintCheck.h"
@@ -96,6 +97,7 @@ class ModernizeModule : public ClangTidyModule {
         "modernize-use-scoped-lock");
     CheckFactories.registerCheck<UseStartsEndsWithCheck>(
         "modernize-use-starts-ends-with");
+    CheckFactories.registerCheck<UseStdBitCheck>("modernize-use-std-bit");
     CheckFactories.registerCheck<UseStdFormatCheck>("modernize-use-std-format");
     CheckFactories.registerCheck<UseStdNumbersCheck>(
         "modernize-use-std-numbers");
diff --git a/clang-tools-extra/clang-tidy/modernize/UseStdBitCheck.cpp b/clang-tools-extra/clang-tidy/modernize/UseStdBitCheck.cpp
new file mode 100644
index 0000000000000..57014ab88a094
--- /dev/null
+++ b/clang-tools-extra/clang-tidy/modernize/UseStdBitCheck.cpp
@@ -0,0 +1,84 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "UseStdBitCheck.h"
+#include "clang/ASTMatchers/ASTMatchFinder.h"
+
+using namespace clang::ast_matchers;
+
+namespace clang::tidy::modernize {
+
+void UseStdBitCheck::registerMatchers(MatchFinder *Finder) {
+  const auto makeBinaryOperatorMatcher = [](auto Op) {
+    return [=](auto LHS, auto RHS) {
+      return binaryOperator(
+          hasOperatorName(Op),
+          hasOperands(ignoringParenImpCasts(LHS), ignoringParenImpCasts(RHS)));
+    };
+  };
+
+  const auto logicalAnd = makeBinaryOperatorMatcher("&&");
+  const auto sub = makeBinaryOperatorMatcher("-");
+  const auto bitwiseAnd = makeBinaryOperatorMatcher("&");
+  const auto cmpNot = makeBinaryOperatorMatcher("!=");
+  const auto cmpGt = makeBinaryOperatorMatcher(">");
+
+  const auto logicalNot = [](auto Expr) {
+    return unaryOperator(hasOperatorName("!"),
+                         hasUnaryOperand(ignoringParenImpCasts(Expr)));
+  };
+
+  const auto isNonNull = [=](auto Expr) {
+    return anyOf(Expr, cmpNot(Expr, integerLiteral(equals(0))),
+                 cmpGt(Expr, integerLiteral(equals(0))));
+  };
+  const auto bindDeclRef = [](auto Name) {
+    return declRefExpr(to(varDecl(hasType(isUnsignedInteger())).bind(Name)));
+  };
+  const auto boundDeclRef = [](auto Name) {
+    return declRefExpr(to(varDecl(equalsBoundNode(Name))));
+  };
+
+  // https://graphics.stanford.edu/~seander/bithacks.html#DetermineIfPowerOf2
+  // has_one_bit(v) = v && !(v & (v - 1));
+  Finder->addMatcher(
+      logicalAnd(isNonNull(bindDeclRef("v")),
+                 logicalNot(bitwiseAnd(
+                     boundDeclRef("v"),
+                     sub(boundDeclRef("v"), integerLiteral(equals(1))))))
+          .bind("expr"),
+      this);
+}
+
+void UseStdBitCheck::registerPPCallbacks(const SourceManager &SM,
+                                         Preprocessor *PP,
+                                         Preprocessor *ModuleExpanderPP) {
+  IncludeInserter.registerPreprocessor(PP);
+}
+
+void UseStdBitCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
+  Options.store(Opts, "IncludeStyle", IncludeInserter.getStyle());
+}
+
+void UseStdBitCheck::check(const MatchFinder::MatchResult &Result) {
+  const ASTContext &Context = *Result.Context;
+  const SourceManager &Source = Context.getSourceManager();
+
+  const auto *MatchedVarDecl = Result.Nodes.getNodeAs<VarDecl>("v");
+  const auto *MatchedExpr = Result.Nodes.getNodeAs<BinaryOperator>("expr");
+
+  diag(MatchedExpr->getBeginLoc(), "use std::has_one_bit instead")
+      << MatchedVarDecl->getName()
+      << FixItHint::CreateReplacement(
+             MatchedExpr->getSourceRange(),
+             ("std::has_one_bit(" + MatchedVarDecl->getName() + ")").str())
+      << IncludeInserter.createIncludeInsertion(
+             Source.getFileID(MatchedExpr->getBeginLoc()), "<bit>");
+}
+
+} // namespace clang::tidy::modernize
diff --git a/clang-tools-extra/clang-tidy/modernize/UseStdBitCheck.h b/clang-tools-extra/clang-tidy/modernize/UseStdBitCheck.h
new file mode 100644
index 0000000000000..e09a3cf35fe18
--- /dev/null
+++ b/clang-tools-extra/clang-tidy/modernize/UseStdBitCheck.h
@@ -0,0 +1,43 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_USESTDBITCHECK_H
+#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_USESTDBITCHECK_H
+
+#include "../ClangTidyCheck.h"
+#include "../utils/IncludeInserter.h"
+
+namespace clang::tidy::modernize {
+
+/// use function from <bit> instead of common idioms.
+///
+/// For the user-facing documentation see:
+/// https://clang.llvm.org/extra/clang-tidy/checks/modernize/use-std-bit.html
+class UseStdBitCheck : public ClangTidyCheck {
+public:
+  UseStdBitCheck(StringRef Name, ClangTidyContext *Context)
+      : ClangTidyCheck(Name, Context),
+        IncludeInserter(Options.getLocalOrGlobal("IncludeStyle",
+                                                 utils::IncludeSorter::IS_LLVM),
+                        areDiagsSelfContained()) {}
+  void registerMatchers(ast_matchers::MatchFinder *Finder) override;
+  void check(const ast_matchers::MatchFinder::MatchResult &Result) override;
+  bool isLanguageVersionSupported(const LangOptions &LangOpts) const override {
+    return LangOpts.CPlusPlus20;
+  }
+  void registerPPCallbacks(const SourceManager &SM, Preprocessor *PP,
+                           Preprocessor *ModuleExpanderPP) override;
+  void storeOptions(ClangTidyOptions::OptionMap &Opts) override;
+
+private:
+  utils::IncludeInserter IncludeInserter;
+};
+
+} // namespace clang::tidy::modernize
+
+#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_USESTDBITCHECK_H
diff --git a/clang-tools-extra/docs/ReleaseNotes.rst b/clang-tools-extra/docs/ReleaseNotes.rst
index 9f4d7e6923fa0..9fdf0c93a4e3f 100644
--- a/clang-tools-extra/docs/ReleaseNotes.rst
+++ b/clang-tools-extra/docs/ReleaseNotes.rst
@@ -124,6 +124,11 @@ New checks
   ``llvm::to_vector(llvm::make_filter_range(...))`` that can be replaced with
   ``llvm::map_to_vector`` and ``llvm::filter_to_vector``.
 
+- New :doc:`modernize-use-std-bit
+  <clang-tidy/checks/modernize/use-std-bit>` check.
+
+  Use functions from ``<bit>`` instead of common idioms.
+
 - New :doc:`modernize-use-string-view
   <clang-tidy/checks/modernize/use-string-view>` check.
 
diff --git a/clang-tools-extra/docs/clang-tidy/checks/list.rst b/clang-tools-extra/docs/clang-tidy/checks/list.rst
index 068431fb5c94c..956e495eb4645 100644
--- a/clang-tools-extra/docs/clang-tidy/checks/list.rst
+++ b/clang-tools-extra/docs/clang-tidy/checks/list.rst
@@ -326,6 +326,7 @@ Clang-Tidy Checks
    :doc:`modernize-use-ranges <modernize/use-ranges>`, "Yes"
    :doc:`modernize-use-scoped-lock <modernize/use-scoped-lock>`, "Yes"
    :doc:`modernize-use-starts-ends-with <modernize/use-starts-ends-with>`, "Yes"
+   :doc:`modernize-use-std-bit <modernize/use-std-bit>`, "Yes"
    :doc:`modernize-use-std-format <modernize/use-std-format>`, "Yes"
    :doc:`modernize-use-std-numbers <modernize/use-std-numbers>`, "Yes"
    :doc:`modernize-use-std-print <modernize/use-std-print>`, "Yes"
diff --git a/clang-tools-extra/test/clang-tidy/checkers/modernize/use-std-bit.cpp b/clang-tools-extra/test/clang-tidy/checkers/modernize/use-std-bit.cpp
new file mode 100644
index 0000000000000..0ef978ff386ce
--- /dev/null
+++ b/clang-tools-extra/test/clang-tidy/checkers/modernize/use-std-bit.cpp
@@ -0,0 +1,50 @@
+// RUN: %check_clang_tidy -std=c++20-or-later %s modernize-use-std-bit %t
+// CHECK-FIXES: #include <bit>
+
+unsigned bithacks(unsigned x) {
+  // CHECK-MESSAGES: :[[@LINE+2]]:10: warning: use std::has_one_bit instead [modernize-use-std-bit]
+  // CHECK-FIXES: return std::has_one_bit(x);
+  return x && !(x & (x - 1));
+}
+
+unsigned long bithacks(unsigned long x) {
+  // CHECK-MESSAGES: :[[@LINE+2]]:10: warning: use std::has_one_bit instead [modernize-use-std-bit]
+  // CHECK-FIXES: return std::has_one_bit(x);
+  return x && !(x & (x - 1));
+}
+
+unsigned short bithacks(unsigned short x) {
+  // CHECK-MESSAGES: :[[@LINE+2]]:10: warning: use std::has_one_bit instead [modernize-use-std-bit]
+  // CHECK-FIXES: return std::has_one_bit(x);
+  return x && !(x & (x - 1));
+}
+
+unsigned bithacks_perm(unsigned x) {
+  // CHECK-MESSAGES: :[[@LINE+2]]:10: warning: use std::has_one_bit instead [modernize-use-std-bit]
+  // CHECK-FIXES: return std::has_one_bit(x);
+  return x && !((x - 1) & (x));
+}
+
+unsigned bithacks_variant_neq(unsigned x) {
+  // CHECK-MESSAGES: :[[@LINE+2]]:10: warning: use std::has_one_bit instead [modernize-use-std-bit]
+  // CHECK-FIXES: return std::has_one_bit(x);
+  return (x != 0) && !(x & (x - 1));
+}
+
+unsigned bithacks_variant_neq_perm(unsigned x) {
+  // CHECK-MESSAGES: :[[@LINE+2]]:10: warning: use std::has_one_bit instead [modernize-use-std-bit]
+  // CHECK-FIXES: return std::has_one_bit(x);
+  return (x != 0) && !(x & (x - 1));
+}
+
+unsigned bithacks_variant_gt(unsigned x) {
+  // CHECK-MESSAGES: :[[@LINE+2]]:10: warning: use std::has_one_bit instead [modernize-use-std-bit]
+  // CHECK-FIXES: return std::has_one_bit(x);
+  return (x > 0) && !(x & (x - 1));
+}
+
+unsigned bithacks_variant_gt_perm(unsigned x) {
+  // CHECK-MESSAGES: :[[@LINE+2]]:10: warning: use std::has_one_bit instead [modernize-use-std-bit]
+  // CHECK-FIXES: return std::has_one_bit(x);
+  return (x > 0) && !(x & (x - 1));
+}

>From daac8d24b70f45272bc0f6d2024a765b7caef1d0 Mon Sep 17 00:00:00 2001
From: serge-sans-paille <sguelton at mozilla.com>
Date: Mon, 9 Mar 2026 16:21:26 +0100
Subject: [PATCH 2/2] fixup! [clang-tidy] New checker: modernize.use-std-bit to
 detect std::has_one_bit idiom

---
 .../clang-tidy/modernize/UseStdBitCheck.cpp   | 34 +++++++++----------
 .../checks/modernize/use-std-bit.rst          | 17 ++++++++++
 2 files changed, 34 insertions(+), 17 deletions(-)
 create mode 100644 clang-tools-extra/docs/clang-tidy/checks/modernize/use-std-bit.rst

diff --git a/clang-tools-extra/clang-tidy/modernize/UseStdBitCheck.cpp b/clang-tools-extra/clang-tidy/modernize/UseStdBitCheck.cpp
index 57014ab88a094..b16b5c446e5f3 100644
--- a/clang-tools-extra/clang-tidy/modernize/UseStdBitCheck.cpp
+++ b/clang-tools-extra/clang-tidy/modernize/UseStdBitCheck.cpp
@@ -14,43 +14,43 @@ using namespace clang::ast_matchers;
 namespace clang::tidy::modernize {
 
 void UseStdBitCheck::registerMatchers(MatchFinder *Finder) {
-  const auto makeBinaryOperatorMatcher = [](auto Op) {
-    return [=](auto LHS, auto RHS) {
+  const auto MakeBinaryOperatorMatcher = [](auto Op) {
+    return [=](const auto &LHS, const auto &RHS) {
       return binaryOperator(
           hasOperatorName(Op),
           hasOperands(ignoringParenImpCasts(LHS), ignoringParenImpCasts(RHS)));
     };
   };
 
-  const auto logicalAnd = makeBinaryOperatorMatcher("&&");
-  const auto sub = makeBinaryOperatorMatcher("-");
-  const auto bitwiseAnd = makeBinaryOperatorMatcher("&");
-  const auto cmpNot = makeBinaryOperatorMatcher("!=");
-  const auto cmpGt = makeBinaryOperatorMatcher(">");
+  const auto LogicalAnd = MakeBinaryOperatorMatcher("&&");
+  const auto Sub = MakeBinaryOperatorMatcher("-");
+  const auto BitwiseAnd = MakeBinaryOperatorMatcher("&");
+  const auto CmpNot = MakeBinaryOperatorMatcher("!=");
+  const auto CmpGt = MakeBinaryOperatorMatcher(">");
 
-  const auto logicalNot = [](auto Expr) {
+  const auto LogicalNot = [](const auto &Expr) {
     return unaryOperator(hasOperatorName("!"),
                          hasUnaryOperand(ignoringParenImpCasts(Expr)));
   };
 
-  const auto isNonNull = [=](auto Expr) {
-    return anyOf(Expr, cmpNot(Expr, integerLiteral(equals(0))),
-                 cmpGt(Expr, integerLiteral(equals(0))));
+  const auto IsNonNull = [=](const auto & Expr) {
+    return anyOf(Expr, CmpNot(Expr, integerLiteral(equals(0))),
+                 CmpGt(Expr, integerLiteral(equals(0))));
   };
-  const auto bindDeclRef = [](auto Name) {
+  const auto BindDeclRef = [](auto Name) {
     return declRefExpr(to(varDecl(hasType(isUnsignedInteger())).bind(Name)));
   };
-  const auto boundDeclRef = [](auto Name) {
+  const auto BoundDeclRef = [](auto Name) {
     return declRefExpr(to(varDecl(equalsBoundNode(Name))));
   };
 
   // https://graphics.stanford.edu/~seander/bithacks.html#DetermineIfPowerOf2
   // has_one_bit(v) = v && !(v & (v - 1));
   Finder->addMatcher(
-      logicalAnd(isNonNull(bindDeclRef("v")),
-                 logicalNot(bitwiseAnd(
-                     boundDeclRef("v"),
-                     sub(boundDeclRef("v"), integerLiteral(equals(1))))))
+      LogicalAnd(IsNonNull(BindDeclRef("v")),
+                 LogicalNot(BitwiseAnd(
+                     BoundDeclRef("v"),
+                     Sub(BoundDeclRef("v"), integerLiteral(equals(1))))))
           .bind("expr"),
       this);
 }
diff --git a/clang-tools-extra/docs/clang-tidy/checks/modernize/use-std-bit.rst b/clang-tools-extra/docs/clang-tidy/checks/modernize/use-std-bit.rst
new file mode 100644
index 0000000000000..1d59e3a485cf9
--- /dev/null
+++ b/clang-tools-extra/docs/clang-tidy/checks/modernize/use-std-bit.rst
@@ -0,0 +1,17 @@
+.. title:: clang-tidy - modernize-use-std-bit
+
+modernize-use-std-bit
+=====================
+
+Find common idioms which can be replaced by standrad functions from the
+``<bit>`` C++20 header.
+
+.. code-block:: c++
+
+    bool has_one_bit = x && !(x & (x - 1));
+
+    // transforms to
+
+    #include <bit>
+
+    bool has_one_bit = std::has_one_bit(x);



More information about the cfe-commits mailing list