Commit ec3d8e61 authored by Nathan James's avatar Nathan James Committed by Aaron Ballman
Browse files

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.
parent a81cb1b8
Loading
Loading
Loading
Loading
+210 −28
Original line number Diff line number Diff line
@@ -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(
          forEach(ifStmt(unless(isConstexpr()),
                         hasThen(stmt(
                             anyOf(InterruptsControlFlow,
                                   compoundStmt(has(InterruptsControlFlow))))),
                         hasElse(stmt().bind("else")))
              .bind("if"))),
                      .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"})

  auto ControlFlowInterruptor = [&]() -> llvm::StringRef {
    for (llvm::StringRef BindingName :
         {ReturnStr, ContinueStr, BreakStr, ThrowStr})
      if (Result.Nodes.getNodeAs<Stmt>(BindingName))
      ControlFlowInterruptor = BindingName;
        return BindingName;
    return {};
  }();

  DiagnosticBuilder Diag = diag(ElseLoc, "do not use 'else' after '%0'")
  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;
  Diag << tooling::fixit::createRemoval(ElseLoc);
      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;
  }

  // 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());
  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;
  removeElseAndBrackets(Diag, *Result.Context, Else, ElseLoc);
}

} // namespace readability
+6 −2
Original line number Diff line number Diff line
@@ -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
+31 −0
Original line number Diff line number Diff line
// 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;
  }
}
+104 −8
Original line number Diff line number Diff line
// 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;
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 {
    h(&x);
    return x;
    // 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;
  }
}
+114 −17
Original line number Diff line number Diff line
@@ -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.
Loading