https://github.com/OmarAzizi updated https://github.com/llvm/llvm-project/pull/207604
>From 88fc084c01a661e276fd50ca932bb108a2045a89 Mon Sep 17 00:00:00 2001 From: OmarAzizi <[email protected]> Date: Sun, 5 Jul 2026 21:47:18 +0300 Subject: [PATCH 1/2] [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 <[email protected]> Date: Sun, 5 Jul 2026 22:39:30 +0300 Subject: [PATCH 2/2] [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; _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
