[clang-tools-extra] [clang-tidy] Add new check `modernize-use-structured-binding` (PR #158462)
via cfe-commits
cfe-commits at lists.llvm.org
Thu Jan 15 06:46:04 PST 2026
================
@@ -0,0 +1,400 @@
+//===----------------------------------------------------------------------===//
+//
+// 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 "UseStructuredBindingCheck.h"
+#include "../utils/DeclRefExprUtils.h"
+#include "clang/Lex/Lexer.h"
+
+using namespace clang::ast_matchers;
+
+namespace clang::tidy::modernize {
+
+static constexpr StringRef PairDeclName = "PairVarD";
+static constexpr StringRef PairVarTypeName = "PairVarType";
+static constexpr StringRef FirstVarDeclName = "FirstVarDecl";
+static constexpr StringRef SecondVarDeclName = "SecondVarDecl";
+static constexpr StringRef BeginDeclStmtName = "BeginDeclStmt";
+static constexpr StringRef EndDeclStmtName = "EndDeclStmt";
+static constexpr StringRef FirstTypeName = "FirstType";
+static constexpr StringRef SecondTypeName = "SecondType";
+static constexpr StringRef ScopeBlockName = "ScopeBlock";
+static constexpr StringRef StdTieAssignStmtName = "StdTieAssign";
+static constexpr StringRef StdTieExprName = "StdTieExpr";
+static constexpr StringRef ForRangeStmtName = "ForRangeStmt";
+static constexpr StringRef InitExprName = "init_expr";
+
+/// Matches a sequence of VarDecls matching the inner matchers, starting from
+/// the \p Iter to \p EndIter and set bindings for the first DeclStmt and the
+/// last DeclStmt if matched.
+///
+/// \p Backwards indicates whether to match the VarDecls in reverse order.
+template <typename Iterator>
+static bool matchNVarDeclStartingWith(
+ Iterator Iter, const Iterator &EndIter,
+ ArrayRef<ast_matchers::internal::Matcher<VarDecl>> InnerMatchers,
+ ast_matchers::internal::ASTMatchFinder *Finder,
+ ast_matchers::internal::BoundNodesTreeBuilder *Builder,
+ bool Backwards = false) {
+ const DeclStmt *BeginDS = nullptr;
+ const DeclStmt *EndDS = nullptr;
+ const size_t N = InnerMatchers.size();
+ size_t Count = 0;
+
+ auto Matches = [&](const Decl *VD) {
+ // We don't want redundant decls in DeclStmt.
+ if (Count == N)
+ return false;
+
+ if (const auto *Var = dyn_cast<VarDecl>(VD);
+ Var && InnerMatchers[Backwards ? N - Count - 1 : Count].matches(
+ *Var, Finder, Builder)) {
+ ++Count;
+ return true;
+ }
+
+ return false;
+ };
+
+ for (; Iter != EndIter; ++Iter) {
+ EndDS = dyn_cast<DeclStmt>(*Iter);
+ if (!EndDS)
+ break;
+
+ if (!BeginDS)
+ BeginDS = EndDS;
+
+ for (const auto *VD :
+ llvm::reverse_conditionally(EndDS->decls(), Backwards)) {
+ if (!Matches(VD))
+ return false;
+ }
+
+ // All the matchers is satisfied in those DeclStmts.
+ if (Count == N) {
+ if (Backwards)
+ std::swap(BeginDS, EndDS);
+ Builder->setBinding(BeginDeclStmtName, DynTypedNode::create(*BeginDS));
+ Builder->setBinding(EndDeclStmtName, DynTypedNode::create(*EndDS));
+ return true;
+ }
+ }
+
+ return false;
+}
+
+namespace {
+/// What qualifiers and specifiers are used to create structured binding
+/// declaration, it only supports the following four cases now.
+enum TransferType : uint8_t {
+ TT_ByVal,
+ TT_ByConstVal,
+ TT_ByRef,
+ TT_ByConstRef
+};
+
+/// Matches a Stmt whose parent is a CompoundStmt, and which is directly
+/// following two VarDecls matching the inner matcher.
+AST_MATCHER_P(Stmt, hasPreTwoVarDecl,
+ llvm::SmallVector<ast_matchers::internal::Matcher<VarDecl>>,
+ InnerMatchers) {
+ const DynTypedNodeList Parents = Finder->getASTContext().getParents(Node);
+ if (Parents.size() != 1)
+ return false;
+
+ const auto *C = Parents[0].get<CompoundStmt>();
+ if (!C)
+ return false;
+
+ const auto It = llvm::find(llvm::reverse(C->body()), &Node);
+ assert(It != C->body_rend() && "C is parent of Node");
+ return matchNVarDeclStartingWith(It + 1, C->body_rend(), InnerMatchers,
+ Finder, Builder, true);
+}
+
+/// Matches a Stmt whose parent is a CompoundStmt, and which is directly
+/// followed by two VarDecls matching the inner matcher.
+AST_MATCHER_P(Stmt, hasNextTwoVarDecl,
+ llvm::SmallVector<ast_matchers::internal::Matcher<VarDecl>>,
+ InnerMatchers) {
+ const DynTypedNodeList Parents = Finder->getASTContext().getParents(Node);
+ if (Parents.size() != 1)
+ return false;
+
+ const auto *C = Parents[0].get<CompoundStmt>();
+ if (!C)
+ return false;
+
+ const auto *It = llvm::find(C->body(), &Node);
+ assert(It != C->body_end() && "C is parent of Node");
+ return matchNVarDeclStartingWith(It + 1, C->body_end(), InnerMatchers, Finder,
+ Builder);
+}
+
+/// Matches a CompoundStmt which has two VarDecls matching the inner matcher in
+/// the beginning.
+AST_MATCHER_P(CompoundStmt, hasFirstTwoVarDecl,
+ llvm::SmallVector<ast_matchers::internal::Matcher<VarDecl>>,
+ InnerMatchers) {
+ return matchNVarDeclStartingWith(Node.body_begin(), Node.body_end(),
+ InnerMatchers, Finder, Builder);
+}
+
+/// It's not very common to have specifiers for variables used to decompose a
+/// pair, so we ignore these cases.
+AST_MATCHER(VarDecl, hasAnySpecifiersShouldBeIgnored) {
----------------
flovent wrote:
Sorry i hadn't considered `std::tie`case, `extern` is now ignored too!
https://github.com/llvm/llvm-project/pull/158462
More information about the cfe-commits
mailing list