[clang-tools-extra] Add checks to convert std library iterator algorithms into c++20 or boost ranges (PR #97764)
Nathan James via cfe-commits
cfe-commits at lists.llvm.org
Fri Jul 5 08:00:42 PDT 2024
https://github.com/njames93 updated https://github.com/llvm/llvm-project/pull/97764
>From 98746ac4cb477b44280de471113138e706147242 Mon Sep 17 00:00:00 2001
From: Nathan James <n.james93 at hotmail.co.uk>
Date: Tue, 2 Jul 2024 14:25:44 +0100
Subject: [PATCH 1/2] Add a modernize-use-ranges check
---
.../clang-tidy/modernize/CMakeLists.txt | 1 +
.../modernize/ModernizeTidyModule.cpp | 2 +
.../clang-tidy/modernize/UseRangesCheck.cpp | 181 +++++++++++++
.../clang-tidy/modernize/UseRangesCheck.h | 31 +++
.../clang-tidy/utils/CMakeLists.txt | 1 +
.../clang-tidy/utils/UseRangesCheck.cpp | 248 ++++++++++++++++++
.../clang-tidy/utils/UseRangesCheck.h | 68 +++++
clang-tools-extra/docs/ReleaseNotes.rst | 6 +
.../docs/clang-tidy/checks/list.rst | 1 +
.../checks/modernize/use-ranges.rst | 61 +++++
.../checkers/modernize/use-ranges.cpp | 183 +++++++++++++
11 files changed, 783 insertions(+)
create mode 100644 clang-tools-extra/clang-tidy/modernize/UseRangesCheck.cpp
create mode 100644 clang-tools-extra/clang-tidy/modernize/UseRangesCheck.h
create mode 100644 clang-tools-extra/clang-tidy/utils/UseRangesCheck.cpp
create mode 100644 clang-tools-extra/clang-tidy/utils/UseRangesCheck.h
create mode 100644 clang-tools-extra/docs/clang-tidy/checks/modernize/use-ranges.rst
create mode 100644 clang-tools-extra/test/clang-tidy/checkers/modernize/use-ranges.cpp
diff --git a/clang-tools-extra/clang-tidy/modernize/CMakeLists.txt b/clang-tools-extra/clang-tidy/modernize/CMakeLists.txt
index 576805c4c7f181..4f68c487cac9d4 100644
--- a/clang-tools-extra/clang-tidy/modernize/CMakeLists.txt
+++ b/clang-tools-extra/clang-tidy/modernize/CMakeLists.txt
@@ -40,6 +40,7 @@ add_clang_library(clangTidyModernizeModule
UseNoexceptCheck.cpp
UseNullptrCheck.cpp
UseOverrideCheck.cpp
+ UseRangesCheck.cpp
UseStartsEndsWithCheck.cpp
UseStdFormatCheck.cpp
UseStdNumbersCheck.cpp
diff --git a/clang-tools-extra/clang-tidy/modernize/ModernizeTidyModule.cpp b/clang-tools-extra/clang-tidy/modernize/ModernizeTidyModule.cpp
index b9c7a2dc383e88..18607593320635 100644
--- a/clang-tools-extra/clang-tidy/modernize/ModernizeTidyModule.cpp
+++ b/clang-tools-extra/clang-tidy/modernize/ModernizeTidyModule.cpp
@@ -41,6 +41,7 @@
#include "UseNoexceptCheck.h"
#include "UseNullptrCheck.h"
#include "UseOverrideCheck.h"
+#include "UseRangesCheck.h"
#include "UseStartsEndsWithCheck.h"
#include "UseStdFormatCheck.h"
#include "UseStdNumbersCheck.h"
@@ -75,6 +76,7 @@ class ModernizeModule : public ClangTidyModule {
CheckFactories.registerCheck<PassByValueCheck>("modernize-pass-by-value");
CheckFactories.registerCheck<UseDesignatedInitializersCheck>(
"modernize-use-designated-initializers");
+ CheckFactories.registerCheck<UseRangesCheck>("modernize-use-ranges");
CheckFactories.registerCheck<UseStartsEndsWithCheck>(
"modernize-use-starts-ends-with");
CheckFactories.registerCheck<UseStdFormatCheck>("modernize-use-std-format");
diff --git a/clang-tools-extra/clang-tidy/modernize/UseRangesCheck.cpp b/clang-tools-extra/clang-tidy/modernize/UseRangesCheck.cpp
new file mode 100644
index 00000000000000..d578491d2fc371
--- /dev/null
+++ b/clang-tools-extra/clang-tidy/modernize/UseRangesCheck.cpp
@@ -0,0 +1,181 @@
+//===--- UseRangesCheck.cpp - clang-tidy ----------------------------------===//
+//
+// 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 "UseRangesCheck.h"
+#include "clang/AST/Decl.h"
+#include "llvm/ADT/ArrayRef.h"
+#include "llvm/ADT/IntrusiveRefCntPtr.h"
+#include "llvm/ADT/SmallVector.h"
+#include "llvm/ADT/StringRef.h"
+#include <initializer_list>
+
+// FixItHint - Let the docs script know that this class does provide fixits
+
+namespace clang::tidy::modernize {
+
+static constexpr const char *SingleRangeNames[] = {
+ "all_of",
+ "any_of",
+ "none_of",
+ "for_each",
+ "find",
+ "find_if",
+ "find_if_not",
+ "adjacent_find",
+ "copy",
+ "copy_if",
+ "copy_backward",
+ "move",
+ "move_backward",
+ "fill",
+ "transform",
+ "replace",
+ "replace_if",
+ "generate",
+ "remove",
+ "remove_if",
+ "remove_copy",
+ "remove_copy_if",
+ "unique",
+ "unique_copy",
+ "sample",
+ "partition_point",
+ "lower_bound",
+ "upper_bound",
+ "equal_range",
+ "binary_search",
+ "push_heap",
+ "pop_heap",
+ "make_heap",
+ "sort_heap",
+ "next_permutation",
+ "prev_permutation",
+ "iota",
+};
+
+static constexpr const char *SingleRangeWithExecNames[] = {
+ "reverse",
+ "reverse_copy",
+ "shift_left",
+ "shift_right",
+ "is_partitioned",
+ "partition",
+ "partition_copy",
+ "stable_partition",
+ "sort",
+ "stable_sort",
+ "is_sorted",
+ "is_sorted_until",
+ "is_heap",
+ "is_heap_until",
+ "max_element",
+ "min_element",
+ "minmax_element",
+ "uninitialized_copy",
+ "uninitialized_fill",
+ "uninitialized_move",
+ "uninitialized_default_construct",
+ "uninitialized_value_construct",
+ "destroy",
+};
+
+static constexpr const char *TwoRangeWithExecNames[] = {
+ "partial_sort_copy",
+ "includes",
+ "set_union",
+ "set_intersection",
+ "set_difference",
+ "set_symmetric_difference",
+ "merge",
+ "lexicographical_compare",
+ "find_end",
+ "search",
+};
+
+static constexpr const char *OneOrTwoRangeNames[] = {
+ "is_permutation",
+};
+
+static constexpr const char *OneOrTwoRangeWithExecNames[] = {
+ "equal",
+ "mismatch",
+};
+
+namespace {
+class StdReplacer : public utils::UseRangesCheck::Replacer {
+public:
+ explicit StdReplacer(SmallVector<UseRangesCheck::Signature> Indexes)
+ : Indexes(std::move(Indexes)) {}
+ std::string getReplaceName(const NamedDecl &OriginalName) const override {
+ return ("std::ranges::" + OriginalName.getName()).str();
+ }
+ ArrayRef<UseRangesCheck::Signature>
+ getReplacementSignatures() const override {
+ return Indexes;
+ }
+ std::optional<std::string>
+ getHeaderInclusion(const NamedDecl & /*OriginalName*/) const override {
+ return "<algorithm>";
+ }
+
+private:
+ SmallVector<UseRangesCheck::Signature> Indexes;
+};
+} // namespace
+
+utils::UseRangesCheck::ReplacerMap UseRangesCheck::getReplacerMap() const {
+
+ utils::UseRangesCheck::ReplacerMap Result;
+
+ // template<typename Iter> Func(Iter first, Iter last,...).
+ static const Signature SingleRangeArgs = {{0}};
+ // template<typename Policy, typename Iter>
+ // Func(Policy policy, Iter first, // Iter last,...).
+ static const Signature SingleRangeExecPolicy = {{1}};
+ // template<typename Iter1, typename Iter2>
+ // Func(Iter1 first1, Iter1 last1, Iter2 first2, Iter2 last2,...).
+ static const Signature TwoRangeArgs = {{0}, {2}};
+ // template<typename Policy, typename Iter1, typename Iter2>
+ // Func(Policy policy, Iter1 first1, Iter1 last1, Iter2 first2, Iter2
+ // last2,...).
+ static const Signature TwoRangeExecPolicy = {{1}, {3}};
+
+ static const Signature SingleRangeFunc[] = {SingleRangeArgs};
+
+ static const Signature SingleRangeExecFunc[] = {SingleRangeArgs,
+ SingleRangeExecPolicy};
+ static const Signature TwoRangeExecFunc[] = {TwoRangeArgs,
+ TwoRangeExecPolicy};
+ static const Signature OneOrTwoFunc[] = {SingleRangeArgs, TwoRangeArgs};
+ static const Signature OneOrTwoExecFunc[] = {
+ SingleRangeArgs, SingleRangeExecPolicy, TwoRangeArgs, TwoRangeExecPolicy};
+
+ static const std::pair<ArrayRef<Signature>, ArrayRef<const char *>> Names[] =
+ {{SingleRangeFunc, SingleRangeNames},
+ {SingleRangeExecFunc, SingleRangeWithExecNames},
+ {TwoRangeExecFunc, TwoRangeWithExecNames},
+ {OneOrTwoFunc, OneOrTwoRangeNames},
+ {OneOrTwoExecFunc, OneOrTwoRangeWithExecNames}};
+ SmallString<64> Buff;
+ for (const auto &[Signature, Values] : Names) {
+ auto Replacer = llvm::makeIntrusiveRefCnt<StdReplacer>(
+ SmallVector<UseRangesCheck::Signature>{Signature.begin(),
+ Signature.end()});
+ for (const auto &Name : Values) {
+ Buff.assign({"::std::", Name});
+ Result.try_emplace(Buff, Replacer);
+ }
+ }
+ return Result;
+}
+
+bool UseRangesCheck::isLanguageVersionSupported(
+ const LangOptions &LangOpts) const {
+ return LangOpts.CPlusPlus20;
+}
+} // namespace clang::tidy::modernize
diff --git a/clang-tools-extra/clang-tidy/modernize/UseRangesCheck.h b/clang-tools-extra/clang-tidy/modernize/UseRangesCheck.h
new file mode 100644
index 00000000000000..3148dd320b982d
--- /dev/null
+++ b/clang-tools-extra/clang-tidy/modernize/UseRangesCheck.h
@@ -0,0 +1,31 @@
+//===--- UseRangesCheck.h - clang-tidy --------------------------*- C++ -*-===//
+//
+// 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_USERANGESCHECK_H
+#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_USERANGESCHECK_H
+
+#include "../utils/UseRangesCheck.h"
+
+namespace clang::tidy::modernize {
+
+/// Detects calls to standard library iterator algorithms that could be
+/// replaced with a ranges version instead
+///
+/// For the user-facing documentation see:
+/// http://clang.llvm.org/extra/clang-tidy/checks/modernize/use-ranges.html
+class UseRangesCheck : public utils::UseRangesCheck {
+public:
+ using utils::UseRangesCheck::UseRangesCheck;
+
+ ReplacerMap getReplacerMap() const override;
+ bool isLanguageVersionSupported(const LangOptions &LangOpts) const override;
+};
+
+} // namespace clang::tidy::modernize
+
+#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_USERANGESCHECK_H
diff --git a/clang-tools-extra/clang-tidy/utils/CMakeLists.txt b/clang-tools-extra/clang-tidy/utils/CMakeLists.txt
index 9cff7d475425d7..1841ea981359cb 100644
--- a/clang-tools-extra/clang-tidy/utils/CMakeLists.txt
+++ b/clang-tools-extra/clang-tidy/utils/CMakeLists.txt
@@ -26,6 +26,7 @@ add_clang_library(clangTidyUtils
TransformerClangTidyCheck.cpp
TypeTraits.cpp
UsingInserter.cpp
+ UseRangesCheck.cpp
LINK_LIBS
clangTidy
diff --git a/clang-tools-extra/clang-tidy/utils/UseRangesCheck.cpp b/clang-tools-extra/clang-tidy/utils/UseRangesCheck.cpp
new file mode 100644
index 00000000000000..214a7b33af621a
--- /dev/null
+++ b/clang-tools-extra/clang-tidy/utils/UseRangesCheck.cpp
@@ -0,0 +1,248 @@
+//===--- UseRangesCheck.cpp - clang-tidy ----------------------------------===//
+//
+// 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 "UseRangesCheck.h"
+#include "Matchers.h"
+#include "clang/AST/ASTContext.h"
+#include "clang/AST/Decl.h"
+#include "clang/AST/Expr.h"
+#include "clang/ASTMatchers/ASTMatchFinder.h"
+#include "clang/ASTMatchers/ASTMatchers.h"
+#include "clang/ASTMatchers/ASTMatchersInternal.h"
+#include "clang/Basic/Diagnostic.h"
+#include "clang/Basic/SourceLocation.h"
+#include "clang/Basic/SourceManager.h"
+#include "clang/Lex/Lexer.h"
+#include "llvm/ADT/ArrayRef.h"
+#include "llvm/ADT/FoldingSet.h"
+#include "llvm/ADT/STLExtras.h"
+#include "llvm/ADT/SmallBitVector.h"
+#include "llvm/ADT/SmallString.h"
+#include "llvm/ADT/SmallVector.h"
+#include "llvm/Support/raw_ostream.h"
+#include <cassert>
+#include <limits>
+#include <optional>
+#include <string>
+
+using namespace clang::ast_matchers;
+
+static constexpr const char BoundCall[] = "CallExpr";
+static constexpr const char FuncDecl[] = "FuncDecl";
+static constexpr const char ArgName[] = "ArgName";
+
+namespace clang::tidy::utils {
+
+static bool operator==(const UseRangesCheck::Indexes &L,
+ const UseRangesCheck::Indexes &R) {
+ return std::tie(L.BeginArg, L.EndArg, L.ReplaceArg) ==
+ std::tie(R.BeginArg, R.EndArg, R.ReplaceArg);
+}
+
+static std::string getFullPrefix(ArrayRef<UseRangesCheck::Indexes> Signature) {
+ std::string Output;
+ llvm::raw_string_ostream OS(Output);
+ for (auto Item : Signature) {
+ OS << Item.BeginArg << ":" << Item.EndArg << ":"
+ << (Item.ReplaceArg == Item.First ? '0' : '1');
+ }
+ return Output;
+}
+
+static llvm::hash_code hash_value(const UseRangesCheck::Indexes &Indexes) {
+ return llvm::hash_combine(Indexes.BeginArg, Indexes.EndArg,
+ Indexes.ReplaceArg);
+}
+
+static llvm::hash_code hash_value(const UseRangesCheck::Signature &Sig) {
+ return llvm::hash_combine_range(Sig.begin(), Sig.end());
+}
+
+namespace {
+
+AST_MATCHER(Expr, hasSideEffects) {
+ return Node.HasSideEffects(Finder->getASTContext());
+}
+} // namespace
+
+static auto makeMatcher(bool IsBegin, StringRef Prefix) {
+ auto Member =
+ IsBegin ? expr(unless(hasSideEffects())).bind((ArgName + Prefix).str())
+ : expr(matchers::isStatementIdenticalToBoundNode(
+ (ArgName + Prefix).str()));
+ return expr(
+ anyOf(cxxMemberCallExpr(
+ callee(cxxMethodDecl(IsBegin ? hasAnyName("begin", "cbegin")
+ : hasAnyName("end", "cend"))),
+ on(Member)),
+ callExpr(argumentCountIs(1), hasArgument(0, Member),
+ hasDeclaration(functionDecl(
+ IsBegin ? hasAnyName("::std::begin", "::std::cbegin")
+ : hasAnyName("::std::end", "::std::cend"))))));
+}
+static ast_matchers::internal::Matcher<CallExpr>
+makeMatcherPair(StringRef State, const UseRangesCheck::Indexes &Indexes) {
+ auto ArgPostfix = std::to_string(Indexes.BeginArg);
+ SmallString<64> ID = {BoundCall, State};
+ return callExpr(argumentCountAtLeast(
+ std::max(Indexes.BeginArg, Indexes.EndArg) + 1),
+ hasArgument(Indexes.BeginArg, makeMatcher(true, ArgPostfix)),
+ hasArgument(Indexes.EndArg, makeMatcher(false, ArgPostfix)))
+ .bind(ID);
+}
+
+void UseRangesCheck::registerMatchers(MatchFinder *Finder) {
+ Replaces = getReplacerMap();
+ llvm::DenseSet<ArrayRef<Signature>> Seen;
+ for (auto I = Replaces.begin(), E = Replaces.end(); I != E; ++I) {
+ const auto &Replacer = I->getValue();
+ const auto &Signatures = Replacer->getReplacementSignatures();
+ if (Seen.contains(Signatures))
+ continue;
+ assert(!Signatures.empty() &&
+ llvm::all_of(Signatures, [](auto index) { return !index.empty(); }));
+ std::vector<StringRef> Names(1, I->getKey());
+ for (auto J = std::next(I); J != E; ++J) {
+ if (J->getValue()->getReplacementSignatures() == Signatures) {
+ Names.push_back(J->getKey());
+ }
+ }
+ std::vector<ast_matchers::internal::DynTypedMatcher> TotalMatchers;
+ // As we match on the first matched signature, we need to sort the
+ // signatures in order of length(longest to shortest). This way any
+ // signature that is a subset of another signature will be matched after the
+ // other.
+ SmallVector<Signature> SigVec(Signatures);
+ llvm::sort(SigVec, [](auto &L, auto &R) { return R.size() < L.size(); });
+ for (const auto &Signature : SigVec) {
+ std::vector<ast_matchers::internal::DynTypedMatcher> Matchers;
+ for (const auto &ArgPair : Signature) {
+ Matchers.push_back(makeMatcherPair(getFullPrefix(Signature), ArgPair));
+ }
+ TotalMatchers.push_back(
+ ast_matchers::internal::DynTypedMatcher::constructVariadic(
+ ast_matchers::internal::DynTypedMatcher::VO_AllOf,
+ ASTNodeKind::getFromNodeKind<CallExpr>(), std::move(Matchers)));
+ }
+ Finder->addMatcher(
+ callExpr(
+ callee(functionDecl(hasAnyName(std::move(Names))).bind(FuncDecl)),
+ ast_matchers::internal::DynTypedMatcher::constructVariadic(
+ ast_matchers::internal::DynTypedMatcher::VO_AnyOf,
+ ASTNodeKind::getFromNodeKind<CallExpr>(),
+ std::move(TotalMatchers))
+ .convertTo<CallExpr>()),
+ this);
+ }
+}
+
+static void removeFunctionArgs(const CallExpr &Call, ArrayRef<unsigned> Indexes,
+ llvm::SmallVectorImpl<FixItHint> &Output,
+ const ASTContext &Ctx) {
+ llvm::SmallVector<unsigned> Sorted(Indexes);
+ // Keep track of commas removed
+ llvm::SmallBitVector Commas(Call.getNumArgs());
+ // The first comma is actually the '(' which we can't remove
+ Commas[0] = true;
+ llvm::sort(Sorted);
+ for (auto Index : Sorted) {
+ const auto *Arg = Call.getArg(Index);
+ if (Commas[Index]) {
+ if (Index >= Commas.size()) {
+ Output.push_back(FixItHint::CreateRemoval(Arg->getSourceRange()));
+ } else {
+ // Remove the next comma
+ Commas[Index + 1] = true;
+ Output.push_back(
+ FixItHint::CreateRemoval(CharSourceRange::getTokenRange(
+ {Arg->getBeginLoc(),
+ Lexer::getLocForEndOfToken(Arg->getEndLoc(), 0,
+ Ctx.getSourceManager(),
+ Ctx.getLangOpts())
+ .getLocWithOffset(1)})));
+ }
+ } else {
+ Output.push_back(FixItHint::CreateRemoval(CharSourceRange::getTokenRange(
+ Arg->getBeginLoc().getLocWithOffset(-1), Arg->getEndLoc())));
+ Commas[Index] = true;
+ }
+ }
+}
+
+void UseRangesCheck::check(const MatchFinder::MatchResult &Result) {
+ const auto *Function = Result.Nodes.getNodeAs<FunctionDecl>(FuncDecl);
+ auto QN = "::" + Function->getQualifiedNameAsString();
+ auto Iter = Replaces.find(QN);
+ assert(Iter != Replaces.end());
+ SmallString<64> Buffer;
+ for (const auto &Signature : Iter->getValue()->getReplacementSignatures()) {
+ Buffer.assign({BoundCall, getFullPrefix(Signature)});
+ const auto *Call = Result.Nodes.getNodeAs<CallExpr>(Buffer);
+ if (!Call)
+ continue;
+ auto Diag = createDiag(*Call);
+ Diag << FixItHint::CreateReplacement(
+ Call->getCallee()->getSourceRange(),
+ Iter->getValue()->getReplaceName(*Function));
+ if (auto Include = Iter->getValue()->getHeaderInclusion(*Function)) {
+ Diag << Inserter.createIncludeInsertion(
+ Result.SourceManager->getFileID(Call->getBeginLoc()), *Include);
+ }
+ llvm::SmallVector<unsigned, 3> ToRemove;
+ for (const auto &[First, Second, Replace] : Signature) {
+ auto ID = std::to_string(First);
+ Diag << FixItHint::CreateReplacement(
+ Call->getArg(Replace == Indexes::Second ? Second : First)
+ ->getSourceRange(),
+ Lexer::getSourceText(
+ CharSourceRange::getTokenRange(
+ Result.Nodes.getNodeAs<Expr>(ArgName + ID)->getSourceRange()),
+ Result.Context->getSourceManager(),
+ Result.Context->getLangOpts()));
+ ToRemove.push_back(Replace == Indexes::Second ? First : Second);
+ }
+ SmallVector<FixItHint> Fixes;
+ removeFunctionArgs(*Call, ToRemove, Fixes, *Result.Context);
+ Diag << Fixes;
+ return;
+ }
+ llvm_unreachable("No valid signature found");
+}
+
+bool UseRangesCheck::isLanguageVersionSupported(
+ const LangOptions &LangOpts) const {
+ return LangOpts.CPlusPlus11;
+}
+
+UseRangesCheck::UseRangesCheck(StringRef Name, ClangTidyContext *Context)
+ : ClangTidyCheck(Name, Context),
+ Inserter(Options.getLocalOrGlobal("IncludeStyle",
+ utils::IncludeSorter::IS_LLVM),
+ areDiagsSelfContained()) {}
+
+void UseRangesCheck::registerPPCallbacks(const SourceManager &,
+ Preprocessor *PP, Preprocessor *) {
+ Inserter.registerPreprocessor(PP);
+}
+
+void UseRangesCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
+ Options.store(Opts, "IncludeStyle", Inserter.getStyle());
+}
+
+std::optional<std::string>
+UseRangesCheck::Replacer::getHeaderInclusion(const NamedDecl &) const {
+ return std::nullopt;
+}
+
+DiagnosticBuilder UseRangesCheck::createDiag(const CallExpr &Call) {
+ return diag(Call.getBeginLoc(), "use a ranges version of this algorithm");
+}
+std::optional<TraversalKind> UseRangesCheck::getCheckTraversalKind() const {
+ return TK_IgnoreUnlessSpelledInSource;
+}
+} // namespace clang::tidy::utils
diff --git a/clang-tools-extra/clang-tidy/utils/UseRangesCheck.h b/clang-tools-extra/clang-tidy/utils/UseRangesCheck.h
new file mode 100644
index 00000000000000..d6a8dedbbb19a9
--- /dev/null
+++ b/clang-tools-extra/clang-tidy/utils/UseRangesCheck.h
@@ -0,0 +1,68 @@
+//===--- UseRangesCheck.h - clang-tidy --------------------------*- C++ -*-===//
+//
+// 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_UTILS_USERANGESCHECK_H
+#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_UTILS_USERANGESCHECK_H
+
+#include "../ClangTidyCheck.h"
+#include "IncludeInserter.h"
+#include "clang/AST/Decl.h"
+#include "clang/AST/Expr.h"
+#include "clang/Basic/Diagnostic.h"
+#include "llvm/ADT/IntrusiveRefCntPtr.h"
+#include "llvm/ADT/StringMap.h"
+
+namespace clang::tidy::utils {
+
+/// Base class for handling converting std iterator algorithms to a range
+/// equivalent.
+class UseRangesCheck : public ClangTidyCheck {
+public:
+ struct Indexes {
+ enum Replace { First, Second };
+ unsigned BeginArg;
+ unsigned EndArg = BeginArg + 1;
+ Replace ReplaceArg = First;
+ };
+
+ using Signature = SmallVector<Indexes, 2>;
+
+ class Replacer : public llvm::RefCountedBase<Replacer> {
+ public:
+ virtual std::string getReplaceName(const NamedDecl &OriginalName) const = 0;
+ virtual std::optional<std::string>
+ getHeaderInclusion(const NamedDecl &OriginalName) const;
+ virtual ArrayRef<Signature> getReplacementSignatures() const = 0;
+ virtual ~Replacer() = default;
+ };
+
+ using ReplacerMap = llvm::StringMap<llvm::IntrusiveRefCntPtr<Replacer>>;
+
+ UseRangesCheck(StringRef Name, ClangTidyContext *Context);
+ /// Gets a map of function to replace and methods to create the replacements
+ virtual ReplacerMap getReplacerMap() const = 0;
+ /// Create a diagnostic for the CallExpr
+ /// Override this to support custom diagnostic messages
+ virtual DiagnosticBuilder createDiag(const CallExpr &Call);
+
+ void registerPPCallbacks(const SourceManager &SM, Preprocessor *PP,
+ Preprocessor *ModuleExpanderPP) final;
+ void registerMatchers(ast_matchers::MatchFinder *Finder) final;
+ void check(const ast_matchers::MatchFinder::MatchResult &Result) final;
+ bool isLanguageVersionSupported(const LangOptions &LangOpts) const override;
+ void storeOptions(ClangTidyOptions::OptionMap &Options) override;
+ std::optional<TraversalKind> getCheckTraversalKind() const override;
+
+private:
+ ReplacerMap Replaces;
+ IncludeInserter Inserter;
+};
+
+} // namespace clang::tidy::utils
+
+#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_UTILS_USERANGESCHECK_H
diff --git a/clang-tools-extra/docs/ReleaseNotes.rst b/clang-tools-extra/docs/ReleaseNotes.rst
index d968100cd57a76..8111fe23386c01 100644
--- a/clang-tools-extra/docs/ReleaseNotes.rst
+++ b/clang-tools-extra/docs/ReleaseNotes.rst
@@ -169,6 +169,12 @@ New checks
Finds initializer lists for aggregate types that could be
written as designated initializers instead.
+- New :doc:`modernize-use-ranges
+ <clang-tidy/checks/modernize/use-ranges>` check.
+
+ Detects calls to standard library iterator algorithms that could be replaced
+ with a ranges version instead
+
- New :doc:`modernize-use-std-format
<clang-tidy/checks/modernize/use-std-format>` check.
diff --git a/clang-tools-extra/docs/clang-tidy/checks/list.rst b/clang-tools-extra/docs/clang-tidy/checks/list.rst
index a698cecc0825c6..55426531a13b70 100644
--- a/clang-tools-extra/docs/clang-tidy/checks/list.rst
+++ b/clang-tools-extra/docs/clang-tidy/checks/list.rst
@@ -300,6 +300,7 @@ Clang-Tidy Checks
:doc:`modernize-use-noexcept <modernize/use-noexcept>`, "Yes"
:doc:`modernize-use-nullptr <modernize/use-nullptr>`, "Yes"
:doc:`modernize-use-override <modernize/use-override>`, "Yes"
+ :doc:`modernize-use-ranges <modernize/use-ranges>`, "Yes"
:doc:`modernize-use-starts-ends-with <modernize/use-starts-ends-with>`, "Yes"
:doc:`modernize-use-std-format <modernize/use-std-format>`, "Yes"
:doc:`modernize-use-std-numbers <modernize/use-std-numbers>`, "Yes"
diff --git a/clang-tools-extra/docs/clang-tidy/checks/modernize/use-ranges.rst b/clang-tools-extra/docs/clang-tidy/checks/modernize/use-ranges.rst
new file mode 100644
index 00000000000000..44bec5d16c6a56
--- /dev/null
+++ b/clang-tools-extra/docs/clang-tidy/checks/modernize/use-ranges.rst
@@ -0,0 +1,61 @@
+.. title:: clang-tidy - modernize-use-ranges
+
+modernize-use-ranges
+====================
+
+Detects calls to standard library iterator algorithms that could be replaced
+with a ranges version instead.
+
+Example
+-------
+
+.. code-block:: c++
+
+ auto Iter1 = std::find(Items.begin(), Items.end(), 0);
+ auto AreSame = std::equal(std::execution::par, Items1.cbegin(), Items1.cend(),
+ std::begin(Items2), std::end(Items2));
+
+
+transforms to:
+
+.. code-block:: c++
+
+ auto Iter1 = std::ranges::find(Items, 0);
+ auto AreSame = std::ranges::equal(std::execution::par, Items1, Items2);
+
+Calls to the following std library algorithms are checked:
+``::std::all_of``,``::std::any_of``,``::std::none_of``,``::std::for_each``,
+``::std::find``,``::std::find_if``,``::std::find_if_not``,
+``::std::adjacent_find``,``::std::copy``,``::std::copy_if``,
+``::std::copy_backward``,``::std::move``,``::std::move_backward``,
+``::std::fill``,``::std::transform``,``::std::replace``,``::std::replace_if``,
+``::std::generate``,``::std::remove``,``::std::remove_if``,
+``::std::remove_copy``,``::std::remove_copy_if``,``::std::unique``,
+``::std::unique_copy``,``::std::sample``,``::std::partition_point``,
+``::std::lower_bound``,``::std::upper_bound``,``::std::equal_range``,
+``::std::binary_search``,``::std::push_heap``,``::std::pop_heap``,
+``::std::make_heap``,``::std::sort_heap``,``::std::next_permutation``,
+``::std::prev_permutation``,``::std::iota``,``::std::reverse``,
+``::std::reverse_copy``,``::std::shift_left``,``::std::shift_right``,
+``::std::is_partitioned``,``::std::partition``,``::std::partition_copy``,
+``::std::stable_partition``,``::std::sort``,``::std::stable_sort``,
+``::std::is_sorted``,``::std::is_sorted_until``,``::std::is_heap``,
+``::std::is_heap_until``,``::std::max_element``,``::std::min_element``,
+``::std::minmax_element``,``::std::uninitialized_copy``,
+``::std::uninitialized_fill``,``::std::uninitialized_move``,
+``::std::uninitialized_default_construct``,
+``::std::uninitialized_value_construct``,``::std::destroy``,
+``::std::partial_sort_copy``,``::std::includes``,
+``::std::set_union``,``::std::set_intersection``,``::std::set_difference``,
+``::std::set_symmetric_difference``,``::std::merge``,
+``::std::lexicographical_compare``,``::std::find_end``,``::std::search``,
+``::std::is_permutation``,``::std::equal``,``::std::mismatch``.
+
+Options
+-------
+
+.. option:: IncludeStyle
+
+ A string specifying which include-style is used, `llvm` or `google`. Default
+ is `llvm`.
+
diff --git a/clang-tools-extra/test/clang-tidy/checkers/modernize/use-ranges.cpp b/clang-tools-extra/test/clang-tidy/checkers/modernize/use-ranges.cpp
new file mode 100644
index 00000000000000..1aa8107725f00c
--- /dev/null
+++ b/clang-tools-extra/test/clang-tidy/checkers/modernize/use-ranges.cpp
@@ -0,0 +1,183 @@
+// RUN: %check_clang_tidy -std=c++20 %s modernize-use-ranges %t
+
+// CHECK-FIXES: #include <algorithm>
+
+namespace std {
+
+template <typename T> class vector {
+public:
+ using iterator = T *;
+ using const_iterator = const T *;
+ constexpr const_iterator begin() const;
+ constexpr const_iterator end() const;
+ constexpr const_iterator cbegin() const;
+ constexpr const_iterator cend() const;
+ constexpr iterator begin();
+ constexpr iterator end();
+};
+
+template <typename Container> constexpr auto begin(const Container &Cont) {
+ return Cont.begin();
+}
+
+template <typename Container> constexpr auto begin(Container &Cont) {
+ return Cont.begin();
+}
+
+template <typename Container> constexpr auto end(const Container &Cont) {
+ return Cont.end();
+}
+
+template <typename Container> constexpr auto end(Container &Cont) {
+ return Cont.end();
+}
+
+template <typename Container> constexpr auto cbegin(const Container &Cont) {
+ return Cont.cbegin();
+}
+
+template <typename Container> constexpr auto cend(const Container &Cont) {
+ return Cont.cend();
+}
+// Find
+template< class InputIt, class T >
+InputIt find( InputIt first, InputIt last, const T& value );
+
+// Reverse
+template <typename Iter> void reverse(Iter begin, Iter end);
+
+template <typename Iter>
+void reverse(int policy, Iter begin, Iter end);
+
+// Includes
+template <class InputIt1, class InputIt2>
+bool includes(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2);
+template <class ForwardIt1, class ForwardIt2>
+bool includes(int policy, ForwardIt1 first1, ForwardIt1 last1,
+ ForwardIt2 first2, ForwardIt2 last2);
+
+// IsPermutation
+template <class ForwardIt1, class ForwardIt2>
+bool is_permutation(ForwardIt1 first1, ForwardIt1 last1, ForwardIt2 first2);
+template <class ForwardIt1, class ForwardIt2>
+bool is_permutation(ForwardIt1 first1, ForwardIt1 last1, ForwardIt2 first2,
+ ForwardIt2 last2);
+
+// Equal
+template <class InputIt1, class InputIt2>
+bool equal(InputIt1 first1, InputIt1 last1, InputIt2 first2);
+
+template <class ForwardIt1, class ForwardIt2>
+bool equal(int policy, ForwardIt1 first1, ForwardIt1 last1,
+ ForwardIt2 first2);
+
+template <class InputIt1, class InputIt2>
+bool equal(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2);
+
+template <class ForwardIt1, class ForwardIt2>
+bool equal(int policy, ForwardIt1 first1, ForwardIt1 last1,
+ ForwardIt2 first2, ForwardIt2 last2);
+
+template <class InputIt1, class InputIt2, class BinaryPred>
+bool equal(InputIt1 first1, InputIt1 last1,
+ InputIt2 first2, InputIt2 last2, BinaryPred p) {
+ // Need a definition to suppress undefined_internal_type when invoked with lambda
+ return true;
+}
+
+} // namespace std
+
+void Positives() {
+ std::vector<int> I, J;
+ std::find(I.begin(), I.end(), 0);
+ // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: use a ranges version of this algorithm
+ // CHECK-FIXES: std::ranges::find(I, 0);
+
+ std::find(I.cbegin(), I.cend(), 1);
+ // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: use a ranges version of this algorithm
+ // CHECK-FIXES: std::ranges::find(I, 1);
+
+ std::find(std::begin(I), std::end(I), 2);
+ // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: use a ranges version of this algorithm
+ // CHECK-FIXES: std::ranges::find(I, 2);
+
+ std::find(std::cbegin(I), std::cend(I), 3);
+ // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: use a ranges version of this algorithm
+ // CHECK-FIXES: std::ranges::find(I, 3);
+
+ std::find(std::cbegin(I), I.cend(), 4);
+ // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: use a ranges version of this algorithm
+ // CHECK-FIXES: std::ranges::find(I, 4);
+
+ std::reverse(I.begin(), I.end());
+ // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: use a ranges version of this algorithm
+ // CHECK-FIXES: std::ranges::reverse(I);
+
+ std::reverse(0, I.begin(), I.end());
+ // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: use a ranges version of this algorithm
+ // CHECK-FIXES: std::ranges::reverse(0, I);
+
+ std::includes(I.begin(), I.end(), I.begin(), I.end());
+ // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: use a ranges version of this algorithm
+ // CHECK-FIXES: std::ranges::includes(I, I);
+
+ std::includes(I.begin(), I.end(), J.begin(), J.end());
+ // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: use a ranges version of this algorithm
+ // CHECK-FIXES: std::ranges::includes(I, J);
+
+
+ std::includes(0, I.begin(), I.end(), I.begin(), I.end());
+ // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: use a ranges version of this algorithm
+ // CHECK-FIXES: std::ranges::includes(0, I, I);
+
+ std::includes(0, I.begin(), I.end(), J.begin(), J.end());
+ // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: use a ranges version of this algorithm
+ // CHECK-FIXES: std::ranges::includes(0, I, J);
+
+ std::is_permutation(I.begin(), I.end(), J.begin());
+ // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: use a ranges version of this algorithm
+ // CHECK-FIXES: std::ranges::is_permutation(I, J.begin());
+
+ std::is_permutation(I.begin(), I.end(), J.begin(), J.end());
+ // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: use a ranges version of this algorithm
+ // CHECK-FIXES: std::ranges::is_permutation(I, J);
+
+ std::equal(I.begin(), I.end(), J.begin());
+ // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: use a ranges version of this algorithm
+ // CHECK-FIXES: std::ranges::equal(I, J.begin());
+
+ std::equal(I.begin(), I.end(), J.begin(), J.end());
+ // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: use a ranges version of this algorithm
+ // CHECK-FIXES: std::ranges::equal(I, J);
+
+ std::equal(0, I.begin(), I.end(), J.begin());
+ // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: use a ranges version of this algorithm
+ // CHECK-FIXES: std::ranges::equal(0, I, J.begin());
+
+ std::equal(1, I.begin(), I.end(), J.begin(), J.end());
+ // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: use a ranges version of this algorithm
+ // CHECK-FIXES: std::ranges::equal(1, I, J);
+
+ std::equal(I.begin(), I.end(), J.end(), J.end());
+ // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: use a ranges version of this algorithm
+ // CHECK-FIXES: std::ranges::equal(I, J.end(), J.end());
+
+ std::equal(I.begin(), I.end(), J.end(), J.end(), [](int a, int b){ return a == b; });
+ // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: use a ranges version of this algorithm
+ // CHECK-FIXES: std::ranges::equal(I, J.end(), J.end(), [](int a, int b){ return a == b; });
+
+
+ using std::find;
+
+ find(I.begin(), I.end(), 5);
+ // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: use a ranges version of this algorithm
+ // CHECK-FIXES: std::ranges::find(I, 5);
+}
+
+void Negatives() {
+ std::vector<int> I, J;
+ std::find(I.begin(), J.end(), 0);
+ std::find(I.begin(), I.begin(), 0);
+ std::find(I.end(), I.begin(), 0);
+ std::equal(I.begin(), J.end(), I.begin(), I.end());
+}
>From 9b4439c1c058bd4376d5f57445374c8c3d77c0ce Mon Sep 17 00:00:00 2001
From: Nathan James <n.james93 at hotmail.co.uk>
Date: Thu, 4 Jul 2024 21:42:16 +0100
Subject: [PATCH 2/2] Add a boost-use-ranges check
Akin to the modernize-use-ranges check but good for users of older
toolchains who can't use c++20 ranges and rely on boost instead
---
.../clang-tidy/boost/BoostTidyModule.cpp | 2 +
.../clang-tidy/boost/CMakeLists.txt | 1 +
.../clang-tidy/boost/UseRangesCheck.cpp | 211 ++++++++++++++++++
.../clang-tidy/boost/UseRangesCheck.h | 37 +++
clang-tools-extra/docs/ReleaseNotes.rst | 6 +
.../clang-tidy/checks/boost/use-ranges.rst | 54 +++++
.../docs/clang-tidy/checks/list.rst | 1 +
.../clang-tidy/checkers/boost/use-ranges.cpp | 142 ++++++++++++
8 files changed, 454 insertions(+)
create mode 100644 clang-tools-extra/clang-tidy/boost/UseRangesCheck.cpp
create mode 100644 clang-tools-extra/clang-tidy/boost/UseRangesCheck.h
create mode 100644 clang-tools-extra/docs/clang-tidy/checks/boost/use-ranges.rst
create mode 100644 clang-tools-extra/test/clang-tidy/checkers/boost/use-ranges.cpp
diff --git a/clang-tools-extra/clang-tidy/boost/BoostTidyModule.cpp b/clang-tools-extra/clang-tidy/boost/BoostTidyModule.cpp
index 4c5808daa6ae74..79d0e380e402d7 100644
--- a/clang-tools-extra/clang-tidy/boost/BoostTidyModule.cpp
+++ b/clang-tools-extra/clang-tidy/boost/BoostTidyModule.cpp
@@ -9,6 +9,7 @@
#include "../ClangTidy.h"
#include "../ClangTidyModule.h"
#include "../ClangTidyModuleRegistry.h"
+#include "UseRangesCheck.h"
#include "UseToStringCheck.h"
using namespace clang::ast_matchers;
@@ -18,6 +19,7 @@ namespace boost {
class BoostModule : public ClangTidyModule {
public:
void addCheckFactories(ClangTidyCheckFactories &CheckFactories) override {
+ CheckFactories.registerCheck<UseRangesCheck>("boost-use-ranges");
CheckFactories.registerCheck<UseToStringCheck>("boost-use-to-string");
}
};
diff --git a/clang-tools-extra/clang-tidy/boost/CMakeLists.txt b/clang-tools-extra/clang-tidy/boost/CMakeLists.txt
index 167b6fab774b7c..fed3c3ba01c169 100644
--- a/clang-tools-extra/clang-tidy/boost/CMakeLists.txt
+++ b/clang-tools-extra/clang-tidy/boost/CMakeLists.txt
@@ -5,6 +5,7 @@ set(LLVM_LINK_COMPONENTS
add_clang_library(clangTidyBoostModule
BoostTidyModule.cpp
+ UseRangesCheck.cpp
UseToStringCheck.cpp
LINK_LIBS
diff --git a/clang-tools-extra/clang-tidy/boost/UseRangesCheck.cpp b/clang-tools-extra/clang-tidy/boost/UseRangesCheck.cpp
new file mode 100644
index 00000000000000..ac7036cfd8c7e6
--- /dev/null
+++ b/clang-tools-extra/clang-tidy/boost/UseRangesCheck.cpp
@@ -0,0 +1,211 @@
+//===--- UseRangesCheck.cpp - clang-tidy ----------------------------------===//
+//
+// 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 "UseRangesCheck.h"
+#include "clang/AST/Decl.h"
+#include "llvm/ADT/ArrayRef.h"
+#include "llvm/ADT/IntrusiveRefCntPtr.h"
+#include "llvm/ADT/StringRef.h"
+#include <initializer_list>
+#include <string>
+
+// FixItHint - Let the docs script know that this class does provide fixits
+
+namespace clang::tidy::boost {
+
+namespace {
+/// Base replacer that handles the boost include path and namespace
+class BoostReplacer : public UseRangesCheck::Replacer {
+public:
+ BoostReplacer(ArrayRef<UseRangesCheck::Signature> Signatures,
+ bool IncludeSystem)
+ : Signature(Signatures), IncludeSystem(IncludeSystem) {}
+
+ ArrayRef<UseRangesCheck::Signature> getReplacementSignatures() const final {
+ return Signature;
+ }
+
+ virtual std::pair<StringRef, StringRef>
+ getBoostName(const NamedDecl &OriginalName) const = 0;
+ virtual std::pair<StringRef, StringRef>
+ getBoostHeader(const NamedDecl &OriginalName) const = 0;
+
+ std::string getReplaceName(const NamedDecl &OriginalName) const final {
+ auto [Namespace, Function] = getBoostName(OriginalName);
+ return ("boost::" + Namespace + (Namespace.empty() ? "" : "::") + Function)
+ .str();
+ }
+
+ std::optional<std::string>
+ getHeaderInclusion(const NamedDecl &OriginalName) const final {
+ auto [Path, HeaderName] = getBoostHeader(OriginalName);
+ return ((IncludeSystem ? "<boost/" : "boost/") + Path +
+ (Path.empty() ? "" : "/") + HeaderName +
+ (IncludeSystem ? ".hpp>" : ".hpp"))
+ .str();
+ }
+
+private:
+ SmallVector<UseRangesCheck::Signature> Signature;
+ bool IncludeSystem;
+};
+
+/// Creates replaces where the header file lives in
+/// `boost/algorithm/<FUNC_NAME>.hpp and the function is named
+/// `boost::range::<FUNC_NAME>`
+class BoostRangeAlgorithmReplacer : public BoostReplacer {
+public:
+ using BoostReplacer::BoostReplacer;
+ std::pair<StringRef, StringRef>
+ getBoostName(const NamedDecl &OriginalName) const override {
+ return {"range", OriginalName.getName()};
+ }
+
+ std::pair<StringRef, StringRef>
+ getBoostHeader(const NamedDecl &OriginalName) const override {
+ return {"range/algorithm", OriginalName.getName()};
+ }
+};
+
+/// Creates replaces where the header file lives in
+/// `boost/algorithm/<CUSTOM_HEADER>.hpp and the function is named
+/// `boost::range::<FUNC_NAME>`
+class CustomBoostAlgorithmHeaderReplacer : public BoostRangeAlgorithmReplacer {
+public:
+ CustomBoostAlgorithmHeaderReplacer(
+ StringRef HeaderName, ArrayRef<UseRangesCheck::Signature> Signature,
+ bool IncludeSystem)
+ : BoostRangeAlgorithmReplacer(Signature, IncludeSystem),
+ HeaderName(HeaderName) {}
+
+ std::pair<StringRef, StringRef>
+ getBoostHeader(const NamedDecl & /*OriginalName*/) const override {
+ return {"range/algorithm", HeaderName};
+ }
+
+private:
+ StringRef HeaderName;
+};
+
+/// Creates replaces where the header file lives in
+/// `boost/algorithm/<SUB_HEADER>.hpp and the function is named
+/// `boost::algorithm::<FUNC_NAME>`
+class BoostAlgorithmReplacer : public BoostReplacer {
+public:
+ BoostAlgorithmReplacer(StringRef SubHeader,
+ ArrayRef<UseRangesCheck::Signature> Signature,
+ bool IncludeSystem)
+ : BoostReplacer(Signature, IncludeSystem),
+ SubHeader(("algorithm/" + SubHeader).str()) {}
+ std::pair<StringRef, StringRef>
+ getBoostName(const NamedDecl &OriginalName) const override {
+ return {"algorithm", OriginalName.getName()};
+ }
+
+ std::pair<StringRef, StringRef>
+ getBoostHeader(const NamedDecl &OriginalName) const override {
+ return {SubHeader, OriginalName.getName()};
+ }
+
+ std::string SubHeader;
+};
+
+/// Creates replaces where the header file lives in
+/// `boost/algorithm/<SUB_HEADER>/<HEADER_NAME>.hpp and the function is named
+/// `boost::algorithm::<FUNC_NAME>`
+class CustomBoostAlgorithmReplacer : public BoostReplacer {
+public:
+ CustomBoostAlgorithmReplacer(StringRef SubHeader, StringRef HeaderName,
+ ArrayRef<UseRangesCheck::Signature> Signature,
+ bool IncludeSystem)
+ : BoostReplacer(Signature, IncludeSystem),
+ SubHeader(("algorithm/" + SubHeader).str()), HeaderName(HeaderName) {}
+ std::pair<StringRef, StringRef>
+ getBoostName(const NamedDecl &OriginalName) const override {
+ return {"algorithm", OriginalName.getName()};
+ }
+
+ std::pair<StringRef, StringRef>
+ getBoostHeader(const NamedDecl & /*OriginalName*/) const override {
+ return {SubHeader, HeaderName};
+ }
+
+ std::string SubHeader;
+ StringRef HeaderName;
+};
+
+} // namespace
+
+utils::UseRangesCheck::ReplacerMap UseRangesCheck::getReplacerMap() const {
+
+ ReplacerMap Results;
+ static const Signature SingleSig = {{0}};
+ static const Signature TwoSig = {{0}, {2}};
+ static const auto Add =
+ [&Results](llvm::IntrusiveRefCntPtr<BoostReplacer> Replacer,
+ std::initializer_list<StringRef> Names) {
+ for (const auto &Name : Names) {
+ Results.try_emplace(("::std::" + Name).str(), Replacer);
+ }
+ };
+
+ Add(llvm::makeIntrusiveRefCnt<CustomBoostAlgorithmHeaderReplacer>(
+ "set_algorithm", TwoSig, IncludeBoostSystem),
+ {"includes", "set_union", "set_intersection", "set_difference",
+ "set_symmetric_difference"});
+ Add(llvm::makeIntrusiveRefCnt<BoostRangeAlgorithmReplacer>(
+ SingleSig, IncludeBoostSystem),
+ {"unique", "lower_bound", "stable_sort",
+ "equal_range", "remove_if", "sort",
+ "random_shuffle", "remove_copy", "stable_partition",
+ "remove_copy_if", "count", "copy_backward",
+ "reverse_copy", "adjacent_find", "remove",
+ "upper_bound", "binary_search", "replace_copy_if",
+ "for_each", "generate", "count_if",
+ "min_element", "reverse", "replace_copy",
+ "fill", "unique_copy", "transform",
+ "copy", "replace", "find",
+ "replace_if", "find_if", "partition",
+ "max_element"});
+ Add(llvm::makeIntrusiveRefCnt<BoostRangeAlgorithmReplacer>(
+ TwoSig, IncludeBoostSystem),
+ {"find_end", "merge", "partial_sort_copy", "find_first_of", "search",
+ "lexicographical_compare", "equal", "mismatch"});
+ Add(llvm::makeIntrusiveRefCnt<CustomBoostAlgorithmHeaderReplacer>(
+ "permutation", SingleSig, IncludeBoostSystem),
+ {"next_permutation", "prev_permutation"});
+ Add(llvm::makeIntrusiveRefCnt<CustomBoostAlgorithmHeaderReplacer>(
+ "heap_algorithm", SingleSig, IncludeBoostSystem),
+ {"push_heap", "pop_heap", "make_heap", "sort_heap"});
+ Add(llvm::makeIntrusiveRefCnt<BoostAlgorithmReplacer>("cxx11", SingleSig,
+ IncludeBoostSystem),
+ {"copy_if", "is_permutation", "is_partitioned", "find_if_not",
+ "partition_copy", "any_of", "iota", "all_of", "partition_point",
+ "is_sorted", "none_of"});
+ Add(llvm::makeIntrusiveRefCnt<CustomBoostAlgorithmReplacer>(
+ "cxx11", "is_sorted", SingleSig, IncludeBoostSystem),
+ {"is_sorted_until"});
+ Add(llvm::makeIntrusiveRefCnt<BoostAlgorithmReplacer>("cxx17", SingleSig,
+ IncludeBoostSystem),
+ {"reduce"});
+
+ return Results;
+}
+
+UseRangesCheck::UseRangesCheck(StringRef Name, ClangTidyContext *Context)
+ : utils::UseRangesCheck(Name, Context),
+ IncludeBoostSystem(Options.get("IncludeBoostSystem", true)) {}
+
+void UseRangesCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
+ utils::UseRangesCheck::storeOptions(Opts);
+ Options.store(Opts, "IncludeBoostSystem", IncludeBoostSystem);
+}
+DiagnosticBuilder UseRangesCheck::createDiag(const CallExpr &Call) {
+ return diag(Call.getBeginLoc(), "use a boost version of this algorithm");
+}
+} // namespace clang::tidy::boost
diff --git a/clang-tools-extra/clang-tidy/boost/UseRangesCheck.h b/clang-tools-extra/clang-tidy/boost/UseRangesCheck.h
new file mode 100644
index 00000000000000..74324c3c4132ff
--- /dev/null
+++ b/clang-tools-extra/clang-tidy/boost/UseRangesCheck.h
@@ -0,0 +1,37 @@
+//===--- UseRangesCheck.h - clang-tidy --------------------------*- C++ -*-===//
+//
+// 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_BOOST_USERANGESCHECK_H
+#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BOOST_USERANGESCHECK_H
+
+#include "../utils/UseRangesCheck.h"
+
+namespace clang::tidy::boost {
+
+/// Detects calls to standard library iterator algorithms that could be
+/// replaced with a boost ranges version instead
+///
+/// For the user-facing documentation see:
+/// http://clang.llvm.org/extra/clang-tidy/checks/boost/use-ranges.html
+class UseRangesCheck : public utils::UseRangesCheck {
+public:
+ UseRangesCheck(StringRef Name, ClangTidyContext *Context);
+
+ void storeOptions(ClangTidyOptions::OptionMap &Options) override;
+
+ ReplacerMap getReplacerMap() const override;
+
+ DiagnosticBuilder createDiag(const CallExpr &Call) override;
+
+private:
+ bool IncludeBoostSystem;
+};
+
+} // namespace clang::tidy::boost
+
+#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BOOST_USERANGESCHECK_H
diff --git a/clang-tools-extra/docs/ReleaseNotes.rst b/clang-tools-extra/docs/ReleaseNotes.rst
index 8111fe23386c01..66e1b6157e95cb 100644
--- a/clang-tools-extra/docs/ReleaseNotes.rst
+++ b/clang-tools-extra/docs/ReleaseNotes.rst
@@ -131,6 +131,12 @@ Improvements to clang-tidy
New checks
^^^^^^^^^^
+- New :doc:`boost-use-ranges
+ <clang-tidy/checks/boost/use-ranges>` check.
+
+ Detects calls to standard library iterator algorithms that could be replaced
+ with a boost ranges version instead
+
- New :doc:`bugprone-crtp-constructor-accessibility
<clang-tidy/checks/bugprone/crtp-constructor-accessibility>` check.
diff --git a/clang-tools-extra/docs/clang-tidy/checks/boost/use-ranges.rst b/clang-tools-extra/docs/clang-tidy/checks/boost/use-ranges.rst
new file mode 100644
index 00000000000000..e2d762387067b0
--- /dev/null
+++ b/clang-tools-extra/docs/clang-tidy/checks/boost/use-ranges.rst
@@ -0,0 +1,54 @@
+.. title:: clang-tidy - boost-use-ranges
+
+boost-use-ranges
+================
+
+Detects calls to standard library iterator algorithms that could be replaced
+with a boost ranges version instead.
+
+Example
+-------
+
+.. code-block:: c++
+
+ auto Iter1 = std::find(Items.begin(), Items.end(), 0);
+ auto AreSame = std::equal(Items1.cbegin(), Items1.cend(), std::begin(Items2),
+ std::end(Items2));
+
+
+transforms to:
+
+.. code-block:: c++
+
+ auto Iter1 = boost::range::find(Items, 0);
+ auto AreSame = boost::range::equal(Items1, Items2);
+
+Calls to the following std library algorithms are checked:
+``includes``,``set_union``,``set_intersection``,``set_difference``,
+``set_symmetric_difference``,``unique``,``lower_bound``,``stable_sort``,
+``equal_range``,``remove_if``,``sort``,``random_shuffle``,``remove_copy``,
+``stable_partition``,``remove_copy_if``,``count``,``copy_backward``,
+``reverse_copy``,``adjacent_find``,``remove``,``upper_bound``,``binary_search``,
+``replace_copy_if``,``for_each``,``generate``,``count_if``,``min_element``,
+``reverse``,``replace_copy``,``fill``,``unique_copy``,``transform``,``copy``,
+``replace``,``find``,``replace_if``,``find_if``,``partition``,``max_element``,
+``find_end``,``merge``,``partial_sort_copy``,``find_first_of``,``search``,
+``lexicographical_compare``,``equal``,``mismatch``,``next_permutation``,
+``prev_permutation``,``push_heap``,``pop_heap``,``make_heap``,``sort_heap``,
+``copy_if``,``is_permutation``,``is_partitioned``,``find_if_not``,
+``partition_copy``,``any_of``,``iota``,``all_of``,``partition_point``,
+``is_sorted``,``none_of``,``is_sorted_until``,``reduce``.
+
+Options
+-------
+
+.. option:: IncludeStyle
+
+ A string specifying which include-style is used, `llvm` or `google`. Default
+ is `llvm`.
+
+.. option:: IncludeBoostSystem
+
+ If `true` the boost headers are included as system headers with angle
+ brackets (`#include <boost.hpp>`), otherwise quotes are used
+ (`#include "boost.hpp"`).
diff --git a/clang-tools-extra/docs/clang-tidy/checks/list.rst b/clang-tools-extra/docs/clang-tidy/checks/list.rst
index 55426531a13b70..53790b19a25b27 100644
--- a/clang-tools-extra/docs/clang-tidy/checks/list.rst
+++ b/clang-tools-extra/docs/clang-tidy/checks/list.rst
@@ -75,6 +75,7 @@ Clang-Tidy Checks
:doc:`android-cloexec-pipe2 <android/cloexec-pipe2>`, "Yes"
:doc:`android-cloexec-socket <android/cloexec-socket>`, "Yes"
:doc:`android-comparison-in-temp-failure-retry <android/comparison-in-temp-failure-retry>`,
+ :doc:`boost-use-ranges <boost/use-ranges>`, "Yes"
:doc:`boost-use-to-string <boost/use-to-string>`, "Yes"
:doc:`bugprone-argument-comment <bugprone/argument-comment>`, "Yes"
:doc:`bugprone-assert-side-effect <bugprone/assert-side-effect>`,
diff --git a/clang-tools-extra/test/clang-tidy/checkers/boost/use-ranges.cpp b/clang-tools-extra/test/clang-tidy/checkers/boost/use-ranges.cpp
new file mode 100644
index 00000000000000..ebf150fabd5c02
--- /dev/null
+++ b/clang-tools-extra/test/clang-tidy/checkers/boost/use-ranges.cpp
@@ -0,0 +1,142 @@
+// RUN: %check_clang_tidy -std=c++14 %s boost-use-ranges %t
+
+// CHECK-FIXES: #include <boost/range/algorithm/find.hpp>
+// CHECK-FIXES: #include <boost/range/algorithm/reverse.hpp>
+// CHECK-FIXES: #include <boost/range/algorithm/set_algorithm.hpp>
+// CHECK-FIXES: #include <boost/range/algorithm/equal.hpp>
+// CHECK-FIXES: #include <boost/range/algorithm/permutation.hpp>
+// CHECK-FIXES: #include <boost/range/algorithm/heap_algorithm.hpp>
+// CHECK-FIXES: #include <boost/algorithm/cxx11/copy_if.hpp>
+// CHECK-FIXES: #include <boost/algorithm/cxx11/is_sorted.hpp>
+// CHECK-FIXES: #include <boost/algorithm/cxx17/reduce.hpp>
+
+namespace std {
+
+template <typename T> class vector {
+public:
+ using iterator = T *;
+ using const_iterator = const T *;
+ constexpr const_iterator begin() const;
+ constexpr const_iterator end() const;
+ constexpr const_iterator cbegin() const;
+ constexpr const_iterator cend() const;
+ constexpr iterator begin();
+ constexpr iterator end();
+};
+
+template <typename Container> constexpr auto begin(const Container &Cont) {
+ return Cont.begin();
+}
+
+template <typename Container> constexpr auto begin(Container &Cont) {
+ return Cont.begin();
+}
+
+template <typename Container> constexpr auto end(const Container &Cont) {
+ return Cont.end();
+}
+
+template <typename Container> constexpr auto end(Container &Cont) {
+ return Cont.end();
+}
+
+template <typename Container> constexpr auto cbegin(const Container &Cont) {
+ return Cont.cbegin();
+}
+
+template <typename Container> constexpr auto cend(const Container &Cont) {
+ return Cont.cend();
+}
+// Find
+template< class InputIt, class T >
+InputIt find(InputIt first, InputIt last, const T& value);
+
+template <typename Iter> void reverse(Iter begin, Iter end);
+
+template <class InputIt1, class InputIt2>
+bool includes(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2);
+
+template <class ForwardIt1, class ForwardIt2>
+bool is_permutation(ForwardIt1 first1, ForwardIt1 last1, ForwardIt2 first2,
+ ForwardIt2 last2);
+
+template <class BidirIt>
+bool next_permutation(BidirIt first, BidirIt last);
+
+template <class ForwardIt1, class ForwardIt2>
+bool equal(ForwardIt1 first1, ForwardIt1 last1,
+ ForwardIt2 first2, ForwardIt2 last2);
+
+template <class RandomIt>
+void push_heap(RandomIt first, RandomIt last);
+
+template <class InputIt, class OutputIt, class UnaryPred>
+OutputIt copy_if(InputIt first, InputIt last, OutputIt d_first, UnaryPred pred);
+
+template <class ForwardIt>
+ForwardIt is_sorted_until(ForwardIt first, ForwardIt last);
+
+template <class InputIt>
+void reduce(InputIt first, InputIt last);
+
+template <class InputIt, class T>
+T reduce(InputIt first, InputIt last, T init);
+
+template <class InputIt, class T, class BinaryOp>
+T reduce(InputIt first, InputIt last, T init, BinaryOp op) {
+ // Need a definition to suppress undefined_internal_type when invoked with lambda
+ return init;
+}
+
+} // namespace std
+
+bool return_true(int val) {
+ return true;
+}
+
+void Positives() {
+ std::vector<int> I, J;
+ std::find(I.begin(), I.end(), 0);
+ // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: use a boost version of this algorithm
+ // CHECK-FIXES: boost::range::find(I, 0);
+
+ std::reverse(I.cbegin(), I.cend());
+ // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: use a boost version of this algorithm
+ // CHECK-FIXES: boost::range::reverse(I);
+
+ std::includes(I.begin(), I.end(), std::begin(J), std::end(J));
+ // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: use a boost version of this algorithm
+ // CHECK-FIXES: boost::range::includes(I, J);
+
+ std::equal(std::cbegin(I), std::cend(I), J.begin(), J.end());
+ // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: use a boost version of this algorithm
+ // CHECK-FIXES: boost::range::equal(I, J);
+
+ std::next_permutation(I.begin(), I.end());
+ // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: use a boost version of this algorithm
+ // CHECK-FIXES: boost::range::next_permutation(I);
+
+ std::push_heap(I.begin(), I.end());
+ // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: use a boost version of this algorithm
+ // CHECK-FIXES: boost::range::push_heap(I);
+
+ std::copy_if(I.begin(), I.end(), J.begin(), &return_true);
+ // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: use a boost version of this algorithm
+ // CHECK-FIXES: boost::algorithm::copy_if(I, J.begin(), &return_true);
+
+ std::is_sorted_until(I.begin(), I.end());
+ // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: use a boost version of this algorithm
+ // CHECK-FIXES: boost::algorithm::is_sorted_until(I);
+
+ std::reduce(I.begin(), I.end());
+ // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: use a boost version of this algorithm
+ // CHECK-FIXES: boost::algorithm::reduce(I);
+
+ std::reduce(I.begin(), I.end(), 2);
+ // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: use a boost version of this algorithm
+ // CHECK-FIXES: boost::algorithm::reduce(I, 2);
+
+ std::reduce(I.begin(), I.end(), 0, [](int a, int b){ return a + b; });
+ // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: use a boost version of this algorithm
+ // CHECK-FIXES: boost::algorithm::reduce(I, 0, [](int a, int b){ return a + b; });
+}
More information about the cfe-commits
mailing list