https://github.com/localspook created 
https://github.com/llvm/llvm-project/pull/184069



We have several checks that need to test whether an expression is discarded.

Currently, all of them implement bespoke solutions, and all of them are buggy
or incomplete in different and exciting ways, because this is genuinely a 
nontrivial
thing to implement. For example, `modernize-use-std-print` won't modernize 
print statements when
they're the target of a label:

```cpp
void f() {
  std::printf("^_^");
label:
  std::printf(";_;");
}
```

This change improves the situation by centralizing all that logic in one 
`isDiscarded` matcher.

It also comes with speedups:

```txt
Before: 0.7031 (  1.7%)   0.0938 (  1.7%)   0.7969 (  1.7%)   0.7208 (  1.5%)  
modernize-use-std-print
After:  0.0938 (  0.2%)   0.0156 (  0.3%)   0.1094 (  0.2%)   0.0628 (  0.1%)  
modernize-use-std-print

Before: 1.0625 (  2.5%)   0.0781 (  1.4%)   1.1406 (  2.4%)   1.1740 (  2.5%)  
bugprone-standalone-empty
After:  0.4844 (  1.2%)   0.0312 (  0.6%)   0.5156 (  1.1%)   0.5182 (  1.1%)  
bugprone-standalone-empty

Before: 0.5000 (  1.2%)   0.0938 (  1.7%)   0.5938 (  1.3%)   0.4162 (  0.9%)  
bugprone-unused-return-value
After:  0.2656 (  0.7%)   0.0156 (  0.3%)   0.2812 (  0.6%)   0.2741 (  0.6%)  
bugprone-unused-return-value
```

We get these speedups by matching bottom-up instead of top-down, which is more 
efficient since, with these checks, the bottom node is rarely matched.

I do measure slight slowdowns for a couple of checks:

```txt
Before: 0.0000 (  0.0%)   0.0000 (  0.0%)   0.0000 (  0.0%)   0.0038 (  0.0%)  
bugprone-throw-keyword-missing
After:  0.0000 (  0.0%)   0.0000 (  0.0%)   0.0000 (  0.0%)   0.0085 (  0.0%)  
bugprone-throw-keyword-missing

Before: 0.2188 (  0.5%)   0.0312 (  0.6%)   0.2500 (  0.5%)   0.2567 (  0.5%)  
bugprone-unused-raii
After:  0.1250 (  0.3%)   0.0938 (  1.8%)   0.2188 (  0.5%)   0.2767 (  0.6%)  
bugprone-unused-raii
```

These are far outweighed by the speedups above.

>From 170fffed2bd9a7a780c92da7c895df511efe66fc Mon Sep 17 00:00:00 2001
From: Victor Chernyakin <[email protected]>
Date: Sun, 1 Mar 2026 22:07:12 -0800
Subject: [PATCH 1/2] [clang-tidy] Centralize the logic for matching discarded
 expressions

---
 .../bugprone/StandaloneEmptyCheck.cpp         | 113 +++---------------
 .../bugprone/ThrowKeywordMissingCheck.cpp     |  20 +---
 .../clang-tidy/bugprone/UnusedRaiiCheck.cpp   |  23 ++--
 .../bugprone/UnusedReturnValueCheck.cpp       |  29 +----
 .../clang-tidy/modernize/UseStdPrintCheck.cpp |  52 +++-----
 clang-tools-extra/clang-tidy/utils/Matchers.h |  57 +++++++++
 .../checkers/modernize/use-std-print.cpp      |   5 +
 7 files changed, 112 insertions(+), 187 deletions(-)

diff --git a/clang-tools-extra/clang-tidy/bugprone/StandaloneEmptyCheck.cpp 
b/clang-tools-extra/clang-tidy/bugprone/StandaloneEmptyCheck.cpp
index 056ae4b80f109..7d787c361c8a5 100644
--- a/clang-tools-extra/clang-tidy/bugprone/StandaloneEmptyCheck.cpp
+++ b/clang-tools-extra/clang-tidy/bugprone/StandaloneEmptyCheck.cpp
@@ -7,86 +7,29 @@
 
//===----------------------------------------------------------------------===//
 
 #include "StandaloneEmptyCheck.h"
-#include "clang/AST/ASTContext.h"
-#include "clang/AST/Decl.h"
-#include "clang/AST/DeclBase.h"
-#include "clang/AST/DeclCXX.h"
-#include "clang/AST/Expr.h"
-#include "clang/AST/ExprCXX.h"
-#include "clang/AST/Stmt.h"
-#include "clang/AST/Type.h"
-#include "clang/ASTMatchers/ASTMatchFinder.h"
+#include "../utils/Matchers.h"
 #include "clang/ASTMatchers/ASTMatchers.h"
-#include "clang/Basic/Diagnostic.h"
-#include "clang/Basic/SourceLocation.h"
 #include "clang/Lex/Lexer.h"
 #include "clang/Sema/HeuristicResolver.h"
-#include "llvm/Support/Casting.h"
 
-namespace clang::tidy::bugprone {
+using namespace clang::ast_matchers;
 
-using ast_matchers::BoundNodes;
-using ast_matchers::callee;
-using ast_matchers::callExpr;
-using ast_matchers::classTemplateDecl;
-using ast_matchers::cxxMemberCallExpr;
-using ast_matchers::cxxMethodDecl;
-using ast_matchers::expr;
-using ast_matchers::functionDecl;
-using ast_matchers::hasAncestor;
-using ast_matchers::hasName;
-using ast_matchers::hasParent;
-using ast_matchers::ignoringImplicit;
-using ast_matchers::ignoringParenImpCasts;
-using ast_matchers::MatchFinder;
-using ast_matchers::optionally;
-using ast_matchers::returns;
-using ast_matchers::stmt;
-using ast_matchers::stmtExpr;
-using ast_matchers::unless;
-using ast_matchers::voidType;
-
-static const Expr *getCondition(const BoundNodes &Nodes,
-                                const StringRef NodeId) {
-  const auto *If = Nodes.getNodeAs<IfStmt>(NodeId);
-  if (If != nullptr)
-    return If->getCond();
-
-  const auto *For = Nodes.getNodeAs<ForStmt>(NodeId);
-  if (For != nullptr)
-    return For->getCond();
-
-  const auto *While = Nodes.getNodeAs<WhileStmt>(NodeId);
-  if (While != nullptr)
-    return While->getCond();
-
-  const auto *Do = Nodes.getNodeAs<DoStmt>(NodeId);
-  if (Do != nullptr)
-    return Do->getCond();
-
-  const auto *Switch = Nodes.getNodeAs<SwitchStmt>(NodeId);
-  if (Switch != nullptr)
-    return Switch->getCond();
-
-  return nullptr;
-}
+namespace clang::tidy::bugprone {
 
-void StandaloneEmptyCheck::registerMatchers(ast_matchers::MatchFinder *Finder) 
{
+void StandaloneEmptyCheck::registerMatchers(MatchFinder *Finder) {
   // Ignore empty calls in a template definition which fall under callExpr
   // non-member matcher even if they are methods.
-  const auto NonMemberMatcher = expr(ignoringImplicit(ignoringParenImpCasts(
-      callExpr(
-          hasParent(stmt(optionally(hasParent(stmtExpr().bind("stexpr"))))
-                        .bind("parent")),
-          unless(hasAncestor(classTemplateDecl())),
-          callee(functionDecl(hasName("empty"), unless(returns(voidType())))))
-          .bind("empty"))));
+  const auto NonMemberMatcher =
+      expr(ignoringParenImpCasts(
+               callExpr(unless(hasAncestor(classTemplateDecl())),
+                        callee(functionDecl(hasName("empty"),
+                                            unless(returns(voidType())))))
+                   .bind("empty")),
+           matchers::isDiscarded());
   const auto MemberMatcher =
-      expr(ignoringImplicit(ignoringParenImpCasts(cxxMemberCallExpr(
-               hasParent(stmt(optionally(hasParent(stmtExpr().bind("stexpr"))))
-                             .bind("parent")),
-               callee(cxxMethodDecl(hasName("empty"),
-                                    unless(returns(voidType()))))))))
+      expr(ignoringParenImpCasts(cxxMemberCallExpr(callee(
+               cxxMethodDecl(hasName("empty"), unless(returns(voidType())))))),
+           matchers::isDiscarded())
           .bind("empty");
 
   Finder->addMatcher(MemberMatcher, this);
@@ -94,29 +37,8 @@ void 
StandaloneEmptyCheck::registerMatchers(ast_matchers::MatchFinder *Finder) {
 }
 
 void StandaloneEmptyCheck::check(const MatchFinder::MatchResult &Result) {
-  // Skip if the parent node is Expr.
-  if (Result.Nodes.getNodeAs<Expr>("parent"))
-    return;
-
-  const auto *PParentStmtExpr = Result.Nodes.getNodeAs<Expr>("stexpr");
-  const auto *ParentCompStmt = Result.Nodes.getNodeAs<CompoundStmt>("parent");
-  const auto *ParentCond = getCondition(Result.Nodes, "parent");
-  const auto *ParentReturnStmt = Result.Nodes.getNodeAs<ReturnStmt>("parent");
-
   if (const auto *MemberCall =
           Result.Nodes.getNodeAs<CXXMemberCallExpr>("empty")) {
-    // Skip if it's a condition of the parent statement.
-    if (ParentCond == MemberCall->getExprStmt())
-      return;
-    // Skip if it's the last statement in the GNU extension
-    // statement expression.
-    if (PParentStmtExpr && ParentCompStmt &&
-        ParentCompStmt->body_back() == MemberCall->getExprStmt())
-      return;
-    // Skip if it's a return statement
-    if (ParentReturnStmt)
-      return;
-
     const SourceLocation MemberLoc = MemberCall->getBeginLoc();
     const SourceLocation ReplacementLoc = MemberCall->getExprLoc();
     const SourceRange ReplacementRange =
@@ -154,13 +76,6 @@ void StandaloneEmptyCheck::check(const 
MatchFinder::MatchResult &Result) {
 
   } else if (const auto *NonMemberCall =
                  Result.Nodes.getNodeAs<CallExpr>("empty")) {
-    if (ParentCond == NonMemberCall->getExprStmt())
-      return;
-    if (PParentStmtExpr && ParentCompStmt &&
-        ParentCompStmt->body_back() == NonMemberCall->getExprStmt())
-      return;
-    if (ParentReturnStmt)
-      return;
     if (NonMemberCall->getNumArgs() != 1)
       return;
 
diff --git a/clang-tools-extra/clang-tidy/bugprone/ThrowKeywordMissingCheck.cpp 
b/clang-tools-extra/clang-tidy/bugprone/ThrowKeywordMissingCheck.cpp
index 7cf9696378c62..44f5ceedc6e44 100644
--- a/clang-tools-extra/clang-tidy/bugprone/ThrowKeywordMissingCheck.cpp
+++ b/clang-tools-extra/clang-tidy/bugprone/ThrowKeywordMissingCheck.cpp
@@ -7,33 +7,23 @@
 
//===----------------------------------------------------------------------===//
 
 #include "ThrowKeywordMissingCheck.h"
+#include "../utils/Matchers.h"
 #include "clang/AST/ASTContext.h"
 #include "clang/ASTMatchers/ASTMatchFinder.h"
 
 using namespace clang::ast_matchers;
-using namespace clang::ast_matchers::internal;
 
 namespace clang::tidy::bugprone {
 
 void ThrowKeywordMissingCheck::registerMatchers(MatchFinder *Finder) {
-  const VariadicDynCastAllOfMatcher<Stmt, AttributedStmt> AttributedStmt;
-  // Matches an 'expression-statement', as defined in [stmt.expr]/1.
-  // Not to be confused with the similarly-named GNU extension, the
-  // statement expression.
-  const auto ExprStmt = [&](const Matcher<Expr> &InnerMatcher) {
-    return expr(hasParent(stmt(anyOf(doStmt(), whileStmt(), forStmt(),
-                                     compoundStmt(), ifStmt(), switchStmt(),
-                                     labelStmt(), AttributedStmt()))),
-                InnerMatcher);
-  };
-
   Finder->addMatcher(
-      ExprStmt(
-          cxxConstructExpr(hasType(cxxRecordDecl(anyOf(
+      cxxConstructExpr(
+          hasType(cxxRecordDecl(anyOf(
               matchesName("[Ee]xception|EXCEPTION"),
               hasAnyBase(hasType(hasCanonicalType(recordType(hasDeclaration(
                   cxxRecordDecl(matchesName("[Ee]xception|EXCEPTION"))
-                      .bind("base")))))))))))
+                      .bind("base"))))))))),
+          matchers::isDiscarded())
           .bind("temporary-exception-not-thrown"),
       this);
 }
diff --git a/clang-tools-extra/clang-tidy/bugprone/UnusedRaiiCheck.cpp 
b/clang-tools-extra/clang-tidy/bugprone/UnusedRaiiCheck.cpp
index 6502fc9bfb89e..b486a454aefe7 100644
--- a/clang-tools-extra/clang-tidy/bugprone/UnusedRaiiCheck.cpp
+++ b/clang-tools-extra/clang-tidy/bugprone/UnusedRaiiCheck.cpp
@@ -7,6 +7,7 @@
 
//===----------------------------------------------------------------------===//
 
 #include "UnusedRaiiCheck.h"
+#include "../utils/Matchers.h"
 #include "clang/AST/ASTContext.h"
 #include "clang/Lex/Lexer.h"
 
@@ -26,12 +27,13 @@ void UnusedRaiiCheck::registerMatchers(MatchFinder *Finder) 
{
   // destroyed.
   Finder->addMatcher(
       mapAnyOf(cxxConstructExpr, cxxUnresolvedConstructExpr)
-          .with(hasParent(compoundStmt().bind("compound")),
-                anyOf(hasType(hasCanonicalType(recordType(hasDeclaration(
+          .with(anyOf(hasType(hasCanonicalType(recordType(hasDeclaration(
                           cxxRecordDecl(hasNonTrivialDestructor()))))),
                       hasType(hasCanonicalType(templateSpecializationType(
                           hasDeclaration(classTemplateDecl(has(
-                              cxxRecordDecl(hasNonTrivialDestructor())))))))))
+                              cxxRecordDecl(hasNonTrivialDestructor())))))))),
+                matchers::isDiscarded(),
+                optionally(hasParent(compoundStmt().bind("compound"))))
           .bind("expr"),
       this);
 }
@@ -39,7 +41,7 @@ void UnusedRaiiCheck::registerMatchers(MatchFinder *Finder) {
 template <typename T>
 static void reportDiagnostic(const DiagnosticBuilder &D, const T *Node,
                              SourceRange SR, bool DefaultConstruction) {
-  const char *Replacement = " give_me_a_name";
+  static constexpr StringRef Replacement = " give_me_a_name";
 
   // If this is a default ctor we have to remove the parens or we'll introduce 
a
   // most vexing parse.
@@ -63,13 +65,12 @@ void UnusedRaiiCheck::check(const MatchFinder::MatchResult 
&Result) {
   if (E->getBeginLoc().isMacroID())
     return;
 
-  // Don't emit a warning for the last statement in the surrounding compound
-  // statement.
-  const auto *CS = Result.Nodes.getNodeAs<CompoundStmt>("compound");
-  const auto *LastExpr = dyn_cast<Expr>(CS->body_back());
-
-  if (LastExpr && E == LastExpr->IgnoreUnlessSpelledInSource())
-    return;
+  // Don't emit a warning if this is the last statement in a surrounding
+  // compound statement.
+  if (const auto *CS = Result.Nodes.getNodeAs<CompoundStmt>("compound"))
+    if (const auto *LastExpr = dyn_cast<Expr>(CS->body_back()))
+      if (E == LastExpr->IgnoreUnlessSpelledInSource())
+        return;
 
   // Emit a warning.
   auto D = diag(E->getBeginLoc(), "object destroyed immediately after "
diff --git a/clang-tools-extra/clang-tidy/bugprone/UnusedReturnValueCheck.cpp 
b/clang-tools-extra/clang-tidy/bugprone/UnusedReturnValueCheck.cpp
index 7aee725cae434..6c9aafd02b14e 100644
--- a/clang-tools-extra/clang-tidy/bugprone/UnusedReturnValueCheck.cpp
+++ b/clang-tools-extra/clang-tidy/bugprone/UnusedReturnValueCheck.cpp
@@ -186,32 +186,13 @@ void UnusedReturnValueCheck::registerMatchers(MatchFinder 
*Finder) {
 
   auto CheckCastToVoid =
       AllowCastToVoid ? castExpr(unless(hasCastKind(CK_ToVoid))) : castExpr();
-  auto MatchedCallExpr = expr(
-      anyOf(MatchedDirectCallExpr,
-            explicitCastExpr(unless(cxxFunctionalCastExpr()), CheckCastToVoid,
-                             hasSourceExpression(MatchedDirectCallExpr))));
-
-  auto UnusedInCompoundStmt =
-      compoundStmt(forEach(MatchedCallExpr),
-                   // The checker can't currently differentiate between the
-                   // return statement and other statements inside GNU 
statement
-                   // expressions, so disable the checker inside them to avoid
-                   // false positives.
-                   unless(hasParent(stmtExpr())));
-  auto UnusedInIfStmt =
-      ifStmt(eachOf(hasThen(MatchedCallExpr), hasElse(MatchedCallExpr)));
-  auto UnusedInWhileStmt = whileStmt(hasBody(MatchedCallExpr));
-  auto UnusedInDoStmt = doStmt(hasBody(MatchedCallExpr));
-  auto UnusedInForStmt =
-      forStmt(eachOf(hasLoopInit(MatchedCallExpr),
-                     hasIncrement(MatchedCallExpr), hasBody(MatchedCallExpr)));
-  auto UnusedInRangeForStmt = cxxForRangeStmt(hasBody(MatchedCallExpr));
-  auto UnusedInCaseStmt = switchCase(forEach(MatchedCallExpr));
 
   Finder->addMatcher(
-      stmt(anyOf(UnusedInCompoundStmt, UnusedInIfStmt, UnusedInWhileStmt,
-                 UnusedInDoStmt, UnusedInForStmt, UnusedInRangeForStmt,
-                 UnusedInCaseStmt)),
+      expr(anyOf(MatchedDirectCallExpr,
+                 explicitCastExpr(unless(cxxFunctionalCastExpr()),
+                                  CheckCastToVoid,
+                                  hasSourceExpression(MatchedDirectCallExpr))),
+           matchers::isDiscarded()),
       this);
 }
 
diff --git a/clang-tools-extra/clang-tidy/modernize/UseStdPrintCheck.cpp 
b/clang-tools-extra/clang-tidy/modernize/UseStdPrintCheck.cpp
index e92155c822dc7..13efe9539c80c 100644
--- a/clang-tools-extra/clang-tidy/modernize/UseStdPrintCheck.cpp
+++ b/clang-tools-extra/clang-tidy/modernize/UseStdPrintCheck.cpp
@@ -69,51 +69,27 @@ void UseStdPrintCheck::registerPPCallbacks(const 
SourceManager &SM,
   this->PP = PP;
 }
 
-static clang::ast_matchers::StatementMatcher unusedReturnValue(
-    const clang::ast_matchers::StatementMatcher &MatchedCallExpr) {
-  auto UnusedInCompoundStmt =
-      compoundStmt(forEach(MatchedCallExpr),
-                   // The checker can't currently differentiate between the
-                   // return statement and other statements inside GNU 
statement
-                   // expressions, so disable the checker inside them to avoid
-                   // false positives.
-                   unless(hasParent(stmtExpr())));
-  auto UnusedInIfStmt =
-      ifStmt(eachOf(hasThen(MatchedCallExpr), hasElse(MatchedCallExpr)));
-  auto UnusedInWhileStmt = whileStmt(hasBody(MatchedCallExpr));
-  auto UnusedInDoStmt = doStmt(hasBody(MatchedCallExpr));
-  auto UnusedInForStmt =
-      forStmt(eachOf(hasLoopInit(MatchedCallExpr),
-                     hasIncrement(MatchedCallExpr), hasBody(MatchedCallExpr)));
-  auto UnusedInRangeForStmt = cxxForRangeStmt(hasBody(MatchedCallExpr));
-  auto UnusedInCaseStmt = switchCase(forEach(MatchedCallExpr));
-
-  return stmt(anyOf(UnusedInCompoundStmt, UnusedInIfStmt, UnusedInWhileStmt,
-                    UnusedInDoStmt, UnusedInForStmt, UnusedInRangeForStmt,
-                    UnusedInCaseStmt));
-}
-
 void UseStdPrintCheck::registerMatchers(MatchFinder *Finder) {
   if (!PrintfLikeFunctions.empty())
     Finder->addMatcher(
-        unusedReturnValue(
-            callExpr(argumentCountAtLeast(1),
-                     hasArgument(0, stringLiteral(isOrdinary())),
-                     callee(functionDecl(matchers::matchesAnyListedRegexName(
-                                             PrintfLikeFunctions))
-                                .bind("func_decl")))
-                .bind("printf")),
+        callExpr(argumentCountAtLeast(1),
+                 hasArgument(0, stringLiteral(isOrdinary())),
+                 callee(functionDecl(matchers::matchesAnyListedRegexName(
+                                         PrintfLikeFunctions))
+                            .bind("func_decl")),
+                 matchers::isDiscarded())
+            .bind("printf"),
         this);
 
   if (!FprintfLikeFunctions.empty())
     Finder->addMatcher(
-        unusedReturnValue(
-            callExpr(argumentCountAtLeast(2),
-                     hasArgument(1, stringLiteral(isOrdinary())),
-                     callee(functionDecl(matchers::matchesAnyListedRegexName(
-                                             FprintfLikeFunctions))
-                                .bind("func_decl")))
-                .bind("fprintf")),
+        callExpr(argumentCountAtLeast(2),
+                 hasArgument(1, stringLiteral(isOrdinary())),
+                 callee(functionDecl(matchers::matchesAnyListedRegexName(
+                                         FprintfLikeFunctions))
+                            .bind("func_decl")),
+                 matchers::isDiscarded())
+            .bind("fprintf"),
         this);
 }
 
diff --git a/clang-tools-extra/clang-tidy/utils/Matchers.h 
b/clang-tools-extra/clang-tidy/utils/Matchers.h
index 83401e85c8da9..b9e8adbba8dfc 100644
--- a/clang-tools-extra/clang-tidy/utils/Matchers.h
+++ b/clang-tools-extra/clang-tidy/utils/Matchers.h
@@ -10,7 +10,9 @@
 #define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_UTILS_MATCHERS_H
 
 #include "TypeTraits.h"
+#include "clang/AST/ASTTypeTraits.h"
 #include "clang/AST/ExprConcepts.h"
+#include "clang/AST/ParentMapContext.h"
 #include "clang/ASTMatchers/ASTMatchers.h"
 #include <optional>
 
@@ -74,6 +76,61 @@ AST_MATCHER(Expr, hasUnevaluatedContext) {
   return false;
 }
 
+AST_MATCHER(Expr, isDiscarded) {
+  const DynTypedNodeList Parents = [&] {
+    const TraversalKindScope _(Finder->getASTContext(),
+                               TK_IgnoreUnlessSpelledInSource);
+    return Finder->getASTContext().getParents(Node);
+  }();
+  if (Parents.size() != 1)
+    return false;
+  const DynTypedNode Parent = Parents[0];
+
+  const Expr *const DesiredNode = Node.IgnoreUnlessSpelledInSource();
+  const auto IsCurrentNode = [&](const Stmt *S) {
+    const auto *const AsExpr = dyn_cast_if_present<Expr>(S);
+    return AsExpr && AsExpr->IgnoreUnlessSpelledInSource() == DesiredNode;
+  };
+
+  if (const auto *While = Parent.get<WhileStmt>())
+    return IsCurrentNode(While->getBody());
+
+  if (const auto *For = Parent.get<ForStmt>())
+    return IsCurrentNode(For->getBody()) || IsCurrentNode(For->getInit()) ||
+           IsCurrentNode(For->getInc());
+
+  if (const auto *Do = Parent.get<DoStmt>())
+    return IsCurrentNode(Do->getBody());
+
+  if (const auto *If = Parent.get<IfStmt>())
+    return IsCurrentNode(If->getThen()) || IsCurrentNode(If->getElse());
+
+  if (const auto *ForRange = Parent.get<CXXForRangeStmt>())
+    return IsCurrentNode(ForRange->getBody());
+
+  if (const auto *Switch = Parent.get<SwitchStmt>())
+    return IsCurrentNode(Switch->getBody());
+
+  if (const auto *Case = Parent.get<SwitchCase>())
+    return true;
+
+  if (const auto *Label = Parent.get<LabelStmt>())
+    return true;
+
+  if (const auto *AttrStmt = Parent.get<AttributedStmt>())
+    return true;
+
+  if (const auto *Compound = Parent.get<CompoundStmt>()) {
+    // Is this statement the return value of a GNU statement expression?
+    const DynTypedNodeList Grandparents =
+        Finder->getASTContext().getParents(Parent);
+    return !(Grandparents.size() == 1 && Grandparents[0].get<StmtExpr>() &&
+             IsCurrentNode(Compound->body_back()));
+  }
+
+  return false;
+}
+
 // A matcher implementation that matches a list of type name regular 
expressions
 // against a NamedDecl. If a regular expression contains the substring "::"
 // matching will occur against the qualified name, otherwise only the typename.
diff --git 
a/clang-tools-extra/test/clang-tidy/checkers/modernize/use-std-print.cpp 
b/clang-tools-extra/test/clang-tidy/checkers/modernize/use-std-print.cpp
index 63972cc0fd25e..e202a9d194113 100644
--- a/clang-tools-extra/test/clang-tidy/checkers/modernize/use-std-print.cpp
+++ b/clang-tools-extra/test/clang-tidy/checkers/modernize/use-std-print.cpp
@@ -184,6 +184,11 @@ int printf_uses_return_value(int choice) {
   // CHECK-MESSAGES-NOT: [[@LINE-1]]:6: warning: use 'std::println' instead of 
'printf' [modernize-use-std-print]
   // CHECK-FIXES-NOT: std::println("GCC statement expression with unused 
result {}", i);
 
+label:
+  printf("Label target %d\n", i);
+  // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 
'printf' [modernize-use-std-print]
+  // CHECK-FIXES: std::println("Label target {}", i);
+
   return printf("Return value used in return\n");
 }
 

>From 768fe17d9005b644d82d036de565bf3057fb8f29 Mon Sep 17 00:00:00 2001
From: Victor Chernyakin <[email protected]>
Date: Sun, 1 Mar 2026 22:58:52 -0800
Subject: [PATCH 2/2] Turn slight slowdown into a speedup in
 `bugprone-unused-return-value`

---
 .../clang-tidy/bugprone/UnusedReturnValueCheck.cpp | 14 +++++++-------
 1 file changed, 7 insertions(+), 7 deletions(-)

diff --git a/clang-tools-extra/clang-tidy/bugprone/UnusedReturnValueCheck.cpp 
b/clang-tools-extra/clang-tidy/bugprone/UnusedReturnValueCheck.cpp
index 6c9aafd02b14e..334d901b0a423 100644
--- a/clang-tools-extra/clang-tidy/bugprone/UnusedReturnValueCheck.cpp
+++ b/clang-tools-extra/clang-tidy/bugprone/UnusedReturnValueCheck.cpp
@@ -171,7 +171,7 @@ void 
UnusedReturnValueCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
 }
 
 void UnusedReturnValueCheck::registerMatchers(MatchFinder *Finder) {
-  auto MatchedDirectCallExpr = expr(
+  auto MatchedDirectCallExpr =
       callExpr(callee(functionDecl(
                    // Don't match copy or move assignment operator.
                    unless(isAssignmentOverloadedOperator()),
@@ -182,17 +182,17 @@ void UnusedReturnValueCheck::registerMatchers(MatchFinder 
*Finder) {
                          returns(hasCanonicalType(hasDeclaration(
                              namedDecl(matchers::matchesAnyListedRegexName(
                                  CheckedReturnTypes)))))))))
-          .bind("match"));
+          .bind("match");
 
   auto CheckCastToVoid =
       AllowCastToVoid ? castExpr(unless(hasCastKind(CK_ToVoid))) : castExpr();
 
+  Finder->addMatcher(callExpr(MatchedDirectCallExpr, matchers::isDiscarded()),
+                     this);
   Finder->addMatcher(
-      expr(anyOf(MatchedDirectCallExpr,
-                 explicitCastExpr(unless(cxxFunctionalCastExpr()),
-                                  CheckCastToVoid,
-                                  hasSourceExpression(MatchedDirectCallExpr))),
-           matchers::isDiscarded()),
+      explicitCastExpr(unless(cxxFunctionalCastExpr()), CheckCastToVoid,
+                       hasSourceExpression(MatchedDirectCallExpr),
+                       matchers::isDiscarded()),
       this);
 }
 

_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to