https://github.com/unterumarmung created 
https://github.com/llvm/llvm-project/pull/196037

Preserve used output iterator results for output algorithm replacements by 
appending .out where the ranges algorithm returns an algorithm result object.

Fix #110223

Assisted by Codex.

>From 62094f15fe5212278c83ac8ed3d1dcfa854e8935 Mon Sep 17 00:00:00 2001
From: Daniil Dudkin <[email protected]>
Date: Wed, 6 May 2026 11:33:15 +0300
Subject: [PATCH] [clang-tidy] `use-ranges`: preserve output results

Preserve used output iterator results for output algorithm replacements by 
appending .out where the ranges algorithm returns an algorithm result object.

Fix #110223

Assisted by Codex.
---
 .../clang-tidy/modernize/UseRangesCheck.cpp   |  46 ++++----
 clang-tools-extra/docs/ReleaseNotes.rst       |   3 +
 .../checks/modernize/use-ranges.rst           |   2 +
 .../modernize/Inputs/use-ranges/fake_std.h    |  59 ++++++++++
 .../checkers/modernize/use-ranges.cpp         | 104 ++++++++++++++++++
 5 files changed, 190 insertions(+), 24 deletions(-)

diff --git a/clang-tools-extra/clang-tidy/modernize/UseRangesCheck.cpp 
b/clang-tools-extra/clang-tidy/modernize/UseRangesCheck.cpp
index e36ce3adeb15f..e25e444cc3819 100644
--- a/clang-tools-extra/clang-tidy/modernize/UseRangesCheck.cpp
+++ b/clang-tools-extra/clang-tidy/modernize/UseRangesCheck.cpp
@@ -27,19 +27,10 @@ static constexpr const char *SingleRangeNames[] = {
     "find_if",
     "find_if_not",
     "adjacent_find",
-    "copy",
-    "copy_if",
-    "copy_backward",
-    "move",
-    "move_backward",
     "fill",
-    "transform",
     "replace",
     "replace_if",
     "generate",
-    "remove_copy",
-    "remove_copy_if",
-    "unique_copy",
     "sample",
     "partition_point",
     "lower_bound",
@@ -53,7 +44,6 @@ static constexpr const char *SingleRangeNames[] = {
     "next_permutation",
     "prev_permutation",
     "reverse",
-    "reverse_copy",
     "shift_left",
     "shift_right",
     "is_partitioned",
@@ -67,9 +57,7 @@ static constexpr const char *SingleRangeNames[] = {
     "max_element",
     "min_element",
     "minmax_element",
-    "uninitialized_copy",
     "uninitialized_fill",
-    "uninitialized_move",
     "uninitialized_default_construct",
     "uninitialized_value_construct",
     "destroy",
@@ -78,27 +66,32 @@ static constexpr const char *SingleRangeNames[] = {
 static constexpr const char *SingleRangeBeginResultNames[] = {
     "remove", "remove_if", "stable_partition", "partition", "unique"};
 
+static constexpr const char *SingleRangeOutResultNames[] = {
+    "copy",          "copy_if",     "copy_backward",      "move",
+    "move_backward", "remove_copy", "remove_copy_if",     "reverse_copy",
+    "transform",     "unique_copy", "uninitialized_copy", "uninitialized_move",
+};
+
 static constexpr const char *TwoRangeNames[] = {
-    "equal",
-    "mismatch",
+    "equal",    "mismatch", "includes",       "lexicographical_compare",
+    "find_end", "search",   "is_permutation",
+};
+
+static constexpr const char *TwoRangeOutResultNames[] = {
+    "merge",
     "partial_sort_copy",
-    "includes",
-    "set_union",
-    "set_intersection",
     "set_difference",
+    "set_intersection",
     "set_symmetric_difference",
-    "merge",
-    "lexicographical_compare",
-    "find_end",
-    "search",
-    "is_permutation",
+    "set_union",
 };
 
-static constexpr const char *SinglePivotRangeNames[] = {"rotate_copy",
-                                                        "inplace_merge"};
+static constexpr const char *SinglePivotRangeNames[] = {"inplace_merge"};
 
 static constexpr const char *SinglePivotRangeBeginResultNames[] = {"rotate"};
 
+static constexpr const char *SinglePivotRangeOutResultNames[] = 
{"rotate_copy"};
+
 namespace {
 class StdReplacer : public utils::UseRangesCheck::Replacer {
 public:
@@ -164,6 +157,8 @@ utils::UseRangesCheck::ReplacerMap 
UseRangesCheck::getReplacerMap() const {
   const ResultPolicy DefaultPolicy;
   const ResultPolicy BeginResultPolicy = {
       PolicyKind::AppendAccessorForUsedResult, ".begin()"};
+  const ResultPolicy OutResultPolicy = 
{PolicyKind::AppendAccessorForUsedResult,
+                                        ".out"};
 
   struct AlgorithmGroup {
     ArrayRef<Signature> Signatures;
@@ -173,9 +168,12 @@ utils::UseRangesCheck::ReplacerMap 
UseRangesCheck::getReplacerMap() const {
   const AlgorithmGroup AlgorithmNames[] = {
       {SingleRangeFunc, SingleRangeNames, DefaultPolicy},
       {SingleRangeFunc, SingleRangeBeginResultNames, BeginResultPolicy},
+      {SingleRangeFunc, SingleRangeOutResultNames, OutResultPolicy},
       {TwoRangeFunc, TwoRangeNames, DefaultPolicy},
+      {TwoRangeFunc, TwoRangeOutResultNames, OutResultPolicy},
       {SinglePivotFunc, SinglePivotRangeNames, DefaultPolicy},
       {SinglePivotFunc, SinglePivotRangeBeginResultNames, BeginResultPolicy},
+      {SinglePivotFunc, SinglePivotRangeOutResultNames, OutResultPolicy},
   };
   SmallString<64> Buff;
   for (const auto &[Signatures, Values, Policy] : AlgorithmNames) {
diff --git a/clang-tools-extra/docs/ReleaseNotes.rst 
b/clang-tools-extra/docs/ReleaseNotes.rst
index 9742b71df4e64..2d1eed13dd3ad 100644
--- a/clang-tools-extra/docs/ReleaseNotes.rst
+++ b/clang-tools-extra/docs/ReleaseNotes.rst
@@ -442,6 +442,9 @@ Changes in existing checks
     ``std::remove_if``, ``std::partition``, ``std::stable_partition``, and
     ``std::rotate`` calls with their ``std::ranges`` counterparts.
 
+  - Preserved used output iterator results when replacing output algorithms
+    such as ``std::copy``.
+
 - Improved :doc:`modernize-use-std-format
   <clang-tidy/checks/modernize/use-std-format>` check:
 
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
index 8d1a12490ceb3..6c2c15b0445d2 100644
--- a/clang-tools-extra/docs/clang-tidy/checks/modernize/use-ranges.rst
+++ b/clang-tools-extra/docs/clang-tidy/checks/modernize/use-ranges.rst
@@ -13,6 +13,7 @@ Example
 
   auto Iter1 = std::find(Items.begin(), Items.end(), 0);
   auto NewEnd = std::unique(Items.begin(), Items.end());
+  auto Out = std::copy(Items.begin(), Items.end(), Output);
   auto AreSame = std::equal(Items1.cbegin(), Items1.cend(),
                             std::begin(Items2), std::end(Items2));
 
@@ -23,6 +24,7 @@ Transforms to:
 
   auto Iter1 = std::ranges::find(Items, 0);
   auto NewEnd = std::ranges::unique(Items).begin();
+  auto Out = std::ranges::copy(Items, Output).out;
   auto AreSame = std::ranges::equal(Items1, Items2);
 
 Supported algorithms
diff --git 
a/clang-tools-extra/test/clang-tidy/checkers/modernize/Inputs/use-ranges/fake_std.h
 
b/clang-tools-extra/test/clang-tidy/checkers/modernize/Inputs/use-ranges/fake_std.h
index 23ca215268c0c..0ed44d99c35c0 100644
--- 
a/clang-tools-extra/test/clang-tidy/checkers/modernize/Inputs/use-ranges/fake_std.h
+++ 
b/clang-tools-extra/test/clang-tidy/checkers/modernize/Inputs/use-ranges/fake_std.h
@@ -56,8 +56,27 @@ template <typename Container> constexpr auto crend(const 
Container &Cont) {
 template <class InputIt, class T>
 InputIt find(InputIt first, InputIt last, const T &value);
 
+// Copy
+template <class InputIt, class OutputIt>
+OutputIt copy(InputIt first, InputIt last, OutputIt d_first);
+template <class InputIt, class OutputIt, class UnaryPred>
+OutputIt copy_if(InputIt first, InputIt last, OutputIt d_first,
+                 UnaryPred pred) {
+  return d_first;
+}
+template <class BidirIt1, class BidirIt2>
+BidirIt2 copy_backward(BidirIt1 first, BidirIt1 last, BidirIt2 d_last);
+
+// Move
+template <class InputIt, class OutputIt>
+OutputIt move(InputIt first, InputIt last, OutputIt d_first);
+template <class BidirIt1, class BidirIt2>
+BidirIt2 move_backward(BidirIt1 first, BidirIt1 last, BidirIt2 d_last);
+
 // Reverse
 template <typename Iter> void reverse(Iter begin, Iter end);
+template <class BidirIt, class OutputIt>
+OutputIt reverse_copy(BidirIt first, BidirIt last, OutputIt d_first);
 
 // Includes
 template <class InputIt1, class InputIt2>
@@ -98,6 +117,14 @@ template <class ForwardIt, class UnaryPred>
 ForwardIt remove_if(ForwardIt first, ForwardIt last, UnaryPred pred) {
   return first;
 }
+template <class InputIt, class OutputIt, class T>
+OutputIt remove_copy(InputIt first, InputIt last, OutputIt d_first,
+                     const T &value);
+template <class InputIt, class OutputIt, class UnaryPred>
+OutputIt remove_copy_if(InputIt first, InputIt last, OutputIt d_first,
+                        UnaryPred pred) {
+  return d_first;
+}
 
 template <class ForwardIt, class UnaryPred>
 ForwardIt partition(ForwardIt first, ForwardIt last, UnaryPred pred) {
@@ -110,6 +137,38 @@ BidirIt stable_partition(BidirIt first, BidirIt last, 
UnaryPred pred) {
 
 template <class ForwardIt>
 ForwardIt rotate(ForwardIt first, ForwardIt middle, ForwardIt last);
+template <class InputIt, class OutputIt>
+OutputIt unique_copy(InputIt first, InputIt last, OutputIt d_first);
+template <class InputIt, class OutputIt, class UnaryOp>
+OutputIt transform(InputIt first, InputIt last, OutputIt d_first, UnaryOp op) {
+  return d_first;
+}
+template <class InputIt1, class InputIt2, class OutputIt>
+OutputIt merge(InputIt1 first1, InputIt1 last1, InputIt2 first2,
+               InputIt2 last2, OutputIt d_first);
+template <class InputIt1, class InputIt2, class OutputIt>
+OutputIt set_union(InputIt1 first1, InputIt1 last1, InputIt2 first2,
+                   InputIt2 last2, OutputIt d_first);
+template <class InputIt1, class InputIt2, class OutputIt>
+OutputIt set_intersection(InputIt1 first1, InputIt1 last1, InputIt2 first2,
+                          InputIt2 last2, OutputIt d_first);
+template <class InputIt1, class InputIt2, class OutputIt>
+OutputIt set_difference(InputIt1 first1, InputIt1 last1, InputIt2 first2,
+                        InputIt2 last2, OutputIt d_first);
+template <class InputIt1, class InputIt2, class OutputIt>
+OutputIt set_symmetric_difference(InputIt1 first1, InputIt1 last1,
+                                  InputIt2 first2, InputIt2 last2,
+                                  OutputIt d_first);
+template <class InputIt, class RandomIt>
+RandomIt partial_sort_copy(InputIt first, InputIt last, RandomIt d_first,
+                           RandomIt d_last);
+template <class InputIt, class ForwardIt>
+ForwardIt uninitialized_copy(InputIt first, InputIt last, ForwardIt d_first);
+template <class InputIt, class ForwardIt>
+ForwardIt uninitialized_move(InputIt first, InputIt last, ForwardIt d_first);
+template <class ForwardIt, class OutputIt>
+OutputIt rotate_copy(ForwardIt first, ForwardIt middle, ForwardIt last,
+                     OutputIt d_first);
 } // namespace _V1
 
 } // namespace std
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
index c48db04c483b8..29d847e8a0653 100644
--- a/clang-tools-extra/test/clang-tidy/checkers/modernize/use-ranges.cpp
+++ b/clang-tools-extra/test/clang-tidy/checkers/modernize/use-ranges.cpp
@@ -76,6 +76,110 @@ void Positives() {
   // CHECK-FIXES: auto StablePartitionPoint =
   // CHECK-FIXES-NEXT: std::ranges::stable_partition(I, [](int N) { return N 
== 0; }).begin();
 
+  auto Output = std::copy(I.begin(), I.end(), J.begin());
+  // CHECK-MESSAGES: :[[@LINE-1]]:17: warning: use a ranges version of this 
algorithm
+  // CHECK-FIXES: auto Output = std::ranges::copy(I, J.begin()).out;
+
+  std::copy(I.begin(), I.end(), J.begin());
+  // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: use a ranges version of this 
algorithm
+  // CHECK-FIXES: std::ranges::copy(I, J.begin());
+
+  auto CopyIfOutput =
+      std::copy_if(I.begin(), I.end(), J.begin(), [](int N) { return N == 0; 
});
+  // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: use a ranges version of this 
algorithm
+  // CHECK-FIXES: auto CopyIfOutput =
+  // CHECK-FIXES-NEXT: std::ranges::copy_if(I, J.begin(), [](int N) { return N 
== 0; }).out;
+
+  auto CopyBackwardOutput = std::copy_backward(I.begin(), I.end(), J.end());
+  // CHECK-MESSAGES: :[[@LINE-1]]:29: warning: use a ranges version of this 
algorithm
+  // CHECK-FIXES: auto CopyBackwardOutput = std::ranges::copy_backward(I, 
J.end()).out;
+
+  auto MoveOutput = std::move(I.begin(), I.end(), J.begin());
+  // CHECK-MESSAGES: :[[@LINE-1]]:21: warning: use a ranges version of this 
algorithm
+  // CHECK-FIXES: auto MoveOutput = std::ranges::move(I, J.begin()).out;
+
+  auto MoveBackwardOutput = std::move_backward(I.begin(), I.end(), J.end());
+  // CHECK-MESSAGES: :[[@LINE-1]]:29: warning: use a ranges version of this 
algorithm
+  // CHECK-FIXES: auto MoveBackwardOutput = std::ranges::move_backward(I, 
J.end()).out;
+
+  auto RemoveCopyOutput = std::remove_copy(I.begin(), I.end(), J.begin(), 0);
+  // CHECK-MESSAGES: :[[@LINE-1]]:27: warning: use a ranges version of this 
algorithm
+  // CHECK-FIXES: auto RemoveCopyOutput = std::ranges::remove_copy(I, 
J.begin(), 0).out;
+
+  auto RemoveCopyIfOutput = std::remove_copy_if(
+      I.begin(), I.end(), J.begin(), [](int N) { return N == 0; });
+  // CHECK-MESSAGES: :[[@LINE-2]]:29: warning: use a ranges version of this 
algorithm
+  // CHECK-FIXES: auto RemoveCopyIfOutput = std::ranges::remove_copy_if(
+  // CHECK-FIXES-NEXT: I, J.begin(), [](int N) { return N == 0; }).out;
+
+  auto ReverseCopyOutput = std::reverse_copy(I.begin(), I.end(), J.begin());
+  // CHECK-MESSAGES: :[[@LINE-1]]:28: warning: use a ranges version of this 
algorithm
+  // CHECK-FIXES: auto ReverseCopyOutput = std::ranges::reverse_copy(I, 
J.begin()).out;
+
+  auto TransformOutput =
+      std::transform(I.begin(), I.end(), J.begin(), [](int N) { return N; });
+  // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: use a ranges version of this 
algorithm
+  // CHECK-FIXES: auto TransformOutput =
+  // CHECK-FIXES-NEXT: std::ranges::transform(I, J.begin(), [](int N) { return 
N; }).out;
+
+  auto UniqueCopyOutput = std::unique_copy(I.begin(), I.end(), J.begin());
+  // CHECK-MESSAGES: :[[@LINE-1]]:27: warning: use a ranges version of this 
algorithm
+  // CHECK-FIXES: auto UniqueCopyOutput = std::ranges::unique_copy(I, 
J.begin()).out;
+
+  auto UninitializedCopyOutput =
+      std::uninitialized_copy(I.begin(), I.end(), J.begin());
+  // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: use a ranges version of this 
algorithm
+  // CHECK-FIXES: auto UninitializedCopyOutput =
+  // CHECK-FIXES-NEXT: std::ranges::uninitialized_copy(I, J.begin()).out;
+
+  auto UninitializedMoveOutput =
+      std::uninitialized_move(I.begin(), I.end(), J.begin());
+  // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: use a ranges version of this 
algorithm
+  // CHECK-FIXES: auto UninitializedMoveOutput =
+  // CHECK-FIXES-NEXT: std::ranges::uninitialized_move(I, J.begin()).out;
+
+  auto MergeOutput =
+      std::merge(I.begin(), I.end(), J.begin(), J.end(), I.begin());
+  // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: use a ranges version of this 
algorithm
+  // CHECK-FIXES: auto MergeOutput =
+  // CHECK-FIXES-NEXT: std::ranges::merge(I, J, I.begin()).out;
+
+  auto SetUnionOutput =
+      std::set_union(I.begin(), I.end(), J.begin(), J.end(), I.begin());
+  // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: use a ranges version of this 
algorithm
+  // CHECK-FIXES: auto SetUnionOutput =
+  // CHECK-FIXES-NEXT: std::ranges::set_union(I, J, I.begin()).out;
+
+  auto SetIntersectionOutput =
+      std::set_intersection(I.begin(), I.end(), J.begin(), J.end(), I.begin());
+  // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: use a ranges version of this 
algorithm
+  // CHECK-FIXES: auto SetIntersectionOutput =
+  // CHECK-FIXES-NEXT: std::ranges::set_intersection(I, J, I.begin()).out;
+
+  auto SetDifferenceOutput =
+      std::set_difference(I.begin(), I.end(), J.begin(), J.end(), I.begin());
+  // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: use a ranges version of this 
algorithm
+  // CHECK-FIXES: auto SetDifferenceOutput =
+  // CHECK-FIXES-NEXT: std::ranges::set_difference(I, J, I.begin()).out;
+
+  auto SetSymmetricDifferenceOutput = std::set_symmetric_difference(
+      I.begin(), I.end(), J.begin(), J.end(), I.begin());
+  // CHECK-MESSAGES: :[[@LINE-2]]:39: warning: use a ranges version of this 
algorithm
+  // CHECK-FIXES: auto SetSymmetricDifferenceOutput = 
std::ranges::set_symmetric_difference(
+  // CHECK-FIXES-NEXT: I, J, I.begin()).out;
+
+  auto PartialSortCopyOutput =
+      std::partial_sort_copy(I.begin(), I.end(), J.begin(), J.end());
+  // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: use a ranges version of this 
algorithm
+  // CHECK-FIXES: auto PartialSortCopyOutput =
+  // CHECK-FIXES-NEXT: std::ranges::partial_sort_copy(I, J).out;
+
+  auto RotateCopyOutput =
+      std::rotate_copy(I.begin(), I.begin() + 2, I.end(), J.begin());
+  // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: use a ranges version of this 
algorithm
+  // CHECK-FIXES: auto RotateCopyOutput =
+  // CHECK-FIXES-NEXT: std::ranges::rotate_copy(I, I.begin() + 2, 
J.begin()).out;
+
   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);

_______________________________________________
llvm-branch-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/llvm-branch-commits

Reply via email to