[clang-tools-extra] [clang-tidy] Add llvm-use-vector-utils (PR #177722)
Jakub Kuderski via cfe-commits
cfe-commits at lists.llvm.org
Sat Jan 24 06:37:14 PST 2026
https://github.com/kuhar updated https://github.com/llvm/llvm-project/pull/177722
>From caebed302b534f4aa1e788ac05f9c5d1d9642012 Mon Sep 17 00:00:00 2001
From: Jakub Kuderski <jakub at nod-labs.com>
Date: Fri, 23 Jan 2026 20:10:03 -0500
Subject: [PATCH 1/5] [clang-tidy] Add llvm-use-vector-utils
This new check suggests the following replacements:
* `llvm::to_vector(llvm::map_range(X, Fn))` -> `llvm::map_to_vector(X, Fn)`
* `llvm::to_vector(llvm::make_filter_range(X, Fn))` -> `llvm::filter_to_vector(X, Fn)`
and add the `SmallVectorExtras.h` include when necessary.
---
.../clang-tidy/llvm/CMakeLists.txt | 1 +
.../clang-tidy/llvm/LLVMTidyModule.cpp | 2 +
.../clang-tidy/llvm/UseVectorUtilsCheck.cpp | 128 ++++++++++++++++++
.../clang-tidy/llvm/UseVectorUtilsCheck.h | 41 ++++++
clang-tools-extra/docs/ReleaseNotes.rst | 7 +
.../docs/clang-tidy/checks/list.rst | 1 +
.../checks/llvm/use-vector-utils.rst | 32 +++++
.../checkers/llvm/use-vector-utils.cpp | 103 ++++++++++++++
8 files changed, 315 insertions(+)
create mode 100644 clang-tools-extra/clang-tidy/llvm/UseVectorUtilsCheck.cpp
create mode 100644 clang-tools-extra/clang-tidy/llvm/UseVectorUtilsCheck.h
create mode 100644 clang-tools-extra/docs/clang-tidy/checks/llvm/use-vector-utils.rst
create mode 100644 clang-tools-extra/test/clang-tidy/checkers/llvm/use-vector-utils.cpp
diff --git a/clang-tools-extra/clang-tidy/llvm/CMakeLists.txt b/clang-tools-extra/clang-tidy/llvm/CMakeLists.txt
index 78ef0444305ff..56bf4f31bd0e8 100644
--- a/clang-tools-extra/clang-tidy/llvm/CMakeLists.txt
+++ b/clang-tools-extra/clang-tidy/llvm/CMakeLists.txt
@@ -13,6 +13,7 @@ add_clang_library(clangTidyLLVMModule STATIC
TwineLocalCheck.cpp
UseNewMLIROpBuilderCheck.cpp
UseRangesCheck.cpp
+ UseVectorUtilsCheck.cpp
LINK_LIBS
clangTidy
diff --git a/clang-tools-extra/clang-tidy/llvm/LLVMTidyModule.cpp b/clang-tools-extra/clang-tidy/llvm/LLVMTidyModule.cpp
index 56c6db05e9792..eb1ae820dabf8 100644
--- a/clang-tools-extra/clang-tidy/llvm/LLVMTidyModule.cpp
+++ b/clang-tools-extra/clang-tidy/llvm/LLVMTidyModule.cpp
@@ -19,6 +19,7 @@
#include "TwineLocalCheck.h"
#include "UseNewMLIROpBuilderCheck.h"
#include "UseRangesCheck.h"
+#include "UseVectorUtilsCheck.h"
namespace clang::tidy {
namespace llvm_check {
@@ -45,6 +46,7 @@ class LLVMModule : public ClangTidyModule {
CheckFactories.registerCheck<UseNewMlirOpBuilderCheck>(
"llvm-use-new-mlir-op-builder");
CheckFactories.registerCheck<UseRangesCheck>("llvm-use-ranges");
+ CheckFactories.registerCheck<UseVectorUtilsCheck>("llvm-use-vector-utils");
}
ClangTidyOptions getModuleOptions() override {
diff --git a/clang-tools-extra/clang-tidy/llvm/UseVectorUtilsCheck.cpp b/clang-tools-extra/clang-tidy/llvm/UseVectorUtilsCheck.cpp
new file mode 100644
index 0000000000000..ba377339c1cd5
--- /dev/null
+++ b/clang-tools-extra/clang-tidy/llvm/UseVectorUtilsCheck.cpp
@@ -0,0 +1,128 @@
+//===----------------------------------------------------------------------===//
+//
+// 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 "UseVectorUtilsCheck.h"
+#include "clang/AST/ASTContext.h"
+#include "clang/ASTMatchers/ASTMatchFinder.h"
+#include "clang/Lex/Lexer.h"
+
+using namespace clang::ast_matchers;
+
+namespace clang::tidy::llvm_check {
+
+UseVectorUtilsCheck::UseVectorUtilsCheck(StringRef Name,
+ ClangTidyContext *Context)
+ : ClangTidyCheck(Name, Context),
+ Inserter(Options.getLocalOrGlobal("IncludeStyle",
+ utils::IncludeSorter::IS_LLVM),
+ areDiagsSelfContained()) {}
+
+void UseVectorUtilsCheck::registerPPCallbacks(const SourceManager &SM,
+ Preprocessor *PP,
+ Preprocessor *ModuleExpanderPP) {
+ Inserter.registerPreprocessor(PP);
+}
+
+void UseVectorUtilsCheck::registerMatchers(MatchFinder *Finder) {
+ // Match `llvm::to_vector(llvm::map_range(X, F))`.
+ Finder->addMatcher(
+ callExpr(
+ callee(functionDecl(hasName("::llvm::to_vector"))),
+ hasArgument(
+ 0, callExpr(callee(functionDecl(hasName("::llvm::map_range"))))
+ .bind("inner_call")))
+ .bind("map_range_call"),
+ this);
+
+ // Match `llvm::to_vector(llvm::make_filter_range(X, Pred))`.
+ Finder->addMatcher(
+ callExpr(
+ callee(functionDecl(hasName("::llvm::to_vector"))),
+ hasArgument(0, callExpr(callee(functionDecl(
+ hasName("::llvm::make_filter_range"))))
+ .bind("inner_call")))
+ .bind("filter_range_call"),
+ this);
+}
+
+void UseVectorUtilsCheck::check(const MatchFinder::MatchResult &Result) {
+ const auto *MapRangeCall =
+ Result.Nodes.getNodeAs<CallExpr>("map_range_call");
+ const auto *FilterRangeCall =
+ Result.Nodes.getNodeAs<CallExpr>("filter_range_call");
+ if (!MapRangeCall && !FilterRangeCall)
+ return;
+
+ const auto *InnerCall = Result.Nodes.getNodeAs<CallExpr>("inner_call");
+ assert(InnerCall && "inner_call must be bound if map_range_call or "
+ "filter_range_call matched");
+ // Only handle the 2-argument overloads of `map_range`/`make_filter_range`, to
+ // future-proof against additional overloads.
+ if (InnerCall->getNumArgs() != 2)
+ return;
+
+ const CallExpr *OuterCall = MapRangeCall ? MapRangeCall : FilterRangeCall;
+
+ const SourceManager &SM = *Result.SourceManager;
+ const LangOptions &LangOpts = getLangOpts();
+
+ // Determine the base replacement function name.
+ const StringRef ReplacementFuncBase =
+ MapRangeCall ? "llvm::map_to_vector" : "llvm::filter_to_vector";
+ const StringRef InnerFuncName =
+ MapRangeCall ? "llvm::map_range" : "llvm::make_filter_range";
+
+ // Check if `to_vector` was called with an explicit size template argument.
+ std::string SizeTemplateArg;
+ if (const auto *DRE =
+ dyn_cast<DeclRefExpr>(OuterCall->getCallee()->IgnoreImplicit())) {
+ if (DRE->hasExplicitTemplateArgs()) {
+ // Extract the template argument text (e.g., `<4>`).
+ const auto TemplateArgsCharRange = CharSourceRange::getTokenRange(
+ DRE->getLAngleLoc(), DRE->getRAngleLoc());
+ SizeTemplateArg =
+ Lexer::getSourceText(TemplateArgsCharRange, SM, LangOpts).str();
+ }
+ }
+
+ const std::string ReplacementFunc =
+ (ReplacementFuncBase + SizeTemplateArg).str();
+ const std::string ToVectorFunc = "llvm::to_vector" + SizeTemplateArg;
+
+ // Build the replacement: Replace the whole expression with the new function
+ // and the arguments from the inner call.
+ auto Diag = diag(OuterCall->getBeginLoc(),
+ "use '%0' instead of '%1(%2(...))'")
+ << ReplacementFunc << ToVectorFunc << InnerFuncName;
+
+ // Get the range argument.
+ const SourceRange RangeArgRange = InnerCall->getArg(0)->getSourceRange();
+ const auto RangeArgCharRange = CharSourceRange::getTokenRange(RangeArgRange);
+ const StringRef RangeArgText =
+ Lexer::getSourceText(RangeArgCharRange, SM, LangOpts);
+
+ // Get the function/predicate argument.
+ const SourceRange FuncArgRange = InnerCall->getArg(1)->getSourceRange();
+ const auto FuncArgCharRange = CharSourceRange::getTokenRange(FuncArgRange);
+ const StringRef FuncArgText =
+ Lexer::getSourceText(FuncArgCharRange, SM, LangOpts);
+
+ // Create the replacement text.
+ const std::string Replacement =
+ (ReplacementFunc + "(" + RangeArgText + ", " + FuncArgText + ")").str();
+
+ Diag << FixItHint::CreateReplacement(OuterCall->getSourceRange(), Replacement);
+
+ // Add include for `SmallVectorExtras.h` if needed.
+ if (auto IncludeFixit = Inserter.createIncludeInsertion(
+ SM.getFileID(OuterCall->getBeginLoc()),
+ "llvm/ADT/SmallVectorExtras.h"))
+ Diag << *IncludeFixit;
+}
+
+} // namespace clang::tidy::llvm_check
diff --git a/clang-tools-extra/clang-tidy/llvm/UseVectorUtilsCheck.h b/clang-tools-extra/clang-tidy/llvm/UseVectorUtilsCheck.h
new file mode 100644
index 0000000000000..e2a2d2fe663a1
--- /dev/null
+++ b/clang-tools-extra/clang-tidy/llvm/UseVectorUtilsCheck.h
@@ -0,0 +1,41 @@
+//===----------------------------------------------------------------------===//
+//
+// 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_LLVM_USEVECTORUTILSCHECK_H
+#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_LLVM_USEVECTORUTILSCHECK_H
+
+#include "../ClangTidyCheck.h"
+#include "../utils/IncludeInserter.h"
+
+namespace clang::tidy::llvm_check {
+
+/// Finds calls to `llvm::to_vector(llvm::map_range(...))` and
+/// `llvm::to_vector(llvm::make_filter_range(...))` that can be replaced with
+/// `llvm::map_to_vector` and `llvm::filter_to_vector` from `SmallVectorExtras.h`.
+///
+/// For the user-facing documentation see:
+/// https://clang.llvm.org/extra/clang-tidy/checks/llvm/use-vector-utils.html
+class UseVectorUtilsCheck : public ClangTidyCheck {
+public:
+ UseVectorUtilsCheck(StringRef Name, ClangTidyContext *Context);
+ bool isLanguageVersionSupported(const LangOptions &LangOpts) const override {
+ return LangOpts.CPlusPlus;
+ }
+ void registerPPCallbacks(const SourceManager &SM, Preprocessor *PP,
+ Preprocessor *ModuleExpanderPP) override;
+ void registerMatchers(ast_matchers::MatchFinder *Finder) override;
+ void check(const ast_matchers::MatchFinder::MatchResult &Result) override;
+
+private:
+ utils::IncludeInserter Inserter;
+};
+
+} // namespace clang::tidy::llvm_check
+
+#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_LLVM_USEVECTORUTILSCHECK_H
+
diff --git a/clang-tools-extra/docs/ReleaseNotes.rst b/clang-tools-extra/docs/ReleaseNotes.rst
index 1e5d4ef5dbb3b..3b3a033f069ef 100644
--- a/clang-tools-extra/docs/ReleaseNotes.rst
+++ b/clang-tools-extra/docs/ReleaseNotes.rst
@@ -97,6 +97,13 @@ Improvements to clang-tidy
New checks
^^^^^^^^^^
+- New :doc:`llvm-use-vector-utils
+ <clang-tidy/checks/llvm/use-vector-utils>` check.
+
+ Finds calls to ``llvm::to_vector(llvm::map_range(...))`` and
+ ``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-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 a34fade4b8027..25d1354fc4c20 100644
--- a/clang-tools-extra/docs/clang-tidy/checks/list.rst
+++ b/clang-tools-extra/docs/clang-tidy/checks/list.rst
@@ -258,6 +258,7 @@ Clang-Tidy Checks
:doc:`llvm-twine-local <llvm/twine-local>`, "Yes"
:doc:`llvm-use-new-mlir-op-builder <llvm/use-new-mlir-op-builder>`, "Yes"
:doc:`llvm-use-ranges <llvm/use-ranges>`, "Yes"
+ :doc:`llvm-use-vector-utils <llvm/use-vector-utils>`, "Yes"
:doc:`llvmlibc-callee-namespace <llvmlibc/callee-namespace>`,
:doc:`llvmlibc-implementation-in-namespace <llvmlibc/implementation-in-namespace>`,
:doc:`llvmlibc-inline-function-decl <llvmlibc/inline-function-decl>`, "Yes"
diff --git a/clang-tools-extra/docs/clang-tidy/checks/llvm/use-vector-utils.rst b/clang-tools-extra/docs/clang-tidy/checks/llvm/use-vector-utils.rst
new file mode 100644
index 0000000000000..2c0d429be7fca
--- /dev/null
+++ b/clang-tools-extra/docs/clang-tidy/checks/llvm/use-vector-utils.rst
@@ -0,0 +1,32 @@
+.. title:: clang-tidy - llvm-use-vector-utils
+
+llvm-use-vector-utils
+=====================
+
+Finds calls to ``llvm::to_vector`` with ``llvm::map_range`` or
+``llvm::make_filter_range`` that can be replaced with the more concise
+``llvm::map_to_vector`` and ``llvm::filter_to_vector`` utilities from
+``llvm/ADT/SmallVectorExtras.h``.
+
+The check will add the necessary ``#include "llvm/ADT/SmallVectorExtras.h"``
+directive when applying fixes.
+
+Example
+-------
+
+.. code-block:: c++
+
+ auto v1 = llvm::to_vector(llvm::map_range(container, func));
+ auto v2 = llvm::to_vector(llvm::make_filter_range(container, pred));
+ auto v3 = llvm::to_vector<4>(llvm::map_range(container, func));
+ auto v4 = llvm::to_vector<4>(llvm::make_filter_range(container, pred));
+
+Transforms to:
+
+.. code-block:: c++
+
+ auto v1 = llvm::map_to_vector(container, func);
+ auto v2 = llvm::filter_to_vector(container, pred);
+ auto v3 = llvm::map_to_vector<4>(container, func);
+ auto v4 = llvm::filter_to_vector<4>(container, pred);
+
diff --git a/clang-tools-extra/test/clang-tidy/checkers/llvm/use-vector-utils.cpp b/clang-tools-extra/test/clang-tidy/checkers/llvm/use-vector-utils.cpp
new file mode 100644
index 0000000000000..b69f6824d13a4
--- /dev/null
+++ b/clang-tools-extra/test/clang-tidy/checkers/llvm/use-vector-utils.cpp
@@ -0,0 +1,103 @@
+// RUN: %check_clang_tidy %s llvm-use-vector-utils %t
+
+// CHECK-FIXES: #include "llvm/ADT/SmallVectorExtras.h"
+
+namespace llvm {
+
+template <typename T> class SmallVector {};
+
+template <typename RangeT>
+SmallVector<int> to_vector(RangeT &&Range);
+
+template <unsigned Size, typename RangeT>
+SmallVector<int> to_vector(RangeT &&Range);
+
+template <typename Out, typename RangeT>
+SmallVector<Out> to_vector_of(RangeT &&Range);
+
+template <typename Out, unsigned Size, typename RangeT>
+SmallVector<Out> to_vector_of(RangeT &&Range);
+
+template <typename ContainerT, typename FuncT>
+struct mapped_range {};
+
+template <typename ContainerT, typename FuncT>
+mapped_range<ContainerT, FuncT> map_range(ContainerT &&C, FuncT &&F);
+
+// Hypothetical 3-arg overload (for future-proofing).
+template <typename ContainerT, typename FuncT, typename ExtraT>
+mapped_range<ContainerT, FuncT> map_range(ContainerT &&C, FuncT &&F, ExtraT &&E);
+
+template <typename ContainerT, typename PredT>
+struct filter_range {};
+
+template <typename ContainerT, typename PredT>
+filter_range<ContainerT, PredT> make_filter_range(ContainerT &&C, PredT &&P);
+
+// Hypothetical 3-arg overload (for future-proofing).
+template <typename ContainerT, typename PredT, typename ExtraT>
+filter_range<ContainerT, PredT> make_filter_range(ContainerT &&C, PredT &&P, ExtraT &&E);
+
+} // namespace llvm
+
+int transform(int x);
+bool is_even(int x);
+
+void test_map_range() {
+ llvm::SmallVector<int> vec;
+
+ auto result = llvm::to_vector(llvm::map_range(vec, transform));
+ // CHECK-MESSAGES: :[[@LINE-1]]:17: warning: use 'llvm::map_to_vector' instead of 'llvm::to_vector(llvm::map_range(...))'
+ // CHECK-FIXES: auto result = llvm::map_to_vector(vec, transform);
+
+ auto result_sized = llvm::to_vector<4>(llvm::map_range(vec, transform));
+ // CHECK-MESSAGES: :[[@LINE-1]]:23: warning: use 'llvm::map_to_vector<4>' instead of 'llvm::to_vector<4>(llvm::map_range(...))'
+ // CHECK-FIXES: auto result_sized = llvm::map_to_vector<4>(vec, transform);
+}
+
+void test_filter_range() {
+ llvm::SmallVector<int> vec;
+
+ auto result = llvm::to_vector(llvm::make_filter_range(vec, is_even));
+ // CHECK-MESSAGES: :[[@LINE-1]]:17: warning: use 'llvm::filter_to_vector' instead of 'llvm::to_vector(llvm::make_filter_range(...))'
+ // CHECK-FIXES: auto result = llvm::filter_to_vector(vec, is_even);
+
+ auto result_sized = llvm::to_vector<6>(llvm::make_filter_range(vec, is_even));
+ // CHECK-MESSAGES: :[[@LINE-1]]:23: warning: use 'llvm::filter_to_vector<6>' instead of 'llvm::to_vector<6>(llvm::make_filter_range(...))'
+ // CHECK-FIXES: auto result_sized = llvm::filter_to_vector<6>(vec, is_even);
+}
+
+namespace llvm {
+
+void test_inside_llvm_namespace() {
+ SmallVector<int> vec;
+
+ // Unprefixed calls inside the `llvm` namespace should also be detected.
+ auto result = to_vector(map_range(vec, transform));
+ // CHECK-MESSAGES: :[[@LINE-1]]:17: warning: use 'llvm::map_to_vector' instead of 'llvm::to_vector(llvm::map_range(...))'
+ // CHECK-FIXES: auto result = llvm::map_to_vector(vec, transform);
+}
+
+} // namespace llvm
+
+void test_negative() {
+ llvm::SmallVector<int> vec;
+
+ // `to_vector` without inner `map_range`/`make_filter_range` should not trigger.
+ auto result1 = llvm::to_vector(vec);
+ auto result2 = llvm::to_vector<4>(vec);
+
+ // Direct use of `map_range`/`make_filter_range` without `to_vector` should not trigger.
+ auto mapped = llvm::map_range(vec, transform);
+ auto filtered = llvm::make_filter_range(vec, is_even);
+
+ // `to_vector_of` variants should not trigger (no `map_to_vector_of` exists).
+ auto result3 = llvm::to_vector_of<long>(llvm::map_range(vec, transform));
+ auto result4 = llvm::to_vector_of<long, 4>(llvm::map_range(vec, transform));
+ auto result5 = llvm::to_vector_of<long>(llvm::make_filter_range(vec, is_even));
+ auto result6 = llvm::to_vector_of<long, 4>(llvm::make_filter_range(vec, is_even));
+
+ // Hypothetical 3-arg overloads should not trigger.
+ auto result7 = llvm::to_vector(llvm::map_range(vec, transform, 0));
+ auto result8 = llvm::to_vector(llvm::make_filter_range(vec, is_even, 0));
+}
>From 28447e42ba1abf2c2f16b275f5d14bf55509fb2a Mon Sep 17 00:00:00 2001
From: Jakub Kuderski <jakub at nod-labs.com>
Date: Fri, 23 Jan 2026 20:21:47 -0500
Subject: [PATCH 2/5] Format
---
.../clang-tidy/llvm/UseVectorUtilsCheck.cpp | 30 +++++++++----------
.../clang-tidy/llvm/UseVectorUtilsCheck.h | 4 +--
2 files changed, 16 insertions(+), 18 deletions(-)
diff --git a/clang-tools-extra/clang-tidy/llvm/UseVectorUtilsCheck.cpp b/clang-tools-extra/clang-tidy/llvm/UseVectorUtilsCheck.cpp
index ba377339c1cd5..2727f91e63fd4 100644
--- a/clang-tools-extra/clang-tidy/llvm/UseVectorUtilsCheck.cpp
+++ b/clang-tools-extra/clang-tidy/llvm/UseVectorUtilsCheck.cpp
@@ -31,28 +31,25 @@ void UseVectorUtilsCheck::registerPPCallbacks(const SourceManager &SM,
void UseVectorUtilsCheck::registerMatchers(MatchFinder *Finder) {
// Match `llvm::to_vector(llvm::map_range(X, F))`.
Finder->addMatcher(
- callExpr(
- callee(functionDecl(hasName("::llvm::to_vector"))),
- hasArgument(
- 0, callExpr(callee(functionDecl(hasName("::llvm::map_range"))))
- .bind("inner_call")))
+ callExpr(callee(functionDecl(hasName("::llvm::to_vector"))),
+ hasArgument(0, callExpr(callee(functionDecl(
+ hasName("::llvm::map_range"))))
+ .bind("inner_call")))
.bind("map_range_call"),
this);
// Match `llvm::to_vector(llvm::make_filter_range(X, Pred))`.
Finder->addMatcher(
- callExpr(
- callee(functionDecl(hasName("::llvm::to_vector"))),
- hasArgument(0, callExpr(callee(functionDecl(
- hasName("::llvm::make_filter_range"))))
- .bind("inner_call")))
+ callExpr(callee(functionDecl(hasName("::llvm::to_vector"))),
+ hasArgument(0, callExpr(callee(functionDecl(hasName(
+ "::llvm::make_filter_range"))))
+ .bind("inner_call")))
.bind("filter_range_call"),
this);
}
void UseVectorUtilsCheck::check(const MatchFinder::MatchResult &Result) {
- const auto *MapRangeCall =
- Result.Nodes.getNodeAs<CallExpr>("map_range_call");
+ const auto *MapRangeCall = Result.Nodes.getNodeAs<CallExpr>("map_range_call");
const auto *FilterRangeCall =
Result.Nodes.getNodeAs<CallExpr>("filter_range_call");
if (!MapRangeCall && !FilterRangeCall)
@@ -96,9 +93,9 @@ void UseVectorUtilsCheck::check(const MatchFinder::MatchResult &Result) {
// Build the replacement: Replace the whole expression with the new function
// and the arguments from the inner call.
- auto Diag = diag(OuterCall->getBeginLoc(),
- "use '%0' instead of '%1(%2(...))'")
- << ReplacementFunc << ToVectorFunc << InnerFuncName;
+ auto Diag =
+ diag(OuterCall->getBeginLoc(), "use '%0' instead of '%1(%2(...))'")
+ << ReplacementFunc << ToVectorFunc << InnerFuncName;
// Get the range argument.
const SourceRange RangeArgRange = InnerCall->getArg(0)->getSourceRange();
@@ -116,7 +113,8 @@ void UseVectorUtilsCheck::check(const MatchFinder::MatchResult &Result) {
const std::string Replacement =
(ReplacementFunc + "(" + RangeArgText + ", " + FuncArgText + ")").str();
- Diag << FixItHint::CreateReplacement(OuterCall->getSourceRange(), Replacement);
+ Diag << FixItHint::CreateReplacement(OuterCall->getSourceRange(),
+ Replacement);
// Add include for `SmallVectorExtras.h` if needed.
if (auto IncludeFixit = Inserter.createIncludeInsertion(
diff --git a/clang-tools-extra/clang-tidy/llvm/UseVectorUtilsCheck.h b/clang-tools-extra/clang-tidy/llvm/UseVectorUtilsCheck.h
index e2a2d2fe663a1..557025856a09a 100644
--- a/clang-tools-extra/clang-tidy/llvm/UseVectorUtilsCheck.h
+++ b/clang-tools-extra/clang-tidy/llvm/UseVectorUtilsCheck.h
@@ -16,7 +16,8 @@ namespace clang::tidy::llvm_check {
/// Finds calls to `llvm::to_vector(llvm::map_range(...))` and
/// `llvm::to_vector(llvm::make_filter_range(...))` that can be replaced with
-/// `llvm::map_to_vector` and `llvm::filter_to_vector` from `SmallVectorExtras.h`.
+/// `llvm::map_to_vector` and `llvm::filter_to_vector` from
+/// `SmallVectorExtras.h`.
///
/// For the user-facing documentation see:
/// https://clang.llvm.org/extra/clang-tidy/checks/llvm/use-vector-utils.html
@@ -38,4 +39,3 @@ class UseVectorUtilsCheck : public ClangTidyCheck {
} // namespace clang::tidy::llvm_check
#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_LLVM_USEVECTORUTILSCHECK_H
-
>From dbd41413b38c3cf7cd0cb8f36de680122dad7382 Mon Sep 17 00:00:00 2001
From: Jakub Kuderski <jakub at nod-labs.com>
Date: Fri, 23 Jan 2026 20:37:55 -0500
Subject: [PATCH 3/5] Format
---
.../docs/clang-tidy/checks/llvm/use-vector-utils.rst | 1 -
1 file changed, 1 deletion(-)
diff --git a/clang-tools-extra/docs/clang-tidy/checks/llvm/use-vector-utils.rst b/clang-tools-extra/docs/clang-tidy/checks/llvm/use-vector-utils.rst
index 2c0d429be7fca..5fddc68f4a614 100644
--- a/clang-tools-extra/docs/clang-tidy/checks/llvm/use-vector-utils.rst
+++ b/clang-tools-extra/docs/clang-tidy/checks/llvm/use-vector-utils.rst
@@ -29,4 +29,3 @@ Transforms to:
auto v2 = llvm::filter_to_vector(container, pred);
auto v3 = llvm::map_to_vector<4>(container, func);
auto v4 = llvm::filter_to_vector<4>(container, pred);
-
>From 616337da3b0f61d9049fad2ef41b2b28ed58b433 Mon Sep 17 00:00:00 2001
From: Jakub Kuderski <jakub at nod-labs.com>
Date: Sat, 24 Jan 2026 09:26:38 -0500
Subject: [PATCH 4/5] Address comments
---
.../clang-tidy/llvm/UseVectorUtilsCheck.cpp | 130 +++++++-----------
.../checkers/llvm/use-vector-utils.cpp | 16 ++-
2 files changed, 63 insertions(+), 83 deletions(-)
diff --git a/clang-tools-extra/clang-tidy/llvm/UseVectorUtilsCheck.cpp b/clang-tools-extra/clang-tidy/llvm/UseVectorUtilsCheck.cpp
index 2727f91e63fd4..78543d9b58144 100644
--- a/clang-tools-extra/clang-tidy/llvm/UseVectorUtilsCheck.cpp
+++ b/clang-tools-extra/clang-tidy/llvm/UseVectorUtilsCheck.cpp
@@ -18,9 +18,7 @@ namespace clang::tidy::llvm_check {
UseVectorUtilsCheck::UseVectorUtilsCheck(StringRef Name,
ClangTidyContext *Context)
: ClangTidyCheck(Name, Context),
- Inserter(Options.getLocalOrGlobal("IncludeStyle",
- utils::IncludeSorter::IS_LLVM),
- areDiagsSelfContained()) {}
+ Inserter(utils::IncludeSorter::IS_LLVM, areDiagsSelfContained()) {}
void UseVectorUtilsCheck::registerPPCallbacks(const SourceManager &SM,
Preprocessor *PP,
@@ -29,94 +27,72 @@ void UseVectorUtilsCheck::registerPPCallbacks(const SourceManager &SM,
}
void UseVectorUtilsCheck::registerMatchers(MatchFinder *Finder) {
- // Match `llvm::to_vector(llvm::map_range(X, F))`.
+ // Match `llvm::to_vector(llvm::map_range(X, F))` or
+ // `llvm::to_vector(llvm::make_filter_range(X, Pred))`.
Finder->addMatcher(
callExpr(callee(functionDecl(hasName("::llvm::to_vector"))),
- hasArgument(0, callExpr(callee(functionDecl(
- hasName("::llvm::map_range"))))
- .bind("inner_call")))
- .bind("map_range_call"),
+ hasArgument(0, callExpr(callee(functionDecl(hasAnyName(
+ "::llvm::map_range",
+ "::llvm::make_filter_range"))),
+ argumentCountIs(2))
+ .bind("inner_call")),
+ argumentCountIs(1))
+ .bind("outer_call"),
this);
+}
- // Match `llvm::to_vector(llvm::make_filter_range(X, Pred))`.
- Finder->addMatcher(
- callExpr(callee(functionDecl(hasName("::llvm::to_vector"))),
- hasArgument(0, callExpr(callee(functionDecl(hasName(
- "::llvm::make_filter_range"))))
- .bind("inner_call")))
- .bind("filter_range_call"),
- this);
+// Returns the original qualifier spelling (e.g., `llvm::` or `::llvm::`) for
+// the diagnostic message.
+static StringRef getQualifierSpelling(const DeclRefExpr *DeclRef,
+ const MatchFinder::MatchResult &Result) {
+ if (const auto QualifierLoc = DeclRef->getQualifierLoc()) {
+ return Lexer::getSourceText(
+ CharSourceRange::getTokenRange(QualifierLoc.getSourceRange()),
+ *Result.SourceManager, Result.Context->getLangOpts());
+ }
+ return "";
}
void UseVectorUtilsCheck::check(const MatchFinder::MatchResult &Result) {
- const auto *MapRangeCall = Result.Nodes.getNodeAs<CallExpr>("map_range_call");
- const auto *FilterRangeCall =
- Result.Nodes.getNodeAs<CallExpr>("filter_range_call");
- if (!MapRangeCall && !FilterRangeCall)
- return;
+ const auto *OuterCall = Result.Nodes.getNodeAs<CallExpr>("outer_call");
+ assert(OuterCall);
const auto *InnerCall = Result.Nodes.getNodeAs<CallExpr>("inner_call");
- assert(InnerCall && "inner_call must be bound if map_range_call or "
- "filter_range_call matched");
- // Only handle the 2-argument overloads of `map_range`/`make_filter_range`, to
- // future-proof against additional overloads.
- if (InnerCall->getNumArgs() != 2)
- return;
-
- const CallExpr *OuterCall = MapRangeCall ? MapRangeCall : FilterRangeCall;
+ assert(InnerCall);
- const SourceManager &SM = *Result.SourceManager;
- const LangOptions &LangOpts = getLangOpts();
+ const auto *OuterCallee =
+ cast<DeclRefExpr>(OuterCall->getCallee()->IgnoreImplicit());
+ const auto *InnerCallee =
+ cast<DeclRefExpr>(InnerCall->getCallee()->IgnoreImplicit());
- // Determine the base replacement function name.
- const StringRef ReplacementFuncBase =
- MapRangeCall ? "llvm::map_to_vector" : "llvm::filter_to_vector";
const StringRef InnerFuncName =
- MapRangeCall ? "llvm::map_range" : "llvm::make_filter_range";
-
- // Check if `to_vector` was called with an explicit size template argument.
- std::string SizeTemplateArg;
- if (const auto *DRE =
- dyn_cast<DeclRefExpr>(OuterCall->getCallee()->IgnoreImplicit())) {
- if (DRE->hasExplicitTemplateArgs()) {
- // Extract the template argument text (e.g., `<4>`).
- const auto TemplateArgsCharRange = CharSourceRange::getTokenRange(
- DRE->getLAngleLoc(), DRE->getRAngleLoc());
- SizeTemplateArg =
- Lexer::getSourceText(TemplateArgsCharRange, SM, LangOpts).str();
- }
- }
-
- const std::string ReplacementFunc =
- (ReplacementFuncBase + SizeTemplateArg).str();
- const std::string ToVectorFunc = "llvm::to_vector" + SizeTemplateArg;
-
- // Build the replacement: Replace the whole expression with the new function
- // and the arguments from the inner call.
- auto Diag =
- diag(OuterCall->getBeginLoc(), "use '%0' instead of '%1(%2(...))'")
- << ReplacementFunc << ToVectorFunc << InnerFuncName;
-
- // Get the range argument.
- const SourceRange RangeArgRange = InnerCall->getArg(0)->getSourceRange();
- const auto RangeArgCharRange = CharSourceRange::getTokenRange(RangeArgRange);
- const StringRef RangeArgText =
- Lexer::getSourceText(RangeArgCharRange, SM, LangOpts);
-
- // Get the function/predicate argument.
- const SourceRange FuncArgRange = InnerCall->getArg(1)->getSourceRange();
- const auto FuncArgCharRange = CharSourceRange::getTokenRange(FuncArgRange);
- const StringRef FuncArgText =
- Lexer::getSourceText(FuncArgCharRange, SM, LangOpts);
-
- // Create the replacement text.
- const std::string Replacement =
- (ReplacementFunc + "(" + RangeArgText + ", " + FuncArgText + ")").str();
-
- Diag << FixItHint::CreateReplacement(OuterCall->getSourceRange(),
- Replacement);
+ cast<NamedDecl>(InnerCallee->getDecl())->getName();
+
+ // Determine the replacement function name (unqualified).
+ const llvm::SmallDenseMap<StringRef, StringRef, 2>
+ InnerFuncNameToReplacementFuncName = {
+ {"map_range", "map_to_vector"},
+ {"make_filter_range", "filter_to_vector"},
+ };
+ const StringRef ReplacementFuncName =
+ InnerFuncNameToReplacementFuncName.lookup(InnerFuncName);
+ assert(!ReplacementFuncName.empty() && "Unhandled function?");
+
+ auto Diag = diag(OuterCall->getBeginLoc(), "use '%0%1'")
+ << getQualifierSpelling(OuterCallee, Result)
+ << ReplacementFuncName;
+
+ // Replace only the unqualified function name, preserving qualifier and
+ // template arguments.
+ const auto InnerCallUntilFirstArg = CharSourceRange::getCharRange(
+ InnerCall->getBeginLoc(), InnerCall->getArg(0)->getBeginLoc());
+ Diag << FixItHint::CreateReplacement(
+ OuterCallee->getNameInfo().getSourceRange(), ReplacementFuncName)
+ << FixItHint::CreateRemoval(InnerCallUntilFirstArg)
+ << FixItHint::CreateRemoval(InnerCall->getRParenLoc());
// Add include for `SmallVectorExtras.h` if needed.
+ const SourceManager &SM = *Result.SourceManager;
if (auto IncludeFixit = Inserter.createIncludeInsertion(
SM.getFileID(OuterCall->getBeginLoc()),
"llvm/ADT/SmallVectorExtras.h"))
diff --git a/clang-tools-extra/test/clang-tidy/checkers/llvm/use-vector-utils.cpp b/clang-tools-extra/test/clang-tidy/checkers/llvm/use-vector-utils.cpp
index b69f6824d13a4..b0f71614f7bec 100644
--- a/clang-tools-extra/test/clang-tidy/checkers/llvm/use-vector-utils.cpp
+++ b/clang-tools-extra/test/clang-tidy/checkers/llvm/use-vector-utils.cpp
@@ -47,23 +47,27 @@ void test_map_range() {
llvm::SmallVector<int> vec;
auto result = llvm::to_vector(llvm::map_range(vec, transform));
- // CHECK-MESSAGES: :[[@LINE-1]]:17: warning: use 'llvm::map_to_vector' instead of 'llvm::to_vector(llvm::map_range(...))'
+ // CHECK-MESSAGES: :[[@LINE-1]]:17: warning: use 'llvm::map_to_vector'
// CHECK-FIXES: auto result = llvm::map_to_vector(vec, transform);
auto result_sized = llvm::to_vector<4>(llvm::map_range(vec, transform));
- // CHECK-MESSAGES: :[[@LINE-1]]:23: warning: use 'llvm::map_to_vector<4>' instead of 'llvm::to_vector<4>(llvm::map_range(...))'
+ // CHECK-MESSAGES: :[[@LINE-1]]:23: warning: use 'llvm::map_to_vector'
// CHECK-FIXES: auto result_sized = llvm::map_to_vector<4>(vec, transform);
+
+ auto result_global = ::llvm::to_vector(::llvm::map_range(vec, transform));
+ // CHECK-MESSAGES: :[[@LINE-1]]:24: warning: use '::llvm::map_to_vector'
+ // CHECK-FIXES: auto result_global = ::llvm::map_to_vector(vec, transform);
}
void test_filter_range() {
llvm::SmallVector<int> vec;
auto result = llvm::to_vector(llvm::make_filter_range(vec, is_even));
- // CHECK-MESSAGES: :[[@LINE-1]]:17: warning: use 'llvm::filter_to_vector' instead of 'llvm::to_vector(llvm::make_filter_range(...))'
+ // CHECK-MESSAGES: :[[@LINE-1]]:17: warning: use 'llvm::filter_to_vector'
// CHECK-FIXES: auto result = llvm::filter_to_vector(vec, is_even);
auto result_sized = llvm::to_vector<6>(llvm::make_filter_range(vec, is_even));
- // CHECK-MESSAGES: :[[@LINE-1]]:23: warning: use 'llvm::filter_to_vector<6>' instead of 'llvm::to_vector<6>(llvm::make_filter_range(...))'
+ // CHECK-MESSAGES: :[[@LINE-1]]:23: warning: use 'llvm::filter_to_vector'
// CHECK-FIXES: auto result_sized = llvm::filter_to_vector<6>(vec, is_even);
}
@@ -74,8 +78,8 @@ void test_inside_llvm_namespace() {
// Unprefixed calls inside the `llvm` namespace should also be detected.
auto result = to_vector(map_range(vec, transform));
- // CHECK-MESSAGES: :[[@LINE-1]]:17: warning: use 'llvm::map_to_vector' instead of 'llvm::to_vector(llvm::map_range(...))'
- // CHECK-FIXES: auto result = llvm::map_to_vector(vec, transform);
+ // CHECK-MESSAGES: :[[@LINE-1]]:17: warning: use 'map_to_vector'
+ // CHECK-FIXES: auto result = map_to_vector(vec, transform);
}
} // namespace llvm
>From 0363df9b347f8d0866d2661519c2663aeacb05ee Mon Sep 17 00:00:00 2001
From: Jakub Kuderski <jakub at nod-labs.com>
Date: Sat, 24 Jan 2026 09:37:00 -0500
Subject: [PATCH 5/5] Simplify diagnostic
---
.../clang-tidy/llvm/UseVectorUtilsCheck.cpp | 31 +++++--------------
.../checkers/llvm/use-vector-utils.cpp | 10 +++---
2 files changed, 12 insertions(+), 29 deletions(-)
diff --git a/clang-tools-extra/clang-tidy/llvm/UseVectorUtilsCheck.cpp b/clang-tools-extra/clang-tidy/llvm/UseVectorUtilsCheck.cpp
index 78543d9b58144..a6de8630172c8 100644
--- a/clang-tools-extra/clang-tidy/llvm/UseVectorUtilsCheck.cpp
+++ b/clang-tools-extra/clang-tidy/llvm/UseVectorUtilsCheck.cpp
@@ -9,7 +9,6 @@
#include "UseVectorUtilsCheck.h"
#include "clang/AST/ASTContext.h"
#include "clang/ASTMatchers/ASTMatchFinder.h"
-#include "clang/Lex/Lexer.h"
using namespace clang::ast_matchers;
@@ -41,18 +40,6 @@ void UseVectorUtilsCheck::registerMatchers(MatchFinder *Finder) {
this);
}
-// Returns the original qualifier spelling (e.g., `llvm::` or `::llvm::`) for
-// the diagnostic message.
-static StringRef getQualifierSpelling(const DeclRefExpr *DeclRef,
- const MatchFinder::MatchResult &Result) {
- if (const auto QualifierLoc = DeclRef->getQualifierLoc()) {
- return Lexer::getSourceText(
- CharSourceRange::getTokenRange(QualifierLoc.getSourceRange()),
- *Result.SourceManager, Result.Context->getLangOpts());
- }
- return "";
-}
-
void UseVectorUtilsCheck::check(const MatchFinder::MatchResult &Result) {
const auto *OuterCall = Result.Nodes.getNodeAs<CallExpr>("outer_call");
assert(OuterCall);
@@ -62,11 +49,9 @@ void UseVectorUtilsCheck::check(const MatchFinder::MatchResult &Result) {
const auto *OuterCallee =
cast<DeclRefExpr>(OuterCall->getCallee()->IgnoreImplicit());
- const auto *InnerCallee =
- cast<DeclRefExpr>(InnerCall->getCallee()->IgnoreImplicit());
const StringRef InnerFuncName =
- cast<NamedDecl>(InnerCallee->getDecl())->getName();
+ cast<NamedDecl>(InnerCall->getCalleeDecl())->getName();
// Determine the replacement function name (unqualified).
const llvm::SmallDenseMap<StringRef, StringRef, 2>
@@ -78,17 +63,15 @@ void UseVectorUtilsCheck::check(const MatchFinder::MatchResult &Result) {
InnerFuncNameToReplacementFuncName.lookup(InnerFuncName);
assert(!ReplacementFuncName.empty() && "Unhandled function?");
- auto Diag = diag(OuterCall->getBeginLoc(), "use '%0%1'")
- << getQualifierSpelling(OuterCallee, Result)
- << ReplacementFuncName;
+ auto Diag = diag(OuterCall->getBeginLoc(), "use '%0'") << ReplacementFuncName;
- // Replace only the unqualified function name, preserving qualifier and
- // template arguments.
- const auto InnerCallUntilFirstArg = CharSourceRange::getCharRange(
- InnerCall->getBeginLoc(), InnerCall->getArg(0)->getBeginLoc());
+ // Replace the outer function name (preserving qualifier and template args),
+ // and then remove the inner call's callee and opening paren and closing
+ // paren.
Diag << FixItHint::CreateReplacement(
OuterCallee->getNameInfo().getSourceRange(), ReplacementFuncName)
- << FixItHint::CreateRemoval(InnerCallUntilFirstArg)
+ << FixItHint::CreateRemoval(CharSourceRange::getCharRange(
+ InnerCall->getBeginLoc(), InnerCall->getArg(0)->getBeginLoc()))
<< FixItHint::CreateRemoval(InnerCall->getRParenLoc());
// Add include for `SmallVectorExtras.h` if needed.
diff --git a/clang-tools-extra/test/clang-tidy/checkers/llvm/use-vector-utils.cpp b/clang-tools-extra/test/clang-tidy/checkers/llvm/use-vector-utils.cpp
index b0f71614f7bec..817db689378ee 100644
--- a/clang-tools-extra/test/clang-tidy/checkers/llvm/use-vector-utils.cpp
+++ b/clang-tools-extra/test/clang-tidy/checkers/llvm/use-vector-utils.cpp
@@ -47,15 +47,15 @@ void test_map_range() {
llvm::SmallVector<int> vec;
auto result = llvm::to_vector(llvm::map_range(vec, transform));
- // CHECK-MESSAGES: :[[@LINE-1]]:17: warning: use 'llvm::map_to_vector'
+ // CHECK-MESSAGES: :[[@LINE-1]]:17: warning: use 'map_to_vector'
// CHECK-FIXES: auto result = llvm::map_to_vector(vec, transform);
auto result_sized = llvm::to_vector<4>(llvm::map_range(vec, transform));
- // CHECK-MESSAGES: :[[@LINE-1]]:23: warning: use 'llvm::map_to_vector'
+ // CHECK-MESSAGES: :[[@LINE-1]]:23: warning: use 'map_to_vector'
// CHECK-FIXES: auto result_sized = llvm::map_to_vector<4>(vec, transform);
auto result_global = ::llvm::to_vector(::llvm::map_range(vec, transform));
- // CHECK-MESSAGES: :[[@LINE-1]]:24: warning: use '::llvm::map_to_vector'
+ // CHECK-MESSAGES: :[[@LINE-1]]:24: warning: use 'map_to_vector'
// CHECK-FIXES: auto result_global = ::llvm::map_to_vector(vec, transform);
}
@@ -63,11 +63,11 @@ void test_filter_range() {
llvm::SmallVector<int> vec;
auto result = llvm::to_vector(llvm::make_filter_range(vec, is_even));
- // CHECK-MESSAGES: :[[@LINE-1]]:17: warning: use 'llvm::filter_to_vector'
+ // CHECK-MESSAGES: :[[@LINE-1]]:17: warning: use 'filter_to_vector'
// CHECK-FIXES: auto result = llvm::filter_to_vector(vec, is_even);
auto result_sized = llvm::to_vector<6>(llvm::make_filter_range(vec, is_even));
- // CHECK-MESSAGES: :[[@LINE-1]]:23: warning: use 'llvm::filter_to_vector'
+ // CHECK-MESSAGES: :[[@LINE-1]]:23: warning: use 'filter_to_vector'
// CHECK-FIXES: auto result_sized = llvm::filter_to_vector<6>(vec, is_even);
}
More information about the cfe-commits
mailing list