[clang-tools-extra] ec3d8e6 - Handle init statements in readability-else-after-return

Aaron Ballman via cfe-commits cfe-commits at lists.llvm.org
Thu Jan 2 10:39:36 PST 2020


Author: Nathan James
Date: 2020-01-02T13:39:27-05:00
New Revision: ec3d8e61b527c6312f77a4dab095ffc34e954927

URL: https://github.com/llvm/llvm-project/commit/ec3d8e61b527c6312f77a4dab095ffc34e954927
DIFF: https://github.com/llvm/llvm-project/commit/ec3d8e61b527c6312f77a4dab095ffc34e954927.diff

LOG: 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.

Added: 
    clang-tools-extra/test/clang-tidy/checkers/readability-else-after-return-no-warn.cpp

Modified: 
    clang-tools-extra/clang-tidy/readability/ElseAfterReturnCheck.cpp
    clang-tools-extra/clang-tidy/readability/ElseAfterReturnCheck.h
    clang-tools-extra/test/clang-tidy/checkers/readability-else-after-return.cpp
    clang/docs/LibASTMatchersReference.html
    clang/include/clang/ASTMatchers/ASTMatchers.h
    clang/lib/ASTMatchers/Dynamic/Registry.cpp
    clang/unittests/ASTMatchers/ASTMatchersNarrowingTest.cpp

Removed: 
    


################################################################################
diff  --git a/clang-tools-extra/clang-tidy/readability/ElseAfterReturnCheck.cpp b/clang-tools-extra/clang-tidy/readability/ElseAfterReturnCheck.cpp
index 520586dfe25d..93b197a52a52 100644
--- a/clang-tools-extra/clang-tidy/readability/ElseAfterReturnCheck.cpp
+++ b/clang-tools-extra/clang-tidy/readability/ElseAfterReturnCheck.cpp
@@ -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'")
-                           << ControlFlowInterruptor;
-  Diag << tooling::fixit::createRemoval(ElseLoc);
+  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;
+  }
 
-  // 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 (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;
+  removeElseAndBrackets(Diag, *Result.Context, Else, ElseLoc);
 }
 
 } // namespace readability

diff  --git a/clang-tools-extra/clang-tidy/readability/ElseAfterReturnCheck.h b/clang-tools-extra/clang-tidy/readability/ElseAfterReturnCheck.h
index 0aa47b5169e4..990d4b4c3f6b 100644
--- a/clang-tools-extra/clang-tidy/readability/ElseAfterReturnCheck.h
+++ b/clang-tools-extra/clang-tidy/readability/ElseAfterReturnCheck.h
@@ -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

diff  --git a/clang-tools-extra/test/clang-tidy/checkers/readability-else-after-return-no-warn.cpp b/clang-tools-extra/test/clang-tidy/checkers/readability-else-after-return-no-warn.cpp
new file mode 100644
index 000000000000..986196339db1
--- /dev/null
+++ b/clang-tools-extra/test/clang-tidy/checkers/readability-else-after-return-no-warn.cpp
@@ -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;
+  }
+}

diff  --git a/clang-tools-extra/test/clang-tidy/checkers/readability-else-after-return.cpp b/clang-tools-extra/test/clang-tidy/checkers/readability-else-after-return.cpp
index f9e52e012430..69ac9eb29571 100644
--- a/clang-tools-extra/test/clang-tidy/checkers/readability-else-after-return.cpp
+++ b/clang-tools-extra/test/clang-tidy/checkers/readability-else-after-return.cpp
@@ -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;
+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;
   }
 }

diff  --git a/clang/docs/LibASTMatchersReference.html b/clang/docs/LibASTMatchersReference.html
index 97c75277c275..338399976f4f 100644
--- a/clang/docs/LibASTMatchersReference.html
+++ b/clang/docs/LibASTMatchersReference.html
@@ -3009,6 +3009,23 @@ <h2 id="narrowing-matchers">Narrowing Matchers</h2>
 </pre></td></tr>
 
 
+<tr><td>Matcher<<a href="https://clang.llvm.org/doxygen/classclang_1_1Expr.html">Expr</a>></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<<a href="https://clang.llvm.org/doxygen/classclang_1_1FieldDecl.html">FieldDecl</a>></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 @@ <h2 id="narrowing-matchers">Narrowing Matchers</h2>
 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 @@ <h2 id="narrowing-matchers">Narrowing Matchers</h2>
 </pre></td></tr>
 
 
-<tr><td>Matcher<internal::Matcher<<a href="https://clang.llvm.org/doxygen/classclang_1_1Expr.html">Expr</a>>></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<internal::Matcher<<a href="https://clang.llvm.org/doxygen/classclang_1_1NamedDecl.html">NamedDecl</a>>></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 @@ <h2 id="traversal-matchers">AST Traversal Matchers</h2>
 </pre></td></tr>
 
 
+<tr><td>Matcher<<a href="https://clang.llvm.org/doxygen/classclang_1_1CXXForRangeStmt.html">CXXForRangeStmt</a>></td><td class="name" onclick="toggle('hasInitStatement2')"><a name="hasInitStatement2Anchor">hasInitStatement</a></td><td>Matcher<<a href="https://clang.llvm.org/doxygen/classclang_1_1Stmt.html">Stmt</a>> 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 > 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()) {} 
+ }
+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<<a href="https://clang.llvm.org/doxygen/classclang_1_1CXXForRangeStmt.html">CXXForRangeStmt</a>></td><td class="name" onclick="toggle('hasLoopVariable0')"><a name="hasLoopVariable0Anchor">hasLoopVariable</a></td><td>Matcher<<a href="https://clang.llvm.org/doxygen/classclang_1_1VarDecl.html">VarDecl</a>> InnerMatcher</td></tr>
 <tr><td colspan="4" class="doc" id="hasLoopVariable0"><pre>Matches the initialization statement of a for loop.
 
@@ -6206,6 +6241,29 @@ <h2 id="traversal-matchers">AST Traversal Matchers</h2>
 </pre></td></tr>
 
 
+<tr><td>Matcher<<a href="https://clang.llvm.org/doxygen/classclang_1_1IfStmt.html">IfStmt</a>></td><td class="name" onclick="toggle('hasInitStatement0')"><a name="hasInitStatement0Anchor">hasInitStatement</a></td><td>Matcher<<a href="https://clang.llvm.org/doxygen/classclang_1_1Stmt.html">Stmt</a>> 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 > 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()) {} 
+ }
+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<<a href="https://clang.llvm.org/doxygen/classclang_1_1IfStmt.html">IfStmt</a>></td><td class="name" onclick="toggle('hasThen0')"><a name="hasThen0Anchor">hasThen</a></td><td>Matcher<<a href="https://clang.llvm.org/doxygen/classclang_1_1Stmt.html">Stmt</a>> InnerMatcher</td></tr>
 <tr><td colspan="4" class="doc" id="hasThen0"><pre>Matches the then-statement of an if statement.
 
@@ -6943,6 +7001,29 @@ <h2 id="traversal-matchers">AST Traversal Matchers</h2>
 </pre></td></tr>
 
 
+<tr><td>Matcher<<a href="https://clang.llvm.org/doxygen/classclang_1_1SwitchStmt.html">SwitchStmt</a>></td><td class="name" onclick="toggle('hasInitStatement1')"><a name="hasInitStatement1Anchor">hasInitStatement</a></td><td>Matcher<<a href="https://clang.llvm.org/doxygen/classclang_1_1Stmt.html">Stmt</a>> 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 > 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()) {} 
+ }
+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<<a href="https://clang.llvm.org/doxygen/classclang_1_1TagType.html">TagType</a>></td><td class="name" onclick="toggle('hasDeclaration4')"><a name="hasDeclaration4Anchor">hasDeclaration</a></td><td>const Matcher<<a href="https://clang.llvm.org/doxygen/classclang_1_1Decl.html">Decl</a>>  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 @@ <h2 id="traversal-matchers">AST Traversal Matchers</h2>
 </pre></td></tr>
 
 
+<tr><td>Matcher<T></td><td class="name" onclick="toggle('traverse0')"><a name="traverse0Anchor">traverse</a></td><td>ast_type_traits::TraversalKind TK, const Matcher<T>  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<<a href="https://clang.llvm.org/doxygen/classclang_1_1TypedefNameDecl.html">TypedefNameDecl</a>></td><td class="name" onclick="toggle('hasType2')"><a name="hasType2Anchor">hasType</a></td><td>Matcher<<a href="https://clang.llvm.org/doxygen/classclang_1_1QualType.html">QualType</a>> 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.

diff  --git a/clang/include/clang/ASTMatchers/ASTMatchers.h b/clang/include/clang/ASTMatchers/ASTMatchers.h
index 2a5f0cf6fa91..7db5f7a5de82 100644
--- a/clang/include/clang/ASTMatchers/ASTMatchers.h
+++ b/clang/include/clang/ASTMatchers/ASTMatchers.h
@@ -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.
 ///

diff  --git a/clang/lib/ASTMatchers/Dynamic/Registry.cpp b/clang/lib/ASTMatchers/Dynamic/Registry.cpp
index 8c11e069cb05..89722489fe2e 100644
--- a/clang/lib/ASTMatchers/Dynamic/Registry.cpp
+++ b/clang/lib/ASTMatchers/Dynamic/Registry.cpp
@@ -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);

diff  --git a/clang/unittests/ASTMatchers/ASTMatchersNarrowingTest.cpp b/clang/unittests/ASTMatchers/ASTMatchersNarrowingTest.cpp
index 342897fd56a2..e7f9232e968f 100644
--- a/clang/unittests/ASTMatchers/ASTMatchersNarrowingTest.cpp
+++ b/clang/unittests/ASTMatchers/ASTMatchersNarrowingTest.cpp
@@ -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;",


        


More information about the cfe-commits mailing list