diff --git a/clang-tools-extra/clang-tidy/modernize/LoopConvertCheck.h b/clang-tools-extra/clang-tidy/modernize/LoopConvertCheck.h --- a/clang-tools-extra/clang-tidy/modernize/LoopConvertCheck.h +++ b/clang-tools-extra/clang-tidy/modernize/LoopConvertCheck.h @@ -10,6 +10,7 @@ #define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_LOOP_CONVERT_H #include "../ClangTidyCheck.h" +#include "../utils/IncludeInserter.h" #include "LoopConvertUtils.h" namespace clang { @@ -24,6 +25,8 @@ } void storeOptions(ClangTidyOptions::OptionMap &Opts) override; 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; private: @@ -34,6 +37,27 @@ bool DerefByValue; std::string ContainerString; QualType ElemType; + bool NeedsReverseCall; + }; + + class ReverseRangeDetail { + public: + ReverseRangeDetail(bool UseCxx20IfAvailable, std::string FunctionName, + std::string HeaderName, const LangOptions &LangOpts); + + bool isEnabled() const { return IsEnabled; } + bool shouldIncludeHeader() const { + return isEnabled() && !getHeader().empty(); + } + bool isUsingCxx20() const { return UseCxx20IfAvailable; } + StringRef getFunction() const { return Function; } + StringRef getHeader() const { return Header; } + + private: + bool IsEnabled{false}; + bool UseCxx20IfAvailable; + std::string Function; + std::string Header; }; void getAliasRange(SourceManager &SM, SourceRange &DeclRange); @@ -71,6 +95,9 @@ const unsigned long long MaxCopySize; const Confidence::Level MinConfidence; const VariableNamer::NamingStyle NamingStyle; + std::unique_ptr Inserter; + const utils::IncludeSorter::IncludeStyle IncludeStyle; + const ReverseRangeDetail ReverseRange; }; } // namespace modernize diff --git a/clang-tools-extra/clang-tidy/modernize/LoopConvertCheck.cpp b/clang-tools-extra/clang-tidy/modernize/LoopConvertCheck.cpp --- a/clang-tools-extra/clang-tidy/modernize/LoopConvertCheck.cpp +++ b/clang-tools-extra/clang-tidy/modernize/LoopConvertCheck.cpp @@ -19,6 +19,7 @@ #include "llvm/ADT/StringRef.h" #include "llvm/ADT/StringSwitch.h" #include "llvm/Support/Casting.h" +#include "llvm/Support/WithColor.h" #include #include #include @@ -32,6 +33,7 @@ static const char LoopNameArray[] = "forLoopArray"; static const char LoopNameIterator[] = "forLoopIterator"; +static const char LoopNameReverseIterator[] = "forLoopReverseIterator"; static const char LoopNamePseudoArray[] = "forLoopPseudoArray"; static const char ConditionBoundName[] = "conditionBound"; static const char ConditionVarName[] = "conditionVar"; @@ -43,7 +45,9 @@ static const char EndVarName[] = "endVar"; static const char DerefByValueResultName[] = "derefByValueResult"; static const char DerefByRefResultName[] = "derefByRefResult"; - +static const char MakeReverseRangeFunction[] = "MakeReverseRangeFunction"; +static const char MakeReverseRangeHeader[] = "MakeReverseRangeHeader"; +static const char UseCxx20ReverseRanges[] = "UseCxx20ReverseRanges"; static ArrayRef> getConfidenceMapping() { static constexpr std::pair Mapping[] = { @@ -144,11 +148,17 @@ /// - The iterator variables 'it', 'f', and 'h' are the same. /// - The two containers on which 'begin' and 'end' are called are the same. /// - If the end iterator variable 'g' is defined, it is the same as 'f'. -StatementMatcher makeIteratorLoopMatcher() { +StatementMatcher makeIteratorLoopMatcher(bool IsReverse) { + + auto BeginNameMatcher = IsReverse ? hasAnyName("rbegin", "crbegin") + : hasAnyName("begin", "cbegin"); + + auto EndNameMatcher = + IsReverse ? hasAnyName("rend", "crend") : hasAnyName("end", "cend"); + StatementMatcher BeginCallMatcher = - cxxMemberCallExpr( - argumentCountIs(0), - callee(cxxMethodDecl(anyOf(hasName("begin"), hasName("cbegin"))))) + cxxMemberCallExpr(argumentCountIs(0), + callee(cxxMethodDecl(BeginNameMatcher))) .bind(BeginCallName); DeclarationMatcher InitDeclMatcher = @@ -162,8 +172,7 @@ varDecl(hasInitializer(anything())).bind(EndVarName); StatementMatcher EndCallMatcher = cxxMemberCallExpr( - argumentCountIs(0), - callee(cxxMethodDecl(anyOf(hasName("end"), hasName("cend"))))); + argumentCountIs(0), callee(cxxMethodDecl(EndNameMatcher))); StatementMatcher IteratorBoundMatcher = expr(anyOf(ignoringParenImpCasts( @@ -221,7 +230,7 @@ hasArgument( 0, declRefExpr(to(varDecl(TestDerefReturnsByValue) .bind(IncrementVarName)))))))) - .bind(LoopNameIterator); + .bind(IsReverse ? LoopNameReverseIterator : LoopNameIterator); } /// The matcher used for array-like containers (pseudoarrays). @@ -322,7 +331,7 @@ /// the output parameter isArrow is set to indicate whether the initialization /// is called via . or ->. static const Expr *getContainerFromBeginEndCall(const Expr *Init, bool IsBegin, - bool *IsArrow) { + bool *IsArrow, bool IsReverse) { // FIXME: Maybe allow declaration/initialization outside of the for loop. const auto *TheCall = dyn_cast_or_null(digThroughConstructors(Init)); @@ -333,9 +342,11 @@ if (!Member) return nullptr; StringRef Name = Member->getMemberDecl()->getName(); - StringRef TargetName = IsBegin ? "begin" : "end"; - StringRef ConstTargetName = IsBegin ? "cbegin" : "cend"; - if (Name != TargetName && Name != ConstTargetName) + if (!Name.consume_back(IsBegin ? "begin" : "end")) + return nullptr; + if (IsReverse && !Name.consume_back("r")) + return nullptr; + if (!Name.empty() && !Name.equals("c")) return nullptr; const Expr *SourceExpr = Member->getBase(); @@ -353,18 +364,19 @@ /// must be a member. static const Expr *findContainer(ASTContext *Context, const Expr *BeginExpr, const Expr *EndExpr, - bool *ContainerNeedsDereference) { + bool *ContainerNeedsDereference, + bool IsReverse) { // Now that we know the loop variable and test expression, make sure they are // valid. bool BeginIsArrow = false; bool EndIsArrow = false; - const Expr *BeginContainerExpr = - getContainerFromBeginEndCall(BeginExpr, /*IsBegin=*/true, &BeginIsArrow); + const Expr *BeginContainerExpr = getContainerFromBeginEndCall( + BeginExpr, /*IsBegin=*/true, &BeginIsArrow, IsReverse); if (!BeginContainerExpr) return nullptr; - const Expr *EndContainerExpr = - getContainerFromBeginEndCall(EndExpr, /*IsBegin=*/false, &EndIsArrow); + const Expr *EndContainerExpr = getContainerFromBeginEndCall( + EndExpr, /*IsBegin=*/false, &EndIsArrow, IsReverse); // Disallow loops that try evil things like this (note the dot and arrow): // for (IteratorType It = Obj.begin(), E = Obj->end(); It != E; ++It) { } if (!EndContainerExpr || BeginIsArrow != EndIsArrow || @@ -472,7 +484,10 @@ LoopConvertCheck::RangeDescriptor::RangeDescriptor() : ContainerNeedsDereference(false), DerefByConstRef(false), - DerefByValue(false) {} + DerefByValue(false), NeedsReverseCall(false) {} + +static constexpr StringRef RangesMakeReverse = "std::ranges::reverse_view"; +static constexpr StringRef RangesMakeReverseHeader = "ranges"; LoopConvertCheck::LoopConvertCheck(StringRef Name, ClangTidyContext *Context) : ClangTidyCheck(Name, Context), TUInfo(new TUTrackingInfo), @@ -480,21 +495,46 @@ MinConfidence(Options.get("MinConfidence", getConfidenceMapping(), Confidence::CL_Reasonable)), NamingStyle(Options.get("NamingStyle", getStyleMapping(), - VariableNamer::NS_CamelCase)) {} + VariableNamer::NS_CamelCase)), + IncludeStyle(Options.getLocalOrGlobal("IncludeStyle", + utils::IncludeSorter::getMapping(), + utils::IncludeSorter::IS_LLVM)), + ReverseRange(Options.get(UseCxx20ReverseRanges, true), + Options.get(MakeReverseRangeFunction, ""), + Options.get(MakeReverseRangeHeader, ""), getLangOpts()) {} void LoopConvertCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { Options.store(Opts, "MaxCopySize", std::to_string(MaxCopySize)); Options.store(Opts, "MinConfidence", MinConfidence, getConfidenceMapping()); Options.store(Opts, "NamingStyle", NamingStyle, getStyleMapping()); + Options.store(Opts, "IncludeStyle", IncludeStyle, + utils::IncludeSorter::getMapping()); + Options.store(Opts, UseCxx20ReverseRanges, ReverseRange.isUsingCxx20()); + Options.store(Opts, MakeReverseRangeFunction, ReverseRange.getFunction()); + Options.store(Opts, MakeReverseRangeHeader, ReverseRange.getHeader()); +} + +void LoopConvertCheck::registerPPCallbacks(const SourceManager &SM, + Preprocessor *PP, + Preprocessor *ModuleExpanderPP) { + if (ReverseRange.shouldIncludeHeader()) { + Inserter = std::make_unique(SM, getLangOpts(), + IncludeStyle); + PP->addPPCallbacks(Inserter->CreatePPCallbacks()); + } } void LoopConvertCheck::registerMatchers(MatchFinder *Finder) { Finder->addMatcher(traverse(ast_type_traits::TK_AsIs, makeArrayLoopMatcher()), this); Finder->addMatcher( - traverse(ast_type_traits::TK_AsIs, makeIteratorLoopMatcher()), this); + traverse(ast_type_traits::TK_AsIs, makeIteratorLoopMatcher(false)), this); Finder->addMatcher( traverse(ast_type_traits::TK_AsIs, makePseudoArrayLoopMatcher()), this); + if (ReverseRange.isEnabled()) + Finder->addMatcher( + traverse(ast_type_traits::TK_AsIs, makeIteratorLoopMatcher(true)), + this); } /// Given the range of a single declaration, such as: @@ -648,8 +688,20 @@ StringRef MaybeDereference = Descriptor.ContainerNeedsDereference ? "*" : ""; std::string TypeString = Type.getAsString(getLangOpts()); std::string Range = ("(" + TypeString + " " + VarName + " : " + - MaybeDereference + Descriptor.ContainerString + ")") + (Descriptor.NeedsReverseCall + ? llvm::Twine(ReverseRange.getFunction(), "(") + : llvm::Twine()) + + MaybeDereference + Descriptor.ContainerString + + (Descriptor.NeedsReverseCall ? ")" : "") + ")") .str(); + + if (Descriptor.NeedsReverseCall && ReverseRange.shouldIncludeHeader()) { + StringRef Header = ReverseRange.getHeader(); + if (Optional Insertion = Inserter->CreateIncludeInsertion( + Context->getSourceManager().getFileID(Loop->getBeginLoc()), Header, + /*IsAngled*/ Header == RangesMakeReverseHeader)) + FixIts.push_back(*Insertion); + } FixIts.push_back(FixItHint::CreateReplacement( CharSourceRange::getTokenRange(ParenRange), Range)); diag(Loop->getForLoc(), "use range-based for loop instead") << FixIts; @@ -761,8 +813,9 @@ const UsageResult &Usages, RangeDescriptor &Descriptor) { Descriptor.ContainerString = std::string(getContainerString(Context, Loop, ContainerExpr)); + Descriptor.NeedsReverseCall = FixerKind == LFK_ReverseIterator; - if (FixerKind == LFK_Iterator) + if (FixerKind == LFK_Iterator || FixerKind == LFK_ReverseIterator) getIteratorLoopQualifiers(Context, Nodes, Descriptor); else getArrayLoopQualifiers(Context, Nodes, ContainerExpr, Usages, Descriptor); @@ -791,7 +844,7 @@ return false; // FIXME: Try to put most of this logic inside a matcher. - if (FixerKind == LFK_Iterator) { + if (FixerKind == LFK_Iterator || FixerKind == LFK_ReverseIterator) { QualType InitVarType = InitVar->getType(); QualType CanonicalInitVarType = InitVarType.getCanonicalType(); @@ -830,6 +883,8 @@ FixerKind = LFK_Array; } else if ((Loop = Nodes.getNodeAs(LoopNameIterator))) { FixerKind = LFK_Iterator; + } else if ((Loop = Nodes.getNodeAs(LoopNameReverseIterator))) { + FixerKind = LFK_ReverseIterator; } else { Loop = Nodes.getNodeAs(LoopNamePseudoArray); assert(Loop && "Bad Callback. No for statement"); @@ -857,10 +912,11 @@ // With array loops, the container is often discovered during the // ForLoopIndexUseVisitor traversal. const Expr *ContainerExpr = nullptr; - if (FixerKind == LFK_Iterator) { - ContainerExpr = findContainer(Context, LoopVar->getInit(), - EndVar ? EndVar->getInit() : EndCall, - &Descriptor.ContainerNeedsDereference); + if (FixerKind == LFK_Iterator || FixerKind == LFK_ReverseIterator) { + ContainerExpr = findContainer( + Context, LoopVar->getInit(), EndVar ? EndVar->getInit() : EndCall, + &Descriptor.ContainerNeedsDereference, + /*IsReverse*/ FixerKind == LFK_ReverseIterator); } else if (FixerKind == LFK_PseudoArray) { ContainerExpr = EndCall->getImplicitObjectArgument(); Descriptor.ContainerNeedsDereference = @@ -926,6 +982,32 @@ Finder.aliasFromForInit(), Loop, Descriptor); } +LoopConvertCheck::ReverseRangeDetail::ReverseRangeDetail( + bool UseCxx20IfAvailable, std::string FunctionName, std::string HeaderName, + const LangOptions &LangOpts) + : UseCxx20IfAvailable(UseCxx20IfAvailable), Function(FunctionName), + Header(HeaderName) { + if (Function.empty() && !Header.empty()) { + llvm::WithColor::warning() + << "modernize-loop-convert: '" << MakeReverseRangeHeader + << "' is set but '" << MakeReverseRangeFunction + << "' is not, disabling reverse loop transformation\n"; + IsEnabled = false; + return; + } + if (Function.empty()) { + if (UseCxx20IfAvailable && LangOpts.CPlusPlus20) { + IsEnabled = true; + Function = RangesMakeReverse.str(); + Header = RangesMakeReverseHeader.str(); + } else { + IsEnabled = false; + } + return; + } + IsEnabled = true; +} + } // namespace modernize } // namespace tidy } // namespace clang diff --git a/clang-tools-extra/clang-tidy/modernize/LoopConvertUtils.h b/clang-tools-extra/clang-tidy/modernize/LoopConvertUtils.h --- a/clang-tools-extra/clang-tidy/modernize/LoopConvertUtils.h +++ b/clang-tools-extra/clang-tidy/modernize/LoopConvertUtils.h @@ -26,7 +26,12 @@ namespace tidy { namespace modernize { -enum LoopFixerKind { LFK_Array, LFK_Iterator, LFK_PseudoArray }; +enum LoopFixerKind { + LFK_Array, + LFK_Iterator, + LFK_ReverseIterator, + LFK_PseudoArray +}; /// A map used to walk the AST in reverse: maps child Stmt to parent Stmt. typedef llvm::DenseMap StmtParentMap; 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 @@ -195,7 +195,12 @@ Changes in existing checks ^^^^^^^^^^^^^^^^^^^^^^^^^^ -- Improved :doc:'readability-identifier-naming +- Improved :doc:`modernize-loop-convert + ` check. + + Now able to transform iterator loops using ``rbegin`` and ``rend`` methods. + +- Improved :doc:`readability-identifier-naming ` check. Now able to rename member references in class template definitions with diff --git a/clang-tools-extra/docs/clang-tidy/checks/modernize-loop-convert.rst b/clang-tools-extra/docs/clang-tidy/checks/modernize-loop-convert.rst --- a/clang-tools-extra/docs/clang-tidy/checks/modernize-loop-convert.rst +++ b/clang-tools-extra/docs/clang-tidy/checks/modernize-loop-convert.rst @@ -118,6 +118,41 @@ for (auto & elem : v) cout << elem; +Reverse Iterator Support +------------------------ + +The converter is also capable of transforming iterator loops which use +``rbegin`` and ``rend`` for looping backwards over a container. Out of the box +this will automatically happen in C++20 mode using the ``ranges`` library, +however the check can be configured to work without C++20 by specifying a +function to reverse a range and optionally the header file where that function +lives. + +.. option:: UseCxx20ReverseRanges + + When set to true convert loops when in C++20 or later mode using + ``std::ranges::reverse_view``. + Default value is ``true``. + +.. option:: MakeReverseRangeFunction + + Specify the function used to reverse an iterator pair, the function should + accept a class with ``rbegin`` and ``rend`` methods and return a + class with ``begin`` and ``end`` methods methods that call the ``rbegin`` and + ``rend`` methods respectively. Common examples are ``ranges::reverse_view`` + and ``llvm::reverse``. + Default value is an empty string. + +.. option:: MakeReverseRangeHeader + + Specifies the header file where :option:`MakeReverseRangeFunction` is + declared. For the previous examples this option would be set to + ``range/v3/view/reverse.hpp`` and ``llvm/ADT/STLExtras.h`` respectively. + If this is an empty string and :option:`MakeReverseRangeFunction` is set, + the check will proceed on the assumption that the function is already + available in the translation unit. + Default value is an empty string. + Limitations ----------- diff --git a/clang-tools-extra/test/clang-tidy/checkers/modernize-loop-convert-reverse.cpp b/clang-tools-extra/test/clang-tidy/checkers/modernize-loop-convert-reverse.cpp new file mode 100644 --- /dev/null +++ b/clang-tools-extra/test/clang-tidy/checkers/modernize-loop-convert-reverse.cpp @@ -0,0 +1,120 @@ +// RUN: %check_clang_tidy -std=c++20 -check-suffix=RANGES %s modernize-loop-convert %t + +// RUN: %check_clang_tidy -check-suffix=CUSTOM %s modernize-loop-convert %t -- \ +// RUN: -config="{CheckOptions: [ \ +// RUN: {key: modernize-loop-convert.MakeReverseRangeFunction, value: 'llvm::reverse'}, \ +// RUN: {key: modernize-loop-convert.MakeReverseRangeHeader, value: 'llvm/ADT/STLExtras.h'}]}" + +// Ensure the check doesn't transform reverse loops when not in c++20 mode or +// when UseCxx20ReverseRanges has been disabled +// RUN: clang-tidy %s -checks=-*,modernize-loop-convert -- -std=c++17 | count 0 + +// RUN: clang-tidy %s -checks=-*,modernize-loop-convert -config="{CheckOptions: \ +// RUN: [{key: modernize-loop-convert.UseCxx20ReverseRanges, value: 'false'}] \ +// RUN: }" -- -std=c++20 | count 0 + +// RUN: %check_clang_tidy -check-suffix=CUSTOM-NO-HEADER %s modernize-loop-convert %t -- \ +// RUN: -config="{CheckOptions: [ \ +// RUN: {key: modernize-loop-convert.MakeReverseRangeFunction, value: 'globalReverse'}]}" + +// Ensure we get a warning if we only supply one of the required reverse range arguments. +// RUN: %check_clang_tidy -check-suffix=BAD-CUSTOM %s modernize-loop-convert %t -- \ +// RUN: -config="{CheckOptions: [ \ +// RUN: {key: modernize-loop-convert.MakeReverseRangeHeader, value: 'ranges/v3/views/reverse.hpp'}]}" + +// CHECK-MESSAGES-BAD-CUSTOM: warning: modernize-loop-convert: 'MakeReverseRangeHeader' is set but 'MakeReverseRangeFunction' is not, disabling reverse loop transformation + +// Make sure appropiate headers are included +// CHECK-FIXES-RANGES: #include +// CHECK-FIXES-CUSTOM: #include "llvm/ADT/STLExtras.h" + +// Make sure no header is included in this example +// CHECK-FIXES-CUSTOM-NO-HEADER-NOT: #include + +template +struct reversable { + using iterator = T *; + using const_iterator = const T *; + + iterator begin(); + iterator end(); + iterator rbegin(); + iterator rend(); + + const_iterator begin() const; + const_iterator end() const; + const_iterator rbegin() const; + const_iterator rend() const; + + const_iterator cbegin() const; + const_iterator cend() const; + const_iterator crbegin() const; + const_iterator crend() const; +}; + +template +void observe(const T &); +template +void mutate(T &); + +void constContainer(const reversable &Numbers) { + // CHECK-MESSAGES-RANGES: :[[@LINE+3]]:3: warning: use range-based for loop instead + // CHECK-MESSAGES-CUSTOM: :[[@LINE+2]]:3: warning: use range-based for loop instead + // CHECK-MESSAGES-CUSTOM-NO-HEADER: :[[@LINE+1]]:3: warning: use range-based for loop instead + for (auto I = Numbers.rbegin(), E = Numbers.rend(); I != E; ++I) { + observe(*I); + // CHECK-FIXES-RANGES: for (int Number : std::ranges::reverse_view(Numbers)) { + // CHECK-FIXES-RANGES-NEXT: observe(Number); + // CHECK-FIXES-CUSTOM: for (int Number : llvm::reverse(Numbers)) { + // CHECK-FIXES-CUSTOM-NEXT: observe(Number); + // CHECK-FIXES-CUSTOM-NO-HEADER: for (int Number : globalReverse(Numbers)) { + // CHECK-FIXES-CUSTOM-NO-HEADER-NEXT: observe(Number); + } + // CHECK-MESSAGES-RANGES: :[[@LINE+3]]:3: warning: use range-based for loop instead + // CHECK-MESSAGES-CUSTOM: :[[@LINE+2]]:3: warning: use range-based for loop instead + // CHECK-MESSAGES-CUSTOM-NO-HEADER: :[[@LINE+1]]:3: warning: use range-based for loop instead + for (auto I = Numbers.crbegin(), E = Numbers.crend(); I != E; ++I) { + observe(*I); + // CHECK-FIXES-RANGES: for (int Number : std::ranges::reverse_view(Numbers)) { + // CHECK-FIXES-RANGES-NEXT: observe(Number); + // CHECK-FIXES-CUSTOM: for (int Number : llvm::reverse(Numbers)) { + // CHECK-FIXES-CUSTOM-NEXT: observe(Number); + // CHECK-FIXES-CUSTOM-NO-HEADER: for (int Number : globalReverse(Numbers)) { + // CHECK-FIXES-CUSTOM-NO-HEADER-NEXT: observe(Number); + } + + // Ensure these bad loops aren't transformed. + for (auto I = Numbers.rbegin(), E = Numbers.end(); I != E; ++I) { + observe(*I); + } + for (auto I = Numbers.begin(), E = Numbers.rend(); I != E; ++I) { + observe(*I); + } +} + +void nonConstContainer(reversable &Numbers) { + // CHECK-MESSAGES-RANGES: :[[@LINE+3]]:3: warning: use range-based for loop instead + // CHECK-MESSAGES-CUSTOM: :[[@LINE+2]]:3: warning: use range-based for loop instead + // CHECK-MESSAGES-CUSTOM-NO-HEADER: :[[@LINE+1]]:3: warning: use range-based for loop instead + for (auto I = Numbers.rbegin(), E = Numbers.rend(); I != E; ++I) { + mutate(*I); + // CHECK-FIXES-RANGES: for (int & Number : std::ranges::reverse_view(Numbers)) { + // CHECK-FIXES-RANGES-NEXT: mutate(Number); + // CHECK-FIXES-CUSTOM: for (int & Number : llvm::reverse(Numbers)) { + // CHECK-FIXES-CUSTOM-NEXT: mutate(Number); + // CHECK-FIXES-CUSTOM-NO-HEADER: for (int & Number : globalReverse(Numbers)) { + // CHECK-FIXES-CUSTOM-NO-HEADER-NEXT: mutate(Number); + } + // CHECK-MESSAGES-RANGES: :[[@LINE+3]]:3: warning: use range-based for loop instead + // CHECK-MESSAGES-CUSTOM: :[[@LINE+2]]:3: warning: use range-based for loop instead + // CHECK-MESSAGES-CUSTOM-NO-HEADER: :[[@LINE+1]]:3: warning: use range-based for loop instead + for (auto I = Numbers.crbegin(), E = Numbers.crend(); I != E; ++I) { + observe(*I); + // CHECK-FIXES-RANGES: for (int Number : std::ranges::reverse_view(Numbers)) { + // CHECK-FIXES-RANGES-NEXT: observe(Number); + // CHECK-FIXES-CUSTOM: for (int Number : llvm::reverse(Numbers)) { + // CHECK-FIXES-CUSTOM-NEXT: observe(Number); + // CHECK-FIXES-CUSTOM-NO-HEADER: for (int Number : globalReverse(Numbers)) { + // CHECK-FIXES-CUSTOM-NO-HEADER-NEXT: observe(Number); + } +}