[clang-tools-extra] [clang-tidy] Detect std::popcount opportunity within modernize.use-st… (PR #185740)
via cfe-commits
cfe-commits at lists.llvm.org
Tue Mar 10 13:46:44 PDT 2026
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 <sguelton at mozilla.com>
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 <sguelton at mozilla.com>
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
More information about the cfe-commits
mailing list