Skip to content

Commit add51e1

Browse files
committed
[clang-tidy] add new check readability-use-anyofallof
Summary: Finds range-based for loops that can be replaced by a call to ``std::any_of`` or ``std::all_of``. In C++ 20 mode, suggests ``std::ranges::any_of`` or ``std::ranges::all_of``. For now, no fixits are produced. Reviewers: aaron.ballman, alexfh, hokein Subscribers: mgorny, xazax.hun, cfe-commits Tags: #clang Differential Revision: https://reviews.llvm.org/D77572
1 parent e6ba0a5 commit add51e1

File tree

9 files changed

+386
-0
lines changed

9 files changed

+386
-0
lines changed

clang-tools-extra/clang-tidy/readability/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,10 @@ add_clang_library(clangTidyReadabilityModule
4242
StringCompareCheck.cpp
4343
UniqueptrDeleteReleaseCheck.cpp
4444
UppercaseLiteralSuffixCheck.cpp
45+
UseAnyOfAllOfCheck.cpp
4546

4647
LINK_LIBS
48+
clangAnalysis
4749
clangAST
4850
clangASTMatchers
4951
clangBasic

clang-tools-extra/clang-tidy/readability/ReadabilityTidyModule.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545
#include "StringCompareCheck.h"
4646
#include "UniqueptrDeleteReleaseCheck.h"
4747
#include "UppercaseLiteralSuffixCheck.h"
48+
#include "UseAnyOfAllOfCheck.h"
4849

4950
namespace clang {
5051
namespace tidy {
@@ -125,6 +126,8 @@ class ReadabilityModule : public ClangTidyModule {
125126
"readability-uniqueptr-delete-release");
126127
CheckFactories.registerCheck<UppercaseLiteralSuffixCheck>(
127128
"readability-uppercase-literal-suffix");
129+
CheckFactories.registerCheck<UseAnyOfAllOfCheck>(
130+
"readability-use-anyofallof");
128131
}
129132
};
130133

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
//===--- UseAnyOfAllOfCheck.cpp - clang-tidy-------------------------------===//
2+
//
3+
// The LLVM Compiler Infrastructure
4+
//
5+
// This file is distributed under the University of Illinois Open Source
6+
// License. See LICENSE.TXT for details.
7+
//
8+
//===----------------------------------------------------------------------===//
9+
10+
#include "UseAnyOfAllOfCheck.h"
11+
#include "clang/AST/ASTContext.h"
12+
#include "clang/ASTMatchers/ASTMatchFinder.h"
13+
#include "clang/Analysis/Analyses/ExprMutationAnalyzer.h"
14+
#include "clang/Frontend/CompilerInstance.h"
15+
16+
using namespace clang::ast_matchers;
17+
18+
namespace clang {
19+
namespace {
20+
/// Matches a Stmt whose parent is a CompoundStmt, and which is directly
21+
/// followed by a Stmt matching the inner matcher.
22+
AST_MATCHER_P(Stmt, nextStmt, ast_matchers::internal::Matcher<Stmt>,
23+
InnerMatcher) {
24+
DynTypedNodeList Parents = Finder->getASTContext().getParents(Node);
25+
if (Parents.size() != 1)
26+
return false;
27+
28+
auto *C = Parents[0].get<CompoundStmt>();
29+
if (!C)
30+
return false;
31+
32+
const auto *I = llvm::find(C->body(), &Node);
33+
assert(I != C->body_end() && "C is parent of Node");
34+
if (++I == C->body_end())
35+
return false; // Node is last statement.
36+
37+
return InnerMatcher.matches(**I, Finder, Builder);
38+
}
39+
} // namespace
40+
41+
namespace tidy {
42+
namespace readability {
43+
44+
void UseAnyOfAllOfCheck::registerMatchers(MatchFinder *Finder) {
45+
auto returns = [](bool V) {
46+
return returnStmt(hasReturnValue(cxxBoolLiteral(equals(V))));
47+
};
48+
49+
auto returnsButNotTrue =
50+
returnStmt(hasReturnValue(unless(cxxBoolLiteral(equals(true)))));
51+
auto returnsButNotFalse =
52+
returnStmt(hasReturnValue(unless(cxxBoolLiteral(equals(false)))));
53+
54+
Finder->addMatcher(
55+
cxxForRangeStmt(
56+
nextStmt(returns(false).bind("final_return")),
57+
hasBody(allOf(hasDescendant(returns(true)),
58+
unless(anyOf(hasDescendant(breakStmt()),
59+
hasDescendant(gotoStmt()),
60+
hasDescendant(returnsButNotTrue))))))
61+
.bind("any_of_loop"),
62+
this);
63+
64+
Finder->addMatcher(
65+
cxxForRangeStmt(
66+
nextStmt(returns(true).bind("final_return")),
67+
hasBody(allOf(hasDescendant(returns(false)),
68+
unless(anyOf(hasDescendant(breakStmt()),
69+
hasDescendant(gotoStmt()),
70+
hasDescendant(returnsButNotFalse))))))
71+
.bind("all_of_loop"),
72+
this);
73+
}
74+
75+
static bool isViableLoop(const CXXForRangeStmt &S, ASTContext &Context) {
76+
77+
ExprMutationAnalyzer Mutations(*S.getBody(), Context);
78+
if (Mutations.isMutated(S.getLoopVariable()))
79+
return false;
80+
const auto Matches =
81+
match(findAll(declRefExpr().bind("decl_ref")), *S.getBody(), Context);
82+
83+
return llvm::none_of(Matches, [&Mutations](auto &DeclRef) {
84+
// TODO: allow modifications of loop-local variables
85+
return Mutations.isMutated(
86+
DeclRef.template getNodeAs<DeclRefExpr>("decl_ref")->getDecl());
87+
});
88+
}
89+
90+
void UseAnyOfAllOfCheck::check(const MatchFinder::MatchResult &Result) {
91+
StringRef Ranges = getLangOpts().CPlusPlus2a ? "::ranges" : "";
92+
93+
if (const auto *S = Result.Nodes.getNodeAs<CXXForRangeStmt>("any_of_loop")) {
94+
if (!isViableLoop(*S, *Result.Context))
95+
return;
96+
97+
diag(S->getForLoc(), "replace loop by 'std%0::any_of()'") << Ranges;
98+
} else if (const auto *S =
99+
Result.Nodes.getNodeAs<CXXForRangeStmt>("all_of_loop")) {
100+
if (!isViableLoop(*S, *Result.Context))
101+
return;
102+
103+
diag(S->getForLoc(), "replace loop by 'std%0::all_of()'") << Ranges;
104+
}
105+
}
106+
107+
} // namespace readability
108+
} // namespace tidy
109+
} // namespace clang
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
//===--- UseAnyOfAllOfCheck.h - clang-tidy-----------------------*- C++ -*-===//
2+
//
3+
// The LLVM Compiler Infrastructure
4+
//
5+
// This file is distributed under the University of Illinois Open Source
6+
// License. See LICENSE.TXT for details.
7+
//
8+
//===----------------------------------------------------------------------===//
9+
10+
#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_USEALGORITHMCHECK_H
11+
#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_USEALGORITHMCHECK_H
12+
13+
#include "../ClangTidy.h"
14+
#include "../utils/IncludeInserter.h"
15+
16+
namespace clang {
17+
namespace tidy {
18+
namespace readability {
19+
20+
/// Finds ranged-based for loops that can be replaced by a call to std::any_of
21+
/// or std::all_of.
22+
///
23+
/// For the user-facing documentation see:
24+
/// http://clang.llvm.org/extra/clang-tidy/checks/readability-use-anyofallof.html
25+
class UseAnyOfAllOfCheck : public ClangTidyCheck {
26+
public:
27+
using ClangTidyCheck::ClangTidyCheck;
28+
29+
bool isLanguageVersionSupported(const LangOptions &LangOpts) const override {
30+
return LangOpts.CPlusPlus11;
31+
}
32+
33+
void registerMatchers(ast_matchers::MatchFinder *Finder) override;
34+
void check(const ast_matchers::MatchFinder::MatchResult &Result) override;
35+
};
36+
37+
} // namespace readability
38+
} // namespace tidy
39+
} // namespace clang
40+
41+
#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_USEALGORITHMCHECK_H

clang-tools-extra/docs/ReleaseNotes.rst

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,12 @@ New checks
152152
Finds calls to ``NSInvocation`` methods under ARC that don't have proper
153153
argument object lifetimes.
154154

155+
- New :doc:`readability-use-anyofallof
156+
<clang-tidy/checks/readability-use-anyofallof>` check.
157+
158+
Finds range-based for loops that can be replaced by a call to ``std::any_of``
159+
or ``std::all_of``.
160+
155161
New check aliases
156162
^^^^^^^^^^^^^^^^^
157163

clang-tools-extra/docs/clang-tidy/checks/list.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -299,6 +299,7 @@ Clang-Tidy Checks
299299
`readability-string-compare <readability-string-compare.html>`_, "Yes"
300300
`readability-uniqueptr-delete-release <readability-uniqueptr-delete-release.html>`_, "Yes"
301301
`readability-uppercase-literal-suffix <readability-uppercase-literal-suffix.html>`_, "Yes"
302+
`readability-use-anyofallof`_, "No"
302303
`zircon-temporary-objects <zircon-temporary-objects.html>`_,
303304

304305

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
.. title:: clang-tidy - readability-use-anyofallof
2+
3+
readability-use-anyofallof
4+
==========================
5+
6+
Finds range-based for loops that can be replaced by a call to ``std::any_of`` or
7+
``std::all_of``. In C++ 20 mode, suggests ``std::ranges::any_of`` or
8+
``std::ranges::all_of``.
9+
10+
Example:
11+
12+
.. code-block:: c++
13+
14+
bool all_even(std::vector<int> V) {
15+
for (int I : V) {
16+
if (I % 2)
17+
return false;
18+
}
19+
return true;
20+
// Replace loop by
21+
// return std::ranges::all_of(V, [](int I) { return I % 2 == 0; });
22+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
// RUN: %check_clang_tidy -std=c++2a-or-later %s readability-use-anyofallof %t
2+
3+
bool good_any_of() {
4+
int v[] = {1, 2, 3};
5+
// CHECK-MESSAGES: :[[@LINE+1]]:3: warning: replace loop by 'std::ranges::any_of()'
6+
for (int i : v)
7+
if (i)
8+
return true;
9+
return false;
10+
}
11+
12+
bool good_all_of() {
13+
int v[] = {1, 2, 3};
14+
// CHECK-MESSAGES: :[[@LINE+1]]:3: warning: replace loop by 'std::ranges::all_of()'
15+
for (int i : v)
16+
if (i)
17+
return false;
18+
return true;
19+
}

0 commit comments

Comments
 (0)