https://github.com/ayyYoSam created 
https://github.com/llvm/llvm-project/pull/226742

# Description

Fixes: #226720

The check detects range-based loops that insert each element individually into 
associative containers and suggests replacing them with a bulk `insert()` call.

For example:

```cpp
for (int i : in) {
  out.insert(i);
}
```

is transformed into:

```cpp
out.insert(in.begin(), in.end());
```

The initial implementation covers standard associative containers including 
`set`, `map`, and `unordered_*` variants.

## Changes

* Added `UseBulkInsertCheck.cpp`
* Added `UseBulkInsertCheck.h`
* Added the corresponding test file
* Registered `UseBulkInsertCheck` in `MiscTidyModule.cpp`
* Added the check to the `misc` module's `CMakeLists.txt`

## Test

I have not performed local build or test validation. I expect the CI checks to 
provide validation, and I am happy to address any issues identified during 
review.


>From 4ef080191328fa39e845391172c40ac6869e05f9 Mon Sep 17 00:00:00 2001
From: ayyYoSam <[email protected]>
Date: Sat, 26 Sep 2026 22:47:25 -0300
Subject: [PATCH] clang-tidy: add misc-use-bulk-insert check

---
 .../clang-tidy/misc/CMakeLists.txt            |  1 +
 .../clang-tidy/misc/MiscTidyModule.cpp        |  3 +
 .../clang-tidy/misc/UseBulkInsertCheck.cpp    | 97 +++++++++++++++++++
 .../clang-tidy/misc/UseBulkInsertCheck.h      | 26 +++++
 .../checkers/misc/use-bulk-insert.cpp         | 22 +++++
 5 files changed, 149 insertions(+)
 create mode 100644 clang-tools-extra/clang-tidy/misc/UseBulkInsertCheck.cpp
 create mode 100644 clang-tools-extra/clang-tidy/misc/UseBulkInsertCheck.h
 create mode 100644 
clang-tools-extra/test/clang-tidy/checkers/misc/use-bulk-insert.cpp

diff --git a/clang-tools-extra/clang-tidy/misc/CMakeLists.txt 
b/clang-tools-extra/clang-tidy/misc/CMakeLists.txt
index 68faf77fa1d5f..faa27bb5fa77e 100644
--- a/clang-tools-extra/clang-tidy/misc/CMakeLists.txt
+++ b/clang-tools-extra/clang-tidy/misc/CMakeLists.txt
@@ -20,6 +20,7 @@ set_target_properties(genconfusable PROPERTIES FOLDER "Clang 
Tools Extra/Sourceg
 add_clang_library(clangTidyMiscModule STATIC
   AnonymousNamespaceInHeaderCheck.cpp
   ConfusableIdentifierCheck.cpp
+  UseBulkInsertCheck.cpp
   ConstCorrectnessCheck.cpp
   CoroutineHostileRAIICheck.cpp
   DefinitionsInHeadersCheck.cpp
diff --git a/clang-tools-extra/clang-tidy/misc/MiscTidyModule.cpp 
b/clang-tools-extra/clang-tidy/misc/MiscTidyModule.cpp
index 52d3b4297ba26..07f1517eec661 100644
--- a/clang-tools-extra/clang-tidy/misc/MiscTidyModule.cpp
+++ b/clang-tools-extra/clang-tidy/misc/MiscTidyModule.cpp
@@ -7,6 +7,7 @@
 
//===----------------------------------------------------------------------===//
 
 #include "../ClangTidy.h"
+#include "UseBulkInsertCheck.h"
 #include "../ClangTidyModule.h"
 #include "AnonymousNamespaceInHeaderCheck.h"
 #include "ConfusableIdentifierCheck.h"
@@ -49,6 +50,8 @@ class MiscModule : public ClangTidyModule {
         "misc-anonymous-namespace-in-header");
     CheckFactories.registerCheck<ConfusableIdentifierCheck>(
         "misc-confusable-identifiers");
+    CheckFactories.registerCheck<UseBulkInsertCheck>(
+        "misc-use-bulk-insert");
     CheckFactories.registerCheck<ConstCorrectnessCheck>(
         "misc-const-correctness");
     CheckFactories.registerCheck<CoroutineHostileRAIICheck>(
diff --git a/clang-tools-extra/clang-tidy/misc/UseBulkInsertCheck.cpp 
b/clang-tools-extra/clang-tidy/misc/UseBulkInsertCheck.cpp
new file mode 100644
index 0000000000000..dbda841ee5e61
--- /dev/null
+++ b/clang-tools-extra/clang-tidy/misc/UseBulkInsertCheck.cpp
@@ -0,0 +1,97 @@
+//===----------------------------------------------------------------------===//
+//
+// 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 "UseBulkInsertCheck.h"
+#include "clang/ASTMatchers/ASTMatchFinder.h"
+#include "clang/Lex/Lexer.h"
+
+using namespace clang::ast_matchers;
+
+namespace clang::tidy::misc {
+
+void UseBulkInsertCheck::registerMatchers(MatchFinder *Finder) {
+  Finder->addMatcher(
+      cxxForRangeStmt(
+          hasLoopVariable(varDecl().bind("loop_var")),
+          hasRangeInit(expr().bind("range")),
+          hasBody(compoundStmt(
+              statementCountIs(1),
+              hasAnySubstatement(cxxMemberCallExpr(
+                  callee(memberExpr(
+                      ofClass(cxxRecordDecl(hasAnyName(
+                          "::std::set", "::std::map", "::std::multiset",
+                          "::std::multimap", "::std::unordered_set",
+                          "::std::unordered_map",
+                          "::std::unordered_multiset",
+                          "::std::unordered_multimap"))))),
+                  argumentCountIs(1),
+                  hasArgument(
+                      0, ignoringParenImpCasts(
+                             declRefExpr(to(varDecl().bind("insert_arg"))))))
+                  .bind("insert_call")))))
+          .bind("for_range"),
+      this);
+}
+
+void UseBulkInsertCheck::check(
+    const MatchFinder::MatchResult &Result) {
+  const auto *Loop =
+      Result.Nodes.getNodeAs<CXXForRangeStmt>("for_range");
+  const auto *LoopVar =
+      Result.Nodes.getNodeAs<VarDecl>("loop_var");
+  const auto *InsertArg =
+      Result.Nodes.getNodeAs<VarDecl>("insert_arg");
+  const auto *Range =
+      Result.Nodes.getNodeAs<Expr>("range");
+  const auto *InsertCall =
+      Result.Nodes.getNodeAs<CXXMemberCallExpr>("insert_call");
+
+  if (!Loop || !LoopVar || !InsertArg || !Range || !InsertCall)
+    return;
+
+  if (LoopVar != InsertArg)
+    return;
+
+  const auto *Member = dyn_cast<MemberExpr>(InsertCall->getCallee());
+  if (!Member)
+    return;
+
+  const Expr *Object = Member->getBase();
+  if (!Object)
+    return;
+
+  const SourceManager &SM = *Result.SourceManager;
+  const LangOptions &LangOpts = Result.Context->getLangOpts();
+
+  StringRef ObjectText = Lexer::getSourceText(
+      CharSourceRange::getTokenRange(Object->getSourceRange()), SM,
+      LangOpts);
+
+  StringRef RangeText = Lexer::getSourceText(
+      CharSourceRange::getTokenRange(Range->getSourceRange()), SM,
+      LangOpts);
+
+  if (ObjectText.empty() || RangeText.empty())
+    return;
+
+  std::string Replacement = ObjectText.str();
+  Replacement += ".insert(";
+  Replacement += RangeText;
+  Replacement += ".begin(), ";
+  Replacement += RangeText;
+  Replacement += ".end());";
+
+  diag(Loop->getForLoc(),
+       "use bulk insertion instead of inserting elements one at a time")
+      << FixItHint::CreateReplacement(
+             CharSourceRange::getTokenRange(Loop->getBeginLoc(),
+                                             Loop->getEndLoc()),
+             Replacement);
+}
+
+} // namespace clang::tidy::misc
\ No newline at end of file
diff --git a/clang-tools-extra/clang-tidy/misc/UseBulkInsertCheck.h 
b/clang-tools-extra/clang-tidy/misc/UseBulkInsertCheck.h
new file mode 100644
index 0000000000000..502cf1d29a55e
--- /dev/null
+++ b/clang-tools-extra/clang-tidy/misc/UseBulkInsertCheck.h
@@ -0,0 +1,26 @@
+//===----------------------------------------------------------------------===//
+//
+// 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_MISC_USEBULKINSERTCHECK_H
+#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_USEBULKINSERTCHECK_H
+
+#include "../ClangTidyCheck.h"
+
+namespace clang::tidy::misc {
+
+class UseBulkInsertCheck : public ClangTidyCheck {
+public:
+  using ClangTidyCheck::ClangTidyCheck;
+
+  void registerMatchers(ast_matchers::MatchFinder *Finder) override;
+  void check(const ast_matchers::MatchFinder::MatchResult &Result) override;
+};
+
+} // namespace clang::tidy::misc
+
+#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_USEBULKINSERTCHECK_H
\ No newline at end of file
diff --git 
a/clang-tools-extra/test/clang-tidy/checkers/misc/use-bulk-insert.cpp 
b/clang-tools-extra/test/clang-tidy/checkers/misc/use-bulk-insert.cpp
new file mode 100644
index 0000000000000..9b524ba3bcaaf
--- /dev/null
+++ b/clang-tools-extra/test/clang-tidy/checkers/misc/use-bulk-insert.cpp
@@ -0,0 +1,22 @@
+// RUN: %check_clang_tidy %s misc-use-bulk-insert %t
+
+#include <set>
+#include <vector>
+
+void test_set(const std::set<int> &In) {
+  std::set<int> Out;
+
+  for (int I : In) {
+    Out.insert(I);
+    // CHECK-MESSAGES: :[[@LINE-1]]:5: warning: use bulk insertion instead of 
inserting elements one at a time [misc-use-bulk-insert]
+    // CHECK-FIXES: Out.insert(In.begin(), In.end());
+  }
+}
+
+void test_vector(const std::vector<int> &In) {
+  std::vector<int> Out;
+
+  for (int I : In) {
+    Out.insert(I);
+  }
+}
\ No newline at end of file

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

Reply via email to