Author: Jakub Kuderski Date: 2026-01-24T18:23:53-05:00 New Revision: 49d464ccaf4461631c5c61cc1ffa52d5a966b4bf
URL: https://github.com/llvm/llvm-project/commit/49d464ccaf4461631c5c61cc1ffa52d5a966b4bf DIFF: https://github.com/llvm/llvm-project/commit/49d464ccaf4461631c5c61cc1ffa52d5a966b4bf.diff LOG: [clang-tidy] Add llvm-use-vector-utils (#177722) 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. The check is called `vector-utils` because we may want to handle more cases in the future, like turning explicit calls to SmallVector constructor to `llvm::to_vector` (which lives in `SmallVector.h`, not `SmallVectorExtras.h`). Assisted-by: claude Added: clang-tools-extra/clang-tidy/llvm/UseVectorUtilsCheck.cpp clang-tools-extra/clang-tidy/llvm/UseVectorUtilsCheck.h clang-tools-extra/docs/clang-tidy/checks/llvm/use-vector-utils.rst clang-tools-extra/test/clang-tidy/checkers/llvm/use-vector-utils.cpp Modified: clang-tools-extra/clang-tidy/llvm/CMakeLists.txt clang-tools-extra/clang-tidy/llvm/LLVMTidyModule.cpp clang-tools-extra/docs/ReleaseNotes.rst clang-tools-extra/docs/clang-tidy/checks/list.rst Removed: ################################################################################ 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..bd915eb55b448 --- /dev/null +++ b/clang-tools-extra/clang-tidy/llvm/UseVectorUtilsCheck.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 "UseVectorUtilsCheck.h" +#include "../utils/LexerUtils.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" + +using namespace clang::ast_matchers; + +namespace clang::tidy::llvm_check { + +UseVectorUtilsCheck::UseVectorUtilsCheck(StringRef Name, + ClangTidyContext *Context) + : ClangTidyCheck(Name, Context), + Inserter(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))` or + // `llvm::to_vector(llvm::make_filter_range(X, Pred))`. + Finder->addMatcher( + callExpr(callee(functionDecl(hasName("::llvm::to_vector"))), + hasArgument(0, callExpr(callee(functionDecl(hasAnyName( + "::llvm::map_range", + "::llvm::make_filter_range"))), + argumentCountIs(2)) + .bind("inner_call")), + argumentCountIs(1)) + .bind("outer_call"), + this); +} + +void UseVectorUtilsCheck::check(const MatchFinder::MatchResult &Result) { + const auto *OuterCall = Result.Nodes.getNodeAs<CallExpr>("outer_call"); + assert(OuterCall); + + const auto *InnerCall = Result.Nodes.getNodeAs<CallExpr>("inner_call"); + assert(InnerCall); + + const auto *OuterCallee = + cast<DeclRefExpr>(OuterCall->getCallee()->IgnoreImplicit()); + + const StringRef InnerFuncName = + cast<NamedDecl>(InnerCall->getCalleeDecl())->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'") << ReplacementFuncName; + + // Replace the outer function name (preserving qualifier and template args), + // and then remove the inner call's callee and opening paren and closing + // paren. Example: + // ``` + // llvm::to_vector<4>(llvm::map_range(X, F)) + // ^replace~^ ^----remove-----^ ^ + // remove + // ``` + const SourceManager &SM = *Result.SourceManager; + const std::optional<Token> InnerLParen = + utils::lexer::findNextTokenSkippingComments( + InnerCall->getCallee()->getEndLoc(), SM, getLangOpts()); + if (!InnerLParen || InnerLParen->isNot(tok::l_paren)) + return; // Unexpected token, possibly a macro? + + Diag << FixItHint::CreateReplacement( + OuterCallee->getNameInfo().getSourceRange(), ReplacementFuncName) + << FixItHint::CreateRemoval(CharSourceRange::getCharRange( + InnerCall->getBeginLoc(), InnerLParen->getEndLoc())) + << FixItHint::CreateRemoval(InnerCall->getRParenLoc()); + + // 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..64de81110f6c2 --- /dev/null +++ b/clang-tools-extra/clang-tidy/llvm/UseVectorUtilsCheck.h @@ -0,0 +1,42 @@ +//===----------------------------------------------------------------------===// +// +// 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); + 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; + + bool isLanguageVersionSupported(const LangOptions &LangOpts) const override { + return LangOpts.CPlusPlus; + } + +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 5cfb6476dcc1f..6c91d34c5539c 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..5fddc68f4a614 --- /dev/null +++ b/clang-tools-extra/docs/clang-tidy/checks/llvm/use-vector-utils.rst @@ -0,0 +1,31 @@ +.. 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..25847bd8d0cd4 --- /dev/null +++ b/clang-tools-extra/test/clang-tidy/checkers/llvm/use-vector-utils.cpp @@ -0,0 +1,149 @@ +// 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 '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 '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 'map_to_vector' + // CHECK-FIXES: auto result_global = ::llvm::map_to_vector(vec, transform); + + // Check that comments between `to_vector(` and `map_range(` are preserved. + auto result_comment1 = llvm::to_vector(/*keep_me*/ llvm::map_range(vec, transform)); + // CHECK-MESSAGES: :[[@LINE-1]]:26: warning: use 'map_to_vector' + // CHECK-FIXES: auto result_comment1 = llvm::map_to_vector(/*keep_me*/ vec, transform); + + // Check that comments between `to_vector<9>(` and `map_range(` are preserved. + auto result_comment2 = llvm::to_vector<9>(/*keep_me*/ llvm::map_range(vec, transform)); + // CHECK-MESSAGES: :[[@LINE-1]]:26: warning: use 'map_to_vector' + // CHECK-FIXES: auto result_comment2 = llvm::map_to_vector<9>(/*keep_me*/ vec, transform); + + // Check that comments inside `map_range(` are also preserved. + auto result_comment3 = llvm::to_vector(llvm::map_range(/*keep_me*/ vec, transform)); + // CHECK-MESSAGES: :[[@LINE-1]]:26: warning: use 'map_to_vector' + // CHECK-FIXES: auto result_comment3 = llvm::map_to_vector(/*keep_me*/ vec, transform); + + // Check that comments inside explicit template argument are preserved. + auto result_comment4 = llvm::to_vector</*keep_me*/ 7>(llvm::map_range(vec, transform)); + // CHECK-MESSAGES: :[[@LINE-1]]:26: warning: use 'map_to_vector' + // CHECK-FIXES: auto result_comment4 = llvm::map_to_vector</*keep_me*/ 7>(vec, transform); + + // Check that whitespace between callee and `(` is handled correctly. + auto result_whitespace1 = llvm::to_vector(llvm::map_range (vec, transform)); + // CHECK-MESSAGES: :[[@LINE-1]]:29: warning: use 'map_to_vector' + // CHECK-FIXES: auto result_whitespace1 = llvm::map_to_vector(vec, transform); + + // Check that comments between callee and `(` are handled correctly. + auto result_whitespace2 = llvm::to_vector(llvm::map_range /*weird but ok*/ (vec, transform)); + // CHECK-MESSAGES: :[[@LINE-1]]:29: warning: use 'map_to_vector' + // CHECK-FIXES: auto result_whitespace2 = 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 '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 'filter_to_vector' + // 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 'map_to_vector' + // CHECK-FIXES: auto result = map_to_vector(vec, transform); +} + +} // namespace llvm + +// Check that an empty macro between callee and `(` is handled. +void test_macro() { + llvm::SmallVector<int> vec; + +#define EMPTY + // No fix-it when a macro appears between callee and `(`. + auto result = llvm::to_vector(llvm::map_range EMPTY (vec, transform)); + // CHECK-MESSAGES: :[[@LINE-1]]:17: warning: use 'map_to_vector' + +#undef EMPTY +} + +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)); +} _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
