Skip to content

Commit 8a548bc

Browse files
committed
[clang-tidy] modernize-loop-convert reverse iteration support
Enables support for transforming loops of the form ``` for (auto I = Cont.rbegin(), E = Cont.rend(); I != E;++I) ``` This is done automatically in C++20 mode using `std::ranges::reverse_view` but there are options to specify a different function to reverse iterator over a container. This is the first step, down the line I'd like to possibly extend this support for array based loops ``` for (unsigned I = Arr.size() - 1;I >=0;--I) Arr[I]... ``` Currently if you pass a reversing function with no header in the options it will just assume that the function exists, however as we have the ASTContext it may be as wise to check before applying, or at least lower the confidence level if we can't find it. Reviewed By: alexfh Differential Revision: https://reviews.llvm.org/D82089
1 parent bcb7b87 commit 8a548bc

File tree

6 files changed

+290
-30
lines changed

6 files changed

+290
-30
lines changed

clang-tools-extra/clang-tidy/modernize/LoopConvertCheck.cpp

Lines changed: 110 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919
#include "llvm/ADT/StringRef.h"
2020
#include "llvm/ADT/StringSwitch.h"
2121
#include "llvm/Support/Casting.h"
22+
#include "llvm/Support/WithColor.h"
23+
#include "llvm/Support/raw_ostream.h"
2224
#include <cassert>
2325
#include <cstring>
2426
#include <utility>
@@ -57,6 +59,7 @@ namespace modernize {
5759

5860
static const char LoopNameArray[] = "forLoopArray";
5961
static const char LoopNameIterator[] = "forLoopIterator";
62+
static const char LoopNameReverseIterator[] = "forLoopReverseIterator";
6063
static const char LoopNamePseudoArray[] = "forLoopPseudoArray";
6164
static const char ConditionBoundName[] = "conditionBound";
6265
static const char ConditionVarName[] = "conditionVar";
@@ -68,7 +71,6 @@ static const char ConditionEndVarName[] = "conditionEndVar";
6871
static const char EndVarName[] = "endVar";
6972
static const char DerefByValueResultName[] = "derefByValueResult";
7073
static const char DerefByRefResultName[] = "derefByRefResult";
71-
7274
// shared matchers
7375
static const TypeMatcher AnyType() { return anything(); }
7476

@@ -150,10 +152,17 @@ StatementMatcher makeArrayLoopMatcher() {
150152
/// - The iterator variables 'it', 'f', and 'h' are the same.
151153
/// - The two containers on which 'begin' and 'end' are called are the same.
152154
/// - If the end iterator variable 'g' is defined, it is the same as 'f'.
153-
StatementMatcher makeIteratorLoopMatcher() {
155+
StatementMatcher makeIteratorLoopMatcher(bool IsReverse) {
156+
157+
auto BeginNameMatcher = IsReverse ? hasAnyName("rbegin", "crbegin")
158+
: hasAnyName("begin", "cbegin");
159+
160+
auto EndNameMatcher =
161+
IsReverse ? hasAnyName("rend", "crend") : hasAnyName("end", "cend");
162+
154163
StatementMatcher BeginCallMatcher =
155164
cxxMemberCallExpr(argumentCountIs(0),
156-
callee(cxxMethodDecl(hasAnyName("begin", "cbegin"))))
165+
callee(cxxMethodDecl(BeginNameMatcher)))
157166
.bind(BeginCallName);
158167

159168
DeclarationMatcher InitDeclMatcher =
@@ -167,7 +176,7 @@ StatementMatcher makeIteratorLoopMatcher() {
167176
varDecl(hasInitializer(anything())).bind(EndVarName);
168177

169178
StatementMatcher EndCallMatcher = cxxMemberCallExpr(
170-
argumentCountIs(0), callee(cxxMethodDecl(hasAnyName("end", "cend"))));
179+
argumentCountIs(0), callee(cxxMethodDecl(EndNameMatcher)));
171180

172181
StatementMatcher IteratorBoundMatcher =
173182
expr(anyOf(ignoringParenImpCasts(
@@ -225,7 +234,7 @@ StatementMatcher makeIteratorLoopMatcher() {
225234
hasArgument(
226235
0, declRefExpr(to(varDecl(TestDerefReturnsByValue)
227236
.bind(IncrementVarName))))))))
228-
.bind(LoopNameIterator);
237+
.bind(IsReverse ? LoopNameReverseIterator : LoopNameIterator);
229238
}
230239

231240
/// The matcher used for array-like containers (pseudoarrays).
@@ -325,7 +334,7 @@ StatementMatcher makePseudoArrayLoopMatcher() {
325334
/// the output parameter isArrow is set to indicate whether the initialization
326335
/// is called via . or ->.
327336
static const Expr *getContainerFromBeginEndCall(const Expr *Init, bool IsBegin,
328-
bool *IsArrow) {
337+
bool *IsArrow, bool IsReverse) {
329338
// FIXME: Maybe allow declaration/initialization outside of the for loop.
330339
const auto *TheCall =
331340
dyn_cast_or_null<CXXMemberCallExpr>(digThroughConstructors(Init));
@@ -336,9 +345,11 @@ static const Expr *getContainerFromBeginEndCall(const Expr *Init, bool IsBegin,
336345
if (!Member)
337346
return nullptr;
338347
StringRef Name = Member->getMemberDecl()->getName();
339-
StringRef TargetName = IsBegin ? "begin" : "end";
340-
StringRef ConstTargetName = IsBegin ? "cbegin" : "cend";
341-
if (Name != TargetName && Name != ConstTargetName)
348+
if (!Name.consume_back(IsBegin ? "begin" : "end"))
349+
return nullptr;
350+
if (IsReverse && !Name.consume_back("r"))
351+
return nullptr;
352+
if (!Name.empty() && !Name.equals("c"))
342353
return nullptr;
343354

344355
const Expr *SourceExpr = Member->getBase();
@@ -356,18 +367,19 @@ static const Expr *getContainerFromBeginEndCall(const Expr *Init, bool IsBegin,
356367
/// must be a member.
357368
static const Expr *findContainer(ASTContext *Context, const Expr *BeginExpr,
358369
const Expr *EndExpr,
359-
bool *ContainerNeedsDereference) {
370+
bool *ContainerNeedsDereference,
371+
bool IsReverse) {
360372
// Now that we know the loop variable and test expression, make sure they are
361373
// valid.
362374
bool BeginIsArrow = false;
363375
bool EndIsArrow = false;
364-
const Expr *BeginContainerExpr =
365-
getContainerFromBeginEndCall(BeginExpr, /*IsBegin=*/true, &BeginIsArrow);
376+
const Expr *BeginContainerExpr = getContainerFromBeginEndCall(
377+
BeginExpr, /*IsBegin=*/true, &BeginIsArrow, IsReverse);
366378
if (!BeginContainerExpr)
367379
return nullptr;
368380

369-
const Expr *EndContainerExpr =
370-
getContainerFromBeginEndCall(EndExpr, /*IsBegin=*/false, &EndIsArrow);
381+
const Expr *EndContainerExpr = getContainerFromBeginEndCall(
382+
EndExpr, /*IsBegin=*/false, &EndIsArrow, IsReverse);
371383
// Disallow loops that try evil things like this (note the dot and arrow):
372384
// for (IteratorType It = Obj.begin(), E = Obj->end(); It != E; ++It) { }
373385
if (!EndContainerExpr || BeginIsArrow != EndIsArrow ||
@@ -475,27 +487,59 @@ static bool containerIsConst(const Expr *ContainerExpr, bool Dereference) {
475487

476488
LoopConvertCheck::RangeDescriptor::RangeDescriptor()
477489
: ContainerNeedsDereference(false), DerefByConstRef(false),
478-
DerefByValue(false) {}
490+
DerefByValue(false), NeedsReverseCall(false) {}
479491

480492
LoopConvertCheck::LoopConvertCheck(StringRef Name, ClangTidyContext *Context)
481493
: ClangTidyCheck(Name, Context), TUInfo(new TUTrackingInfo),
482494
MaxCopySize(Options.get("MaxCopySize", 16ULL)),
483495
MinConfidence(Options.get("MinConfidence", Confidence::CL_Reasonable)),
484-
NamingStyle(Options.get("NamingStyle", VariableNamer::NS_CamelCase)) {}
496+
NamingStyle(Options.get("NamingStyle", VariableNamer::NS_CamelCase)),
497+
Inserter(Options.getLocalOrGlobal("IncludeStyle",
498+
utils::IncludeSorter::IS_LLVM)),
499+
UseCxx20IfAvailable(Options.get("UseCxx20ReverseRanges", true)),
500+
ReverseFunction(Options.get("MakeReverseRangeFunction", "")),
501+
ReverseHeader(Options.get("MakeReverseRangeHeader", "")) {
502+
503+
if (ReverseFunction.empty() && !ReverseHeader.empty()) {
504+
llvm::WithColor::warning()
505+
<< "modernize-loop-convert: 'MakeReverseRangeHeader' is set but "
506+
"'MakeReverseRangeFunction' is not, disabling reverse loop "
507+
"transformation\n";
508+
UseReverseRanges = false;
509+
} else if (ReverseFunction.empty()) {
510+
UseReverseRanges = UseCxx20IfAvailable && getLangOpts().CPlusPlus20;
511+
} else {
512+
UseReverseRanges = true;
513+
}
514+
}
485515

486516
void LoopConvertCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
487-
Options.store(Opts, "MaxCopySize", std::to_string(MaxCopySize));
517+
Options.store(Opts, "MaxCopySize", MaxCopySize);
488518
Options.store(Opts, "MinConfidence", MinConfidence);
489519
Options.store(Opts, "NamingStyle", NamingStyle);
520+
Options.store(Opts, "IncludeStyle", Inserter.getStyle());
521+
Options.store(Opts, "UseCxx20ReverseRanges", UseCxx20IfAvailable);
522+
Options.store(Opts, "MakeReverseRangeFunction", ReverseFunction);
523+
Options.store(Opts, "MakeReverseRangeHeader", ReverseHeader);
524+
}
525+
526+
void LoopConvertCheck::registerPPCallbacks(const SourceManager &SM,
527+
Preprocessor *PP,
528+
Preprocessor *ModuleExpanderPP) {
529+
Inserter.registerPreprocessor(PP);
490530
}
491531

492532
void LoopConvertCheck::registerMatchers(MatchFinder *Finder) {
493533
Finder->addMatcher(traverse(ast_type_traits::TK_AsIs, makeArrayLoopMatcher()),
494534
this);
495535
Finder->addMatcher(
496-
traverse(ast_type_traits::TK_AsIs, makeIteratorLoopMatcher()), this);
536+
traverse(ast_type_traits::TK_AsIs, makeIteratorLoopMatcher(false)), this);
497537
Finder->addMatcher(
498538
traverse(ast_type_traits::TK_AsIs, makePseudoArrayLoopMatcher()), this);
539+
if (UseReverseRanges)
540+
Finder->addMatcher(
541+
traverse(ast_type_traits::TK_AsIs, makeIteratorLoopMatcher(true)),
542+
this);
499543
}
500544

501545
/// Given the range of a single declaration, such as:
@@ -646,13 +690,29 @@ void LoopConvertCheck::doConversion(
646690
}
647691
}
648692

649-
StringRef MaybeDereference = Descriptor.ContainerNeedsDereference ? "*" : "";
650-
std::string TypeString = Type.getAsString(getLangOpts());
651-
std::string Range = ("(" + TypeString + " " + VarName + " : " +
652-
MaybeDereference + Descriptor.ContainerString + ")")
653-
.str();
693+
SmallString<128> Range;
694+
llvm::raw_svector_ostream Output(Range);
695+
Output << '(';
696+
Type.print(Output, getLangOpts());
697+
Output << ' ' << VarName << " : ";
698+
if (Descriptor.NeedsReverseCall)
699+
Output << getReverseFunction() << '(';
700+
if (Descriptor.ContainerNeedsDereference)
701+
Output << '*';
702+
Output << Descriptor.ContainerString;
703+
if (Descriptor.NeedsReverseCall)
704+
Output << "))";
705+
else
706+
Output << ')';
654707
FixIts.push_back(FixItHint::CreateReplacement(
655708
CharSourceRange::getTokenRange(ParenRange), Range));
709+
710+
if (Descriptor.NeedsReverseCall && !getReverseHeader().empty()) {
711+
if (Optional<FixItHint> Insertion = Inserter.createIncludeInsertion(
712+
Context->getSourceManager().getFileID(Loop->getBeginLoc()),
713+
getReverseHeader()))
714+
FixIts.push_back(*Insertion);
715+
}
656716
diag(Loop->getForLoc(), "use range-based for loop instead") << FixIts;
657717
TUInfo->getGeneratedDecls().insert(make_pair(Loop, VarName));
658718
}
@@ -762,8 +822,9 @@ void LoopConvertCheck::determineRangeDescriptor(
762822
const UsageResult &Usages, RangeDescriptor &Descriptor) {
763823
Descriptor.ContainerString =
764824
std::string(getContainerString(Context, Loop, ContainerExpr));
825+
Descriptor.NeedsReverseCall = (FixerKind == LFK_ReverseIterator);
765826

766-
if (FixerKind == LFK_Iterator)
827+
if (FixerKind == LFK_Iterator || FixerKind == LFK_ReverseIterator)
767828
getIteratorLoopQualifiers(Context, Nodes, Descriptor);
768829
else
769830
getArrayLoopQualifiers(Context, Nodes, ContainerExpr, Usages, Descriptor);
@@ -792,7 +853,7 @@ bool LoopConvertCheck::isConvertible(ASTContext *Context,
792853
return false;
793854

794855
// FIXME: Try to put most of this logic inside a matcher.
795-
if (FixerKind == LFK_Iterator) {
856+
if (FixerKind == LFK_Iterator || FixerKind == LFK_ReverseIterator) {
796857
QualType InitVarType = InitVar->getType();
797858
QualType CanonicalInitVarType = InitVarType.getCanonicalType();
798859

@@ -831,6 +892,8 @@ void LoopConvertCheck::check(const MatchFinder::MatchResult &Result) {
831892
FixerKind = LFK_Array;
832893
} else if ((Loop = Nodes.getNodeAs<ForStmt>(LoopNameIterator))) {
833894
FixerKind = LFK_Iterator;
895+
} else if ((Loop = Nodes.getNodeAs<ForStmt>(LoopNameReverseIterator))) {
896+
FixerKind = LFK_ReverseIterator;
834897
} else {
835898
Loop = Nodes.getNodeAs<ForStmt>(LoopNamePseudoArray);
836899
assert(Loop && "Bad Callback. No for statement");
@@ -858,10 +921,11 @@ void LoopConvertCheck::check(const MatchFinder::MatchResult &Result) {
858921
// With array loops, the container is often discovered during the
859922
// ForLoopIndexUseVisitor traversal.
860923
const Expr *ContainerExpr = nullptr;
861-
if (FixerKind == LFK_Iterator) {
862-
ContainerExpr = findContainer(Context, LoopVar->getInit(),
863-
EndVar ? EndVar->getInit() : EndCall,
864-
&Descriptor.ContainerNeedsDereference);
924+
if (FixerKind == LFK_Iterator || FixerKind == LFK_ReverseIterator) {
925+
ContainerExpr = findContainer(
926+
Context, LoopVar->getInit(), EndVar ? EndVar->getInit() : EndCall,
927+
&Descriptor.ContainerNeedsDereference,
928+
/*IsReverse=*/FixerKind == LFK_ReverseIterator);
865929
} else if (FixerKind == LFK_PseudoArray) {
866930
ContainerExpr = EndCall->getImplicitObjectArgument();
867931
Descriptor.ContainerNeedsDereference =
@@ -927,6 +991,23 @@ void LoopConvertCheck::check(const MatchFinder::MatchResult &Result) {
927991
Finder.aliasFromForInit(), Loop, Descriptor);
928992
}
929993

994+
llvm::StringRef LoopConvertCheck::getReverseFunction() const {
995+
if (!ReverseFunction.empty())
996+
return ReverseFunction;
997+
if (UseReverseRanges)
998+
return "std::ranges::reverse_view";
999+
return "";
1000+
}
1001+
1002+
llvm::StringRef LoopConvertCheck::getReverseHeader() const {
1003+
if (!ReverseHeader.empty())
1004+
return ReverseHeader;
1005+
if (UseReverseRanges && ReverseFunction.empty()) {
1006+
return "<ranges>";
1007+
}
1008+
return "";
1009+
}
1010+
9301011
} // namespace modernize
9311012
} // namespace tidy
9321013
} // namespace clang

clang-tools-extra/clang-tidy/modernize/LoopConvertCheck.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_LOOP_CONVERT_H
1111

1212
#include "../ClangTidyCheck.h"
13+
#include "../utils/IncludeInserter.h"
1314
#include "LoopConvertUtils.h"
1415

1516
namespace clang {
@@ -24,6 +25,8 @@ class LoopConvertCheck : public ClangTidyCheck {
2425
}
2526
void storeOptions(ClangTidyOptions::OptionMap &Opts) override;
2627
void registerMatchers(ast_matchers::MatchFinder *Finder) override;
28+
void registerPPCallbacks(const SourceManager &SM, Preprocessor *PP,
29+
Preprocessor *ModuleExpanderPP) override;
2730
void check(const ast_matchers::MatchFinder::MatchResult &Result) override;
2831

2932
private:
@@ -34,6 +37,7 @@ class LoopConvertCheck : public ClangTidyCheck {
3437
bool DerefByValue;
3538
std::string ContainerString;
3639
QualType ElemType;
40+
bool NeedsReverseCall;
3741
};
3842

3943
void getAliasRange(SourceManager &SM, SourceRange &DeclRange);
@@ -67,10 +71,18 @@ class LoopConvertCheck : public ClangTidyCheck {
6771
bool isConvertible(ASTContext *Context, const ast_matchers::BoundNodes &Nodes,
6872
const ForStmt *Loop, LoopFixerKind FixerKind);
6973

74+
StringRef getReverseFunction() const;
75+
StringRef getReverseHeader() const;
76+
7077
std::unique_ptr<TUTrackingInfo> TUInfo;
7178
const unsigned long long MaxCopySize;
7279
const Confidence::Level MinConfidence;
7380
const VariableNamer::NamingStyle NamingStyle;
81+
utils::IncludeInserter Inserter;
82+
bool UseReverseRanges;
83+
const bool UseCxx20IfAvailable;
84+
std::string ReverseFunction;
85+
std::string ReverseHeader;
7486
};
7587

7688
} // namespace modernize

clang-tools-extra/clang-tidy/modernize/LoopConvertUtils.h

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,12 @@ namespace clang {
2626
namespace tidy {
2727
namespace modernize {
2828

29-
enum LoopFixerKind { LFK_Array, LFK_Iterator, LFK_PseudoArray };
29+
enum LoopFixerKind {
30+
LFK_Array,
31+
LFK_Iterator,
32+
LFK_ReverseIterator,
33+
LFK_PseudoArray
34+
};
3035

3136
/// A map used to walk the AST in reverse: maps child Stmt to parent Stmt.
3237
typedef llvm::DenseMap<const clang::Stmt *, const clang::Stmt *> StmtParentMap;

clang-tools-extra/docs/ReleaseNotes.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,11 @@ New checks
114114
Changes in existing checks
115115
^^^^^^^^^^^^^^^^^^^^^^^^^^
116116

117+
- Improved :doc:`modernize-loop-convert
118+
<clang-tidy/checks/modernize-loop-convert>` check.
119+
120+
Now able to transform iterator loops using ``rbegin`` and ``rend`` methods.
121+
117122
- Improved :doc:`readability-identifier-naming
118123
<clang-tidy/checks/readability-identifier-naming>` check.
119124

clang-tools-extra/docs/clang-tidy/checks/modernize-loop-convert.rst

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,48 @@ After applying the check with minimum confidence level set to `reasonable` (defa
118118
for (auto & elem : v)
119119
cout << elem;
120120

121+
Reverse Iterator Support
122+
------------------------
123+
124+
The converter is also capable of transforming iterator loops which use
125+
``rbegin`` and ``rend`` for looping backwards over a container. Out of the box
126+
this will automatically happen in C++20 mode using the ``ranges`` library,
127+
however the check can be configured to work without C++20 by specifying a
128+
function to reverse a range and optionally the header file where that function
129+
lives.
130+
131+
.. option:: UseCxx20ReverseRanges
132+
133+
When set to true convert loops when in C++20 or later mode using
134+
``std::ranges::reverse_view``.
135+
Default value is ``true``.
136+
137+
.. option:: MakeReverseRangeFunction
138+
139+
Specify the function used to reverse an iterator pair, the function should
140+
accept a class with ``rbegin`` and ``rend`` methods and return a
141+
class with ``begin`` and ``end`` methods methods that call the ``rbegin`` and
142+
``rend`` methods respectively. Common examples are ``ranges::reverse_view``
143+
and ``llvm::reverse``.
144+
Default value is an empty string.
145+
146+
.. option:: MakeReverseRangeHeader
147+
148+
Specifies the header file where :option:`MakeReverseRangeFunction` is
149+
declared. For the previous examples this option would be set to
150+
``range/v3/view/reverse.hpp`` and ``llvm/ADT/STLExtras.h`` respectively.
151+
If this is an empty string and :option:`MakeReverseRangeFunction` is set,
152+
the check will proceed on the assumption that the function is already
153+
available in the translation unit.
154+
This can be wrapped in angle brackets to signify to add the include as a
155+
system include.
156+
Default value is an empty string.
157+
158+
.. option:: IncludeStyle
159+
160+
A string specifying which include-style is used, `llvm` or `google`. Default
161+
is `llvm`.
162+
121163
Limitations
122164
-----------
123165

0 commit comments

Comments
 (0)