[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