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

>From 2fdf0382f89028411966040c6bd98de473e78bb6 Mon Sep 17 00:00:00 2001
From: serge-sans-paille <[email protected]>
Date: Tue, 10 Mar 2026 18:04:30 +0100
Subject: [PATCH 1/2] [clang-tidy] Detect std::popcount opportunity within
 modernize.use-std-bit

Change std::bitset<N>(x).count() into std::popcount(x).
---
 .../clang-tidy/modernize/UseStdBitCheck.cpp   | 68 ++++++++++---
 .../checks/modernize/use-std-bit.rst          |  1 +
 .../checkers/modernize/use-std-bit.cpp        | 99 +++++++++++++++----
 3 files changed, 137 insertions(+), 31 deletions(-)

diff --git a/clang-tools-extra/clang-tidy/modernize/UseStdBitCheck.cpp 
b/clang-tools-extra/clang-tidy/modernize/UseStdBitCheck.cpp
index ff43f707a867b..cbc7301e6519f 100644
--- a/clang-tools-extra/clang-tidy/modernize/UseStdBitCheck.cpp
+++ b/clang-tools-extra/clang-tidy/modernize/UseStdBitCheck.cpp
@@ -72,7 +72,20 @@ void UseStdBitCheck::registerMatchers(MatchFinder *Finder) {
                  LogicalNot(BitwiseAnd(
                      BoundDeclRef("v"),
                      Sub(BoundDeclRef("v"), integerLiteral(equals(1))))))
-          .bind("expr"),
+          .bind("has_one_bit_expr"),
+      this);
+
+  // Computing popcount with following pattern:
+  // std::bitset<N>(val).count()
+  Finder->addMatcher(
+      cxxMemberCallExpr(
+          argumentCountIs(0),
+          callee(cxxMethodDecl(
+              hasName("count"),
+              ofClass(cxxRecordDecl(hasName("bitset"), isInStdNamespace())))),
+          on(cxxConstructExpr(
+              hasArgument(0, expr(hasType(isUnsignedInteger())).bind("v")))))
+          .bind("popcount_expr"),
       this);
 }
 
@@ -90,19 +103,46 @@ 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");
-
-  auto Diag =
-      diag(MatchedExpr->getBeginLoc(), "use 'std::has_one_bit' instead");
-  if (auto R = MatchedExpr->getSourceRange();
-      !R.getBegin().isMacroID() && !R.getEnd().isMacroID()) {
-    Diag << FixItHint::CreateReplacement(
-                MatchedExpr->getSourceRange(),
-                ("std::has_one_bit(" + MatchedVarDecl->getName() + ")").str())
-         << IncludeInserter.createIncludeInsertion(
-                Source.getFileID(MatchedExpr->getBeginLoc()), "<bit>");
+  if (const auto *MatchedExpr =
+          Result.Nodes.getNodeAs<BinaryOperator>("expr")) {
+    const auto *MatchedVarDecl = Result.Nodes.getNodeAs<VarDecl>("v");
+
+    auto Diag =
+        diag(MatchedExpr->getBeginLoc(), "use 'std::has_one_bit' instead");
+    if (auto R = MatchedExpr->getSourceRange();
+        !R.getBegin().isMacroID() && !R.getEnd().isMacroID()) {
+      Diag << FixItHint::CreateReplacement(
+                  MatchedExpr->getSourceRange(),
+                  ("std::has_one_bit(" + MatchedVarDecl->getName() + 
")").str())
+           << IncludeInserter.createIncludeInsertion(
+                  Source.getFileID(MatchedExpr->getBeginLoc()), "<bit>");
+    } else if (const auto *MatchedExpr =
+                   Result.Nodes.getNodeAs<CXXMemberCallExpr>("popcount_expr")) 
{
+      const auto *BitsetInstantiatedDecl =
+          cast<ClassTemplateSpecializationDecl>(MatchedExpr->getRecordDecl());
+      const llvm::APSInt BitsetSize =
+          BitsetInstantiatedDecl->getTemplateArgs()[0].getAsIntegral();
+      const auto *MatchedArg = Result.Nodes.getNodeAs<Expr>("v");
+      const uint64_t MatchedVarSize =
+          Context.getTypeSize(MatchedArg->getType());
+      if (BitsetSize >= MatchedVarSize) {
+        MatchedArg->dump();
+        auto Diag =
+            diag(MatchedExpr->getBeginLoc(), "use 'std::popcount' instead");
+        Diag << FixItHint::CreateRemoval(CharSourceRange::getTokenRange(
+                    MatchedArg->getEndLoc().getLocWithOffset(1),
+                    MatchedExpr->getRParenLoc().getLocWithOffset(-1)))
+             << FixItHint::CreateReplacement(
+                    CharSourceRange::getTokenRange(
+                        MatchedExpr->getBeginLoc(),
+                        MatchedArg->getBeginLoc().getLocWithOffset(-1)),
+                    "std::popcount(")
+             << IncludeInserter.createIncludeInsertion(
+                    Source.getFileID(MatchedExpr->getBeginLoc()), "<bit>");
+      }
+    } else {
+      llvm_unreachable();
+    }
   }
-}
 
 } // namespace clang::tidy::modernize
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
index 87d102d1f6ff8..26ef1b0841654 100644
--- 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
@@ -14,4 +14,5 @@ Expression                     Replacement
 ``x && !(x & (x - 1))``        ``std::has_one_bit(x)``
 ``(x != 0) && !(x & (x - 1))`` ``std::has_one_bit(x)``
 ``(x > 0) && !(x & (x - 1))``  ``std::has_one_bit(x)``
+``std::bitset<N>(x).count()``  ``std::popcount(x)``
 ============================== =======================
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
index 51f9d3485fc26..5e2898c01b259 100644
--- 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
@@ -1,87 +1,90 @@
 // RUN: %check_clang_tidy -std=c++20-or-later %s modernize-use-std-bit %t
 // CHECK-FIXES: #include <bit>
 
-unsigned bithacks(unsigned x) {
+/*
+ * has_one_bit pattern
+ */
+unsigned has_one_bit_bithack(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) {
+unsigned long has_one_bit_bithack(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) {
+unsigned short has_one_bit_bithack(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) {
+unsigned has_one_bit_bithack_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_otherperm(unsigned x) {
+unsigned has_one_bit_bithack_otherperm(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 - 1) & (x)) && x;
 }
 
-unsigned bithacks_variant_neq(unsigned x) {
+unsigned has_one_bit_bithack_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) {
+unsigned has_one_bit_bithack_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) {
+unsigned has_one_bit_bithack_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_gte(unsigned x) {
+unsigned has_one_bit_bithacks_variant_gte(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 >= 1) && !(x & (x - 1));
 }
 
-unsigned bithacks_variant_lt(unsigned x) {
+unsigned has_one_bit_bithacks_variant_lt(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 (0 < x) && !(x & (x - 1));
 }
 
-unsigned bithacks_variant_lte(unsigned x) {
+unsigned has_one_bit_bithacks_variant_lte(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 (1 <= x) && !(x & (x - 1));
 }
 
-unsigned bithacks_variant_gt_perm(unsigned x) {
+unsigned has_one_bit_bithack_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));
 }
 
 #define HAS_ONE_BIT v && !(v & (v - 1))
-unsigned bithacks_macro(unsigned v) {
+unsigned has_one_bit_bithack_macro(unsigned v) {
   // CHECK-MESSAGES: :[[@LINE+2]]:10: warning: use 'std::has_one_bit' instead 
[modernize-use-std-bit]
   // No fixes, it comes from macro expansion.
   return HAS_ONE_BIT;
 }
 
 /*
- * Invalid patterns
+ * Invalid has_one_bit patterns
  */
 struct integer_like {
   integer_like operator!() const;
@@ -90,7 +93,7 @@ struct integer_like {
   friend integer_like operator-(integer_like, unsigned);
 };
 
-unsigned invalid_bithacks(integer_like w, unsigned x, signed y, unsigned z) {
+unsigned invalid_has_one_bit_bithack(integer_like w, unsigned x, signed y, 
unsigned z) {
   bool patterns[] = {
     // non commutative operators
     x && !(x & (1 - x)),
@@ -111,7 +114,69 @@ unsigned invalid_bithacks(integer_like w, unsigned x, 
signed y, unsigned z) {
 }
 
 template <class T>
-T bithacks_generic(T x) {
-  // substitution only valid for some instantiation of bithacks_generic
+T has_one_bit_bithack_generic(T x) {
+  // substitution only valid for some instantiation of 
has_one_bit_bithack_generic
   return x && !(x & (x - 1));
 }
+
+/*
+ * popcount pattern
+ */
+namespace std {
+using size_t = decltype(sizeof(0));
+template<size_t N> class bitset {
+  public:
+  bitset(unsigned long);
+  size_t count() const;
+};
+}
+
+unsigned popcount_bitset(unsigned x) {
+  // CHECK-MESSAGES: :[[@LINE+2]]:10: warning: use 'std::popcount' instead 
[modernize-use-std-bit]
+  // CHECK-FIXES: return std::popcount(x);
+  return std::bitset<sizeof(x) * 8>(x).count();
+}
+
+unsigned popcount_bitset_short(unsigned short x) {
+  // CHECK-MESSAGES: :[[@LINE+2]]:10: warning: use 'std::popcount' instead 
[modernize-use-std-bit]
+  // CHECK-FIXES: return std::popcount(x);
+  return std::bitset<sizeof(x) * 8>(x).count();
+}
+
+unsigned popcount_bitset_larger(unsigned x) {
+  // CHECK-MESSAGES: :[[@LINE+2]]:10: warning: use 'std::popcount' instead 
[modernize-use-std-bit]
+  // CHECK-FIXES: return std::popcount(x);
+  return std::bitset<sizeof(x) * 16>(x).count();
+}
+
+unsigned popcount_bitset_uniform_init(unsigned x) {
+  // CHECK-MESSAGES: :[[@LINE+2]]:10: warning: use 'std::popcount' instead 
[modernize-use-std-bit]
+  // CHECK-FIXES: return std::popcount(x);
+  return std::bitset<sizeof(x) * 16>{x}.count();
+}
+
+unsigned popcount_bitset_expr(unsigned x) {
+  // CHECK-MESSAGES: :[[@LINE+2]]:10: warning: use 'std::popcount' instead 
[modernize-use-std-bit]
+  // CHECK-FIXES: return std::popcount(x + 1);
+  return std::bitset<sizeof(x) * 8>{x + 1}.count();
+}
+
+/*
+ * Invalid has_one_bit patterns
+ */
+template<std::size_t N> class bitset {
+  public:
+  bitset(unsigned long);
+  std::size_t count() const;
+};
+
+unsigned invalid_popcount_bitset(unsigned x, signed y) {
+  std::size_t patterns[] = {
+    // truncating bitset
+    std::bitset<1>{x}.count(),
+    // unsupported types
+    std::bitset<sizeof(y) * 8>(y).count(),
+    bitset<sizeof(x) * 8>{x}.count(),
+  };
+}
+

>From 84eb817cec10f7a853eb08601a200876025daf3f Mon Sep 17 00:00:00 2001
From: serge-sans-paille <[email protected]>
Date: Tue, 10 Mar 2026 21:44:41 +0100
Subject: [PATCH 2/2] fixup! [clang-tidy] Detect std::popcount opportunity
 within modernize.use-std-bit

---
 .../clang-tidy/modernize/UseStdBitCheck.cpp   | 52 +++++++++----------
 1 file changed, 26 insertions(+), 26 deletions(-)

diff --git a/clang-tools-extra/clang-tidy/modernize/UseStdBitCheck.cpp 
b/clang-tools-extra/clang-tidy/modernize/UseStdBitCheck.cpp
index cbc7301e6519f..32a887c734bc0 100644
--- a/clang-tools-extra/clang-tidy/modernize/UseStdBitCheck.cpp
+++ b/clang-tools-extra/clang-tidy/modernize/UseStdBitCheck.cpp
@@ -116,33 +116,33 @@ void UseStdBitCheck::check(const MatchFinder::MatchResult 
&Result) {
                   ("std::has_one_bit(" + MatchedVarDecl->getName() + 
")").str())
            << IncludeInserter.createIncludeInsertion(
                   Source.getFileID(MatchedExpr->getBeginLoc()), "<bit>");
-    } else if (const auto *MatchedExpr =
-                   Result.Nodes.getNodeAs<CXXMemberCallExpr>("popcount_expr")) 
{
-      const auto *BitsetInstantiatedDecl =
-          cast<ClassTemplateSpecializationDecl>(MatchedExpr->getRecordDecl());
-      const llvm::APSInt BitsetSize =
-          BitsetInstantiatedDecl->getTemplateArgs()[0].getAsIntegral();
-      const auto *MatchedArg = Result.Nodes.getNodeAs<Expr>("v");
-      const uint64_t MatchedVarSize =
-          Context.getTypeSize(MatchedArg->getType());
-      if (BitsetSize >= MatchedVarSize) {
-        MatchedArg->dump();
-        auto Diag =
-            diag(MatchedExpr->getBeginLoc(), "use 'std::popcount' instead");
-        Diag << FixItHint::CreateRemoval(CharSourceRange::getTokenRange(
-                    MatchedArg->getEndLoc().getLocWithOffset(1),
-                    MatchedExpr->getRParenLoc().getLocWithOffset(-1)))
-             << FixItHint::CreateReplacement(
-                    CharSourceRange::getTokenRange(
-                        MatchedExpr->getBeginLoc(),
-                        MatchedArg->getBeginLoc().getLocWithOffset(-1)),
-                    "std::popcount(")
-             << IncludeInserter.createIncludeInsertion(
-                    Source.getFileID(MatchedExpr->getBeginLoc()), "<bit>");
-      }
-    } else {
-      llvm_unreachable();
     }
+  } else if (const auto *MatchedExpr =
+                 Result.Nodes.getNodeAs<CXXMemberCallExpr>("popcount_expr")) {
+    const auto *BitsetInstantiatedDecl =
+        cast<ClassTemplateSpecializationDecl>(MatchedExpr->getRecordDecl());
+    const llvm::APSInt BitsetSize =
+        BitsetInstantiatedDecl->getTemplateArgs()[0].getAsIntegral();
+    const auto *MatchedArg = Result.Nodes.getNodeAs<Expr>("v");
+    const uint64_t MatchedVarSize = Context.getTypeSize(MatchedArg->getType());
+    if (BitsetSize >= MatchedVarSize) {
+      MatchedArg->dump();
+      auto Diag =
+          diag(MatchedExpr->getBeginLoc(), "use 'std::popcount' instead");
+      Diag << FixItHint::CreateRemoval(CharSourceRange::getTokenRange(
+                  MatchedArg->getEndLoc().getLocWithOffset(1),
+                  MatchedExpr->getRParenLoc().getLocWithOffset(-1)))
+           << FixItHint::CreateReplacement(
+                  CharSourceRange::getTokenRange(
+                      MatchedExpr->getBeginLoc(),
+                      MatchedArg->getBeginLoc().getLocWithOffset(-1)),
+                  "std::popcount(")
+           << IncludeInserter.createIncludeInsertion(
+                  Source.getFileID(MatchedExpr->getBeginLoc()), "<bit>");
+    }
+  } else {
+    llvm_unreachable("unexpected match");
   }
+}
 
 } // namespace clang::tidy::modernize

_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to