https://github.com/njames93 updated https://github.com/llvm/llvm-project/pull/97764
>From dd529ead075012e4c74ff54fb534e1f34efe0c7f Mon Sep 17 00:00:00 2001 From: Nathan James <n.jame...@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 | 151 +++++++++++ .../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 | 33 +++ .../checkers/modernize/use-ranges.cpp | 183 +++++++++++++ 11 files changed, 725 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 576805c4c7f18..4f68c487cac9d 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 b9c7a2dc383e8..1860759332063 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 0000000000000..84183046d4705 --- /dev/null +++ b/clang-tools-extra/clang-tidy/modernize/UseRangesCheck.cpp @@ -0,0 +1,151 @@ +//===--- 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" + +namespace clang::tidy::modernize { + +utils::UseRangesCheck::ReplacerMap UseRangesCheck::getReplacerMap() const { + 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; + }; + + 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<StringRef>> + Names[] = { + {SingleRangeFunc, + {"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"}}, + {SingleRangeExecFunc, + {"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"}}, + {TwoRangeExecFunc, + {"partial_sort_copy", "includes", "set_union", "set_intersection", + "set_difference", "set_symmetric_difference", "merge", + "lexicographical_compare", "find_end", "search"}}, + {OneOrTwoFunc, {"is_permutation"}}, + {OneOrTwoExecFunc, {"equal", "mismatch"}}}; + 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.clear(); + Result.try_emplace(("::std::" + Name).toStringRef(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 0000000000000..3148dd320b982 --- /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 9cff7d475425d..1841ea981359c 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 0000000000000..214a7b33af621 --- /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 0000000000000..d6a8dedbbb19a --- /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 d968100cd57a7..8111fe23386c0 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 a698cecc0825c..55426531a13b7 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 0000000000000..e9dae6acade0e --- /dev/null +++ b/clang-tools-extra/docs/clang-tidy/checks/modernize/use-ranges.rst @@ -0,0 +1,33 @@ +.. 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); + +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 0000000000000..1aa8107725f00 --- /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 e23f13c84c7274b857c0d447d646645955e64ced Mon Sep 17 00:00:00 2001 From: Nathan James <n.jame...@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 | 36 +++ clang-tools-extra/docs/ReleaseNotes.rst | 6 + .../clang-tidy/checks/boost/use-ranges.rst | 24 ++ .../docs/clang-tidy/checks/list.rst | 1 + .../clang-tidy/checkers/boost/use-ranges.cpp | 142 ++++++++++++ 8 files changed, 423 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 4c5808daa6ae7..79d0e380e402d 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 167b6fab774b7..fed3c3ba01c16 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 0000000000000..6f8246e575746 --- /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> + +using namespace clang::ast_matchers; + +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 0000000000000..2033aa993268e --- /dev/null +++ b/clang-tools-extra/clang-tidy/boost/UseRangesCheck.h @@ -0,0 +1,36 @@ +//===--- 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 { + +/// FIXME: Write a short description. +/// +/// 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 8111fe23386c0..66e1b6157e95c 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 0000000000000..63cbe81f6c3e2 --- /dev/null +++ b/clang-tools-extra/docs/clang-tidy/checks/boost/use-ranges.rst @@ -0,0 +1,24 @@ +.. title:: clang-tidy - boost-use-ranges + +boost-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(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); diff --git a/clang-tools-extra/docs/clang-tidy/checks/list.rst b/clang-tools-extra/docs/clang-tidy/checks/list.rst index 55426531a13b7..53790b19a25b2 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 0000000000000..ebf150fabd5c0 --- /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; }); +} _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits