[clang-tools-extra] [clang-tidy] Centralize the logic for matching discarded expressions (PR #184069)
Victor Chernyakin via cfe-commits
cfe-commits at lists.llvm.org
Wed Mar 4 12:54:36 PST 2026
https://github.com/localspook updated https://github.com/llvm/llvm-project/pull/184069
>From 170fffed2bd9a7a780c92da7c895df511efe66fc Mon Sep 17 00:00:00 2001
From: Victor Chernyakin <chernyakin.victor.j at outlook.com>
Date: Sun, 1 Mar 2026 22:07:12 -0800
Subject: [PATCH 1/4] [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 <chernyakin.victor.j at outlook.com>
Date: Sun, 1 Mar 2026 22:58:52 -0800
Subject: [PATCH 2/4] 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);
}
>From c511854426e9190ea2f1b285a681f941146547ea Mon Sep 17 00:00:00 2001
From: Victor Chernyakin <chernyakin.victor.j at outlook.com>
Date: Sun, 1 Mar 2026 23:08:07 -0800
Subject: [PATCH 3/4] Remove unused locals
---
clang-tools-extra/clang-tidy/utils/Matchers.h | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/clang-tools-extra/clang-tidy/utils/Matchers.h b/clang-tools-extra/clang-tidy/utils/Matchers.h
index b9e8adbba8dfc..e3b7d612bc63c 100644
--- a/clang-tools-extra/clang-tidy/utils/Matchers.h
+++ b/clang-tools-extra/clang-tidy/utils/Matchers.h
@@ -111,13 +111,13 @@ AST_MATCHER(Expr, isDiscarded) {
if (const auto *Switch = Parent.get<SwitchStmt>())
return IsCurrentNode(Switch->getBody());
- if (const auto *Case = Parent.get<SwitchCase>())
+ if (Parent.get<SwitchCase>())
return true;
- if (const auto *Label = Parent.get<LabelStmt>())
+ if (Parent.get<LabelStmt>())
return true;
- if (const auto *AttrStmt = Parent.get<AttributedStmt>())
+ if (Parent.get<AttributedStmt>())
return true;
if (const auto *Compound = Parent.get<CompoundStmt>()) {
>From 674c2597842964ab25fe40e94857be549d474fd0 Mon Sep 17 00:00:00 2001
From: Victor Chernyakin <chernyakin.victor.j at outlook.com>
Date: Wed, 4 Mar 2026 13:53:18 -0700
Subject: [PATCH 4/4] Add more tests
---
.../checkers/bugprone/standalone-empty.cpp | 3 +++
.../checkers/bugprone/throw-keyword-missing.cpp | 12 ++++++++++++
.../clang-tidy/checkers/bugprone/unused-raii.cpp | 4 ++++
.../checkers/bugprone/unused-return-value.cpp | 16 ++++++++++++++++
.../checkers/modernize/use-std-print.cpp | 7 +++++++
5 files changed, 42 insertions(+)
diff --git a/clang-tools-extra/test/clang-tidy/checkers/bugprone/standalone-empty.cpp b/clang-tools-extra/test/clang-tidy/checkers/bugprone/standalone-empty.cpp
index bbb48d06ed924..1aa448c8aa2ff 100644
--- a/clang-tools-extra/test/clang-tidy/checkers/bugprone/standalone-empty.cpp
+++ b/clang-tools-extra/test/clang-tidy/checkers/bugprone/standalone-empty.cpp
@@ -630,6 +630,9 @@ void test_empty_expressions() {
bool StmtExprReturn = ({std::empty(s); std::empty(s);});
// CHECK-MESSAGES: :[[#@LINE-1]]:27: warning: ignoring the result of 'std::empty' [bugprone-standalone-empty]
+
+ // FIXME: This is a false negative.
+ ({ std::empty(s); });
}
bool test_clear_in_base_class() {
diff --git a/clang-tools-extra/test/clang-tidy/checkers/bugprone/throw-keyword-missing.cpp b/clang-tools-extra/test/clang-tidy/checkers/bugprone/throw-keyword-missing.cpp
index 34326c4799e7b..c3bbd6e01e2ec 100644
--- a/clang-tools-extra/test/clang-tidy/checkers/bugprone/throw-keyword-missing.cpp
+++ b/clang-tools-extra/test/clang-tidy/checkers/bugprone/throw-keyword-missing.cpp
@@ -226,3 +226,15 @@ struct ExceptionInConstructorTest {
RegularException{0};
}
};
+
+void statementExpressionTest() {
+ auto E = ({
+ // CHECK-MESSAGES: :[[@LINE+1]]:5: warning: suspicious exception
+ RegularException();
+
+ RegularException();
+ });
+
+ // FIXME: We should probably warn here.
+ ({ RegularException(); });
+}
diff --git a/clang-tools-extra/test/clang-tidy/checkers/bugprone/unused-raii.cpp b/clang-tools-extra/test/clang-tidy/checkers/bugprone/unused-raii.cpp
index 1b285768fdb33..30acb0eb0a7c9 100644
--- a/clang-tools-extra/test/clang-tidy/checkers/bugprone/unused-raii.cpp
+++ b/clang-tools-extra/test/clang-tidy/checkers/bugprone/unused-raii.cpp
@@ -145,6 +145,10 @@ void test() {
// CHECK-MESSAGES: :[[@LINE-1]]:3: warning: object destroyed immediately after creation; did you mean to name the object?
// CHECK-FIXES: TCtorDefaultArg<int> give_me_a_name;
+ for (Foo(42);;) continue;
+ // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: object destroyed immediately after creation; did you mean to name the object?
+ // CHECK-FIXES: for (Foo give_me_a_name(42);;) continue;
+
templ<FooBar>();
templ<Bar>();
diff --git a/clang-tools-extra/test/clang-tidy/checkers/bugprone/unused-return-value.cpp b/clang-tools-extra/test/clang-tidy/checkers/bugprone/unused-return-value.cpp
index e784c9b85172c..07b5cd9a705e4 100644
--- a/clang-tools-extra/test/clang-tidy/checkers/bugprone/unused-return-value.cpp
+++ b/clang-tools-extra/test/clang-tidy/checkers/bugprone/unused-return-value.cpp
@@ -203,6 +203,22 @@ void warning() {
// CHECK-MESSAGES: [[@LINE-2]]:5: note: cast the expression to void to silence this warning
}
+label:
+ std::remove(nullptr, nullptr, 1);
+ // CHECK-MESSAGES: [[@LINE-1]]:3: warning: the value returned by this function should not be disregarded; neglecting it may lead to errors
+ // CHECK-MESSAGES: [[@LINE-2]]:3: note: cast the expression to void to silence this warning
+
+ auto v = ({
+ std::remove(nullptr, nullptr, 1);
+ // CHECK-MESSAGES: [[@LINE-1]]:5: warning: the value returned by this function should not be disregarded; neglecting it may lead to errors
+ // CHECK-MESSAGES: [[@LINE-2]]:5: note: cast the expression to void to silence this warning
+
+ std::remove(nullptr, nullptr, 1);
+ });
+
+ // FIXME: This is a false negative.
+ ({ std::remove(nullptr, nullptr, 1); });
+
errorFunc();
// CHECK-MESSAGES: [[@LINE-1]]:3: warning: the value returned by this function should not be disregarded; neglecting it may lead to errors
// CHECK-MESSAGES: [[@LINE-2]]:3: note: cast the expression to void to silence this warning
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 e202a9d194113..28e9508d00f3d 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
@@ -178,6 +178,13 @@ int printf_uses_return_value(int choice) {
const int x = ({ printf("GCC statement expression using return value immediately %d\n", i); });
const int y = ({ const int y = printf("GCC statement expression using return value immediately %d\n", i); y; });
+ ({
+ printf("Inside GCC statement expression %d\n", i);
+ // CHECK-MESSAGES: [[@LINE-1]]:5: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+ // CHECK-FIXES: std::println("Inside GCC statement expression {}", i);
+ 10;
+ });
+
// Ideally we would convert this one, but the current check doesn't cope with
// that.
({ printf("GCC statement expression with unused result %d\n", i); });
More information about the cfe-commits
mailing list