diff --git a/clang-tools-extra/clang-tidy/modernize/CMakeLists.txt b/clang-tools-extra/clang-tidy/modernize/CMakeLists.txt --- a/clang-tools-extra/clang-tidy/modernize/CMakeLists.txt +++ b/clang-tools-extra/clang-tidy/modernize/CMakeLists.txt @@ -34,6 +34,7 @@ UseNoexceptCheck.cpp UseNullptrCheck.cpp UseOverrideCheck.cpp + UseRangesCheck.cpp UseTrailingReturnTypeCheck.cpp UseTransparentFunctorsCheck.cpp UseUncaughtExceptionsCheck.cpp diff --git a/clang-tools-extra/clang-tidy/modernize/ModernizeTidyModule.cpp b/clang-tools-extra/clang-tidy/modernize/ModernizeTidyModule.cpp --- a/clang-tools-extra/clang-tidy/modernize/ModernizeTidyModule.cpp +++ b/clang-tools-extra/clang-tidy/modernize/ModernizeTidyModule.cpp @@ -36,6 +36,7 @@ #include "UseNoexceptCheck.h" #include "UseNullptrCheck.h" #include "UseOverrideCheck.h" +#include "UseRangesCheck.h" #include "UseTrailingReturnTypeCheck.h" #include "UseTransparentFunctorsCheck.h" #include "UseUncaughtExceptionsCheck.h" @@ -91,6 +92,7 @@ CheckFactories.registerCheck("modernize-use-noexcept"); CheckFactories.registerCheck("modernize-use-nullptr"); CheckFactories.registerCheck("modernize-use-override"); + CheckFactories.registerCheck("modernize-use-ranges"); CheckFactories.registerCheck( "modernize-use-trailing-return-type"); CheckFactories.registerCheck( diff --git a/clang-tools-extra/clang-tidy/modernize/UseRangesCheck.h b/clang-tools-extra/clang-tidy/modernize/UseRangesCheck.h new file mode 100644 --- /dev/null +++ b/clang-tools-extra/clang-tidy/modernize/UseRangesCheck.h @@ -0,0 +1,46 @@ +//===--- 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 "../ClangTidyCheck.h" +#include "../utils/IncludeInserter.h" + +namespace clang { +namespace tidy { +namespace modernize { + +/// Converts calls to standard library algorithms that take a pair of iterators +/// and replaces them with the equivalent function from the C++20 `std::ranges` +/// library. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/modernize-use-ranges.html +class UseRangesCheck : public ClangTidyCheck { +public: + UseRangesCheck(StringRef Name, ClangTidyContext *Context); + bool isLanguageVersionSupported(const LangOptions &LangOpts) const override { + return LangOpts.CPlusPlus20; + } + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void registerPPCallbacks(const SourceManager &SM, Preprocessor *PP, + Preprocessor *ModuleExpanderPP) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + void storeOptions(ClangTidyOptions::OptionMap &Opts) override; + +private: + utils::IncludeInserter Inserter; + const bool HandleReverseRanges; +}; + +} // namespace modernize +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_USERANGESCHECK_H diff --git a/clang-tools-extra/clang-tidy/modernize/UseRangesCheck.cpp b/clang-tools-extra/clang-tidy/modernize/UseRangesCheck.cpp new file mode 100644 --- /dev/null +++ b/clang-tools-extra/clang-tidy/modernize/UseRangesCheck.cpp @@ -0,0 +1,298 @@ +//===--- 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/DeclCXX.h" +#include "clang/ASTMatchers/ASTMatchers.h" +#include "clang/ASTMatchers/ASTMatchersInternal.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace modernize { + +// Functions that take a single iterator pair: +// void func(Iter Begin, Iter End, AnyOtherArgs); +// void func(ExecutionPolicy&&, Iter Begin, Iter End, AnyOtherArgs); +static constexpr StringRef SingleRangeFunctions[] = { + "::std::any_of", "::std::all_of", "::std::none_of", + "::std::for_each", "::std::count", "::std::count_if", + "::std::find", "::std::find_if", "::std::find_if_not", + "::std::adjacent_find", "::std::copy", "::std::copy_if", + "::std::fill", "::std::is_heap", "::std::is_heap_until", + "::std::make_heap", "::std::push_heap", "::std::pop_heap", + "::std::sort_heap", "::std::remove", "::std::remove_if", + "::std::reverse", "::std::unique", "::std::lower_bound", + "::std::upper_bound", "::std::binary_search", "::std::equal_range", + "::std::max_element", "::std::min_element", "::std::minmax_element", +}; + +// Functions that can take one or two iterator pairs: +// void func(Iter Begin, Iter End, AnyOtherArgs); +// void func(Iter Begin, Iter End, Iter2 Begin2, Iter2 End2, AnyOtherArgs); +// void func(ExecutionPolicy&&, Iter Begin, Iter End, AnyOtherArgs); +// void func(ExecutionPolicy&&, Iter Begin, Iter End, Iter2 Begin2, Iter2 End2, +// AnyOtherArgs); +static constexpr StringRef MaybeDualRangeFunctions[] = { + "::std::search", + "::std::equal", +}; + +// Functions that take two iterator pairs: +// void func(Iter Begin, Iter End, Iter2 Begin2, Iter2 End2, AnyOtherArgs); +// void func(ExecutionPolicy&&, Iter Begin, Iter End, Iter2 Begin2, Iter2 End2, +// AnyOtherArgs); +static constexpr StringRef DualRangeFunctions[] = { + "::std::merge", + "::std::includes", + "::std::set_difference", + "::std::set_intesection", + "::std::set_symmetric_difference", + "::std::set_union", +}; + +namespace { +enum class PointerMatchMode { None, RawPointers, SmartPointers }; +} // namespace + +namespace detail { +static internal::Matcher +matchSingleCall(std::string BindName, + internal::Matcher DeclMatcher, + internal::Matcher NameMatcher, + internal::Matcher MethodMatcher, + PointerMatchMode PointerMode) { + internal::Matcher Container = + anyOf(declRefExpr(to(declaratorDecl(DeclMatcher))), + memberExpr(hasDeclaration(declaratorDecl(DeclMatcher)))); + + auto DerefMatcher = [&]() -> internal::Matcher { + if (PointerMode == PointerMatchMode::None) + return Container; + auto NormalPointerMatch = + unaryOperator(hasOperatorName("*"), hasUnaryOperand(Container)); + if (PointerMode == PointerMatchMode::RawPointers) + return NormalPointerMatch; + assert(PointerMode == PointerMatchMode::SmartPointers); + return anyOf(NormalPointerMatch, + cxxOperatorCallExpr(argumentCountIs(1), + hasOverloadedOperatorName("*"), + hasArgument(0, Container))); + }(); + auto MemberMatcher = [&]() -> internal::Matcher { + if (PointerMode == PointerMatchMode::SmartPointers) + return anyOf(Container, + cxxOperatorCallExpr(argumentCountIs(1), + hasOverloadedOperatorName("->"), + hasArgument(0, Container))); + return Container; + }(); + + return traverse( + TK_IgnoreImplicitCastsAndParentheses, + expr( + anyOf(callExpr(callee(functionDecl(unless(cxxMethodDecl()), + NameMatcher, parameterCountIs(1))), + hasArgument(0, DerefMatcher)), + cxxMemberCallExpr( + on(MemberMatcher), + callee(PointerMode != PointerMatchMode::None + ? memberExpr(isArrow()) + : memberExpr(unless(isArrow()))), + callee(cxxMethodDecl(MethodMatcher, parameterCountIs(0)))))) + .bind(BindName)); +} + +static internal::Matcher matchCallPair(unsigned ArgIndex, + StringRef ID, + PointerMatchMode PointerMode, + bool IsReverse) { + std::string BoundName = + (ID + "Container" + (PointerMode != PointerMatchMode::None ? "Ptr" : "") + + (IsReverse ? "Rev" : "")) + .str(); + return allOf( + hasArgument(ArgIndex, + matchSingleCall( + (ID + "Begin").str(), namedDecl().bind(BoundName), + IsReverse ? hasAnyName("::std::rbegin", "::std::crbegin") + : hasAnyName("::std::begin", "::std::cbegin"), + IsReverse ? hasAnyName("rbegin", "crbegin") + : hasAnyName("begin", "cbegin"), + PointerMode)), + hasArgument( + ArgIndex + 1, + matchSingleCall((ID + "End").str(), equalsBoundNode(BoundName), + IsReverse ? hasAnyName("::std::rend", "::std::crend") + : hasAnyName("::std::end", "::std::cend"), + IsReverse ? hasAnyName("rend", "crend") + : hasAnyName("end", "cend"), + PointerMode))); +} +} // namespace detail + +/// Tries to match a call expr where 2 consecutive arguments at \p ArgIndex are +/// calls to begin/end of the same container. If successful the following nodes +/// are bound, all prefixed with \p ID: +/// +/// "Begin" - The call to .begin() or std::begin(...) +/// - Will be equivalent to the argument at \p ArgIndex +/// "End" - The call to .end() or std::end(...) +/// - Will be equivalent to the argument at \p ArgIndex + 1 +/// "Container" - The container. +/// +/// If \p PointerMode is \c RawPointers, it will also search for calls to +/// ->begin() or std::begin(*...) If \p PointerMode is \cSmartPointers it will +/// search for pointer calls using user defined \c operator* and \c operator->. +/// If its matched using raw or smart pointers, the \c Container bound node will +/// have a "Ptr" Suffix. If \p AllowReverse is \c true, It will also search for +/// calls to rbegin or std::rbegin(...). If its matched a reverse iterator, the +/// \c Container bound node will have a "Rev" Suffix. The "Ptr" Suffix will +/// appear before the "Rev" suffix if both are matched. Example the match for: +/// +/// \code +/// func(Container->rbegin(), Container->rend()); +/// \endcode +/// with \p ID set to "Test" will look like: +/// "TestBegin", "TestEnd", "TestContainerPtrRev". +static internal::Matcher +matchBeginAndEndCall(unsigned ArgIndex, StringRef ID, + PointerMatchMode PointerMode, bool AllowReverse) { + if (PointerMode != PointerMatchMode::None) { + if (AllowReverse) { + return anyOf( + detail::matchCallPair(ArgIndex, ID, PointerMatchMode::None, false), + detail::matchCallPair(ArgIndex, ID, PointerMatchMode::None, true), + detail::matchCallPair(ArgIndex, ID, PointerMode, false), + detail::matchCallPair(ArgIndex, ID, PointerMode, true)); + } + return anyOf( + detail::matchCallPair(ArgIndex, ID, PointerMatchMode::None, false), + detail::matchCallPair(ArgIndex, ID, PointerMode, false)); + } + if (AllowReverse) { + return anyOf( + detail::matchCallPair(ArgIndex, ID, PointerMatchMode::None, false), + detail::matchCallPair(ArgIndex, ID, PointerMatchMode::None, true)); + } + return detail::matchCallPair(ArgIndex, ID, PointerMatchMode::None, false); +} + +static FixItHint +createContainerReplacement(const MatchFinder::MatchResult &Result, + SourceRange Range, StringRef ID) { + SmallString<128> Buffer; + if (const auto *Target = Result.Nodes.getNodeAs(ID)) + return FixItHint::CreateReplacement(Range, Target->getName()); + Buffer.clear(); + if (const auto *Target = + Result.Nodes.getNodeAs((ID + "Ptr").toStringRef(Buffer))) { + Buffer.clear(); + return FixItHint::CreateReplacement( + Range, ("*" + Target->getName()).toStringRef(Buffer)); + } + Buffer.clear(); + if (const auto *Target = + Result.Nodes.getNodeAs((ID + "Rev").toStringRef(Buffer))) { + Buffer.clear(); + return FixItHint::CreateReplacement( + Range, ("std::ranges::reverse_view(" + Target->getName() + ")") + .toStringRef(Buffer)); + } + Buffer.clear(); + if (const auto *Target = Result.Nodes.getNodeAs( + (ID + "PtrRev").toStringRef(Buffer))) { + Buffer.clear(); + return FixItHint::CreateReplacement( + Range, ("std::ranges::reverse_view(*" + Target->getName() + ")") + .toStringRef(Buffer)); + } + llvm_unreachable("Bound node not found"); +} + +UseRangesCheck::UseRangesCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context), + Inserter(Options.getLocalOrGlobal("IncludeStyle", + utils::IncludeSorter::IS_LLVM)), + HandleReverseRanges(Options.get("HandleReverseRanges", false)) {} + +void UseRangesCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { + Options.store(Opts, "IncludeStyle", Inserter.getStyle()); + Options.store(Opts, "HandleReverseRanges", HandleReverseRanges); +} + +void UseRangesCheck::registerPPCallbacks(const SourceManager &SM, + Preprocessor *PP, + Preprocessor *ModuleExpanderPP) { + Inserter.registerPreprocessor(PP); +} + +void UseRangesCheck::registerMatchers(MatchFinder *Finder) { + + // Match at first or second arg incase there is an ExecutionPolicy at first + // arg. + auto MatchBeginAndEnd = [&](unsigned ArgIndex, StringRef ID) { + return matchBeginAndEndCall(ArgIndex, ID, PointerMatchMode::SmartPointers, + HandleReverseRanges); + }; + + Finder->addMatcher( + callExpr( + callee( + functionDecl(hasAnyName(SingleRangeFunctions)).bind("Function")), + anyOf(MatchBeginAndEnd(0, ""), MatchBeginAndEnd(1, ""))) + .bind("Call"), + this); + Finder->addMatcher( + callExpr( + callee(functionDecl(hasAnyName(DualRangeFunctions)).bind("Function")), + anyOf(allOf(MatchBeginAndEnd(0, ""), MatchBeginAndEnd(2, "Second")), + allOf(MatchBeginAndEnd(1, ""), MatchBeginAndEnd(3, "Second")))) + .bind("Call"), + this); + Finder->addMatcher( + callExpr(callee(functionDecl(hasAnyName(MaybeDualRangeFunctions)) + .bind("Function")), + anyOf(MatchBeginAndEnd(0, ""), MatchBeginAndEnd(1, "")), + optionally(anyOf(MatchBeginAndEnd(2, "Second"), + MatchBeginAndEnd(3, "Second")))) + .bind("Call"), + this); +} + +void UseRangesCheck::check(const MatchFinder::MatchResult &Result) { + const auto *Begin = Result.Nodes.getNodeAs("Begin"); + const auto *End = Result.Nodes.getNodeAs("End"); + const auto *Function = Result.Nodes.getNodeAs("Function"); + const auto *Call = Result.Nodes.getNodeAs("Call"); + assert(Begin && End && Function && Call && "Missing bound node"); + + const auto *Begin2 = Result.Nodes.getNodeAs("SecondBegin"); + const auto *End2 = Result.Nodes.getNodeAs("SecondEnd"); + assert(((Begin2 && End2) || (!Begin2 && !End2)) && + "Missing secondary bound nodes"); + + DiagnosticBuilder Diag = + diag(Call->getBeginLoc(), "replace %0 with std::ranges::%0"); + Diag << Function->getName() + << Inserter.createIncludeInsertion( + Result.SourceManager->getFileID(Call->getBeginLoc()), "ranges", + true) + << FixItHint::CreateReplacement( + {Call->getBeginLoc(), Call->getCallee()->getEndLoc()}, + ("std::ranges::" + Function->getName()).str()) + << createContainerReplacement( + Result, {Begin->getBeginLoc(), End->getEndLoc()}, "Container"); + if (Begin2) + Diag << createContainerReplacement( + Result, {Begin2->getBeginLoc(), End2->getEndLoc()}, "SecondContainer"); +} +} // namespace modernize +} // namespace tidy +} // namespace clang diff --git a/clang-tools-extra/docs/ReleaseNotes.rst b/clang-tools-extra/docs/ReleaseNotes.rst --- a/clang-tools-extra/docs/ReleaseNotes.rst +++ b/clang-tools-extra/docs/ReleaseNotes.rst @@ -67,6 +67,16 @@ Improvements to clang-tidy -------------------------- +New checks +^^^^^^^^^^ + +- New :doc:`modernize-use-ranges + ` check. + + Converts calls to standard library algorithms that take a pair of iterators + and replaces them with the equivalent function from the C++20 ``std::ranges`` + library. + Changes in existing checks ^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/clang-tools-extra/docs/clang-tidy/checks/list.rst b/clang-tools-extra/docs/clang-tidy/checks/list.rst --- a/clang-tools-extra/docs/clang-tidy/checks/list.rst +++ b/clang-tools-extra/docs/clang-tidy/checks/list.rst @@ -235,6 +235,7 @@ `modernize-use-noexcept `_, "Yes" `modernize-use-nullptr `_, "Yes" `modernize-use-override `_, "Yes" + `modernize-use-ranges `_, "Yes" `modernize-use-trailing-return-type `_, "Yes" `modernize-use-transparent-functors `_, "Yes" `modernize-use-uncaught-exceptions `_, "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 --- /dev/null +++ b/clang-tools-extra/docs/clang-tidy/checks/modernize-use-ranges.rst @@ -0,0 +1,68 @@ +.. title:: clang-tidy - modernize-use-ranges + +modernize-use-ranges +==================== + +Converts calls to standard library algorithms that take a pair of iterators and +replaces them with the equivalent function from the C++20 ``std::ranges`` +library. + +The check is only applicable for C++20 and later code. + +The iterators must be attained using either: + - ``std::begin(Container)`` + - ``std::cbegin(Container)`` + - ``Container.begin()`` + - ``Container.cbegin()`` +and: + - ``std::end(Container)`` + - ``std::cend(Container)`` + - ``Container.end()`` + - ``Container.cend()`` + +Example +------- + +.. code-block:: c++ + + auto It = std::find(Vec.cbegin(), Vec.cend(), 1); + bool B1 = std::includes(std::begin(Set1), std::end(Set1), Set2.begin(), Set2.end()); + bool B2 = std::equal(std::cbegin(Vec1), std::cend(Vec1), OtherIter); + +transforms to: + +.. code-block:: c++ + + auto It = std::ranges::find(Vec, 1); + bool B1 = std::ranges::includes(Set1, Set2); + bool B2 = std::ranges::equal(Vec1, OtherIter); + +Options +------- + +.. option:: HandleReverseRanges + + If true, the check will look for calls where reverse iterators are used + (``rbegin`` and ``rend``) and replace those by wrapping them inside a call to + ``std::ranges::reverse_view()``. + Default value is `false` + +Example +------- + +.. code-block:: c++ + + auto It = std:find(Vec.rbegin(), Vec.rend(), 1); + +When `HandleReverseRanges` is `true` transforms to: + +.. code-block:: c++ + + auto It = std::ranges::find(std::ranges::reverse_view(Vec), 1); + + +.. 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/Inputs/modernize-use-ranges/algorithm b/clang-tools-extra/test/clang-tidy/checkers/Inputs/modernize-use-ranges/algorithm new file mode 100644 --- /dev/null +++ b/clang-tools-extra/test/clang-tidy/checkers/Inputs/modernize-use-ranges/algorithm @@ -0,0 +1,60 @@ +#ifndef ALGORITHM_H +#define ALGORITHM_H + +namespace std { + +template +auto begin(Container &C) -> decltype(C.begin()); +template +auto cbegin(Container &C) -> decltype(C.cbegin()); +template +auto end(Container &C) -> decltype(C.end()); +template +auto cend(Container &C) -> decltype(C.cend()); +template +auto rbegin(Container &C) -> decltype(C.rbegin()); +template +auto crbegin(Container &C) -> decltype(C.crbegin()); +template +auto rend(Container &C) -> decltype(C.rend()); +template +auto crend(Container &C) -> decltype(C.crend()); + +template +T *begin(T (&Array)[N]) noexcept; +template +T *end(T (&Array)[N]) noexcept; +template +T *rbegin(T (&Array)[N]) noexcept; +template +T *rend(T (&Array)[N]) noexcept; + +template +InputIt find(InputIt First, InputIt Last, const T &Value); + +template +ForwardIt find(ExecutionPolicy &&Policy, ForwardIt First, ForwardIt Last, const T &Value); + +template +bool equal(InputIt1 First1, InputIt1 Last1, + InputIt2 First2); +template +bool equal(ExecutionPolicy &&Policy, ForwardIt1 First1, ForwardIt1 Last1, + ForwardIt2 First2); +template +bool equal(InputIt1 First1, InputIt1 Last1, + InputIt2 First2, InputIt2 Last2); +template +bool equal(ExecutionPolicy &&Policy, ForwardIt1 First1, ForwardIt1 Last1, + ForwardIt2 First2, ForwardIt2 Last2); + +template +bool includes(InputIt1 First1, InputIt1 Last1, + InputIt2 First2, InputIt2 Last2); +template +bool includes(ExecutionPolicy &&Policy, ForwardIt1 First1, ForwardIt1 Last1, + ForwardIt2 First2, ForwardIt2 Last2); + +} // namespace std + +#endif // ALGORITHM_H diff --git a/clang-tools-extra/test/clang-tidy/checkers/Inputs/modernize-use-ranges/execution b/clang-tools-extra/test/clang-tidy/checkers/Inputs/modernize-use-ranges/execution new file mode 100644 --- /dev/null +++ b/clang-tools-extra/test/clang-tidy/checkers/Inputs/modernize-use-ranges/execution @@ -0,0 +1,13 @@ +#ifndef EXECUTION_H +#define EXECUTION_H + +namespace std { +namespace execution { +class sequenced_policy {}; +class parallel_policy {}; +class parallel_unsequenced_policy {}; +class unsequenced_policy {}; +} // namespace execution +} // namespace std + +#endif // EXECUTION_H diff --git a/clang-tools-extra/test/clang-tidy/checkers/Inputs/modernize-use-ranges/vector b/clang-tools-extra/test/clang-tidy/checkers/Inputs/modernize-use-ranges/vector new file mode 100644 --- /dev/null +++ b/clang-tools-extra/test/clang-tidy/checkers/Inputs/modernize-use-ranges/vector @@ -0,0 +1,29 @@ +#ifndef VECTOR_H +#define VECTOR_H + +namespace std { +template +class vector { +public: + using iterator = T *; + using const_iterator = const T *; + + iterator begin(); + const_iterator begin() const; + const_iterator cbegin() const; + + iterator end(); + const_iterator end() const; + const_iterator cend() const; + + iterator rbegin(); + const_iterator rbegin() const; + const_iterator crbegin() const; + + iterator rend(); + const_iterator rend() const; + const_iterator crend() const; +}; +} // namespace std + +#endif // VECTOR_H 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 --- /dev/null +++ b/clang-tools-extra/test/clang-tidy/checkers/modernize-use-ranges.cpp @@ -0,0 +1,135 @@ +// RUN: %check_clang_tidy -std=c++20 %s modernize-use-ranges %t -- --config="{CheckOptions: [ \ +// RUN: {key: modernize-use-ranges.HandleReverseRanges, value: true}]}" \ +// RUN: -- -isystem %S/Inputs/modernize-use-ranges + +// Ensure no warnings are generated when not in c++20 mode +// RUN: clang-tidy %s -checks=-*,modernize-use-ranges --warnings-as-errors=modernize-use-ranges -- -nostdinc++ -std=c++17 -isystem %S/Inputs/modernize-use-ranges + +#include +#include +#include +// CHECK-FIXES: #include + +template +class smart_pointer { +public: + T &operator*(); + T *operator->(); +}; + +void goodBeginEndCalls(const std::vector &Vec) { + std::find(Vec.begin(), Vec.end(), 1); + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: replace find with std::ranges::find + std::find(Vec.cbegin(), Vec.cend(), 1); + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: replace find with std::ranges::find + std::find(std::begin(Vec), std::end(Vec), 1); + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: replace find with std::ranges::find + std::find(std::cbegin(Vec), std::cend(Vec), 1); + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: replace find with std::ranges::find + std::find(std::execution::parallel_policy(), Vec.begin(), Vec.end(), 1); + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: replace find with std::ranges::find + + // CHECK-FIXES: std::ranges::find(Vec, 1); + // CHECK-FIXES-NEXT: // + // CHECK-FIXES-NEXT: std::ranges::find(Vec, 1); + // CHECK-FIXES-NEXT: // + // CHECK-FIXES-NEXT: std::ranges::find(Vec, 1); + // CHECK-FIXES-NEXT: // + // CHECK-FIXES-NEXT: std::ranges::find(Vec, 1); + // CHECK-FIXES-NEXT: // + // CHECK-FIXES-NEXT: std::ranges::find(std::execution::parallel_policy(), Vec, 1); + // CHECK-FIXES-NEXT: // +} + +void badBeginEndCalls(const std::vector &Vec, + const std::vector &Vec2) { + // end, begin. + std::find(Vec.end(), Vec.begin(), 1); + std::find(Vec.cend(), Vec.cbegin(), 1); + std::find(std::end(Vec), std::begin(Vec), 1); + std::find(std::cend(Vec), std::cbegin(Vec), 1); + + // begin, begin. + std::find(Vec.begin(), Vec.begin(), 1); + // end, end. + std::find(Vec.end(), Vec.end(), 1); + + // Different containers, definitely bad, but not what this check is for. + std::find(Vec.begin(), Vec2.end(), 1); +} + +void maybeDualArg(const std::vector &Vec1, std::vector &Vec2) { + std::equal(Vec1.begin(), Vec1.end(), Vec2.begin()); + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: replace equal with std::ranges::equal + std::equal(Vec1.begin(), Vec1.end(), Vec2.begin(), Vec2.end()); + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: replace equal with std::ranges::equal + std::equal(std::execution::sequenced_policy(), Vec1.begin(), Vec1.end(), Vec2.begin()); + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: replace equal with std::ranges::equal + std::equal(std::execution::unsequenced_policy(), Vec1.begin(), Vec1.end(), Vec2.begin(), Vec2.end()); + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: replace equal with std::ranges::equal + + // CHECK-FIXES: std::ranges::equal(Vec1, Vec2.begin()); + // CHECK-FIXES-NEXT: // + // CHECK-FIXES-NEXT: std::ranges::equal(Vec1, Vec2); + // CHECK-FIXES-NEXT: // + // CHECK-FIXES-NEXT: std::ranges::equal(std::execution::sequenced_policy(), Vec1, Vec2.begin()); + // CHECK-FIXES-NEXT: // + // CHECK-FIXES-NEXT: std::ranges::equal(std::execution::unsequenced_policy(), Vec1, Vec2); + // CHECK-FIXES-NEXT: // +} + +void dualArg(const std::vector &Vec1, const std::vector &Vec2) { + std::includes(Vec1.begin(), Vec1.end(), Vec2.begin(), Vec2.end()); + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: replace includes with std::ranges::includes + std::includes(std::execution::parallel_unsequenced_policy(), Vec1.begin(), Vec1.end(), Vec2.begin(), Vec2.end()); + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: replace includes with std::ranges::includes + + // CHECK-FIXES: std::ranges::includes(Vec1, Vec2); + // CHECK-FIXES-NEXT: // + // CHECK-FIXES-NEXT: std::ranges::includes(std::execution::parallel_unsequenced_policy(), Vec1, Vec2); + // CHECK-FIXES-NEXT: // + + // begin,begin,end,end - no warning. + std::includes(Vec1.begin(), Vec2.begin(), Vec1.end(), Vec2.end()); + // container mismatch - no warnings. + std::includes(Vec1.begin(), Vec2.end(), Vec2.begin(), Vec1.end()); +} + +void checkArray() { + // Arrays can be passed to ranges functions. + int Array[] = {1, 2, 3}; + std::find(std::begin(Array), std::end(Array), 1); + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: replace find with std::ranges::find +} + +void checkPointer(const std::vector *VecPtr) { + std::find(VecPtr->begin(), VecPtr->cend(), 1); + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: replace find with std::ranges::find + std::find(std::begin(*VecPtr), std::end(*VecPtr), 2); + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: replace find with std::ranges::find + // CHECK-FIXES: std::ranges::find(*VecPtr, 2); +} + +void checkFancyPointer(smart_pointer> VecFancyPointer) { + std::find(VecFancyPointer->begin(), VecFancyPointer->end(), 1); + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: replace find with std::ranges::find + // CHECK-FIXES: std::ranges::find(*VecFancyPointer, 1); + std::find(std::begin(*VecFancyPointer), std::end(*VecFancyPointer), 2); + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: replace find with std::ranges::find + // CHECK-FIXES: std::ranges::find(*VecFancyPointer, 2); +} + +void checkReverseIteration(std::vector Vec, std::vector *PtrVec) { + std::find(Vec.rbegin(), Vec.rend(), 1); + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: replace find with std::ranges::find + // CHECK-FIXES: std::ranges::find(std::ranges::reverse_view(Vec), 1); + std::find(std::rbegin(Vec), std::rend(Vec), 2); + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: replace find with std::ranges::find + // CHECK-FIXES: std::ranges::find(std::ranges::reverse_view(Vec), 2); + std::find(PtrVec->crbegin(), PtrVec->crend(), 1); + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: replace find with std::ranges::find + // CHECK-FIXES: std::ranges::find(std::ranges::reverse_view(*PtrVec), 1); + std::find(std::crbegin(*PtrVec), std::crend(*PtrVec), 2); + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: replace find with std::ranges::find + // CHECK-FIXES: std::ranges::find(std::ranges::reverse_view(*PtrVec), 2); +}