Handle init statements in readability-else-after-return

Adds a new ASTMatcher condition called 'hasInitStatement()' that matches if,
switch and range-for statements with an initializer. Reworked clang-tidy
readability-else-after-return to handle variables in the if condition or init
statements in c++17 ifs. Also checks if removing the else would affect object
lifetimes in the else branch.

Fixes PR44364.
This commit is contained in:
Nathan James 2020-01-02 13:37:41 -05:00 committed by Aaron Ballman
parent a81cb1b8bf
commit ec3d8e61b5
8 changed files with 525 additions and 56 deletions

View File

@ -10,6 +10,7 @@
#include "clang/AST/ASTContext.h"
#include "clang/ASTMatchers/ASTMatchFinder.h"
#include "clang/Tooling/FixIt.h"
#include "llvm/ADT/SmallVector.h"
using namespace clang::ast_matchers;
@ -17,45 +18,226 @@ namespace clang {
namespace tidy {
namespace readability {
namespace {
static const char ReturnStr[] = "return";
static const char ContinueStr[] = "continue";
static const char BreakStr[] = "break";
static const char ThrowStr[] = "throw";
static const char WarningMessage[] = "do not use 'else' after '%0'";
static const char WarnOnUnfixableStr[] = "WarnOnUnfixable";
const DeclRefExpr *findUsage(const Stmt *Node, int64_t DeclIdentifier) {
if (const auto *DeclRef = dyn_cast<DeclRefExpr>(Node)) {
if (DeclRef->getDecl()->getID() == DeclIdentifier) {
return DeclRef;
}
} else {
for (const Stmt *ChildNode : Node->children()) {
if (const DeclRefExpr *Result = findUsage(ChildNode, DeclIdentifier)) {
return Result;
}
}
}
return nullptr;
}
const DeclRefExpr *
findUsageRange(const Stmt *Node,
const llvm::iterator_range<int64_t *> &DeclIdentifiers) {
if (const auto *DeclRef = dyn_cast<DeclRefExpr>(Node)) {
if (llvm::is_contained(DeclIdentifiers, DeclRef->getDecl()->getID())) {
return DeclRef;
}
} else {
for (const Stmt *ChildNode : Node->children()) {
if (const DeclRefExpr *Result =
findUsageRange(ChildNode, DeclIdentifiers)) {
return Result;
}
}
}
return nullptr;
}
const DeclRefExpr *checkInitDeclUsageInElse(const IfStmt *If) {
const auto *InitDeclStmt = dyn_cast_or_null<DeclStmt>(If->getInit());
if (!InitDeclStmt)
return nullptr;
if (InitDeclStmt->isSingleDecl()) {
const Decl *InitDecl = InitDeclStmt->getSingleDecl();
assert(isa<VarDecl>(InitDecl) && "SingleDecl must be a VarDecl");
return findUsage(If->getElse(), InitDecl->getID());
}
llvm::SmallVector<int64_t, 4> DeclIdentifiers;
for (const Decl *ChildDecl : InitDeclStmt->decls()) {
assert(isa<VarDecl>(ChildDecl) && "Init Decls must be a VarDecl");
DeclIdentifiers.push_back(ChildDecl->getID());
}
return findUsageRange(If->getElse(), DeclIdentifiers);
}
const DeclRefExpr *checkConditionVarUsageInElse(const IfStmt *If) {
const VarDecl *CondVar = If->getConditionVariable();
return CondVar != nullptr ? findUsage(If->getElse(), CondVar->getID())
: nullptr;
}
bool containsDeclInScope(const Stmt *Node) {
if (isa<DeclStmt>(Node)) {
return true;
}
if (const auto *Compound = dyn_cast<CompoundStmt>(Node)) {
return llvm::any_of(Compound->body(), [](const Stmt *SubNode) {
return isa<DeclStmt>(SubNode);
});
}
return false;
}
void removeElseAndBrackets(DiagnosticBuilder &Diag, ASTContext &Context,
const Stmt *Else, SourceLocation ElseLoc) {
auto Remap = [&](SourceLocation Loc) {
return Context.getSourceManager().getExpansionLoc(Loc);
};
auto TokLen = [&](SourceLocation Loc) {
return Lexer::MeasureTokenLength(Loc, Context.getSourceManager(),
Context.getLangOpts());
};
if (const auto *CS = dyn_cast<CompoundStmt>(Else)) {
Diag << tooling::fixit::createRemoval(ElseLoc);
SourceLocation LBrace = CS->getLBracLoc();
SourceLocation RBrace = CS->getRBracLoc();
SourceLocation RangeStart =
Remap(LBrace).getLocWithOffset(TokLen(LBrace) + 1);
SourceLocation RangeEnd = Remap(RBrace).getLocWithOffset(-1);
llvm::StringRef Repl = Lexer::getSourceText(
CharSourceRange::getTokenRange(RangeStart, RangeEnd),
Context.getSourceManager(), Context.getLangOpts());
Diag << tooling::fixit::createReplacement(CS->getSourceRange(), Repl);
} else {
SourceLocation ElseExpandedLoc = Remap(ElseLoc);
SourceLocation EndLoc = Remap(Else->getEndLoc());
llvm::StringRef Repl = Lexer::getSourceText(
CharSourceRange::getTokenRange(
ElseExpandedLoc.getLocWithOffset(TokLen(ElseLoc) + 1), EndLoc),
Context.getSourceManager(), Context.getLangOpts());
Diag << tooling::fixit::createReplacement(
SourceRange(ElseExpandedLoc, EndLoc), Repl);
}
}
} // namespace
ElseAfterReturnCheck::ElseAfterReturnCheck(StringRef Name,
ClangTidyContext *Context)
: ClangTidyCheck(Name, Context),
WarnOnUnfixable(Options.get(WarnOnUnfixableStr, true)) {}
void ElseAfterReturnCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
Options.store(Opts, WarnOnUnfixableStr, WarnOnUnfixable);
}
void ElseAfterReturnCheck::registerMatchers(MatchFinder *Finder) {
const auto InterruptsControlFlow =
stmt(anyOf(returnStmt().bind("return"), continueStmt().bind("continue"),
breakStmt().bind("break"),
expr(ignoringImplicit(cxxThrowExpr().bind("throw")))));
stmt(anyOf(returnStmt().bind(ReturnStr), continueStmt().bind(ContinueStr),
breakStmt().bind(BreakStr),
expr(ignoringImplicit(cxxThrowExpr().bind(ThrowStr)))));
Finder->addMatcher(
compoundStmt(forEach(
ifStmt(unless(isConstexpr()),
// FIXME: Explore alternatives for the
// `if (T x = ...) {... return; } else { <use x> }`
// pattern:
// * warn, but don't fix;
// * fix by pulling out the variable declaration out of
// the condition.
unless(hasConditionVariableStatement(anything())),
hasThen(stmt(anyOf(InterruptsControlFlow,
compoundStmt(has(InterruptsControlFlow))))),
hasElse(stmt().bind("else")))
.bind("if"))),
compoundStmt(
forEach(ifStmt(unless(isConstexpr()),
hasThen(stmt(
anyOf(InterruptsControlFlow,
compoundStmt(has(InterruptsControlFlow))))),
hasElse(stmt().bind("else")))
.bind("if")))
.bind("cs"),
this);
}
void ElseAfterReturnCheck::check(const MatchFinder::MatchResult &Result) {
const auto *If = Result.Nodes.getNodeAs<IfStmt>("if");
const auto *Else = Result.Nodes.getNodeAs<Stmt>("else");
const auto *OuterScope = Result.Nodes.getNodeAs<CompoundStmt>("cs");
bool IsLastInScope = OuterScope->body_back() == If;
SourceLocation ElseLoc = If->getElseLoc();
std::string ControlFlowInterruptor;
for (const auto *BindingName : {"return", "continue", "break", "throw"})
if (Result.Nodes.getNodeAs<Stmt>(BindingName))
ControlFlowInterruptor = BindingName;
DiagnosticBuilder Diag = diag(ElseLoc, "do not use 'else' after '%0'")
auto ControlFlowInterruptor = [&]() -> llvm::StringRef {
for (llvm::StringRef BindingName :
{ReturnStr, ContinueStr, BreakStr, ThrowStr})
if (Result.Nodes.getNodeAs<Stmt>(BindingName))
return BindingName;
return {};
}();
if (!IsLastInScope && containsDeclInScope(Else)) {
if (WarnOnUnfixable) {
// Warn, but don't attempt an autofix.
diag(ElseLoc, WarningMessage) << ControlFlowInterruptor;
}
return;
}
if (checkConditionVarUsageInElse(If) != nullptr) {
if (IsLastInScope) {
// If the if statement is the last statement its enclosing statements
// scope, we can pull the decl out of the if statement.
DiagnosticBuilder Diag =
diag(ElseLoc, WarningMessage, clang::DiagnosticIDs::Level::Remark)
<< ControlFlowInterruptor;
if (checkInitDeclUsageInElse(If) != nullptr) {
Diag << tooling::fixit::createReplacement(
SourceRange(If->getIfLoc()),
(tooling::fixit::getText(*If->getInit(), *Result.Context) +
llvm::StringRef("\n"))
.str())
<< tooling::fixit::createRemoval(If->getInit()->getSourceRange());
}
const DeclStmt *VDeclStmt = If->getConditionVariableDeclStmt();
const VarDecl *VDecl = If->getConditionVariable();
std::string Repl =
(tooling::fixit::getText(*VDeclStmt, *Result.Context) +
llvm::StringRef(";\n") +
tooling::fixit::getText(If->getIfLoc(), *Result.Context))
.str();
Diag << tooling::fixit::createReplacement(SourceRange(If->getIfLoc()),
Repl)
<< tooling::fixit::createReplacement(VDeclStmt->getSourceRange(),
VDecl->getName());
removeElseAndBrackets(Diag, *Result.Context, Else, ElseLoc);
} else if (WarnOnUnfixable) {
// Warn, but don't attempt an autofix.
diag(ElseLoc, WarningMessage) << ControlFlowInterruptor;
}
return;
}
if (checkInitDeclUsageInElse(If) != nullptr) {
if (IsLastInScope) {
// If the if statement is the last statement its enclosing statements
// scope, we can pull the decl out of the if statement.
DiagnosticBuilder Diag = diag(ElseLoc, WarningMessage)
<< ControlFlowInterruptor;
Diag << tooling::fixit::createReplacement(
SourceRange(If->getIfLoc()),
(tooling::fixit::getText(*If->getInit(), *Result.Context) +
"\n" +
tooling::fixit::getText(If->getIfLoc(), *Result.Context))
.str())
<< tooling::fixit::createRemoval(If->getInit()->getSourceRange());
removeElseAndBrackets(Diag, *Result.Context, Else, ElseLoc);
} else if (WarnOnUnfixable) {
// Warn, but don't attempt an autofix.
diag(ElseLoc, WarningMessage) << ControlFlowInterruptor;
}
return;
}
DiagnosticBuilder Diag = diag(ElseLoc, WarningMessage)
<< ControlFlowInterruptor;
Diag << tooling::fixit::createRemoval(ElseLoc);
// FIXME: Removing the braces isn't always safe. Do a more careful analysis.
// FIXME: Change clang-format to correctly un-indent the code.
if (const auto *CS = Result.Nodes.getNodeAs<CompoundStmt>("else"))
Diag << tooling::fixit::createRemoval(CS->getLBracLoc())
<< tooling::fixit::createRemoval(CS->getRBracLoc());
removeElseAndBrackets(Diag, *Result.Context, Else, ElseLoc);
}
} // namespace readability

View File

@ -20,10 +20,14 @@ namespace readability {
/// http://llvm.org/docs/CodingStandards.html#don-t-use-else-after-a-return
class ElseAfterReturnCheck : public ClangTidyCheck {
public:
ElseAfterReturnCheck(StringRef Name, ClangTidyContext *Context)
: ClangTidyCheck(Name, Context) {}
ElseAfterReturnCheck(StringRef Name, ClangTidyContext *Context);
void storeOptions(ClangTidyOptions::OptionMap &Opts) override;
void registerMatchers(ast_matchers::MatchFinder *Finder) override;
void check(const ast_matchers::MatchFinder::MatchResult &Result) override;
private:
const bool WarnOnUnfixable;
};
} // namespace readability

View File

@ -0,0 +1,31 @@
// RUN: %check_clang_tidy %s readability-else-after-return %t -- \
// RUN: -config='{CheckOptions: [ \
// RUN: {key: readability-else-after-return.WarnOnUnfixable, value: 0}, \
// RUN: ]}'
int h(int);
int lifeTimeExtensionTests(int a) {
if (a > 0) {
return a;
} else {
// CHECK-MESSAGES-NOT: :[[@LINE-1]]:5: warning: do not use 'else' after 'return'
int b = 0;
h(b);
}
if (int b = a) {
return a;
} else {
// CHECK-MESSAGES-NOT: :[[@LINE-1]]:5: warning: do not use 'else' after 'return'
b++;
}
if (int b = a) { // comment-0
// CHECK-FIXES: {{^}} int b = a;
// CHECK-FIXES-NEXT: {{^}}if (b) { // comment-0
return a;
} else { // comment-0
// CHECK-MESSAGES: :[[@LINE-1]]:5: warning: do not use 'else' after 'return'
// CHECK-FIXES: {{^}} } // comment-0
return b;
}
}

View File

@ -1,4 +1,4 @@
// RUN: %check_clang_tidy %s readability-else-after-return %t -- -- -fexceptions
// RUN: %check_clang_tidy %s readability-else-after-return %t -- -- -fexceptions -std=c++17
namespace std {
struct string {
@ -106,14 +106,110 @@ void foo() {
}
}
extern int *g();
extern void h(int **x);
int g();
int h(int);
int *decl_in_condition() {
if (int *x = g()) {
return x;
} else {
h(&x);
return x;
int declInConditionUsedInElse() {
if (int X = g()) { // comment-11
// CHECK-FIXES: {{^}} int X = g();
// CHECK-FIXES-NEXT: {{^}}if (X) { // comment-11
return X;
} else { // comment-11
// CHECK-MESSAGES: :[[@LINE-1]]:5: warning: do not use 'else' after 'return'
// CHECK-FIXES: {{^}} } // comment-11
return h(X);
}
}
int declInConditionUnusedInElse() {
if (int X = g()) {
return h(X);
} else { // comment-12
// CHECK-MESSAGES: :[[@LINE-1]]:5: warning: do not use 'else' after 'return'
// CHECK-FIXES: {{^}} } // comment-12
return 0;
}
}
int varInitAndCondition() {
if (int X = g(); X != 0) { // comment-13
// CHECK-FIXES: {{^}} int X = g();
// CHECK-FIXES-NEXT: {{^}}if ( X != 0) { // comment-13
return X;
} else { // comment-13
// CHECK-MESSAGES: :[[@LINE-1]]:5: warning: do not use 'else' after 'return'
// CHECK-FIXES: {{^}} } // comment-13
return h(X);
}
}
int varInitAndConditionUnusedInElse() {
if (int X = g(); X != 0) {
return X;
} else { // comment-14
// CHECK-MESSAGES: :[[@LINE-1]]:5: warning: do not use 'else' after 'return'
// CHECK-FIXES: {{^}} } // comment-14
return 0;
}
}
int initAndCondition() {
int X;
if (X = g(); X != 0) {
return X;
} else { // comment-15
// CHECK-MESSAGES: :[[@LINE-1]]:5: warning: do not use 'else' after 'return'
// CHECK-FIXES: {{^}} } // comment-15
return h(X);
}
}
int varInitAndConditionUnusedInElseWithDecl() {
int Y = g();
if (int X = g(); X != 0) {
return X;
} else { // comment-16
// CHECK-MESSAGES: :[[@LINE-1]]:5: warning: do not use 'else' after 'return'
// CHECK-FIXES-NOT: {{^}} } //comment-16
int Y = g();
h(Y);
}
return Y;
}
int varInitAndCondVarUsedInElse() {
if (int X = g(); int Y = g()) { // comment-17
// CHECK-FIXES: {{^}} int X = g();
// CHECK-FIXES-NEXT: {{^}}int Y = g();
// CHECK-FIXES-NEXT: {{^}}if ( Y) { // comment-17
return X ? X : Y;
} else { // comment-17
// CHECK-MESSAGES: :[[@LINE-1]]:5: warning: do not use 'else' after 'return'
// CHECK-FIXES: {{^}} } // comment-17
return X ? X : h(Y);
}
}
int lifeTimeExtensionTests(int a) {
if (a > 0) {
return a;
} else {
// CHECK-MESSAGES: :[[@LINE-1]]:5: warning: do not use 'else' after 'return'
int b = 0;
h(b);
}
if (int b = a; (b & 1) == 0) {
return a;
} else {
// CHECK-MESSAGES: :[[@LINE-1]]:5: warning: do not use 'else' after 'return'
b++;
}
if (int b = a; b > 1) { // comment-18
// CHECK-FIXES: {{^}} int b = a;
// CHECK-FIXES-NEXT: {{^}}if ( b > 1) { // comment-18
return a;
} else { // comment-18
// CHECK-MESSAGES: :[[@LINE-1]]:5: warning: do not use 'else' after 'return'
// CHECK-FIXES: {{^}} } // comment-18
return b;
}
}

View File

@ -3009,6 +3009,23 @@ expr(isValueDependent()) matches return Size
</pre></td></tr>
<tr><td>Matcher&lt;<a href="https://clang.llvm.org/doxygen/classclang_1_1Expr.html">Expr</a>&gt;</td><td class="name" onclick="toggle('nullPointerConstant0')"><a name="nullPointerConstant0Anchor">nullPointerConstant</a></td><td></td></tr>
<tr><td colspan="4" class="doc" id="nullPointerConstant0"><pre>Matches expressions that resolve to a null pointer constant, such as
GNU's __null, C++11's nullptr, or C's NULL macro.
Given:
void *v1 = NULL;
void *v2 = nullptr;
void *v3 = __null; // GNU extension
char *cp = (char *)0;
int *ip = 0;
int i = 0;
expr(nullPointerConstant())
matches the initializer for v1, v2, v3, cp, and ip. Does not match the
initializer for i.
</pre></td></tr>
<tr><td>Matcher&lt;<a href="https://clang.llvm.org/doxygen/classclang_1_1FieldDecl.html">FieldDecl</a>&gt;</td><td class="name" onclick="toggle('hasBitWidth0')"><a name="hasBitWidth0Anchor">hasBitWidth</a></td><td>unsigned Width</td></tr>
<tr><td colspan="4" class="doc" id="hasBitWidth0"><pre>Matches non-static data members that are bit-fields of the specified
bit width.
@ -3761,6 +3778,18 @@ but not
Example matches y (matcher = parmVarDecl(hasDefaultArgument()))
void x(int val) {}
void y(int val = 0) {}
Deprecated. Use hasInitializer() instead to be able to
match on the contents of the default argument. For example:
void x(int val = 7) {}
void y(int val = 42) {}
parmVarDecl(hasInitializer(integerLiteral(equals(42))))
matches the parameter of y
A matcher such as
parmVarDecl(hasInitializer(anything()))
is equivalent to parmVarDecl(hasDefaultArgument()).
</pre></td></tr>
@ -4479,23 +4508,6 @@ functionDecl(isInstantiated())
</pre></td></tr>
<tr><td>Matcher&lt;internal::Matcher&lt;<a href="https://clang.llvm.org/doxygen/classclang_1_1Expr.html">Expr</a>&gt;&gt;</td><td class="name" onclick="toggle('nullPointerConstant0')"><a name="nullPointerConstant0Anchor">nullPointerConstant</a></td><td></td></tr>
<tr><td colspan="4" class="doc" id="nullPointerConstant0"><pre>Matches expressions that resolve to a null pointer constant, such as
GNU's __null, C++11's nullptr, or C's NULL macro.
Given:
void *v1 = NULL;
void *v2 = nullptr;
void *v3 = __null; // GNU extension
char *cp = (char *)0;
int *ip = 0;
int i = 0;
expr(nullPointerConstant())
matches the initializer for v1, v2, v3, cp, and ip. Does not match the
initializer for i.
</pre></td></tr>
<tr><td>Matcher&lt;internal::Matcher&lt;<a href="https://clang.llvm.org/doxygen/classclang_1_1NamedDecl.html">NamedDecl</a>&gt;&gt;</td><td class="name" onclick="toggle('hasAnyName0')"><a name="hasAnyName0Anchor">hasAnyName</a></td><td>StringRef, ..., StringRef</td></tr>
<tr><td colspan="4" class="doc" id="hasAnyName0"><pre>Matches NamedDecl nodes that have any of the specified names.
@ -5082,6 +5094,29 @@ with compoundStmt()
</pre></td></tr>
<tr><td>Matcher&lt;<a href="https://clang.llvm.org/doxygen/classclang_1_1CXXForRangeStmt.html">CXXForRangeStmt</a>&gt;</td><td class="name" onclick="toggle('hasInitStatement2')"><a name="hasInitStatement2Anchor">hasInitStatement</a></td><td>Matcher&lt;<a href="https://clang.llvm.org/doxygen/classclang_1_1Stmt.html">Stmt</a>&gt; InnerMatcher</td></tr>
<tr><td colspan="4" class="doc" id="hasInitStatement2"><pre>Matches selection statements with initializer.
Given:
void foo() {
if (int i = foobar(); i &gt; 0) {}
switch (int i = foobar(); i) {}
for (auto&amp; a = get_range(); auto&amp; x : a) {}
}
void bar() {
if (foobar() &gt; 0) {}
switch (foobar()) {}
for (auto&amp; x : get_range()) {}
}
ifStmt(hasInitStatement(anything()))
matches the if statement in foo but not in bar.
switchStmt(hasInitStatement(anything()))
matches the switch statement in foo but not in bar.
cxxForRangeStmt(hasInitStatement(anything()))
matches the range for statement in foo but not in bar.
</pre></td></tr>
<tr><td>Matcher&lt;<a href="https://clang.llvm.org/doxygen/classclang_1_1CXXForRangeStmt.html">CXXForRangeStmt</a>&gt;</td><td class="name" onclick="toggle('hasLoopVariable0')"><a name="hasLoopVariable0Anchor">hasLoopVariable</a></td><td>Matcher&lt;<a href="https://clang.llvm.org/doxygen/classclang_1_1VarDecl.html">VarDecl</a>&gt; InnerMatcher</td></tr>
<tr><td colspan="4" class="doc" id="hasLoopVariable0"><pre>Matches the initialization statement of a for loop.
@ -6206,6 +6241,29 @@ Examples matches the if statement
</pre></td></tr>
<tr><td>Matcher&lt;<a href="https://clang.llvm.org/doxygen/classclang_1_1IfStmt.html">IfStmt</a>&gt;</td><td class="name" onclick="toggle('hasInitStatement0')"><a name="hasInitStatement0Anchor">hasInitStatement</a></td><td>Matcher&lt;<a href="https://clang.llvm.org/doxygen/classclang_1_1Stmt.html">Stmt</a>&gt; InnerMatcher</td></tr>
<tr><td colspan="4" class="doc" id="hasInitStatement0"><pre>Matches selection statements with initializer.
Given:
void foo() {
if (int i = foobar(); i &gt; 0) {}
switch (int i = foobar(); i) {}
for (auto&amp; a = get_range(); auto&amp; x : a) {}
}
void bar() {
if (foobar() &gt; 0) {}
switch (foobar()) {}
for (auto&amp; x : get_range()) {}
}
ifStmt(hasInitStatement(anything()))
matches the if statement in foo but not in bar.
switchStmt(hasInitStatement(anything()))
matches the switch statement in foo but not in bar.
cxxForRangeStmt(hasInitStatement(anything()))
matches the range for statement in foo but not in bar.
</pre></td></tr>
<tr><td>Matcher&lt;<a href="https://clang.llvm.org/doxygen/classclang_1_1IfStmt.html">IfStmt</a>&gt;</td><td class="name" onclick="toggle('hasThen0')"><a name="hasThen0Anchor">hasThen</a></td><td>Matcher&lt;<a href="https://clang.llvm.org/doxygen/classclang_1_1Stmt.html">Stmt</a>&gt; InnerMatcher</td></tr>
<tr><td colspan="4" class="doc" id="hasThen0"><pre>Matches the then-statement of an if statement.
@ -6943,6 +7001,29 @@ Example matches true (matcher = hasCondition(cxxBoolLiteral(equals(true))))
</pre></td></tr>
<tr><td>Matcher&lt;<a href="https://clang.llvm.org/doxygen/classclang_1_1SwitchStmt.html">SwitchStmt</a>&gt;</td><td class="name" onclick="toggle('hasInitStatement1')"><a name="hasInitStatement1Anchor">hasInitStatement</a></td><td>Matcher&lt;<a href="https://clang.llvm.org/doxygen/classclang_1_1Stmt.html">Stmt</a>&gt; InnerMatcher</td></tr>
<tr><td colspan="4" class="doc" id="hasInitStatement1"><pre>Matches selection statements with initializer.
Given:
void foo() {
if (int i = foobar(); i &gt; 0) {}
switch (int i = foobar(); i) {}
for (auto&amp; a = get_range(); auto&amp; x : a) {}
}
void bar() {
if (foobar() &gt; 0) {}
switch (foobar()) {}
for (auto&amp; x : get_range()) {}
}
ifStmt(hasInitStatement(anything()))
matches the if statement in foo but not in bar.
switchStmt(hasInitStatement(anything()))
matches the switch statement in foo but not in bar.
cxxForRangeStmt(hasInitStatement(anything()))
matches the range for statement in foo but not in bar.
</pre></td></tr>
<tr><td>Matcher&lt;<a href="https://clang.llvm.org/doxygen/classclang_1_1TagType.html">TagType</a>&gt;</td><td class="name" onclick="toggle('hasDeclaration4')"><a name="hasDeclaration4Anchor">hasDeclaration</a></td><td>const Matcher&lt;<a href="https://clang.llvm.org/doxygen/classclang_1_1Decl.html">Decl</a>&gt; InnerMatcher</td></tr>
<tr><td colspan="4" class="doc" id="hasDeclaration4"><pre>Matches a node if the declaration associated with that node
matches the given matcher.
@ -7170,6 +7251,22 @@ Usable as: Any Matcher
</pre></td></tr>
<tr><td>Matcher&lt;T&gt;</td><td class="name" onclick="toggle('traverse0')"><a name="traverse0Anchor">traverse</a></td><td>ast_type_traits::TraversalKind TK, const Matcher&lt;T&gt; InnerMatcher</td></tr>
<tr><td colspan="4" class="doc" id="traverse0"><pre>Causes all nested matchers to be matched with the specified traversal kind.
Given
void foo()
{
int i = 3.0;
}
The matcher
traverse(ast_type_traits::TK_IgnoreImplicitCastsAndParentheses,
varDecl(hasInitializer(floatLiteral().bind("init")))
)
matches the variable declaration with "init" bound to the "3.0".
</pre></td></tr>
<tr><td>Matcher&lt;<a href="https://clang.llvm.org/doxygen/classclang_1_1TypedefNameDecl.html">TypedefNameDecl</a>&gt;</td><td class="name" onclick="toggle('hasType2')"><a name="hasType2Anchor">hasType</a></td><td>Matcher&lt;<a href="https://clang.llvm.org/doxygen/classclang_1_1QualType.html">QualType</a>&gt; InnerMatcher</td></tr>
<tr><td colspan="4" class="doc" id="hasType2"><pre>Matches if the expression's or declaration's type matches a type
matcher.

View File

@ -4343,6 +4343,35 @@ AST_POLYMORPHIC_MATCHER(isConstexpr,
return Node.isConstexpr();
}
/// Matches selection statements with initializer.
///
/// Given:
/// \code
/// void foo() {
/// if (int i = foobar(); i > 0) {}
/// switch (int i = foobar(); i) {}
/// for (auto& a = get_range(); auto& x : a) {}
/// }
/// void bar() {
/// if (foobar() > 0) {}
/// switch (foobar()) {}
/// for (auto& x : get_range()) {}
/// }
/// \endcode
/// ifStmt(hasInitStatement(anything()))
/// matches the if statement in foo but not in bar.
/// switchStmt(hasInitStatement(anything()))
/// matches the switch statement in foo but not in bar.
/// cxxForRangeStmt(hasInitStatement(anything()))
/// matches the range for statement in foo but not in bar.
AST_POLYMORPHIC_MATCHER_P(hasInitStatement,
AST_POLYMORPHIC_SUPPORTED_TYPES(IfStmt, SwitchStmt,
CXXForRangeStmt),
internal::Matcher<Stmt>, InnerMatcher) {
const Stmt *Init = Node.getInit();
return Init != nullptr && InnerMatcher.matches(*Init, Finder, Builder);
}
/// Matches the condition expression of an if statement, for loop,
/// switch statement or conditional operator.
///

View File

@ -279,6 +279,7 @@ RegistryMaps::RegistryMaps() {
REGISTER_MATCHER(hasIndex);
REGISTER_MATCHER(hasInit);
REGISTER_MATCHER(hasInitializer);
REGISTER_MATCHER(hasInitStatement);
REGISTER_MATCHER(hasKeywordSelector);
REGISTER_MATCHER(hasLHS);
REGISTER_MATCHER(hasLocalQualifiers);

View File

@ -1071,6 +1071,35 @@ TEST(isConstexpr, MatchesConstexprDeclarations) {
LanguageMode::Cxx17OrLater));
}
TEST(hasInitStatement, MatchesSelectionInitializers) {
EXPECT_TRUE(matches("void baz() { if (int i = 1; i > 0) {} }",
ifStmt(hasInitStatement(anything())),
LanguageMode::Cxx17OrLater));
EXPECT_TRUE(notMatches("void baz() { if (int i = 1) {} }",
ifStmt(hasInitStatement(anything()))));
EXPECT_TRUE(notMatches("void baz() { if (1 > 0) {} }",
ifStmt(hasInitStatement(anything()))));
EXPECT_TRUE(matches(
"void baz(int i) { switch (int j = i; j) { default: break; } }",
switchStmt(hasInitStatement(anything())), LanguageMode::Cxx17OrLater));
EXPECT_TRUE(notMatches("void baz(int i) { switch (i) { default: break; } }",
switchStmt(hasInitStatement(anything()))));
}
TEST(hasInitStatement, MatchesRangeForInitializers) {
EXPECT_TRUE(matches("void baz() {"
"int items[] = {};"
"for (auto &arr = items; auto &item : arr) {}"
"}",
cxxForRangeStmt(hasInitStatement(anything())),
LanguageMode::Cxx2aOrLater));
EXPECT_TRUE(notMatches("void baz() {"
"int items[] = {};"
"for (auto &item : items) {}"
"}",
cxxForRangeStmt(hasInitStatement(anything()))));
}
TEST(TemplateArgumentCountIs, Matches) {
EXPECT_TRUE(
matches("template<typename T> struct C {}; C<int> c;",