[clang-tools-extra] [clang-tidy] Support C++26 placeholder bindings (PR #207604)
via cfe-commits
cfe-commits at lists.llvm.org
Wed Jul 8 04:27:34 PDT 2026
https://github.com/OmarAzizi updated https://github.com/llvm/llvm-project/pull/207604
>From 88fc084c01a661e276fd50ca932bb108a2045a89 Mon Sep 17 00:00:00 2001
From: OmarAzizi <oalazizi75 at gmail.com>
Date: Sun, 5 Jul 2026 21:47:18 +0300
Subject: [PATCH 1/3] [clang-tidy] Support C++26 placeholder bindings
---
.../clang-tidy/modernize/CMakeLists.txt | 1 +
.../modernize/ModernizeTidyModule.cpp | 3 +
.../modernize/UsePlaceholderBindingCheck.cpp | 88 ++++++++++
.../modernize/UsePlaceholderBindingCheck.h | 35 ++++
clang-tools-extra/docs/ReleaseNotes.rst | 7 +
.../docs/clang-tidy/checks/list.rst | 1 +
.../modernize/use-placeholder-binding.rst | 47 +++++
.../modernize/use-placeholder-binding.cpp | 163 ++++++++++++++++++
8 files changed, 345 insertions(+)
create mode 100644 clang-tools-extra/clang-tidy/modernize/UsePlaceholderBindingCheck.cpp
create mode 100644 clang-tools-extra/clang-tidy/modernize/UsePlaceholderBindingCheck.h
create mode 100644 clang-tools-extra/docs/clang-tidy/checks/modernize/use-placeholder-binding.rst
create mode 100644 clang-tools-extra/test/clang-tidy/checkers/modernize/use-placeholder-binding.cpp
diff --git a/clang-tools-extra/clang-tidy/modernize/CMakeLists.txt b/clang-tools-extra/clang-tidy/modernize/CMakeLists.txt
index 2c5c44db587fe..dca3abd63756f 100644
--- a/clang-tools-extra/clang-tidy/modernize/CMakeLists.txt
+++ b/clang-tools-extra/clang-tidy/modernize/CMakeLists.txt
@@ -44,6 +44,7 @@ add_clang_library(clangTidyModernizeModule STATIC
UseNoexceptCheck.cpp
UseNullptrCheck.cpp
UseOverrideCheck.cpp
+ UsePlaceholderBindingCheck.cpp
UseRangesCheck.cpp
UseScopedLockCheck.cpp
UseStartsEndsWithCheck.cpp
diff --git a/clang-tools-extra/clang-tidy/modernize/ModernizeTidyModule.cpp b/clang-tools-extra/clang-tidy/modernize/ModernizeTidyModule.cpp
index cc13da7535bcb..32241ea319b78 100644
--- a/clang-tools-extra/clang-tidy/modernize/ModernizeTidyModule.cpp
+++ b/clang-tools-extra/clang-tidy/modernize/ModernizeTidyModule.cpp
@@ -44,6 +44,7 @@
#include "UseNoexceptCheck.h"
#include "UseNullptrCheck.h"
#include "UseOverrideCheck.h"
+#include "UsePlaceholderBindingCheck.h"
#include "UseRangesCheck.h"
#include "UseScopedLockCheck.h"
#include "UseStartsEndsWithCheck.h"
@@ -92,6 +93,8 @@ class ModernizeModule : public ClangTidyModule {
"modernize-use-designated-initializers");
CheckFactories.registerCheck<UseIntegerSignComparisonCheck>(
"modernize-use-integer-sign-comparison");
+ CheckFactories.registerCheck<UsePlaceholderBindingCheck>(
+ "modernize-use-placeholder-binding");
CheckFactories.registerCheck<UseRangesCheck>("modernize-use-ranges");
CheckFactories.registerCheck<UseScopedLockCheck>(
"modernize-use-scoped-lock");
diff --git a/clang-tools-extra/clang-tidy/modernize/UsePlaceholderBindingCheck.cpp b/clang-tools-extra/clang-tidy/modernize/UsePlaceholderBindingCheck.cpp
new file mode 100644
index 0000000000000..8411b4396ff53
--- /dev/null
+++ b/clang-tools-extra/clang-tidy/modernize/UsePlaceholderBindingCheck.cpp
@@ -0,0 +1,88 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "UsePlaceholderBindingCheck.h"
+#include "../utils/DeclRefExprUtils.h"
+#include "clang/AST/ASTContext.h"
+#include "clang/ASTMatchers/ASTMatchFinder.h"
+#include "clang/Lex/Lexer.h"
+
+using namespace clang::ast_matchers;
+
+namespace clang::tidy::modernize {
+
+namespace {
+AST_POLYMORPHIC_MATCHER(isInMacro,
+ AST_POLYMORPHIC_SUPPORTED_TYPES(Stmt, Decl)) {
+ return Node.getBeginLoc().isMacroID() || Node.getEndLoc().isMacroID();
+}
+
+AST_MATCHER(VarDecl, hasAutomaticStorageDurationAndNoSpecifiers) {
+ return !Node.isStaticLocal() && !Node.isConstexpr() && !Node.hasAttrs() &&
+ Node.getStorageClass() == SC_None &&
+ Node.getTSCSpec() == TSCS_unspecified;
+}
+} // namespace
+
+void UsePlaceholderBindingCheck::registerMatchers(MatchFinder *Finder) {
+ Finder->addMatcher(
+ decompositionDecl(unless(isInMacro()),
+ hasAutomaticStorageDurationAndNoSpecifiers(),
+ hasAncestor(compoundStmt().bind("scope")))
+ .bind("decomp"),
+ this);
+}
+
+void UsePlaceholderBindingCheck::check(const MatchFinder::MatchResult &Result) {
+ const auto *Decomp = Result.Nodes.getNodeAs<DecompositionDecl>("decomp");
+ const auto *Scope = Result.Nodes.getNodeAs<CompoundStmt>("scope");
+ ASTContext &Context = *Result.Context;
+
+ for (const BindingDecl *Binding : Decomp->bindings()) {
+ if (Binding->isParameterPack() ||
+ Binding->isPlaceholderVar(Context.getLangOpts()))
+ continue;
+
+ llvm::SmallPtrSet<const DeclRefExpr *, 16> Refs =
+ utils::decl_ref_expr::allDeclRefExprs(*Binding, *Scope, Context);
+ if (Refs.size() != 1)
+ continue;
+
+ const DeclRefExpr *Ref = *Refs.begin();
+
+ const DynTypedNodeList RefParents = Context.getParents(*Ref);
+ if (RefParents.size() != 1)
+ continue;
+ const auto *Cast =
+ dyn_cast_or_null<CStyleCastExpr>(RefParents[0].get<Expr>());
+ if (!Cast || !Cast->getType()->isVoidType() ||
+ Cast->getSubExpr()->IgnoreParens() != Ref ||
+ Cast->getBeginLoc().isMacroID() || Cast->getEndLoc().isMacroID())
+ continue;
+
+ const DynTypedNodeList CastParents = Context.getParents(*Cast);
+ if (CastParents.size() != 1 || !CastParents[0].get<CompoundStmt>())
+ continue;
+
+ const SourceLocation SemiLoc = Lexer::findLocationAfterToken(
+ Cast->getEndLoc(), tok::semi, Context.getSourceManager(),
+ Context.getLangOpts(), /*SkipTrailingWhitespaceAndNewLine=*/true);
+ if (SemiLoc.isInvalid())
+ continue;
+
+ diag(Binding->getLocation(),
+ "binding %0 is only used to suppress an unused variable warning; "
+ "use a placeholder '_' instead")
+ << Binding
+ << FixItHint::CreateReplacement(Binding->getSourceRange(), "_")
+ << FixItHint::CreateRemoval(
+ CharSourceRange::getCharRange(Cast->getBeginLoc(), SemiLoc));
+ }
+}
+
+} // namespace clang::tidy::modernize
diff --git a/clang-tools-extra/clang-tidy/modernize/UsePlaceholderBindingCheck.h b/clang-tools-extra/clang-tidy/modernize/UsePlaceholderBindingCheck.h
new file mode 100644
index 0000000000000..4fff939f99138
--- /dev/null
+++ b/clang-tools-extra/clang-tidy/modernize/UsePlaceholderBindingCheck.h
@@ -0,0 +1,35 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_USEPLACEHOLDERBINDINGCHECK_H
+#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_USEPLACEHOLDERBINDINGCHECK_H
+
+#include "../ClangTidyCheck.h"
+
+namespace clang::tidy::modernize {
+
+/// Finds structured bindings where a binding is only used to suppress an
+/// "unused variable" warning via a ``(void)name;`` statement, and suggests
+/// replacing the binding with a C++26 placeholder (``_``) instead.
+///
+/// For the user-facing documentation see:
+/// https://clang.llvm.org/extra/clang-tidy/checks/modernize/use-placeholder-binding.html
+class UsePlaceholderBindingCheck : public ClangTidyCheck {
+public:
+ UsePlaceholderBindingCheck(StringRef Name, ClangTidyContext *Context)
+ : ClangTidyCheck(Name, Context) {}
+ void registerMatchers(ast_matchers::MatchFinder *Finder) override;
+ void check(const ast_matchers::MatchFinder::MatchResult &Result) override;
+ bool isLanguageVersionSupported(const LangOptions &LangOpts) const override {
+ return LangOpts.CPlusPlus26;
+ }
+};
+
+} // namespace clang::tidy::modernize
+
+#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_USEPLACEHOLDERBINDINGCHECK_H
diff --git a/clang-tools-extra/docs/ReleaseNotes.rst b/clang-tools-extra/docs/ReleaseNotes.rst
index 68f0fa568dbf9..018d0cdda8fba 100644
--- a/clang-tools-extra/docs/ReleaseNotes.rst
+++ b/clang-tools-extra/docs/ReleaseNotes.rst
@@ -265,6 +265,13 @@ New checks
Finds cyclical initialization of static variables.
+- New :doc:`modernize-use-placeholder-binding
+ <clang-tidy/checks/modernize/use-placeholder-binding>` check.
+
+ Finds structured bindings where a binding is only used to suppress an
+ unused variable warning and suggests replacing it with a C++26
+ placeholder (``_``).
+
- New :doc:`modernize-use-std-bit
<clang-tidy/checks/modernize/use-std-bit>` check.
diff --git a/clang-tools-extra/docs/clang-tidy/checks/list.rst b/clang-tools-extra/docs/clang-tidy/checks/list.rst
index 2a44dc78fbc89..4e1f98f29c08a 100644
--- a/clang-tools-extra/docs/clang-tidy/checks/list.rst
+++ b/clang-tools-extra/docs/clang-tidy/checks/list.rst
@@ -325,6 +325,7 @@ Clang-Tidy Checks
:doc:`modernize-use-noexcept <modernize/use-noexcept>`, "Yes"
:doc:`modernize-use-nullptr <modernize/use-nullptr>`, "Yes"
:doc:`modernize-use-override <modernize/use-override>`, "Yes"
+ :doc:`modernize-use-placeholder-binding <modernize/use-placeholder-binding>`, "Yes"
:doc:`modernize-use-ranges <modernize/use-ranges>`, "Yes"
:doc:`modernize-use-scoped-lock <modernize/use-scoped-lock>`, "Yes"
:doc:`modernize-use-starts-ends-with <modernize/use-starts-ends-with>`, "Yes"
diff --git a/clang-tools-extra/docs/clang-tidy/checks/modernize/use-placeholder-binding.rst b/clang-tools-extra/docs/clang-tidy/checks/modernize/use-placeholder-binding.rst
new file mode 100644
index 0000000000000..0f847454e30ae
--- /dev/null
+++ b/clang-tools-extra/docs/clang-tidy/checks/modernize/use-placeholder-binding.rst
@@ -0,0 +1,47 @@
+.. title:: clang-tidy - modernize-use-placeholder-binding
+
+modernize-use-placeholder-binding
+==================================
+
+Finds structured bindings where one of the bindings is only used to suppress
+an "unused variable" warning via a ``(void)name;`` statement, and suggests
+replacing that binding with a C++26 placeholder (``_``) instead, removing the
+now unnecessary suppression statement.
+
+.. code-block:: c++
+
+ const auto [x, y] = compute();
+ (void)y;
+ use(x);
+
+ // becomes
+
+ const auto [x, _] = compute();
+ use(x);
+
+This also applies to structured bindings declared in the init-statement of an
+``if``, ``switch``, or ``for`` statement:
+
+.. code-block:: c++
+
+ if (const auto [it, inserted] = set.insert(value); inserted) {
+ (void)it;
+ // ...
+ }
+
+ // becomes
+
+ if (const auto [_, inserted] = set.insert(value); inserted) {
+ // ...
+ }
+
+Limitations
+-----------
+
+The check only recognizes the ``(void)name;`` suppression idiom as a
+standalone statement directly inside a compound statement. It does not
+diagnose bindings that are simply unused with no suppression statement, nor
+bindings suppressed some other way. It also does not diagnose a suppression
+cast that is the sole substatement of a ``case`` or ``default`` label (for
+example, inside a ``switch``), since removing it safely would require
+special-casing the label rather than the statement.
diff --git a/clang-tools-extra/test/clang-tidy/checkers/modernize/use-placeholder-binding.cpp b/clang-tools-extra/test/clang-tidy/checkers/modernize/use-placeholder-binding.cpp
new file mode 100644
index 0000000000000..943b873c63023
--- /dev/null
+++ b/clang-tools-extra/test/clang-tidy/checkers/modernize/use-placeholder-binding.cpp
@@ -0,0 +1,163 @@
+// RUN: %check_clang_tidy -std=c++26-or-later %s modernize-use-placeholder-binding %t
+
+struct Pair {
+ int first;
+ bool second;
+};
+
+struct Triple {
+ int a, b, c;
+};
+
+Pair getPair();
+Triple getTriple();
+void use(int);
+
+void suppressedBindingIsReplaced() {
+ const auto [x, y] = getPair();
+ // CHECK-MESSAGES: :[[@LINE-1]]:18: warning: binding 'y' is only used to suppress an unused variable warning; use a placeholder '_' instead [modernize-use-placeholder-binding]
+ // CHECK-FIXES: const auto [x, _] = getPair();
+ (void)y;
+ // CHECK-FIXES-NOT: (void)y;
+ use(x);
+}
+
+void ifInitBindingIsReplaced() {
+ if (const auto [it, inserted] = getPair(); inserted) {
+ // CHECK-MESSAGES: :[[@LINE-1]]:19: warning: binding 'it' is only used to suppress an unused variable warning; use a placeholder '_' instead [modernize-use-placeholder-binding]
+ // CHECK-FIXES: if (const auto [_, inserted] = getPair(); inserted) {
+ (void)it;
+ // CHECK-FIXES-NOT: (void)it;
+ }
+}
+
+void bothBindingsUsed() {
+ const auto [x, y] = getPair();
+ use(x);
+ if (y)
+ use(x);
+}
+
+void noSuppressionCast() {
+ const auto [x, y] = getPair();
+ use(x);
+}
+
+void alreadyPlaceholder() {
+ const auto [x, _] = getPair();
+ use(x);
+}
+
+void bothBindingsSuppressed() {
+ auto [x, y] = getPair();
+ // CHECK-MESSAGES: :[[@LINE-1]]:9: warning: binding 'x' is only used to suppress an unused variable warning; use a placeholder '_' instead [modernize-use-placeholder-binding]
+ // CHECK-MESSAGES: :[[@LINE-2]]:12: warning: binding 'y' is only used to suppress an unused variable warning; use a placeholder '_' instead [modernize-use-placeholder-binding]
+ // CHECK-FIXES: auto [_, _] = getPair();
+ (void)x;
+ // CHECK-FIXES-NOT: (void)x;
+ (void)y;
+ // CHECK-FIXES-NOT: (void)y;
+}
+
+void threeBindings() {
+ auto [a, b, c] = getTriple();
+ // CHECK-MESSAGES: :[[@LINE-1]]:12: warning: binding 'b' is only used to suppress an unused variable warning; use a placeholder '_' instead [modernize-use-placeholder-binding]
+ // CHECK-MESSAGES: :[[@LINE-2]]:15: warning: binding 'c' is only used to suppress an unused variable warning; use a placeholder '_' instead [modernize-use-placeholder-binding]
+ // CHECK-FIXES: auto [a, _, _] = getTriple();
+ (void)b;
+ (void)c;
+ use(a);
+}
+
+void referenceBinding() {
+ Pair p{};
+ auto &[x, y] = p;
+ // CHECK-MESSAGES: :[[@LINE-1]]:13: warning: binding 'y' is only used to suppress an unused variable warning; use a placeholder '_' instead [modernize-use-placeholder-binding]
+ // CHECK-FIXES: auto &[x, _] = p;
+ (void)y;
+ use(x);
+}
+
+struct Item {
+ int key;
+ int value;
+};
+
+void forRangeBindingIsReplaced() {
+ Item items[1] = {};
+ for (auto [k, v] : items) {
+ // CHECK-MESSAGES: :[[@LINE-1]]:17: warning: binding 'v' is only used to suppress an unused variable warning; use a placeholder '_' instead [modernize-use-placeholder-binding]
+ // CHECK-FIXES: for (auto [k, _] : items) {
+ (void)v;
+ // CHECK-FIXES-NOT: (void)v;
+ use(k);
+ }
+}
+
+void switchCaseSuppressionIsNotDiagnosed() {
+ switch (auto [a, b] = getPair(); a) {
+ case 0:
+ (void)b;
+ break;
+ }
+}
+
+void lambdaCaptureIsUnaffected() {
+ auto [x, y] = getPair();
+ // CHECK-MESSAGES: :[[@LINE-1]]:12: warning: binding 'y' is only used to suppress an unused variable warning; use a placeholder '_' instead [modernize-use-placeholder-binding]
+ // CHECK-FIXES: auto [x, _] = getPair();
+ (void)y;
+ // CHECK-FIXES-NOT: (void)y;
+ auto l = [x] { use(x); };
+ l();
+}
+
+#define SUPPRESS(x) (void)(x)
+void macroSuppressionIsNotDiagnosed() {
+ auto [x, y] = getPair();
+ SUPPRESS(y);
+ use(x);
+}
+
+void multipleDecompositionsInSameScope() {
+ auto [x1, y1] = getPair();
+ // CHECK-MESSAGES: :[[@LINE-1]]:13: warning: binding 'y1' is only used to suppress an unused variable warning; use a placeholder '_' instead [modernize-use-placeholder-binding]
+ // CHECK-FIXES: auto [x1, _] = getPair();
+ (void)y1;
+ // CHECK-FIXES-NOT: (void)y1;
+ use(x1);
+
+ auto [x2, y2] = getPair();
+ // CHECK-MESSAGES: :[[@LINE-1]]:9: warning: binding 'x2' is only used to suppress an unused variable warning; use a placeholder '_' instead [modernize-use-placeholder-binding]
+ // CHECK-FIXES: auto [_, y2] = getPair();
+ (void)x2;
+ // CHECK-FIXES-NOT: (void)x2;
+ use(y2 ? 1 : 0);
+}
+
+Pair &&getPairRef();
+
+void rvalueReferenceBinding() {
+ auto &&[x, y] = getPairRef();
+ // CHECK-MESSAGES: :[[@LINE-1]]:14: warning: binding 'y' is only used to suppress an unused variable warning; use a placeholder '_' instead [modernize-use-placeholder-binding]
+ // CHECK-FIXES: auto &&[x, _] = getPairRef();
+ (void)y;
+ // CHECK-FIXES-NOT: (void)y;
+ use(x);
+ // CHECK-FIXES: use(x);
+}
+
+void staticBindingIsNotDiagnosed() {
+ static auto [x1, y1] = getPair();
+ (void)y1;
+ static auto [x2, y2] = getPair();
+ (void)y2;
+ use(x1);
+ use(x2);
+}
+
+void threadLocalBindingIsNotDiagnosed() {
+ thread_local auto [x, y] = getPair();
+ (void)y;
+ use(x);
+}
>From 2ce4174e35683659933950e1f270ee5f95aeadd7 Mon Sep 17 00:00:00 2001
From: OmarAzizi <oalazizi75 at gmail.com>
Date: Sun, 5 Jul 2026 22:39:30 +0300
Subject: [PATCH 2/3] [clang-tidy] fix linter error in the CI
---
.../clang-tidy/modernize/UsePlaceholderBindingCheck.cpp | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/clang-tools-extra/clang-tidy/modernize/UsePlaceholderBindingCheck.cpp b/clang-tools-extra/clang-tidy/modernize/UsePlaceholderBindingCheck.cpp
index 8411b4396ff53..95282b6d84530 100644
--- a/clang-tools-extra/clang-tidy/modernize/UsePlaceholderBindingCheck.cpp
+++ b/clang-tools-extra/clang-tidy/modernize/UsePlaceholderBindingCheck.cpp
@@ -48,7 +48,7 @@ void UsePlaceholderBindingCheck::check(const MatchFinder::MatchResult &Result) {
Binding->isPlaceholderVar(Context.getLangOpts()))
continue;
- llvm::SmallPtrSet<const DeclRefExpr *, 16> Refs =
+ const llvm::SmallPtrSet<const DeclRefExpr *, 16> Refs =
utils::decl_ref_expr::allDeclRefExprs(*Binding, *Scope, Context);
if (Refs.size() != 1)
continue;
>From 5d38a3ac648bde83d12ed6e5e39c38d5347cfdda Mon Sep 17 00:00:00 2001
From: OmarAzizi <oalazizi75 at gmail.com>
Date: Wed, 8 Jul 2026 14:26:34 +0300
Subject: [PATCH 3/3] [clang-tidy] Address review feedback on
UsePlaceholderBindingCheck
Drop redundant matcher conditions, move suppression-cast matching
into the AST matcher, support case/default labels, fix doc nits.
---
.../modernize/UsePlaceholderBindingCheck.cpp | 76 ++++++++-----------
clang-tools-extra/docs/ReleaseNotes.rst | 7 +-
.../modernize/use-placeholder-binding.rst | 30 ++++++--
.../modernize/use-placeholder-binding.cpp | 15 +++-
4 files changed, 73 insertions(+), 55 deletions(-)
diff --git a/clang-tools-extra/clang-tidy/modernize/UsePlaceholderBindingCheck.cpp b/clang-tools-extra/clang-tidy/modernize/UsePlaceholderBindingCheck.cpp
index 95282b6d84530..c0379a5e34acc 100644
--- a/clang-tools-extra/clang-tidy/modernize/UsePlaceholderBindingCheck.cpp
+++ b/clang-tools-extra/clang-tidy/modernize/UsePlaceholderBindingCheck.cpp
@@ -23,66 +23,54 @@ AST_POLYMORPHIC_MATCHER(isInMacro,
}
AST_MATCHER(VarDecl, hasAutomaticStorageDurationAndNoSpecifiers) {
- return !Node.isStaticLocal() && !Node.isConstexpr() && !Node.hasAttrs() &&
- Node.getStorageClass() == SC_None &&
+ return !Node.hasAttrs() && Node.getStorageClass() == SC_None &&
Node.getTSCSpec() == TSCS_unspecified;
}
} // namespace
void UsePlaceholderBindingCheck::registerMatchers(MatchFinder *Finder) {
Finder->addMatcher(
- decompositionDecl(unless(isInMacro()),
- hasAutomaticStorageDurationAndNoSpecifiers(),
- hasAncestor(compoundStmt().bind("scope")))
- .bind("decomp"),
+ cStyleCastExpr(
+ unless(isInMacro()), hasType(voidType()),
+ hasParent(stmt(anyOf(compoundStmt(), switchCase()))),
+ hasSourceExpression(ignoringParens(declRefExpr(
+ unless(isInMacro()),
+ to(bindingDecl(forDecomposition(varDecl(
+ unless(isInMacro()),
+ hasAutomaticStorageDurationAndNoSpecifiers(),
+ hasAncestor(compoundStmt().bind("scope")))))
+ .bind("binding"))))))
+ .bind("cast"),
this);
}
void UsePlaceholderBindingCheck::check(const MatchFinder::MatchResult &Result) {
- const auto *Decomp = Result.Nodes.getNodeAs<DecompositionDecl>("decomp");
+ const auto *Cast = Result.Nodes.getNodeAs<CStyleCastExpr>("cast");
+ const auto *Binding = Result.Nodes.getNodeAs<BindingDecl>("binding");
const auto *Scope = Result.Nodes.getNodeAs<CompoundStmt>("scope");
ASTContext &Context = *Result.Context;
- for (const BindingDecl *Binding : Decomp->bindings()) {
- if (Binding->isParameterPack() ||
- Binding->isPlaceholderVar(Context.getLangOpts()))
- continue;
+ if (Binding->isParameterPack() ||
+ Binding->isPlaceholderVar(Context.getLangOpts()))
+ return;
- const llvm::SmallPtrSet<const DeclRefExpr *, 16> Refs =
- utils::decl_ref_expr::allDeclRefExprs(*Binding, *Scope, Context);
- if (Refs.size() != 1)
- continue;
+ const llvm::SmallPtrSet<const DeclRefExpr *, 16> Refs =
+ utils::decl_ref_expr::allDeclRefExprs(*Binding, *Scope, Context);
+ if (Refs.size() != 1)
+ return;
- const DeclRefExpr *Ref = *Refs.begin();
+ const SourceLocation SemiLoc = Lexer::findLocationAfterToken(
+ Cast->getEndLoc(), tok::semi, Context.getSourceManager(),
+ Context.getLangOpts(), /*SkipTrailingWhitespaceAndNewLine=*/true);
+ if (SemiLoc.isInvalid())
+ return;
- const DynTypedNodeList RefParents = Context.getParents(*Ref);
- if (RefParents.size() != 1)
- continue;
- const auto *Cast =
- dyn_cast_or_null<CStyleCastExpr>(RefParents[0].get<Expr>());
- if (!Cast || !Cast->getType()->isVoidType() ||
- Cast->getSubExpr()->IgnoreParens() != Ref ||
- Cast->getBeginLoc().isMacroID() || Cast->getEndLoc().isMacroID())
- continue;
-
- const DynTypedNodeList CastParents = Context.getParents(*Cast);
- if (CastParents.size() != 1 || !CastParents[0].get<CompoundStmt>())
- continue;
-
- const SourceLocation SemiLoc = Lexer::findLocationAfterToken(
- Cast->getEndLoc(), tok::semi, Context.getSourceManager(),
- Context.getLangOpts(), /*SkipTrailingWhitespaceAndNewLine=*/true);
- if (SemiLoc.isInvalid())
- continue;
-
- diag(Binding->getLocation(),
- "binding %0 is only used to suppress an unused variable warning; "
- "use a placeholder '_' instead")
- << Binding
- << FixItHint::CreateReplacement(Binding->getSourceRange(), "_")
- << FixItHint::CreateRemoval(
- CharSourceRange::getCharRange(Cast->getBeginLoc(), SemiLoc));
- }
+ diag(Binding->getLocation(),
+ "binding %0 is only used to suppress an unused variable warning; "
+ "use a placeholder '_' instead")
+ << Binding << FixItHint::CreateReplacement(Binding->getSourceRange(), "_")
+ << FixItHint::CreateRemoval(
+ CharSourceRange::getCharRange(Cast->getBeginLoc(), SemiLoc));
}
} // namespace clang::tidy::modernize
diff --git a/clang-tools-extra/docs/ReleaseNotes.rst b/clang-tools-extra/docs/ReleaseNotes.rst
index 018d0cdda8fba..c6fcbcc5cb39a 100644
--- a/clang-tools-extra/docs/ReleaseNotes.rst
+++ b/clang-tools-extra/docs/ReleaseNotes.rst
@@ -268,9 +268,10 @@ New checks
- New :doc:`modernize-use-placeholder-binding
<clang-tidy/checks/modernize/use-placeholder-binding>` check.
- Finds structured bindings where a binding is only used to suppress an
- unused variable warning and suggests replacing it with a C++26
- placeholder (``_``).
+ Finds structured bindings where one of the bindings is only used to
+ suppress an "unused variable" warning via a ``(void)name;`` statement,
+ and suggests replacing that binding with a C++26 placeholder (``_``)
+ instead, removing the now unnecessary suppression statement.
- New :doc:`modernize-use-std-bit
<clang-tidy/checks/modernize/use-std-bit>` check.
diff --git a/clang-tools-extra/docs/clang-tidy/checks/modernize/use-placeholder-binding.rst b/clang-tools-extra/docs/clang-tidy/checks/modernize/use-placeholder-binding.rst
index 0f847454e30ae..c81b8e9035965 100644
--- a/clang-tools-extra/docs/clang-tidy/checks/modernize/use-placeholder-binding.rst
+++ b/clang-tools-extra/docs/clang-tidy/checks/modernize/use-placeholder-binding.rst
@@ -1,7 +1,7 @@
.. title:: clang-tidy - modernize-use-placeholder-binding
modernize-use-placeholder-binding
-==================================
+=================================
Finds structured bindings where one of the bindings is only used to suppress
an "unused variable" warning via a ``(void)name;`` statement, and suggests
@@ -35,13 +35,29 @@ This also applies to structured bindings declared in the init-statement of an
// ...
}
+The suppression statement is also recognized when it is the sole
+substatement of a ``case`` or ``default`` label:
+
+.. code-block:: c++
+
+ switch (auto [a, b] = compute(); a) {
+ case 0:
+ (void)b;
+ break;
+ }
+
+ // becomes
+
+ switch (auto [a, _] = compute(); a) {
+ case 0:
+ break;
+ }
+
Limitations
-----------
The check only recognizes the ``(void)name;`` suppression idiom as a
-standalone statement directly inside a compound statement. It does not
-diagnose bindings that are simply unused with no suppression statement, nor
-bindings suppressed some other way. It also does not diagnose a suppression
-cast that is the sole substatement of a ``case`` or ``default`` label (for
-example, inside a ``switch``), since removing it safely would require
-special-casing the label rather than the statement.
+standalone statement directly inside a compound statement, or as the sole
+substatement of a ``case`` or ``default`` label. It does not diagnose
+bindings that are simply unused with no suppression statement, nor bindings
+suppressed some other way.
diff --git a/clang-tools-extra/test/clang-tidy/checkers/modernize/use-placeholder-binding.cpp b/clang-tools-extra/test/clang-tidy/checkers/modernize/use-placeholder-binding.cpp
index 943b873c63023..89b5e13e26b51 100644
--- a/clang-tools-extra/test/clang-tidy/checkers/modernize/use-placeholder-binding.cpp
+++ b/clang-tools-extra/test/clang-tidy/checkers/modernize/use-placeholder-binding.cpp
@@ -94,14 +94,27 @@ void forRangeBindingIsReplaced() {
}
}
-void switchCaseSuppressionIsNotDiagnosed() {
+void switchCaseBindingIsReplaced() {
switch (auto [a, b] = getPair(); a) {
+ // CHECK-MESSAGES: :[[@LINE-1]]:20: warning: binding 'b' is only used to suppress an unused variable warning; use a placeholder '_' instead [modernize-use-placeholder-binding]
+ // CHECK-FIXES: switch (auto [a, _] = getPair(); a) {
case 0:
(void)b;
+ // CHECK-FIXES-NOT: (void)b;
break;
}
}
+void defaultCaseSoleSubstatementIsReplaced() {
+ switch (auto [a, b] = getPair(); a) {
+ // CHECK-MESSAGES: :[[@LINE-1]]:20: warning: binding 'b' is only used to suppress an unused variable warning; use a placeholder '_' instead [modernize-use-placeholder-binding]
+ // CHECK-FIXES: switch (auto [a, _] = getPair(); a) {
+ default:
+ (void)b;
+ // CHECK-FIXES-NOT: (void)b;
+ }
+}
+
void lambdaCaptureIsUnaffected() {
auto [x, y] = getPair();
// CHECK-MESSAGES: :[[@LINE-1]]:12: warning: binding 'y' is only used to suppress an unused variable warning; use a placeholder '_' instead [modernize-use-placeholder-binding]
More information about the cfe-commits
mailing list