njames93 updated this revision to Diff 271855.
njames93 added a comment.

Added `IncludeInserter` to include the `<ranges>` header.
Added support for reverse iteration controlled by config.
Added support for begin/end calls via pointers.
Made the begin/end call matcher more robust if other checks want to use it down 
the line.
Moved the std lib parts out of the test case into header files.


Repository:
  rG LLVM Github Monorepo

CHANGES SINCE LAST ACTION
  https://reviews.llvm.org/D81923/new/

https://reviews.llvm.org/D81923

Files:
  clang-tools-extra/clang-tidy/modernize/CMakeLists.txt
  clang-tools-extra/clang-tidy/modernize/ModernizeTidyModule.cpp
  clang-tools-extra/clang-tidy/modernize/UseRangesCheck.cpp
  clang-tools-extra/clang-tidy/modernize/UseRangesCheck.h
  clang-tools-extra/docs/ReleaseNotes.rst
  clang-tools-extra/docs/clang-tidy/checks/list.rst
  clang-tools-extra/docs/clang-tidy/checks/modernize-use-ranges.rst
  
clang-tools-extra/test/clang-tidy/checkers/Inputs/modernize-use-ranges/algorithm
  
clang-tools-extra/test/clang-tidy/checkers/Inputs/modernize-use-ranges/execution
  clang-tools-extra/test/clang-tidy/checkers/Inputs/modernize-use-ranges/vector
  clang-tools-extra/test/clang-tidy/checkers/modernize-use-ranges.cpp

Index: clang-tools-extra/test/clang-tidy/checkers/modernize-use-ranges.cpp
===================================================================
--- /dev/null
+++ clang-tools-extra/test/clang-tidy/checkers/modernize-use-ranges.cpp
@@ -0,0 +1,133 @@
+// RUN: %check_clang_tidy -std=c++20 %s modernize-use-ranges %t -- -- -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 <algorithm>
+#include <execution>
+#include <vector>
+// CHECK-FIXES: #include <ranges>
+
+template <typename T>
+class smart_pointer {
+public:
+  T &operator*();
+  T *operator->();
+};
+
+void goodBeginEndCalls(const std::vector<int> &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<int> &Vec,
+                      const std::vector<int> &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<int> &Vec1, std::vector<int> &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<int> &Vec1, const std::vector<int> &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<int> *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<std::vector<int>> 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<int> Vec, std::vector<int> *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);
+}
Index: clang-tools-extra/test/clang-tidy/checkers/Inputs/modernize-use-ranges/vector
===================================================================
--- /dev/null
+++ 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 T>
+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
Index: clang-tools-extra/test/clang-tidy/checkers/Inputs/modernize-use-ranges/execution
===================================================================
--- /dev/null
+++ 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
Index: clang-tools-extra/test/clang-tidy/checkers/Inputs/modernize-use-ranges/algorithm
===================================================================
--- /dev/null
+++ 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 <class Container>
+auto begin(Container &C) -> decltype(C.begin());
+template <class Container>
+auto cbegin(Container &C) -> decltype(C.cbegin());
+template <class Container>
+auto end(Container &C) -> decltype(C.end());
+template <class Container>
+auto cend(Container &C) -> decltype(C.cend());
+template <class Container>
+auto rbegin(Container &C) -> decltype(C.rbegin());
+template <class Container>
+auto crbegin(Container &C) -> decltype(C.crbegin());
+template <class Container>
+auto rend(Container &C) -> decltype(C.rend());
+template <class Container>
+auto crend(Container &C) -> decltype(C.crend());
+
+template <class T, unsigned N>
+T *begin(T (&Array)[N]) noexcept;
+template <class T, unsigned N>
+T *end(T (&Array)[N]) noexcept;
+template <class T, unsigned N>
+T *rbegin(T (&Array)[N]) noexcept;
+template <class T, unsigned N>
+T *rend(T (&Array)[N]) noexcept;
+
+template <class InputIt, class T>
+InputIt find(InputIt First, InputIt Last, const T &Value);
+
+template <class ExecutionPolicy, class ForwardIt, class T>
+ForwardIt find(ExecutionPolicy &&Policy, ForwardIt First, ForwardIt Last, const T &Value);
+
+template <class InputIt1, class InputIt2>
+bool equal(InputIt1 First1, InputIt1 Last1,
+           InputIt2 First2);
+template <class ExecutionPolicy, class ForwardIt1, class ForwardIt2>
+bool equal(ExecutionPolicy &&Policy, ForwardIt1 First1, ForwardIt1 Last1,
+           ForwardIt2 First2);
+template <class InputIt1, class InputIt2>
+bool equal(InputIt1 First1, InputIt1 Last1,
+           InputIt2 First2, InputIt2 Last2);
+template <class ExecutionPolicy, class ForwardIt1, class ForwardIt2>
+bool equal(ExecutionPolicy &&Policy, ForwardIt1 First1, ForwardIt1 Last1,
+           ForwardIt2 First2, ForwardIt2 Last2);
+
+template <class InputIt1, class InputIt2>
+bool includes(InputIt1 First1, InputIt1 Last1,
+              InputIt2 First2, InputIt2 Last2);
+template <class ExecutionPolicy, class ForwardIt1, class ForwardIt2>
+bool includes(ExecutionPolicy &&Policy, ForwardIt1 First1, ForwardIt1 Last1,
+              ForwardIt2 First2, ForwardIt2 Last2);
+
+} // namespace std
+
+#endif // ALGORITHM_H
Index: clang-tools-extra/docs/clang-tidy/checks/modernize-use-ranges.rst
===================================================================
--- /dev/null
+++ clang-tools-extra/docs/clang-tidy/checks/modernize-use-ranges.rst
@@ -0,0 +1,39 @@
+.. 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);
+
Index: clang-tools-extra/docs/clang-tidy/checks/list.rst
===================================================================
--- clang-tools-extra/docs/clang-tidy/checks/list.rst
+++ clang-tools-extra/docs/clang-tidy/checks/list.rst
@@ -233,6 +233,7 @@
    `modernize-use-noexcept <modernize-use-noexcept.html>`_, "Yes"
    `modernize-use-nullptr <modernize-use-nullptr.html>`_, "Yes"
    `modernize-use-override <modernize-use-override.html>`_, "Yes"
+   `modernize-use-ranges <modernize-use-ranges.html>`_, "Yes"
    `modernize-use-trailing-return-type <modernize-use-trailing-return-type.html>`_, "Yes"
    `modernize-use-transparent-functors <modernize-use-transparent-functors.html>`_, "Yes"
    `modernize-use-uncaught-exceptions <modernize-use-uncaught-exceptions.html>`_, "Yes"
Index: clang-tools-extra/docs/ReleaseNotes.rst
===================================================================
--- clang-tools-extra/docs/ReleaseNotes.rst
+++ clang-tools-extra/docs/ReleaseNotes.rst
@@ -142,6 +142,13 @@
   Finds macro expansions of ``DISALLOW_COPY_AND_ASSIGN`` and replaces them with
   a deleted copy constructor and a deleted assignment operator.
 
+- New :doc:`modernize-use-ranges
+  <clang-tidy/checks/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.
+
 - New :doc:`objc-dealloc-in-category
   <clang-tidy/checks/objc-dealloc-in-category>` check.
 
Index: clang-tools-extra/clang-tidy/modernize/UseRangesCheck.h
===================================================================
--- /dev/null
+++ clang-tools-extra/clang-tidy/modernize/UseRangesCheck.h
@@ -0,0 +1,47 @@
+//===--- 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:
+  std::unique_ptr<utils::IncludeInserter> Inserter;
+  const utils::IncludeSorter::IncludeStyle IncludeStyle;
+  const bool HandleReverseRanges;
+};
+
+} // namespace modernize
+} // namespace tidy
+} // namespace clang
+
+#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_USERANGESCHECK_H
Index: clang-tools-extra/clang-tidy/modernize/UseRangesCheck.cpp
===================================================================
--- /dev/null
+++ clang-tools-extra/clang-tidy/modernize/UseRangesCheck.cpp
@@ -0,0 +1,301 @@
+//===--- 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<Expr>
+matchSingleCall(std::string BindName,
+                internal::Matcher<DeclaratorDecl> DeclMatcher,
+                internal::Matcher<FunctionDecl> NameMatcher,
+                internal::Matcher<CXXMethodDecl> MethodMatcher,
+                PointerMatchMode PointerMode) {
+  internal::Matcher<Expr> Container =
+      anyOf(declRefExpr(to(declaratorDecl(DeclMatcher))),
+            memberExpr(hasDeclaration(declaratorDecl(DeclMatcher))));
+
+  auto DerefMatcher = [&]() -> internal::Matcher<Expr> {
+    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<Expr> {
+    if (PointerMode == PointerMatchMode::SmartPointers)
+      return anyOf(Container,
+                   cxxOperatorCallExpr(argumentCountIs(1),
+                                       hasOverloadedOperatorName("->"),
+                                       hasArgument(0, Container)));
+    return Container;
+  }();
+
+  return expr(ignoringParenImpCasts(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<CallExpr> 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<CallExpr>
+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<NamedDecl>(ID))
+    return FixItHint::CreateReplacement(Range, Target->getName());
+  Buffer.clear();
+  if (const auto *Target =
+          Result.Nodes.getNodeAs<NamedDecl>((ID + "Ptr").toStringRef(Buffer))) {
+    Buffer.clear();
+    return FixItHint::CreateReplacement(
+        Range, ("*" + Target->getName()).toStringRef(Buffer));
+  }
+  Buffer.clear();
+  if (const auto *Target =
+          Result.Nodes.getNodeAs<NamedDecl>((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<NamedDecl>(
+          (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),
+      IncludeStyle(Options.getLocalOrGlobal("IncludeStyle",
+                                            utils::IncludeSorter::getMapping(),
+                                            utils::IncludeSorter::IS_LLVM)),
+      HandleReverseRanges(Options.get("HandleReverseRanges", true)) {}
+
+void UseRangesCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
+  Options.store(Opts, "IncludeStyle", IncludeStyle,
+                utils::IncludeSorter::getMapping());
+  Options.store(Opts, "HandleReverseRanges", HandleReverseRanges);
+}
+
+void UseRangesCheck::registerPPCallbacks(const SourceManager &SM,
+                                         Preprocessor *PP,
+                                         Preprocessor *ModuleExpanderPP) {
+  Inserter =
+      std::make_unique<utils::IncludeInserter>(SM, getLangOpts(), IncludeStyle);
+  PP->addPPCallbacks(Inserter->CreatePPCallbacks());
+}
+
+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<Expr>("Begin");
+  const auto *End = Result.Nodes.getNodeAs<Expr>("End");
+  const auto *Function = Result.Nodes.getNodeAs<FunctionDecl>("Function");
+  const auto *Call = Result.Nodes.getNodeAs<CallExpr>("Call");
+  assert(Begin && End && Function && Call && "Missing bound node");
+
+  const auto *Begin2 = Result.Nodes.getNodeAs<Expr>("SecondBegin");
+  const auto *End2 = Result.Nodes.getNodeAs<Expr>("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
Index: clang-tools-extra/clang-tidy/modernize/ModernizeTidyModule.cpp
===================================================================
--- clang-tools-extra/clang-tidy/modernize/ModernizeTidyModule.cpp
+++ 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<UseNoexceptCheck>("modernize-use-noexcept");
     CheckFactories.registerCheck<UseNullptrCheck>("modernize-use-nullptr");
     CheckFactories.registerCheck<UseOverrideCheck>("modernize-use-override");
+    CheckFactories.registerCheck<UseRangesCheck>("modernize-use-ranges");
     CheckFactories.registerCheck<UseTrailingReturnTypeCheck>(
         "modernize-use-trailing-return-type");
     CheckFactories.registerCheck<UseTransparentFunctorsCheck>(
Index: clang-tools-extra/clang-tidy/modernize/CMakeLists.txt
===================================================================
--- clang-tools-extra/clang-tidy/modernize/CMakeLists.txt
+++ 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
_______________________________________________
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to