njames93 updated this revision to Diff 271191.
njames93 marked 4 inline comments as done.
njames93 added a comment.

Tweaked documentation


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/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,156 @@
+// RUN: %check_clang_tidy -std=c++20 %s modernize-use-ranges %t
+
+// 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 --extra-arg=-std=c++17
+
+namespace std {
+
+class sequenced_policy {};
+class parallel_policy {};
+class parallel_unsequenced_policy {};
+class unsequenced_policy {};
+
+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 T, unsigned N>
+T *begin(T (&Array)[N]) noexcept;
+
+template <class T, unsigned N>
+T *end(T (&Array)[N]) noexcept;
+
+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;
+};
+
+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
+
+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::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::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::sequenced_policy(), Vec1.begin(), Vec1.end(), Vec2.begin());
+  // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: replace equal with std::ranges::equal
+  std::equal(std::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::sequenced_policy(), Vec1, Vec2.begin());
+  // CHECK-FIXES-NEXT: //
+  // CHECK-FIXES-NEXT: std::ranges::equal(std::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::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::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
+  // CHECK-FIXES:  std::ranges::find(Array, 1);
+}
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,40 @@
+//===--- 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 "clang/Basic/LangOptions.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)
+      : ClangTidyCheck(Name, Context) {}
+  bool isLanguageVersionSupported(const LangOptions &LangOpts) const override {
+    return LangOpts.CPlusPlus20;
+  }
+  void registerMatchers(ast_matchers::MatchFinder *Finder) override;
+  void check(const ast_matchers::MatchFinder::MatchResult &Result) override;
+};
+
+} // 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,143 @@
+//===--- 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/ASTMatchers/ASTMatchers.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",
+};
+
+static internal::Matcher<CallExpr> matchBeginAndEndCall(unsigned ArgIndex,
+                                                        StringRef ID = "") {
+  std::string Range = (ID + "Range").str();
+
+  auto MatchCallTo = [](std::string BindName, DeclarationMatcher DeclMatcher,
+                        auto NameMatcher, auto MethodMatcher) {
+    auto Container = anyOf(declRefExpr(to(DeclMatcher)),
+                           memberExpr(hasDeclaration(DeclMatcher)));
+    return expr(ignoringParenImpCasts(anyOf(
+                    callExpr(
+                        callee(functionDecl(NameMatcher, parameterCountIs(1))),
+                        hasArgument(0, Container)),
+                    cxxMemberCallExpr(on(Container),
+                                      callee(cxxMethodDecl(MethodMatcher))))))
+        .bind(BindName);
+  };
+  return allOf(
+      hasArgument(ArgIndex,
+                  MatchCallTo((ID + "Begin").str(), namedDecl().bind(Range),
+                              hasAnyName("::std::begin", "::std::cbegin"),
+                              hasAnyName("begin", "cbegin"))),
+      hasArgument(ArgIndex + 1,
+                  MatchCallTo((ID + "End").str(), equalsBoundNode(Range),
+                              hasAnyName("::std::end", "::std::cend"),
+                              hasAnyName("end", "cend"))));
+}
+
+void UseRangesCheck::registerMatchers(MatchFinder *Finder) {
+  // Match at first or second arg incase there is an ExecutionPolicy at first
+  // arg.
+  Finder->addMatcher(
+      callExpr(
+          callee(
+              functionDecl(hasAnyName(SingleRangeFunctions)).bind("Function")),
+          anyOf(matchBeginAndEndCall(0), matchBeginAndEndCall(1)))
+          .bind("Call"),
+      this);
+  Finder->addMatcher(
+      callExpr(
+          callee(functionDecl(hasAnyName(DualRangeFunctions)).bind("Function")),
+          anyOf(
+              allOf(matchBeginAndEndCall(0), matchBeginAndEndCall(2, "Second")),
+              allOf(matchBeginAndEndCall(1),
+                    matchBeginAndEndCall(3, "Second"))))
+          .bind("Call"),
+      this);
+  Finder->addMatcher(
+      callExpr(callee(functionDecl(hasAnyName(MaybeDualRangeFunctions))
+                          .bind("Function")),
+               anyOf(matchBeginAndEndCall(0), matchBeginAndEndCall(1)),
+               optionally(anyOf(matchBeginAndEndCall(2, "Second"),
+                                matchBeginAndEndCall(3, "Second"))))
+          .bind("Call"),
+      this);
+}
+
+void UseRangesCheck::check(const MatchFinder::MatchResult &Result) {
+  const auto *Target = Result.Nodes.getNodeAs<NamedDecl>("Range");
+  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(Target && Begin && End && Function && Call && "Missing bound node");
+
+  const auto *Target2 = Result.Nodes.getNodeAs<NamedDecl>("SecondRange");
+  const auto *Begin2 = Result.Nodes.getNodeAs<Expr>("SecondBegin");
+  const auto *End2 = Result.Nodes.getNodeAs<Expr>("SecondEnd");
+  assert(((Target2 && Begin2 && End2) || (!Target2 && !Begin2 && !End2)) &&
+         "Missing secondary bound nodes");
+
+  DiagnosticBuilder Diag =
+      diag(Call->getBeginLoc(), "replace %0 with std::ranges::%0");
+  Diag << Function->getName()
+       << FixItHint::CreateReplacement(
+              {Call->getBeginLoc(), Call->getCallee()->getEndLoc()},
+              ("std::ranges::" + Function->getName()).str())
+       << FixItHint::CreateReplacement({Begin->getBeginLoc(), End->getEndLoc()},
+                                       Target->getName());
+  if (Target2) // Two ranges argument function.
+    Diag << FixItHint::CreateReplacement(
+        {Begin2->getBeginLoc(), End2->getEndLoc()}, Target2->getName());
+}
+
+} // 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