From a1941312179171a8752c27f886158ce4fd90db33 Mon Sep 17 00:00:00 2001 From: flovent Date: Sun, 14 Sep 2025 14:33:59 +0800 Subject: [PATCH 01/44] [clang-tidy] Add new check `modernize-use-structured-binding` --- .../clang-tidy/modernize/CMakeLists.txt | 1 + .../modernize/ModernizeTidyModule.cpp | 3 + .../modernize/UseStructuredBindingCheck.cpp | 419 ++++++++++++++++++ .../modernize/UseStructuredBindingCheck.h | 36 ++ clang-tools-extra/docs/ReleaseNotes.rst | 6 + .../docs/clang-tidy/checks/list.rst | 1 + .../modernize/use-structured-binding.rst | 58 +++ .../fake_std_pair_tuple.h | 23 + .../use-structured-binding-custom.cpp | 32 ++ ...d-binding-skip-lambda-capture-in-cxx17.cpp | 67 +++ .../modernize/use-structured-binding.cpp | 216 +++++++++ 11 files changed, 862 insertions(+) create mode 100644 clang-tools-extra/clang-tidy/modernize/UseStructuredBindingCheck.cpp create mode 100644 clang-tools-extra/clang-tidy/modernize/UseStructuredBindingCheck.h create mode 100644 clang-tools-extra/docs/clang-tidy/checks/modernize/use-structured-binding.rst create mode 100644 clang-tools-extra/test/clang-tidy/checkers/modernize/Inputs/use-structured-binding/fake_std_pair_tuple.h create mode 100644 clang-tools-extra/test/clang-tidy/checkers/modernize/use-structured-binding-custom.cpp create mode 100644 clang-tools-extra/test/clang-tidy/checkers/modernize/use-structured-binding-skip-lambda-capture-in-cxx17.cpp create mode 100644 clang-tools-extra/test/clang-tidy/checkers/modernize/use-structured-binding.cpp diff --git a/clang-tools-extra/clang-tidy/modernize/CMakeLists.txt b/clang-tools-extra/clang-tidy/modernize/CMakeLists.txt index 619a27b2f9bb6..094f0a72b1570 100644 --- a/clang-tools-extra/clang-tidy/modernize/CMakeLists.txt +++ b/clang-tools-extra/clang-tidy/modernize/CMakeLists.txt @@ -47,6 +47,7 @@ add_clang_library(clangTidyModernizeModule STATIC UseStdFormatCheck.cpp UseStdNumbersCheck.cpp UseStdPrintCheck.cpp + UseStructuredBindingCheck.cpp UseTrailingReturnTypeCheck.cpp UseTransparentFunctorsCheck.cpp UseUncaughtExceptionsCheck.cpp diff --git a/clang-tools-extra/clang-tidy/modernize/ModernizeTidyModule.cpp b/clang-tools-extra/clang-tidy/modernize/ModernizeTidyModule.cpp index fdf38bc4b6308..a79908500e904 100644 --- a/clang-tools-extra/clang-tidy/modernize/ModernizeTidyModule.cpp +++ b/clang-tools-extra/clang-tidy/modernize/ModernizeTidyModule.cpp @@ -48,6 +48,7 @@ #include "UseStdFormatCheck.h" #include "UseStdNumbersCheck.h" #include "UseStdPrintCheck.h" +#include "UseStructuredBindingCheck.h" #include "UseTrailingReturnTypeCheck.h" #include "UseTransparentFunctorsCheck.h" #include "UseUncaughtExceptionsCheck.h" @@ -121,6 +122,8 @@ class ModernizeModule : public ClangTidyModule { CheckFactories.registerCheck("modernize-use-noexcept"); CheckFactories.registerCheck("modernize-use-nullptr"); CheckFactories.registerCheck("modernize-use-override"); + CheckFactories.registerCheck( + "modernize-use-structured-binding"); CheckFactories.registerCheck( "modernize-use-trailing-return-type"); CheckFactories.registerCheck( diff --git a/clang-tools-extra/clang-tidy/modernize/UseStructuredBindingCheck.cpp b/clang-tools-extra/clang-tidy/modernize/UseStructuredBindingCheck.cpp new file mode 100644 index 0000000000000..d6d6ae6cb83b3 --- /dev/null +++ b/clang-tools-extra/clang-tidy/modernize/UseStructuredBindingCheck.cpp @@ -0,0 +1,419 @@ +//===--- UseStructuredBindingCheck.cpp - clang-tidy -----------------------===// +// +// 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 "../utils/OptionsUtils.h" +#include "clang/Lex/Lexer.h" + +using namespace clang::ast_matchers; + +namespace clang::tidy::modernize { +namespace { +constexpr const char *DefaultPairTypes = "std::pair"; +constexpr llvm::StringLiteral PairDeclName = "PairVarD"; +constexpr llvm::StringLiteral PairVarTypeName = "PairVarType"; +constexpr llvm::StringLiteral FirstVarDeclName = "FirstVarDecl"; +constexpr llvm::StringLiteral SecondVarDeclName = "SecondVarDecl"; +constexpr llvm::StringLiteral FirstDeclStmtName = "FirstDeclStmt"; +constexpr llvm::StringLiteral SecondDeclStmtName = "SecondDeclStmt"; +constexpr llvm::StringLiteral FirstTypeName = "FirstType"; +constexpr llvm::StringLiteral SecondTypeName = "SecondType"; +constexpr llvm::StringLiteral ScopeBlockName = "ScopeBlock"; +constexpr llvm::StringLiteral StdTieAssignStmtName = "StdTieAssign"; +constexpr llvm::StringLiteral StdTieExprName = "StdTieExpr"; +constexpr llvm::StringLiteral ForRangeStmtName = "ForRangeStmt"; + +/// 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 +}; + +/// Try to match exactly two VarDecl inside two DeclStmts, and set binding for +/// the used DeclStmts. +bool matchTwoVarDecl(const DeclStmt *DS1, const DeclStmt *DS2, + ast_matchers::internal::Matcher InnerMatcher1, + ast_matchers::internal::Matcher InnerMatcher2, + internal::ASTMatchFinder *Finder, + internal::BoundNodesTreeBuilder *Builder) { + SmallVector, 2> Vars; + auto CollectVarsInDeclStmt = [&Vars](const DeclStmt *DS) -> bool { + if (!DS) + return true; + + for (const auto *VD : DS->decls()) { + if (Vars.size() == 2) + return false; + + if (const auto *Var = dyn_cast(VD)) + Vars.emplace_back(Var, DS); + else + return false; + } + + return true; + }; + + if (!CollectVarsInDeclStmt(DS1) || !CollectVarsInDeclStmt(DS2)) + return false; + + if (Vars.size() != 2) + return false; + + if (InnerMatcher1.matches(*Vars[0].first, Finder, Builder) && + InnerMatcher2.matches(*Vars[1].first, Finder, Builder)) { + Builder->setBinding(FirstDeclStmtName, + clang::DynTypedNode::create(*Vars[0].second)); + if (Vars[0].second != Vars[1].second) + Builder->setBinding(SecondDeclStmtName, + clang::DynTypedNode::create(*Vars[1].second)); + return true; + } + + return false; +} + +/// Matches a Stmt whose parent is a CompoundStmt, and which is directly +/// following two VarDecls matching the inner matcher, at the same time set +/// binding for the CompoundStmt. +AST_MATCHER_P2(Stmt, hasPreTwoVarDecl, ast_matchers::internal::Matcher, + InnerMatcher1, ast_matchers::internal::Matcher, + InnerMatcher2) { + DynTypedNodeList Parents = Finder->getASTContext().getParents(Node); + if (Parents.size() != 1) + return false; + + auto *C = Parents[0].get(); + if (!C) + return false; + + const auto I = + llvm::find(llvm::make_range(C->body_rbegin(), C->body_rend()), &Node); + assert(I != C->body_rend() && "C is parent of Node"); + if ((I + 1) == C->body_rend()) + return false; + + const auto *DS2 = dyn_cast(*(I + 1)); + if (!DS2) + return false; + + const DeclStmt *DS1 = (!DS2->isSingleDecl() || ((I + 2) == C->body_rend()) + ? nullptr + : dyn_cast(*(I + 2))); + + if (matchTwoVarDecl(DS1, DS2, InnerMatcher1, InnerMatcher2, Finder, + Builder)) { + Builder->setBinding(ScopeBlockName, clang::DynTypedNode::create(*C)); + return true; + } + + return false; +} + +/// Matches a Stmt whose parent is a CompoundStmt, and which is directly +/// followed by two VarDecls matching the inner matcher, at the same time set +/// binding for the CompoundStmt. +AST_MATCHER_P2(Stmt, hasNextTwoVarDecl, + ast_matchers::internal::Matcher, InnerMatcher1, + ast_matchers::internal::Matcher, InnerMatcher2) { + DynTypedNodeList Parents = Finder->getASTContext().getParents(Node); + if (Parents.size() != 1) + return false; + + auto *C = Parents[0].get(); + if (!C) + return false; + + const auto *I = llvm::find(C->body(), &Node); + assert(I != C->body_end() && "C is parent of Node"); + if ((I + 1) == C->body_end()) + return false; + + if (matchTwoVarDecl( + dyn_cast(*(I + 1)), + ((I + 2) == C->body_end() ? nullptr : dyn_cast(*(I + 2))), + InnerMatcher1, InnerMatcher2, Finder, Builder)) { + Builder->setBinding(ScopeBlockName, clang::DynTypedNode::create(*C)); + return true; + } + + return false; +} + +/// Matches a Stmt whose parent is a CompoundStmt, and there a two VarDecls +/// matching the inner matcher in the beginning of CompoundStmt. +AST_MATCHER_P2(CompoundStmt, hasFirstTwoVarDecl, + ast_matchers::internal::Matcher, InnerMatcher1, + ast_matchers::internal::Matcher, InnerMatcher2) { + const auto *I = Node.body_begin(); + if ((I) == Node.body_end()) + return false; + + return matchTwoVarDecl( + dyn_cast(*(I)), + ((I + 1) == Node.body_end() ? nullptr : dyn_cast(*(I + 1))), + InnerMatcher1, InnerMatcher2, 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) { + return Node.isStaticLocal() || Node.isConstexpr() || Node.hasAttrs() || + Node.isInlineSpecified(); +} + +// Ignore nodes inside macros. +AST_POLYMORPHIC_MATCHER(isInMarco, + AST_POLYMORPHIC_SUPPORTED_TYPES(Stmt, Decl)) { + return Node.getBeginLoc().isMacroID() || Node.getEndLoc().isMacroID(); +} + +AST_MATCHER_P(Expr, ignoringCopyCtorAndImplicitCast, + ast_matchers::internal::Matcher, InnerMatcher) { + if (const auto *CtorE = dyn_cast(&Node)) { + if (const auto *CtorD = CtorE->getConstructor(); + CtorD->isCopyConstructor() && CtorE->getNumArgs() == 1) { + return InnerMatcher.matches(*CtorE->getArg(0)->IgnoreImpCasts(), Finder, + Builder); + } + } + + return InnerMatcher.matches(*Node.IgnoreImpCasts(), Finder, Builder); +} + +} // namespace + +UseStructuredBindingCheck::UseStructuredBindingCheck(StringRef Name, + ClangTidyContext *Context) + : ClangTidyCheck(Name, Context), + PairTypes(utils::options::parseStringList( + Options.get("PairTypes", DefaultPairTypes))) { + ; +} + +static auto getVarInitWithMemberMatcher(StringRef PairName, + StringRef MemberName, + StringRef TypeName, + StringRef BindingName) { + return varDecl( + unless(hasAnySpecifiersShouldBeIgnored()), unless(isInMarco()), + hasInitializer( + ignoringImpCasts(ignoringCopyCtorAndImplicitCast(memberExpr( + hasObjectExpression(ignoringImpCasts(declRefExpr( + to(equalsBoundNode(std::string(PairName)))))), + member(fieldDecl(hasName(MemberName), + hasType(qualType().bind(TypeName))))))))) + .bind(BindingName); +} + +void UseStructuredBindingCheck::registerMatchers(MatchFinder *Finder) { + auto PairType = + qualType(unless(isVolatileQualified()), + hasUnqualifiedDesugaredType(recordType( + hasDeclaration(cxxRecordDecl(hasAnyName(PairTypes)))))); + + auto VarInitWithFirstMember = getVarInitWithMemberMatcher( + PairDeclName, "first", FirstTypeName, FirstVarDeclName); + auto VarInitWithSecondMember = getVarInitWithMemberMatcher( + PairDeclName, "second", SecondTypeName, SecondVarDeclName); + + // X x; + // Y y; + // std::tie(x, y) = ...; + Finder->addMatcher( + exprWithCleanups( + unless(isInMarco()), + has(cxxOperatorCallExpr( + hasOverloadedOperatorName("="), + hasLHS(ignoringImplicit( + callExpr( + callee( + functionDecl(isInStdNamespace(), hasName("tie"))), + hasArgument( + 0, + declRefExpr(to( + varDecl( + unless(hasAnySpecifiersShouldBeIgnored()), + unless(isInMarco())) + .bind(FirstVarDeclName)))), + hasArgument( + 1, + declRefExpr(to( + varDecl( + unless(hasAnySpecifiersShouldBeIgnored()), + unless(isInMarco())) + .bind(SecondVarDeclName))))) + .bind(StdTieExprName))), + hasRHS(expr(hasType(PairType)))) + .bind(StdTieAssignStmtName)), + hasPreTwoVarDecl( + varDecl(equalsBoundNode(std::string(FirstVarDeclName))), + varDecl(equalsBoundNode(std::string(SecondVarDeclName))))), + this); + + // pair p = ...; + // X x = p.first; + // Y y = p.second; + Finder->addMatcher( + declStmt( + unless(isInMarco()), + hasSingleDecl( + varDecl(unless(hasAnySpecifiersShouldBeIgnored()), + hasType(qualType(anyOf(PairType, lValueReferenceType( + pointee(PairType)))) + .bind(PairVarTypeName)), + hasInitializer(expr())) + .bind(PairDeclName)), + hasNextTwoVarDecl(VarInitWithFirstMember, VarInitWithSecondMember)), + this); + + // for (pair p : map) { + // X x = p.first; + // Y y = p.second; + // } + Finder->addMatcher( + cxxForRangeStmt( + unless(isInMarco()), + hasLoopVariable( + varDecl(hasType(qualType(anyOf(PairType, lValueReferenceType( + pointee(PairType)))) + .bind(PairVarTypeName)), + hasInitializer(expr())) + .bind(PairDeclName)), + hasBody(compoundStmt(hasFirstTwoVarDecl(VarInitWithFirstMember, + VarInitWithSecondMember)) + .bind(ScopeBlockName))) + .bind(ForRangeStmtName), + this); +} + +static std::optional getTransferType(const ASTContext &Ctx, + QualType ResultType, + QualType OriginType) { + ResultType = ResultType.getCanonicalType(); + OriginType = OriginType.getCanonicalType(); + + if (ResultType == Ctx.getLValueReferenceType(OriginType.withConst())) + return TT_ByConstRef; + + if (ResultType == Ctx.getLValueReferenceType(OriginType)) + return TT_ByRef; + + if (ResultType == OriginType.withConst()) + return TT_ByConstVal; + + if (ResultType == OriginType) + return TT_ByVal; + + return std::nullopt; +} + +void UseStructuredBindingCheck::check(const MatchFinder::MatchResult &Result) { + const auto *FirstVar = Result.Nodes.getNodeAs(FirstVarDeclName); + const auto *SecondVar = Result.Nodes.getNodeAs(SecondVarDeclName); + + const auto *DS1 = Result.Nodes.getNodeAs(FirstDeclStmtName); + const auto *DS2 = Result.Nodes.getNodeAs(SecondDeclStmtName); + const auto *ScopeBlock = Result.Nodes.getNodeAs(ScopeBlockName); + + // Captured structured bindings are a C++20 extension + if (!Result.Context->getLangOpts().CPlusPlus20) { + if (auto Matchers = match( + compoundStmt( + hasDescendant(lambdaExpr(hasAnyCapture(capturesVar(varDecl( + anyOf(equalsNode(FirstVar), equalsNode(SecondVar)))))))), + *ScopeBlock, *Result.Context); + !Matchers.empty()) + return; + } + + const auto *CFRS = Result.Nodes.getNodeAs(ForRangeStmtName); + auto DiagAndFix = [&](SourceLocation DiagLoc, SourceRange ReplaceRange, + TransferType TT = TT_ByVal) { + StringRef Prefix; + switch (TT) { + case TT_ByVal: + Prefix = "auto"; + break; + case TT_ByConstVal: + Prefix = "const auto"; + break; + case TT_ByRef: + Prefix = "auto&"; + break; + case TT_ByConstRef: + Prefix = "const auto&"; + break; + } + std::vector Hints; + if (DS1) + Hints.emplace_back(FixItHint::CreateRemoval(DS1->getSourceRange())); + if (DS2) + Hints.emplace_back(FixItHint::CreateRemoval(DS2->getSourceRange())); + + std::string ReplacementText = Prefix.str() + " [" + + FirstVar->getNameAsString() + ", " + + SecondVar->getNameAsString() + "]"; + if (CFRS) + ReplacementText += " :"; + diag(DiagLoc, "Should use structured binding to decompose pair") + << FixItHint::CreateReplacement(ReplaceRange, ReplacementText) << Hints; + }; + + if (const auto *COCE = + Result.Nodes.getNodeAs(StdTieAssignStmtName)) { + DiagAndFix(COCE->getBeginLoc(), + Result.Nodes.getNodeAs(StdTieExprName)->getSourceRange()); + return; + } + + // Check whether PairVar, FirstVar and SecondVar have the same transfer type, + // so they can be combined to structured binding. + const auto *PairVar = Result.Nodes.getNodeAs(PairDeclName); + const Expr *InitE = PairVar->getInit(); + if (auto Res = + match(expr(ignoringCopyCtorAndImplicitCast(expr().bind("init_expr"))), + *InitE, *Result.Context); + !Res.empty()) + InitE = Res[0].getNodeAs("init_expr"); + + std::optional PairCaptureType = + getTransferType(*Result.Context, PairVar->getType(), InitE->getType()); + std::optional FirstVarCaptureType = + getTransferType(*Result.Context, FirstVar->getType(), + *Result.Nodes.getNodeAs(FirstTypeName)); + std::optional SecondVarCaptureType = + getTransferType(*Result.Context, SecondVar->getType(), + *Result.Nodes.getNodeAs(SecondTypeName)); + if (!PairCaptureType || !FirstVarCaptureType || !SecondVarCaptureType || + *PairCaptureType != *FirstVarCaptureType || + *FirstVarCaptureType != *SecondVarCaptureType) + return; + + // Check PairVar is not used except for assignment members to firstVar and + // SecondVar. + if (auto AllRef = utils::decl_ref_expr::allDeclRefExprs(*PairVar, *ScopeBlock, + *Result.Context); + AllRef.size() != 2) + return; + + DiagAndFix(PairVar->getBeginLoc(), + CFRS ? PairVar->getSourceRange() + : SourceRange(PairVar->getBeginLoc(), + Lexer::getLocForEndOfToken( + PairVar->getLocation(), 0, + Result.Context->getSourceManager(), + Result.Context->getLangOpts())), + *PairCaptureType); +} + +} // namespace clang::tidy::modernize diff --git a/clang-tools-extra/clang-tidy/modernize/UseStructuredBindingCheck.h b/clang-tools-extra/clang-tidy/modernize/UseStructuredBindingCheck.h new file mode 100644 index 0000000000000..63bc0a8c3da45 --- /dev/null +++ b/clang-tools-extra/clang-tidy/modernize/UseStructuredBindingCheck.h @@ -0,0 +1,36 @@ +//===--- UseStructuredBindingCheck.h - clang-tidy ---------------*- C++ -*-===// +// +// 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_USESTRUCTUREDBINDINGCHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_USESTRUCTUREDBINDINGCHECK_H + +#include "../ClangTidyCheck.h" + +namespace clang::tidy::modernize { + +/// Finds places where structured bindings could be used to decompose pairs and +/// suggests replacing them. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/modernize/use-structured-binding.html +class UseStructuredBindingCheck : public ClangTidyCheck { +public: + UseStructuredBindingCheck(StringRef Name, ClangTidyContext *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.CPlusPlus17; + } + +private: + const std::vector PairTypes; +}; + +} // namespace clang::tidy::modernize + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_USESTRUCTUREDBINDINGCHECK_H diff --git a/clang-tools-extra/docs/ReleaseNotes.rst b/clang-tools-extra/docs/ReleaseNotes.rst index 34091906cbff2..8302cbf64f095 100644 --- a/clang-tools-extra/docs/ReleaseNotes.rst +++ b/clang-tools-extra/docs/ReleaseNotes.rst @@ -179,6 +179,12 @@ New checks Finds virtual function overrides with different visibility than the function in the base class. +- New :doc:`modernize-use-structured-binding + ` check. + + Finds places where structured bindings could be used to decompose pairs and + suggests replacing them. + New check aliases ^^^^^^^^^^^^^^^^^ diff --git a/clang-tools-extra/docs/clang-tidy/checks/list.rst b/clang-tools-extra/docs/clang-tidy/checks/list.rst index c490d2ece2e0a..843a031dd2dd1 100644 --- a/clang-tools-extra/docs/clang-tidy/checks/list.rst +++ b/clang-tools-extra/docs/clang-tidy/checks/list.rst @@ -324,6 +324,7 @@ Clang-Tidy Checks :doc:`modernize-use-std-format `, "Yes" :doc:`modernize-use-std-numbers `, "Yes" :doc:`modernize-use-std-print `, "Yes" + :doc:`modernize-use-structured-binding `, "Yes" :doc:`modernize-use-trailing-return-type `, "Yes" :doc:`modernize-use-transparent-functors `, "Yes" :doc:`modernize-use-uncaught-exceptions `, "Yes" diff --git a/clang-tools-extra/docs/clang-tidy/checks/modernize/use-structured-binding.rst b/clang-tools-extra/docs/clang-tidy/checks/modernize/use-structured-binding.rst new file mode 100644 index 0000000000000..66af859d4428f --- /dev/null +++ b/clang-tools-extra/docs/clang-tidy/checks/modernize/use-structured-binding.rst @@ -0,0 +1,58 @@ +.. title:: clang-tidy - modernize-use-structured-binding + +modernize-use-structured-binding +================================ + +Suggests using C++17 structured bindings to decompose pairs. + +This check finds three code patterns and recommends using structured bindings for clearer, more idiomatic C++17 code. + +1. Decompose a pair variable by assigning its members to separate variables right after its definition: + +.. code-block:: c++ + + auto p = getPair(); + int x = p.first; + int y = p.second; + + into: + + auto [x, y] = getPair(); + +2. Use `std::tie` to decompose a pair into two predefined variables: + +.. code-block:: c++ + + int a; + int b; + std::tie(a, b) = getPair(); + + into: + + auto [a, b] = getPair(); + +3. Manually decompose a pair by assigning to its members to local variables in a range-based for loop: + +.. code-block:: c++ + + for (autop : vecOfPairs) { + int x = p.first; + int y = p.second; + // ... + } + + into: + + for (auto [x, y] : vecOfPairs) { + // use x and y + } + +The check also supports custom pair-like types via the `PairTypes` option. + +Options +------- + +.. option:: PairTypes + + A Semicolon-separated list of type names to be treated as pair-like for structured binding suggestions. + Example: `PairTypes=MyPairType; OtherPairType`. Default is `std::pair`. diff --git a/clang-tools-extra/test/clang-tidy/checkers/modernize/Inputs/use-structured-binding/fake_std_pair_tuple.h b/clang-tools-extra/test/clang-tidy/checkers/modernize/Inputs/use-structured-binding/fake_std_pair_tuple.h new file mode 100644 index 0000000000000..b77341c852edb --- /dev/null +++ b/clang-tools-extra/test/clang-tidy/checkers/modernize/Inputs/use-structured-binding/fake_std_pair_tuple.h @@ -0,0 +1,23 @@ +namespace std { + template + struct pair { + T1 first; + T2 second; + }; + + template + struct tuple { + tuple(Args&...) {} + + template + tuple operator=(const std::pair&); + }; + + template + tuple tie(Args&... args) { + return tuple(args...); + } +} + +template +std::pair getPair(); diff --git a/clang-tools-extra/test/clang-tidy/checkers/modernize/use-structured-binding-custom.cpp b/clang-tools-extra/test/clang-tidy/checkers/modernize/use-structured-binding-custom.cpp new file mode 100644 index 0000000000000..d6d73430d6a3c --- /dev/null +++ b/clang-tools-extra/test/clang-tidy/checkers/modernize/use-structured-binding-custom.cpp @@ -0,0 +1,32 @@ +// RUN: %check_clang_tidy -std=c++17-or-later %s modernize-use-structured-binding %t \ +// RUN: -config="{CheckOptions: {modernize-use-structured-binding.PairTypes: 'custom::pair; otherPair'}}" + +namespace custom { + struct pair { + int first; + int second; + }; +} + +struct otherPair { + int first; + int second; +}; + +void OptionTest() { + { + auto P = custom::pair(); + // CHECK-MESSAGES: :[[@LINE-1]]:5: warning: Should use structured binding to decompose pair [modernize-use-structured-binding] + // CHECK-FIXES: {{^}} auto [x, y] = custom::pair(); + int x = P.first; + int y = P.second; + } + + { + auto P = otherPair(); + // CHECK-MESSAGES: :[[@LINE-1]]:5: warning: Should use structured binding to decompose pair [modernize-use-structured-binding] + // CHECK-FIXES: {{^}} auto [x, y] = otherPair(); + int x = P.first; + int y = P.second; + } +} diff --git a/clang-tools-extra/test/clang-tidy/checkers/modernize/use-structured-binding-skip-lambda-capture-in-cxx17.cpp b/clang-tools-extra/test/clang-tidy/checkers/modernize/use-structured-binding-skip-lambda-capture-in-cxx17.cpp new file mode 100644 index 0000000000000..57f9f3488fa21 --- /dev/null +++ b/clang-tools-extra/test/clang-tidy/checkers/modernize/use-structured-binding-skip-lambda-capture-in-cxx17.cpp @@ -0,0 +1,67 @@ +// RUN: %check_clang_tidy -std=c++17 %s modernize-use-structured-binding %t -- -- -I %S/Inputs/use-structured-binding/ + +#include "fake_std_pair_tuple.h" + +void captureByVal() { + auto P = getPair(); + int x = P.first; + int y = P.second; + + auto lambda = [x]() { + int y = x; + }; +} + +void captureByRef() { + auto P = getPair(); + int x = P.first; + int y = P.second; + + auto lambda = [&x]() { + x = 1; + }; +} + +void captureByAllRef() { + auto P = getPair(); + int x = P.first; + int y = P.second; + + auto lambda = [&]() { + x = 1; + }; +} + +void deepLambda() { + auto P = getPair(); + int x = P.first; + int y = P.second; + + { + auto lambda = [x]() { + int y = x; + }; + } +} + +void forRangeNotWarn() { + std::pair Pairs[10]; + for (auto P : Pairs) { + int x = P.first; + int y = P.second; + + auto lambda = [&]() { + x = 1; + }; + } +} + +void stdTieNotWarn() { + int x = 0; + int y = 0; + std::tie(x, y) = getPair(); + + auto lambda = [&x]() { + x = 1; + }; +} diff --git a/clang-tools-extra/test/clang-tidy/checkers/modernize/use-structured-binding.cpp b/clang-tools-extra/test/clang-tidy/checkers/modernize/use-structured-binding.cpp new file mode 100644 index 0000000000000..97da76fea1d5f --- /dev/null +++ b/clang-tools-extra/test/clang-tidy/checkers/modernize/use-structured-binding.cpp @@ -0,0 +1,216 @@ +// RUN: %check_clang_tidy -std=c++17-or-later %s modernize-use-structured-binding %t -- -- -I %S/Inputs/use-structured-binding/ + +#include "fake_std_pair_tuple.h" + +template +void MarkUsed(T x); + +struct TestClass { + int a; + int b; + TestClass() : a(0), b(0) {} + TestClass(int x, int y) : a(x), b(y) {} +}; + +void DecomposeByAssignWarnCases() { + { + auto P = getPair(); + // CHECK-MESSAGES: :[[@LINE-1]]:5: warning: Should use structured binding to decompose pair [modernize-use-structured-binding] + // CHECK-FIXES: {{^}} auto [x, y] = getPair(); + int x = P.first; + int y = P.second; + } + + { + auto P = getPair(); + // CHECK-MESSAGES: :[[@LINE-1]]:5: warning: Should use structured binding to decompose pair [modernize-use-structured-binding] + // CHECK-FIXES: {{^}} auto [x, y] = getPair(); + int x = P.first; + auto y = P.second; + } + + { + const auto P = getPair(); + // CHECK-MESSAGES: :[[@LINE-1]]:5: warning: Should use structured binding to decompose pair [modernize-use-structured-binding] + // CHECK-FIXES: {{^}} const auto [x, y] = getPair(); + const int x = P.first; + const auto y = P.second; + } + + { + std::pair otherP; + auto& P = otherP; + // CHECK-MESSAGES: :[[@LINE-1]]:5: warning: Should use structured binding to decompose pair [modernize-use-structured-binding] + // CHECK-FIXES: {{^}} auto& [x, y] = otherP; + int& x = P.first; + auto& y = P.second; + } + + { + std::pair otherP; + const auto& P = otherP; + // CHECK-MESSAGES: :[[@LINE-1]]:5: warning: Should use structured binding to decompose pair [modernize-use-structured-binding] + // CHECK-FIXES: {{^}} const auto& [x, y] = otherP; + const int& x = P.first; + const auto& y = P.second; + } +} + +void forRangeWarnCases() { + std::pair Pairs[10]; + for (auto P : Pairs) { + // CHECK-MESSAGES: :[[@LINE-1]]:8: warning: Should use structured binding to decompose pair [modernize-use-structured-binding] + // CHECK-FIXES: {{^}} for (auto [x, y] : Pairs) { + int x = P.first; + int y = P.second; + } + + for (const auto P : Pairs) { + // CHECK-MESSAGES: :[[@LINE-1]]:8: warning: Should use structured binding to decompose pair [modernize-use-structured-binding] + // CHECK-FIXES: {{^}} for (const auto [x, y] : Pairs) { + const int x = P.first; + const int y = P.second; + } + + for (auto& P : Pairs) { + // CHECK-MESSAGES: :[[@LINE-1]]:8: warning: Should use structured binding to decompose pair [modernize-use-structured-binding] + // CHECK-FIXES: {{^}} for (auto& [x, y] : Pairs) { + int& x = P.first; + int& y = P.second; + } + + for (const auto& P : Pairs) { + // CHECK-MESSAGES: :[[@LINE-1]]:8: warning: Should use structured binding to decompose pair [modernize-use-structured-binding] + // CHECK-FIXES: {{^}} for (const auto& [x, y] : Pairs) { + const int& x = P.first; + const int& y = P.second; + } + + std::pair ClassPairs[10]; + for (auto P : ClassPairs) { + // CHECK-MESSAGES: :[[@LINE-1]]:8: warning: Should use structured binding to decompose pair [modernize-use-structured-binding] + // CHECK-FIXES: {{^}} for (auto [c1, c2] : ClassPairs) { + TestClass c1 = P.first; + TestClass c2 = P.second; + } + + for (const auto P : ClassPairs) { + // CHECK-MESSAGES: :[[@LINE-1]]:8: warning: Should use structured binding to decompose pair [modernize-use-structured-binding] + // CHECK-FIXES: {{^}} for (const auto [c1, c2] : ClassPairs) { + const TestClass c1 = P.first; + const TestClass c2 = P.second; + } +} + +void stdTieWarnCases() { + int a = 0; + int b = 0; + std::tie(a, b) = getPair(); + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: Should use structured binding to decompose pair [modernize-use-structured-binding] + // CHECK-FIXES: {{^}} auto [a, b] = getPair(); + + int* pa = nullptr; + int* pb = nullptr; + std::tie(pa, pb) = getPair(); + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: Should use structured binding to decompose pair [modernize-use-structured-binding] + // CHECK-FIXES: {{^}} auto [pa, pb] = getPair(); + + TestClass c1 (1, 2); + TestClass c2 = TestClass {3, 4}; + std::tie(c1, c2) = getPair(); + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: Should use structured binding to decompose pair [modernize-use-structured-binding] + // CHECK-FIXES: {{^}} auto [c1, c2] = getPair(); +} + +void stdTieNotWarnCases() { + int a = 0; + int b = 0; + a = 4; + std::tie(a, b) = getPair(); // no warning + + int* pa = nullptr; + int* pb = nullptr; + MarkUsed(pa); + std::tie(pa, pb) = getPair(); // no warning + + TestClass c1 (1, 2); + TestClass c2 = TestClass {3, 4}; + MarkUsed(c2); + std::tie(c1, c2) = getPair(); +} + +void NotWarnForVarHasSpecifiers() { + { + auto P = getPair(); + const int x = P.first; + int y = P.second; + } + + { + auto P = getPair(); + volatile int x = P.first; + int y = P.second; + } + + { + auto P = getPair(); + int x = P.first; + [[maybe_unused]] int y = P.second; + } + + { + static auto P = getPair(); + int x = P.first; + int y = P.second; + } +} + +void NotWarnForMultiUsedPairVar() { + { + auto P = getPair(); + int x = P.first; + int y = P.second; + MarkUsed(P); + } + + { + auto P = getPair(); + int x = P.first; + MarkUsed(P); + int y = P.second; + } + + { + auto P = getPair(); + MarkUsed(P); + int x = P.first; + int y = P.second; + } + + { + std::pair Pairs[10]; + for (auto P : Pairs) { + int x = P.first; + int y = P.second; + + MarkUsed(P); + } + } +} + +#define DECOMPOSE(P) \ + int x = P.first; \ + int y = P.second; \ + +void NotWarnForMacro1() { + auto P = getPair(); + DECOMPOSE(P); +} + +#define GETPAIR auto P = getPair() + +void NotWarnForMacro2() { + GETPAIR; + int x = P.first; + int y = P.second; +} From dd9bb2a4c980877f384d8159233c1fb75d3217fc Mon Sep 17 00:00:00 2001 From: flovent Date: Sun, 14 Sep 2025 16:40:16 +0800 Subject: [PATCH 02/44] Update clang-tools-extra/docs/clang-tidy/checks/modernize/use-structured-binding.rst Co-authored-by: Baranov Victor --- .../docs/clang-tidy/checks/modernize/use-structured-binding.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clang-tools-extra/docs/clang-tidy/checks/modernize/use-structured-binding.rst b/clang-tools-extra/docs/clang-tidy/checks/modernize/use-structured-binding.rst index 66af859d4428f..e25dd8e2130f3 100644 --- a/clang-tools-extra/docs/clang-tidy/checks/modernize/use-structured-binding.rst +++ b/clang-tools-extra/docs/clang-tidy/checks/modernize/use-structured-binding.rst @@ -55,4 +55,4 @@ Options .. option:: PairTypes A Semicolon-separated list of type names to be treated as pair-like for structured binding suggestions. - Example: `PairTypes=MyPairType; OtherPairType`. Default is `std::pair`. + Example: `MyPairType;OtherPairType`. Default is `std::pair`. From e37026ab52eb32b9b49aa50e1def060a0be0e51c Mon Sep 17 00:00:00 2001 From: flovent Date: Sun, 14 Sep 2025 16:40:26 +0800 Subject: [PATCH 03/44] Update clang-tools-extra/docs/clang-tidy/checks/modernize/use-structured-binding.rst Co-authored-by: Baranov Victor --- .../clang-tidy/checks/modernize/use-structured-binding.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/clang-tools-extra/docs/clang-tidy/checks/modernize/use-structured-binding.rst b/clang-tools-extra/docs/clang-tidy/checks/modernize/use-structured-binding.rst index e25dd8e2130f3..377f48b822ded 100644 --- a/clang-tools-extra/docs/clang-tidy/checks/modernize/use-structured-binding.rst +++ b/clang-tools-extra/docs/clang-tidy/checks/modernize/use-structured-binding.rst @@ -3,7 +3,8 @@ modernize-use-structured-binding ================================ -Suggests using C++17 structured bindings to decompose pairs. +Finds places where structured bindings could be used to decompose pairs and +suggests replacing them. This check finds three code patterns and recommends using structured bindings for clearer, more idiomatic C++17 code. From 1d01ffd04ccb87591d88e7f56391f81982cb8c8c Mon Sep 17 00:00:00 2001 From: flovent Date: Sun, 14 Sep 2025 16:40:48 +0800 Subject: [PATCH 04/44] Update clang-tools-extra/docs/clang-tidy/checks/modernize/use-structured-binding.rst Co-authored-by: Baranov Victor --- .../docs/clang-tidy/checks/modernize/use-structured-binding.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clang-tools-extra/docs/clang-tidy/checks/modernize/use-structured-binding.rst b/clang-tools-extra/docs/clang-tidy/checks/modernize/use-structured-binding.rst index 377f48b822ded..38f4daaf97ec6 100644 --- a/clang-tools-extra/docs/clang-tidy/checks/modernize/use-structured-binding.rst +++ b/clang-tools-extra/docs/clang-tidy/checks/modernize/use-structured-binding.rst @@ -20,7 +20,7 @@ This check finds three code patterns and recommends using structured bindings fo auto [x, y] = getPair(); -2. Use `std::tie` to decompose a pair into two predefined variables: +2. Use ``std::tie`` to decompose a pair into two predefined variables: .. code-block:: c++ From 4127b7b7c73720c0a170cf4b334d7de687bdccf7 Mon Sep 17 00:00:00 2001 From: flovent Date: Sun, 14 Sep 2025 16:41:17 +0800 Subject: [PATCH 05/44] Update clang-tools-extra/docs/clang-tidy/checks/modernize/use-structured-binding.rst Co-authored-by: Baranov Victor --- .../docs/clang-tidy/checks/modernize/use-structured-binding.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clang-tools-extra/docs/clang-tidy/checks/modernize/use-structured-binding.rst b/clang-tools-extra/docs/clang-tidy/checks/modernize/use-structured-binding.rst index 38f4daaf97ec6..0d227b75968a7 100644 --- a/clang-tools-extra/docs/clang-tidy/checks/modernize/use-structured-binding.rst +++ b/clang-tools-extra/docs/clang-tidy/checks/modernize/use-structured-binding.rst @@ -48,7 +48,7 @@ This check finds three code patterns and recommends using structured bindings fo // use x and y } -The check also supports custom pair-like types via the `PairTypes` option. +The check also supports custom pair-like types via the :option:`PairTypes` option. Options ------- From f6c129b090262365fe0db9073a4a5a2ef9a73da0 Mon Sep 17 00:00:00 2001 From: flovent Date: Sun, 14 Sep 2025 16:44:32 +0800 Subject: [PATCH 06/44] [NFC] Change warning message to 'use structured binding to decompose a pair' --- .../modernize/UseStructuredBindingCheck.cpp | 2 +- .../use-structured-binding-custom.cpp | 4 +-- .../modernize/use-structured-binding.cpp | 28 +++++++++---------- 3 files changed, 17 insertions(+), 17 deletions(-) diff --git a/clang-tools-extra/clang-tidy/modernize/UseStructuredBindingCheck.cpp b/clang-tools-extra/clang-tidy/modernize/UseStructuredBindingCheck.cpp index d6d6ae6cb83b3..fab4e8dc16cf1 100644 --- a/clang-tools-extra/clang-tidy/modernize/UseStructuredBindingCheck.cpp +++ b/clang-tools-extra/clang-tidy/modernize/UseStructuredBindingCheck.cpp @@ -365,7 +365,7 @@ void UseStructuredBindingCheck::check(const MatchFinder::MatchResult &Result) { SecondVar->getNameAsString() + "]"; if (CFRS) ReplacementText += " :"; - diag(DiagLoc, "Should use structured binding to decompose pair") + diag(DiagLoc, "use structured binding to decompose a pair") << FixItHint::CreateReplacement(ReplaceRange, ReplacementText) << Hints; }; diff --git a/clang-tools-extra/test/clang-tidy/checkers/modernize/use-structured-binding-custom.cpp b/clang-tools-extra/test/clang-tidy/checkers/modernize/use-structured-binding-custom.cpp index d6d73430d6a3c..a007447d172b7 100644 --- a/clang-tools-extra/test/clang-tidy/checkers/modernize/use-structured-binding-custom.cpp +++ b/clang-tools-extra/test/clang-tidy/checkers/modernize/use-structured-binding-custom.cpp @@ -16,7 +16,7 @@ struct otherPair { void OptionTest() { { auto P = custom::pair(); - // CHECK-MESSAGES: :[[@LINE-1]]:5: warning: Should use structured binding to decompose pair [modernize-use-structured-binding] + // CHECK-MESSAGES: :[[@LINE-1]]:5: warning: use structured binding to decompose a pair [modernize-use-structured-binding] // CHECK-FIXES: {{^}} auto [x, y] = custom::pair(); int x = P.first; int y = P.second; @@ -24,7 +24,7 @@ void OptionTest() { { auto P = otherPair(); - // CHECK-MESSAGES: :[[@LINE-1]]:5: warning: Should use structured binding to decompose pair [modernize-use-structured-binding] + // CHECK-MESSAGES: :[[@LINE-1]]:5: warning: use structured binding to decompose a pair [modernize-use-structured-binding] // CHECK-FIXES: {{^}} auto [x, y] = otherPair(); int x = P.first; int y = P.second; diff --git a/clang-tools-extra/test/clang-tidy/checkers/modernize/use-structured-binding.cpp b/clang-tools-extra/test/clang-tidy/checkers/modernize/use-structured-binding.cpp index 97da76fea1d5f..a4016397f52ef 100644 --- a/clang-tools-extra/test/clang-tidy/checkers/modernize/use-structured-binding.cpp +++ b/clang-tools-extra/test/clang-tidy/checkers/modernize/use-structured-binding.cpp @@ -15,7 +15,7 @@ struct TestClass { void DecomposeByAssignWarnCases() { { auto P = getPair(); - // CHECK-MESSAGES: :[[@LINE-1]]:5: warning: Should use structured binding to decompose pair [modernize-use-structured-binding] + // CHECK-MESSAGES: :[[@LINE-1]]:5: warning: use structured binding to decompose a pair [modernize-use-structured-binding] // CHECK-FIXES: {{^}} auto [x, y] = getPair(); int x = P.first; int y = P.second; @@ -23,7 +23,7 @@ void DecomposeByAssignWarnCases() { { auto P = getPair(); - // CHECK-MESSAGES: :[[@LINE-1]]:5: warning: Should use structured binding to decompose pair [modernize-use-structured-binding] + // CHECK-MESSAGES: :[[@LINE-1]]:5: warning: use structured binding to decompose a pair [modernize-use-structured-binding] // CHECK-FIXES: {{^}} auto [x, y] = getPair(); int x = P.first; auto y = P.second; @@ -31,7 +31,7 @@ void DecomposeByAssignWarnCases() { { const auto P = getPair(); - // CHECK-MESSAGES: :[[@LINE-1]]:5: warning: Should use structured binding to decompose pair [modernize-use-structured-binding] + // CHECK-MESSAGES: :[[@LINE-1]]:5: warning: use structured binding to decompose a pair [modernize-use-structured-binding] // CHECK-FIXES: {{^}} const auto [x, y] = getPair(); const int x = P.first; const auto y = P.second; @@ -40,7 +40,7 @@ void DecomposeByAssignWarnCases() { { std::pair otherP; auto& P = otherP; - // CHECK-MESSAGES: :[[@LINE-1]]:5: warning: Should use structured binding to decompose pair [modernize-use-structured-binding] + // CHECK-MESSAGES: :[[@LINE-1]]:5: warning: use structured binding to decompose a pair [modernize-use-structured-binding] // CHECK-FIXES: {{^}} auto& [x, y] = otherP; int& x = P.first; auto& y = P.second; @@ -49,7 +49,7 @@ void DecomposeByAssignWarnCases() { { std::pair otherP; const auto& P = otherP; - // CHECK-MESSAGES: :[[@LINE-1]]:5: warning: Should use structured binding to decompose pair [modernize-use-structured-binding] + // CHECK-MESSAGES: :[[@LINE-1]]:5: warning: use structured binding to decompose a pair [modernize-use-structured-binding] // CHECK-FIXES: {{^}} const auto& [x, y] = otherP; const int& x = P.first; const auto& y = P.second; @@ -59,28 +59,28 @@ void DecomposeByAssignWarnCases() { void forRangeWarnCases() { std::pair Pairs[10]; for (auto P : Pairs) { - // CHECK-MESSAGES: :[[@LINE-1]]:8: warning: Should use structured binding to decompose pair [modernize-use-structured-binding] + // CHECK-MESSAGES: :[[@LINE-1]]:8: warning: use structured binding to decompose a pair [modernize-use-structured-binding] // CHECK-FIXES: {{^}} for (auto [x, y] : Pairs) { int x = P.first; int y = P.second; } for (const auto P : Pairs) { - // CHECK-MESSAGES: :[[@LINE-1]]:8: warning: Should use structured binding to decompose pair [modernize-use-structured-binding] + // CHECK-MESSAGES: :[[@LINE-1]]:8: warning: use structured binding to decompose a pair [modernize-use-structured-binding] // CHECK-FIXES: {{^}} for (const auto [x, y] : Pairs) { const int x = P.first; const int y = P.second; } for (auto& P : Pairs) { - // CHECK-MESSAGES: :[[@LINE-1]]:8: warning: Should use structured binding to decompose pair [modernize-use-structured-binding] + // CHECK-MESSAGES: :[[@LINE-1]]:8: warning: use structured binding to decompose a pair [modernize-use-structured-binding] // CHECK-FIXES: {{^}} for (auto& [x, y] : Pairs) { int& x = P.first; int& y = P.second; } for (const auto& P : Pairs) { - // CHECK-MESSAGES: :[[@LINE-1]]:8: warning: Should use structured binding to decompose pair [modernize-use-structured-binding] + // CHECK-MESSAGES: :[[@LINE-1]]:8: warning: use structured binding to decompose a pair [modernize-use-structured-binding] // CHECK-FIXES: {{^}} for (const auto& [x, y] : Pairs) { const int& x = P.first; const int& y = P.second; @@ -88,14 +88,14 @@ void forRangeWarnCases() { std::pair ClassPairs[10]; for (auto P : ClassPairs) { - // CHECK-MESSAGES: :[[@LINE-1]]:8: warning: Should use structured binding to decompose pair [modernize-use-structured-binding] + // CHECK-MESSAGES: :[[@LINE-1]]:8: warning: use structured binding to decompose a pair [modernize-use-structured-binding] // CHECK-FIXES: {{^}} for (auto [c1, c2] : ClassPairs) { TestClass c1 = P.first; TestClass c2 = P.second; } for (const auto P : ClassPairs) { - // CHECK-MESSAGES: :[[@LINE-1]]:8: warning: Should use structured binding to decompose pair [modernize-use-structured-binding] + // CHECK-MESSAGES: :[[@LINE-1]]:8: warning: use structured binding to decompose a pair [modernize-use-structured-binding] // CHECK-FIXES: {{^}} for (const auto [c1, c2] : ClassPairs) { const TestClass c1 = P.first; const TestClass c2 = P.second; @@ -106,19 +106,19 @@ void stdTieWarnCases() { int a = 0; int b = 0; std::tie(a, b) = getPair(); - // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: Should use structured binding to decompose pair [modernize-use-structured-binding] + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: use structured binding to decompose a pair [modernize-use-structured-binding] // CHECK-FIXES: {{^}} auto [a, b] = getPair(); int* pa = nullptr; int* pb = nullptr; std::tie(pa, pb) = getPair(); - // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: Should use structured binding to decompose pair [modernize-use-structured-binding] + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: use structured binding to decompose a pair [modernize-use-structured-binding] // CHECK-FIXES: {{^}} auto [pa, pb] = getPair(); TestClass c1 (1, 2); TestClass c2 = TestClass {3, 4}; std::tie(c1, c2) = getPair(); - // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: Should use structured binding to decompose pair [modernize-use-structured-binding] + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: use structured binding to decompose a pair [modernize-use-structured-binding] // CHECK-FIXES: {{^}} auto [c1, c2] = getPair(); } From 24e9d4ea3ebfa20d804aafb420bf5a34cc0e86aa Mon Sep 17 00:00:00 2001 From: flovent Date: Sun, 14 Sep 2025 16:53:53 +0800 Subject: [PATCH 07/44] [NFC] Add `forRangeNotWarnCases` in test file --- .../modernize/use-structured-binding.cpp | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/clang-tools-extra/test/clang-tidy/checkers/modernize/use-structured-binding.cpp b/clang-tools-extra/test/clang-tidy/checkers/modernize/use-structured-binding.cpp index a4016397f52ef..033b380585258 100644 --- a/clang-tools-extra/test/clang-tidy/checkers/modernize/use-structured-binding.cpp +++ b/clang-tools-extra/test/clang-tidy/checkers/modernize/use-structured-binding.cpp @@ -102,6 +102,35 @@ void forRangeWarnCases() { } } +void forRangeNotWarnCases() { + std::pair Pairs[10]; + for (auto P : Pairs) { + int x = P.first; + MarkUsed(x); + int y = P.second; + } + + for (auto P : Pairs) { + MarkUsed(P); + int x = P.first; + int y = P.second; + } + + for (auto P : Pairs) { + int x = P.first; + int y = P.second; + MarkUsed(P); + } + + std::pair ClassPairs[10]; + int c; + for (auto P : ClassPairs) { + TestClass c1 = P.first; + c ++ ; + TestClass c2 = P.second; + } +} + void stdTieWarnCases() { int a = 0; int b = 0; From 08fd8db44d728ed0cdafcd5f88ac4bc8a0819e24 Mon Sep 17 00:00:00 2001 From: flovent Date: Sun, 14 Sep 2025 16:56:27 +0800 Subject: [PATCH 08/44] [NFC] Remove {{^}} --- .../use-structured-binding-custom.cpp | 4 +-- .../modernize/use-structured-binding.cpp | 28 +++++++++---------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/clang-tools-extra/test/clang-tidy/checkers/modernize/use-structured-binding-custom.cpp b/clang-tools-extra/test/clang-tidy/checkers/modernize/use-structured-binding-custom.cpp index a007447d172b7..bd335e1edaf6e 100644 --- a/clang-tools-extra/test/clang-tidy/checkers/modernize/use-structured-binding-custom.cpp +++ b/clang-tools-extra/test/clang-tidy/checkers/modernize/use-structured-binding-custom.cpp @@ -17,7 +17,7 @@ void OptionTest() { { auto P = custom::pair(); // CHECK-MESSAGES: :[[@LINE-1]]:5: warning: use structured binding to decompose a pair [modernize-use-structured-binding] - // CHECK-FIXES: {{^}} auto [x, y] = custom::pair(); + // CHECK-FIXES: auto [x, y] = custom::pair(); int x = P.first; int y = P.second; } @@ -25,7 +25,7 @@ void OptionTest() { { auto P = otherPair(); // CHECK-MESSAGES: :[[@LINE-1]]:5: warning: use structured binding to decompose a pair [modernize-use-structured-binding] - // CHECK-FIXES: {{^}} auto [x, y] = otherPair(); + // CHECK-FIXES: auto [x, y] = otherPair(); int x = P.first; int y = P.second; } diff --git a/clang-tools-extra/test/clang-tidy/checkers/modernize/use-structured-binding.cpp b/clang-tools-extra/test/clang-tidy/checkers/modernize/use-structured-binding.cpp index 033b380585258..b2246e68a4d73 100644 --- a/clang-tools-extra/test/clang-tidy/checkers/modernize/use-structured-binding.cpp +++ b/clang-tools-extra/test/clang-tidy/checkers/modernize/use-structured-binding.cpp @@ -16,7 +16,7 @@ void DecomposeByAssignWarnCases() { { auto P = getPair(); // CHECK-MESSAGES: :[[@LINE-1]]:5: warning: use structured binding to decompose a pair [modernize-use-structured-binding] - // CHECK-FIXES: {{^}} auto [x, y] = getPair(); + // CHECK-FIXES: auto [x, y] = getPair(); int x = P.first; int y = P.second; } @@ -24,7 +24,7 @@ void DecomposeByAssignWarnCases() { { auto P = getPair(); // CHECK-MESSAGES: :[[@LINE-1]]:5: warning: use structured binding to decompose a pair [modernize-use-structured-binding] - // CHECK-FIXES: {{^}} auto [x, y] = getPair(); + // CHECK-FIXES: auto [x, y] = getPair(); int x = P.first; auto y = P.second; } @@ -32,7 +32,7 @@ void DecomposeByAssignWarnCases() { { const auto P = getPair(); // CHECK-MESSAGES: :[[@LINE-1]]:5: warning: use structured binding to decompose a pair [modernize-use-structured-binding] - // CHECK-FIXES: {{^}} const auto [x, y] = getPair(); + // CHECK-FIXES: const auto [x, y] = getPair(); const int x = P.first; const auto y = P.second; } @@ -41,7 +41,7 @@ void DecomposeByAssignWarnCases() { std::pair otherP; auto& P = otherP; // CHECK-MESSAGES: :[[@LINE-1]]:5: warning: use structured binding to decompose a pair [modernize-use-structured-binding] - // CHECK-FIXES: {{^}} auto& [x, y] = otherP; + // CHECK-FIXES: auto& [x, y] = otherP; int& x = P.first; auto& y = P.second; } @@ -50,7 +50,7 @@ void DecomposeByAssignWarnCases() { std::pair otherP; const auto& P = otherP; // CHECK-MESSAGES: :[[@LINE-1]]:5: warning: use structured binding to decompose a pair [modernize-use-structured-binding] - // CHECK-FIXES: {{^}} const auto& [x, y] = otherP; + // CHECK-FIXES: const auto& [x, y] = otherP; const int& x = P.first; const auto& y = P.second; } @@ -60,28 +60,28 @@ void forRangeWarnCases() { std::pair Pairs[10]; for (auto P : Pairs) { // CHECK-MESSAGES: :[[@LINE-1]]:8: warning: use structured binding to decompose a pair [modernize-use-structured-binding] - // CHECK-FIXES: {{^}} for (auto [x, y] : Pairs) { + // CHECK-FIXES: for (auto [x, y] : Pairs) { int x = P.first; int y = P.second; } for (const auto P : Pairs) { // CHECK-MESSAGES: :[[@LINE-1]]:8: warning: use structured binding to decompose a pair [modernize-use-structured-binding] - // CHECK-FIXES: {{^}} for (const auto [x, y] : Pairs) { + // CHECK-FIXES: for (const auto [x, y] : Pairs) { const int x = P.first; const int y = P.second; } for (auto& P : Pairs) { // CHECK-MESSAGES: :[[@LINE-1]]:8: warning: use structured binding to decompose a pair [modernize-use-structured-binding] - // CHECK-FIXES: {{^}} for (auto& [x, y] : Pairs) { + // CHECK-FIXES: for (auto& [x, y] : Pairs) { int& x = P.first; int& y = P.second; } for (const auto& P : Pairs) { // CHECK-MESSAGES: :[[@LINE-1]]:8: warning: use structured binding to decompose a pair [modernize-use-structured-binding] - // CHECK-FIXES: {{^}} for (const auto& [x, y] : Pairs) { + // CHECK-FIXES: for (const auto& [x, y] : Pairs) { const int& x = P.first; const int& y = P.second; } @@ -89,14 +89,14 @@ void forRangeWarnCases() { std::pair ClassPairs[10]; for (auto P : ClassPairs) { // CHECK-MESSAGES: :[[@LINE-1]]:8: warning: use structured binding to decompose a pair [modernize-use-structured-binding] - // CHECK-FIXES: {{^}} for (auto [c1, c2] : ClassPairs) { + // CHECK-FIXES: for (auto [c1, c2] : ClassPairs) { TestClass c1 = P.first; TestClass c2 = P.second; } for (const auto P : ClassPairs) { // CHECK-MESSAGES: :[[@LINE-1]]:8: warning: use structured binding to decompose a pair [modernize-use-structured-binding] - // CHECK-FIXES: {{^}} for (const auto [c1, c2] : ClassPairs) { + // CHECK-FIXES: for (const auto [c1, c2] : ClassPairs) { const TestClass c1 = P.first; const TestClass c2 = P.second; } @@ -136,19 +136,19 @@ void stdTieWarnCases() { int b = 0; std::tie(a, b) = getPair(); // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: use structured binding to decompose a pair [modernize-use-structured-binding] - // CHECK-FIXES: {{^}} auto [a, b] = getPair(); + // CHECK-FIXES: auto [a, b] = getPair(); int* pa = nullptr; int* pb = nullptr; std::tie(pa, pb) = getPair(); // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: use structured binding to decompose a pair [modernize-use-structured-binding] - // CHECK-FIXES: {{^}} auto [pa, pb] = getPair(); + // CHECK-FIXES: auto [pa, pb] = getPair(); TestClass c1 (1, 2); TestClass c2 = TestClass {3, 4}; std::tie(c1, c2) = getPair(); // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: use structured binding to decompose a pair [modernize-use-structured-binding] - // CHECK-FIXES: {{^}} auto [c1, c2] = getPair(); + // CHECK-FIXES: auto [c1, c2] = getPair(); } void stdTieNotWarnCases() { From 49bfcae8c804af3eed8369dd736f2283644fc7ce Mon Sep 17 00:00:00 2001 From: flovent Date: Sun, 14 Sep 2025 17:04:53 +0800 Subject: [PATCH 09/44] [NFC] Add more testcase --- .../checkers/modernize/use-structured-binding.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/clang-tools-extra/test/clang-tidy/checkers/modernize/use-structured-binding.cpp b/clang-tools-extra/test/clang-tidy/checkers/modernize/use-structured-binding.cpp index b2246e68a4d73..1037944de26dd 100644 --- a/clang-tools-extra/test/clang-tidy/checkers/modernize/use-structured-binding.cpp +++ b/clang-tools-extra/test/clang-tidy/checkers/modernize/use-structured-binding.cpp @@ -9,6 +9,7 @@ struct TestClass { int a; int b; TestClass() : a(0), b(0) {} + TestClass& operator++(); TestClass(int x, int y) : a(x), b(y) {} }; @@ -123,6 +124,12 @@ void forRangeNotWarnCases() { } std::pair ClassPairs[10]; + for (auto P : ClassPairs) { + TestClass c1 = P.first; + ++ c1 ; + TestClass c2 = P.second; + } + int c; for (auto P : ClassPairs) { TestClass c1 = P.first; From aa3bf50840d07f276cd4a032d5eaf12c11cfa51b Mon Sep 17 00:00:00 2001 From: flovent Date: Sun, 14 Sep 2025 17:53:53 +0800 Subject: [PATCH 10/44] [NFC] Merge lambda testcase to main test file for also test c++20-or-later --- ...d-binding-skip-lambda-capture-in-cxx17.cpp | 67 --------- .../modernize/use-structured-binding.cpp | 136 ++++++++++++++---- 2 files changed, 106 insertions(+), 97 deletions(-) delete mode 100644 clang-tools-extra/test/clang-tidy/checkers/modernize/use-structured-binding-skip-lambda-capture-in-cxx17.cpp diff --git a/clang-tools-extra/test/clang-tidy/checkers/modernize/use-structured-binding-skip-lambda-capture-in-cxx17.cpp b/clang-tools-extra/test/clang-tidy/checkers/modernize/use-structured-binding-skip-lambda-capture-in-cxx17.cpp deleted file mode 100644 index 57f9f3488fa21..0000000000000 --- a/clang-tools-extra/test/clang-tidy/checkers/modernize/use-structured-binding-skip-lambda-capture-in-cxx17.cpp +++ /dev/null @@ -1,67 +0,0 @@ -// RUN: %check_clang_tidy -std=c++17 %s modernize-use-structured-binding %t -- -- -I %S/Inputs/use-structured-binding/ - -#include "fake_std_pair_tuple.h" - -void captureByVal() { - auto P = getPair(); - int x = P.first; - int y = P.second; - - auto lambda = [x]() { - int y = x; - }; -} - -void captureByRef() { - auto P = getPair(); - int x = P.first; - int y = P.second; - - auto lambda = [&x]() { - x = 1; - }; -} - -void captureByAllRef() { - auto P = getPair(); - int x = P.first; - int y = P.second; - - auto lambda = [&]() { - x = 1; - }; -} - -void deepLambda() { - auto P = getPair(); - int x = P.first; - int y = P.second; - - { - auto lambda = [x]() { - int y = x; - }; - } -} - -void forRangeNotWarn() { - std::pair Pairs[10]; - for (auto P : Pairs) { - int x = P.first; - int y = P.second; - - auto lambda = [&]() { - x = 1; - }; - } -} - -void stdTieNotWarn() { - int x = 0; - int y = 0; - std::tie(x, y) = getPair(); - - auto lambda = [&x]() { - x = 1; - }; -} diff --git a/clang-tools-extra/test/clang-tidy/checkers/modernize/use-structured-binding.cpp b/clang-tools-extra/test/clang-tidy/checkers/modernize/use-structured-binding.cpp index 1037944de26dd..05b842e4d6614 100644 --- a/clang-tools-extra/test/clang-tidy/checkers/modernize/use-structured-binding.cpp +++ b/clang-tools-extra/test/clang-tidy/checkers/modernize/use-structured-binding.cpp @@ -1,5 +1,5 @@ -// RUN: %check_clang_tidy -std=c++17-or-later %s modernize-use-structured-binding %t -- -- -I %S/Inputs/use-structured-binding/ - +// RUN: %check_clang_tidy -check-suffix=ALL,CPP20ORLATER -std=c++20-or-later %s modernize-use-structured-binding %t -- -- -I %S/Inputs/use-structured-binding/ +// RUN: %check_clang_tidy -check-suffix=ALL -std=c++17 %s modernize-use-structured-binding %t -- -- -I %S/Inputs/use-structured-binding/ #include "fake_std_pair_tuple.h" template @@ -16,24 +16,24 @@ struct TestClass { void DecomposeByAssignWarnCases() { { auto P = getPair(); - // CHECK-MESSAGES: :[[@LINE-1]]:5: warning: use structured binding to decompose a pair [modernize-use-structured-binding] - // CHECK-FIXES: auto [x, y] = getPair(); + // CHECK-MESSAGES-ALL: :[[@LINE-1]]:5: warning: use structured binding to decompose a pair [modernize-use-structured-binding] + // CHECK-FIXES-ALL: auto [x, y] = getPair(); int x = P.first; int y = P.second; } { auto P = getPair(); - // CHECK-MESSAGES: :[[@LINE-1]]:5: warning: use structured binding to decompose a pair [modernize-use-structured-binding] - // CHECK-FIXES: auto [x, y] = getPair(); + // CHECK-MESSAGES-ALL: :[[@LINE-1]]:5: warning: use structured binding to decompose a pair [modernize-use-structured-binding] + // CHECK-FIXES-ALL: auto [x, y] = getPair(); int x = P.first; auto y = P.second; } { const auto P = getPair(); - // CHECK-MESSAGES: :[[@LINE-1]]:5: warning: use structured binding to decompose a pair [modernize-use-structured-binding] - // CHECK-FIXES: const auto [x, y] = getPair(); + // CHECK-MESSAGES-ALL: :[[@LINE-1]]:5: warning: use structured binding to decompose a pair [modernize-use-structured-binding] + // CHECK-FIXES-ALL: const auto [x, y] = getPair(); const int x = P.first; const auto y = P.second; } @@ -41,8 +41,8 @@ void DecomposeByAssignWarnCases() { { std::pair otherP; auto& P = otherP; - // CHECK-MESSAGES: :[[@LINE-1]]:5: warning: use structured binding to decompose a pair [modernize-use-structured-binding] - // CHECK-FIXES: auto& [x, y] = otherP; + // CHECK-MESSAGES-ALL: :[[@LINE-1]]:5: warning: use structured binding to decompose a pair [modernize-use-structured-binding] + // CHECK-FIXES-ALL: auto& [x, y] = otherP; int& x = P.first; auto& y = P.second; } @@ -50,8 +50,8 @@ void DecomposeByAssignWarnCases() { { std::pair otherP; const auto& P = otherP; - // CHECK-MESSAGES: :[[@LINE-1]]:5: warning: use structured binding to decompose a pair [modernize-use-structured-binding] - // CHECK-FIXES: const auto& [x, y] = otherP; + // CHECK-MESSAGES-ALL: :[[@LINE-1]]:5: warning: use structured binding to decompose a pair [modernize-use-structured-binding] + // CHECK-FIXES-ALL: const auto& [x, y] = otherP; const int& x = P.first; const auto& y = P.second; } @@ -60,44 +60,44 @@ void DecomposeByAssignWarnCases() { void forRangeWarnCases() { std::pair Pairs[10]; for (auto P : Pairs) { - // CHECK-MESSAGES: :[[@LINE-1]]:8: warning: use structured binding to decompose a pair [modernize-use-structured-binding] - // CHECK-FIXES: for (auto [x, y] : Pairs) { + // CHECK-MESSAGES-ALL: :[[@LINE-1]]:8: warning: use structured binding to decompose a pair [modernize-use-structured-binding] + // CHECK-FIXES-ALL: for (auto [x, y] : Pairs) { int x = P.first; int y = P.second; } for (const auto P : Pairs) { - // CHECK-MESSAGES: :[[@LINE-1]]:8: warning: use structured binding to decompose a pair [modernize-use-structured-binding] - // CHECK-FIXES: for (const auto [x, y] : Pairs) { + // CHECK-MESSAGES-ALL: :[[@LINE-1]]:8: warning: use structured binding to decompose a pair [modernize-use-structured-binding] + // CHECK-FIXES-ALL: for (const auto [x, y] : Pairs) { const int x = P.first; const int y = P.second; } for (auto& P : Pairs) { - // CHECK-MESSAGES: :[[@LINE-1]]:8: warning: use structured binding to decompose a pair [modernize-use-structured-binding] - // CHECK-FIXES: for (auto& [x, y] : Pairs) { + // CHECK-MESSAGES-ALL: :[[@LINE-1]]:8: warning: use structured binding to decompose a pair [modernize-use-structured-binding] + // CHECK-FIXES-ALL: for (auto& [x, y] : Pairs) { int& x = P.first; int& y = P.second; } for (const auto& P : Pairs) { - // CHECK-MESSAGES: :[[@LINE-1]]:8: warning: use structured binding to decompose a pair [modernize-use-structured-binding] - // CHECK-FIXES: for (const auto& [x, y] : Pairs) { + // CHECK-MESSAGES-ALL: :[[@LINE-1]]:8: warning: use structured binding to decompose a pair [modernize-use-structured-binding] + // CHECK-FIXES-ALL: for (const auto& [x, y] : Pairs) { const int& x = P.first; const int& y = P.second; } std::pair ClassPairs[10]; for (auto P : ClassPairs) { - // CHECK-MESSAGES: :[[@LINE-1]]:8: warning: use structured binding to decompose a pair [modernize-use-structured-binding] - // CHECK-FIXES: for (auto [c1, c2] : ClassPairs) { + // CHECK-MESSAGES-ALL: :[[@LINE-1]]:8: warning: use structured binding to decompose a pair [modernize-use-structured-binding] + // CHECK-FIXES-ALL: for (auto [c1, c2] : ClassPairs) { TestClass c1 = P.first; TestClass c2 = P.second; } for (const auto P : ClassPairs) { - // CHECK-MESSAGES: :[[@LINE-1]]:8: warning: use structured binding to decompose a pair [modernize-use-structured-binding] - // CHECK-FIXES: for (const auto [c1, c2] : ClassPairs) { + // CHECK-MESSAGES-ALL: :[[@LINE-1]]:8: warning: use structured binding to decompose a pair [modernize-use-structured-binding] + // CHECK-FIXES-ALL: for (const auto [c1, c2] : ClassPairs) { const TestClass c1 = P.first; const TestClass c2 = P.second; } @@ -142,20 +142,20 @@ void stdTieWarnCases() { int a = 0; int b = 0; std::tie(a, b) = getPair(); - // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: use structured binding to decompose a pair [modernize-use-structured-binding] - // CHECK-FIXES: auto [a, b] = getPair(); + // CHECK-MESSAGES-ALL: :[[@LINE-1]]:3: warning: use structured binding to decompose a pair [modernize-use-structured-binding] + // CHECK-FIXES-ALL: auto [a, b] = getPair(); int* pa = nullptr; int* pb = nullptr; std::tie(pa, pb) = getPair(); - // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: use structured binding to decompose a pair [modernize-use-structured-binding] - // CHECK-FIXES: auto [pa, pb] = getPair(); + // CHECK-MESSAGES-ALL: :[[@LINE-1]]:3: warning: use structured binding to decompose a pair [modernize-use-structured-binding] + // CHECK-FIXES-ALL: auto [pa, pb] = getPair(); TestClass c1 (1, 2); TestClass c2 = TestClass {3, 4}; std::tie(c1, c2) = getPair(); - // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: use structured binding to decompose a pair [modernize-use-structured-binding] - // CHECK-FIXES: auto [c1, c2] = getPair(); + // CHECK-MESSAGES-ALL: :[[@LINE-1]]:3: warning: use structured binding to decompose a pair [modernize-use-structured-binding] + // CHECK-FIXES-ALL: auto [c1, c2] = getPair(); } void stdTieNotWarnCases() { @@ -250,3 +250,79 @@ void NotWarnForMacro2() { int x = P.first; int y = P.second; } + +void captureByVal() { + auto P = getPair(); + // CHECK-MESSAGES-CPP20ORLATER: :[[@LINE-1]]:3: warning: use structured binding to decompose a pair [modernize-use-structured-binding] + // CHECK-FIXES-CPP20ORLATER: auto [x, y] = getPair(); + int x = P.first; + int y = P.second; + + auto lambda = [x]() { + int y = x; + }; +} + +void captureByRef() { + auto P = getPair(); + // CHECK-MESSAGES-CPP20ORLATER: :[[@LINE-1]]:3: warning: use structured binding to decompose a pair [modernize-use-structured-binding] + // CHECK-FIXES-CPP20ORLATER: auto [x, y] = getPair(); + int x = P.first; + int y = P.second; + + auto lambda = [&x]() { + x = 1; + }; +} + +void captureByAllRef() { + auto P = getPair(); + // CHECK-MESSAGES-CPP20ORLATER: :[[@LINE-1]]:3: warning: use structured binding to decompose a pair [modernize-use-structured-binding] + // CHECK-FIXES-CPP20ORLATER: auto [x, y] = getPair(); + int x = P.first; + int y = P.second; + + auto lambda = [&]() { + x = 1; + }; +} + +void deepLambda() { + auto P = getPair(); + // CHECK-MESSAGES-CPP20ORLATER: :[[@LINE-1]]:3: warning: use structured binding to decompose a pair [modernize-use-structured-binding] + // CHECK-FIXES-CPP20ORLATER: auto [x, y] = getPair(); + int x = P.first; + int y = P.second; + + { + auto lambda = [x]() { + int y = x; + }; + } +} + +void forRangeNotWarn() { + std::pair Pairs[10]; + for (auto P : Pairs) { + // CHECK-MESSAGES-CPP20ORLATER: :[[@LINE-1]]:8: warning: use structured binding to decompose a pair [modernize-use-structured-binding] + // CHECK-FIXES-CPP20ORLATER: for (auto [x, y] : Pairs) { + int x = P.first; + int y = P.second; + + auto lambda = [&]() { + x = 1; + }; + } +} + +void stdTieNotWarn() { + int x = 0; + int y = 0; + std::tie(x, y) = getPair(); + // CHECK-MESSAGES-CPP20ORLATER: :[[@LINE-1]]:3: warning: use structured binding to decompose a pair [modernize-use-structured-binding] + // CHECK-FIXES-CPP20ORLATER: auto [x, y] = getPair(); + + auto lambda = [&x]() { + x = 1; + }; +} From c97ce9b2c775c9d0967483404ff82e2ff6ed68a8 Mon Sep 17 00:00:00 2001 From: flovent Date: Sun, 14 Sep 2025 18:10:43 +0800 Subject: [PATCH 11/44] [NFC] Test removed lines --- .../modernize/use-structured-binding.cpp | 120 ++++++++++++------ 1 file changed, 80 insertions(+), 40 deletions(-) diff --git a/clang-tools-extra/test/clang-tidy/checkers/modernize/use-structured-binding.cpp b/clang-tools-extra/test/clang-tidy/checkers/modernize/use-structured-binding.cpp index 05b842e4d6614..55bdda5150d0f 100644 --- a/clang-tools-extra/test/clang-tidy/checkers/modernize/use-structured-binding.cpp +++ b/clang-tools-extra/test/clang-tidy/checkers/modernize/use-structured-binding.cpp @@ -18,24 +18,30 @@ void DecomposeByAssignWarnCases() { auto P = getPair(); // CHECK-MESSAGES-ALL: :[[@LINE-1]]:5: warning: use structured binding to decompose a pair [modernize-use-structured-binding] // CHECK-FIXES-ALL: auto [x, y] = getPair(); - int x = P.first; - int y = P.second; + int x = P.first; // REMOVE + // CHECK-FIXES-ALL: // REMOVE + int y = P.second; // REMOVE + // CHECK-FIXES-ALL: // REMOVE } { auto P = getPair(); // CHECK-MESSAGES-ALL: :[[@LINE-1]]:5: warning: use structured binding to decompose a pair [modernize-use-structured-binding] // CHECK-FIXES-ALL: auto [x, y] = getPair(); - int x = P.first; - auto y = P.second; + int x = P.first; // REMOVE + // CHECK-FIXES-ALL: // REMOVE + auto y = P.second; // REMOVE + // CHECK-FIXES-ALL: // REMOVE } { const auto P = getPair(); // CHECK-MESSAGES-ALL: :[[@LINE-1]]:5: warning: use structured binding to decompose a pair [modernize-use-structured-binding] // CHECK-FIXES-ALL: const auto [x, y] = getPair(); - const int x = P.first; - const auto y = P.second; + const int x = P.first; // REMOVE + // CHECK-FIXES-ALL: // REMOVE + const auto y = P.second; // REMOVE + // CHECK-FIXES-ALL: // REMOVE } { @@ -43,8 +49,10 @@ void DecomposeByAssignWarnCases() { auto& P = otherP; // CHECK-MESSAGES-ALL: :[[@LINE-1]]:5: warning: use structured binding to decompose a pair [modernize-use-structured-binding] // CHECK-FIXES-ALL: auto& [x, y] = otherP; - int& x = P.first; - auto& y = P.second; + int& x = P.first; // REMOVE + // CHECK-FIXES-ALL: // REMOVE + auto& y = P.second; // REMOVE + // CHECK-FIXES-ALL: // REMOVE } { @@ -52,8 +60,10 @@ void DecomposeByAssignWarnCases() { const auto& P = otherP; // CHECK-MESSAGES-ALL: :[[@LINE-1]]:5: warning: use structured binding to decompose a pair [modernize-use-structured-binding] // CHECK-FIXES-ALL: const auto& [x, y] = otherP; - const int& x = P.first; - const auto& y = P.second; + const int& x = P.first; // REMOVE + // CHECK-FIXES-ALL: // REMOVE + const auto& y = P.second; // REMOVE + // CHECK-FIXES-ALL: // REMOVE } } @@ -62,44 +72,56 @@ void forRangeWarnCases() { for (auto P : Pairs) { // CHECK-MESSAGES-ALL: :[[@LINE-1]]:8: warning: use structured binding to decompose a pair [modernize-use-structured-binding] // CHECK-FIXES-ALL: for (auto [x, y] : Pairs) { - int x = P.first; - int y = P.second; + int x = P.first; // REMOVE + // CHECK-FIXES-ALL: // REMOVE + int y = P.second; // REMOVE + // CHECK-FIXES-ALL: // REMOVE } for (const auto P : Pairs) { // CHECK-MESSAGES-ALL: :[[@LINE-1]]:8: warning: use structured binding to decompose a pair [modernize-use-structured-binding] // CHECK-FIXES-ALL: for (const auto [x, y] : Pairs) { - const int x = P.first; - const int y = P.second; + const int x = P.first; // REMOVE + // CHECK-FIXES-ALL: // REMOVE + const int y = P.second; // REMOVE + // CHECK-FIXES-ALL: // REMOVE } for (auto& P : Pairs) { // CHECK-MESSAGES-ALL: :[[@LINE-1]]:8: warning: use structured binding to decompose a pair [modernize-use-structured-binding] // CHECK-FIXES-ALL: for (auto& [x, y] : Pairs) { - int& x = P.first; - int& y = P.second; + int& x = P.first; // REMOVE + // CHECK-FIXES-ALL: // REMOVE + int& y = P.second; // REMOVE + // CHECK-FIXES-ALL: // REMOVE } for (const auto& P : Pairs) { // CHECK-MESSAGES-ALL: :[[@LINE-1]]:8: warning: use structured binding to decompose a pair [modernize-use-structured-binding] // CHECK-FIXES-ALL: for (const auto& [x, y] : Pairs) { - const int& x = P.first; - const int& y = P.second; + const int& x = P.first; // REMOVE + // CHECK-FIXES-ALL: // REMOVE + const int& y = P.second; // REMOVE + // CHECK-FIXES-ALL: // REMOVE } std::pair ClassPairs[10]; for (auto P : ClassPairs) { // CHECK-MESSAGES-ALL: :[[@LINE-1]]:8: warning: use structured binding to decompose a pair [modernize-use-structured-binding] // CHECK-FIXES-ALL: for (auto [c1, c2] : ClassPairs) { - TestClass c1 = P.first; - TestClass c2 = P.second; + TestClass c1 = P.first; // REMOVE + // CHECK-FIXES-ALL: // REMOVE + TestClass c2 = P.second; // REMOVE + // CHECK-FIXES-ALL: // REMOVE } for (const auto P : ClassPairs) { // CHECK-MESSAGES-ALL: :[[@LINE-1]]:8: warning: use structured binding to decompose a pair [modernize-use-structured-binding] // CHECK-FIXES-ALL: for (const auto [c1, c2] : ClassPairs) { - const TestClass c1 = P.first; - const TestClass c2 = P.second; + const TestClass c1 = P.first; // REMOVE + // CHECK-FIXES-ALL: // REMOVE + const TestClass c2 = P.second; // REMOVE + // CHECK-FIXES-ALL: // REMOVE } } @@ -139,20 +161,26 @@ void forRangeNotWarnCases() { } void stdTieWarnCases() { - int a = 0; - int b = 0; + int a = 0; // REMOVE + // CHECK-FIXES-ALL: // REMOVE + int b = 0; // REMOVE + // CHECK-FIXES-ALL: // REMOVE std::tie(a, b) = getPair(); // CHECK-MESSAGES-ALL: :[[@LINE-1]]:3: warning: use structured binding to decompose a pair [modernize-use-structured-binding] // CHECK-FIXES-ALL: auto [a, b] = getPair(); - int* pa = nullptr; - int* pb = nullptr; + int* pa = nullptr; // REMOVE + // CHECK-FIXES-ALL: // REMOVE + int* pb = nullptr; // REMOVE + // CHECK-FIXES-ALL: // REMOVE std::tie(pa, pb) = getPair(); // CHECK-MESSAGES-ALL: :[[@LINE-1]]:3: warning: use structured binding to decompose a pair [modernize-use-structured-binding] // CHECK-FIXES-ALL: auto [pa, pb] = getPair(); - TestClass c1 (1, 2); - TestClass c2 = TestClass {3, 4}; + TestClass c1 (1, 2); // REMOVE + // CHECK-FIXES-ALL: // REMOVE + TestClass c2 = TestClass {3, 4}; // REMOVE + // CHECK-FIXES-ALL: // REMOVE std::tie(c1, c2) = getPair(); // CHECK-MESSAGES-ALL: :[[@LINE-1]]:3: warning: use structured binding to decompose a pair [modernize-use-structured-binding] // CHECK-FIXES-ALL: auto [c1, c2] = getPair(); @@ -255,8 +283,10 @@ void captureByVal() { auto P = getPair(); // CHECK-MESSAGES-CPP20ORLATER: :[[@LINE-1]]:3: warning: use structured binding to decompose a pair [modernize-use-structured-binding] // CHECK-FIXES-CPP20ORLATER: auto [x, y] = getPair(); - int x = P.first; - int y = P.second; + int x = P.first; // REMOVE + // CHECK-FIXES-CPP20ORLATER: // REMOVE + int y = P.second; // REMOVE + // CHECK-FIXES-CPP20ORLATER: // REMOVE auto lambda = [x]() { int y = x; @@ -267,8 +297,10 @@ void captureByRef() { auto P = getPair(); // CHECK-MESSAGES-CPP20ORLATER: :[[@LINE-1]]:3: warning: use structured binding to decompose a pair [modernize-use-structured-binding] // CHECK-FIXES-CPP20ORLATER: auto [x, y] = getPair(); - int x = P.first; - int y = P.second; + int x = P.first; // REMOVE + // CHECK-FIXES-CPP20ORLATER: // REMOVE + int y = P.second; // REMOVE + // CHECK-FIXES-CPP20ORLATER: // REMOVE auto lambda = [&x]() { x = 1; @@ -279,8 +311,10 @@ void captureByAllRef() { auto P = getPair(); // CHECK-MESSAGES-CPP20ORLATER: :[[@LINE-1]]:3: warning: use structured binding to decompose a pair [modernize-use-structured-binding] // CHECK-FIXES-CPP20ORLATER: auto [x, y] = getPair(); - int x = P.first; - int y = P.second; + int x = P.first; // REMOVE + // CHECK-FIXES-CPP20ORLATER: // REMOVE + int y = P.second; // REMOVE + // CHECK-FIXES-CPP20ORLATER: // REMOVE auto lambda = [&]() { x = 1; @@ -291,8 +325,10 @@ void deepLambda() { auto P = getPair(); // CHECK-MESSAGES-CPP20ORLATER: :[[@LINE-1]]:3: warning: use structured binding to decompose a pair [modernize-use-structured-binding] // CHECK-FIXES-CPP20ORLATER: auto [x, y] = getPair(); - int x = P.first; - int y = P.second; + int x = P.first; // REMOVE + // CHECK-FIXES-CPP20ORLATER: // REMOVE + int y = P.second; // REMOVE + // CHECK-FIXES-CPP20ORLATER: // REMOVE { auto lambda = [x]() { @@ -306,8 +342,10 @@ void forRangeNotWarn() { for (auto P : Pairs) { // CHECK-MESSAGES-CPP20ORLATER: :[[@LINE-1]]:8: warning: use structured binding to decompose a pair [modernize-use-structured-binding] // CHECK-FIXES-CPP20ORLATER: for (auto [x, y] : Pairs) { - int x = P.first; - int y = P.second; + int x = P.first; // REMOVE + // CHECK-FIXES-CPP20ORLATER: // REMOVE + int y = P.second; // REMOVE + // CHECK-FIXES-CPP20ORLATER: // REMOVE auto lambda = [&]() { x = 1; @@ -316,8 +354,10 @@ void forRangeNotWarn() { } void stdTieNotWarn() { - int x = 0; - int y = 0; + int x = 0; // REMOVE + // CHECK-FIXES-CPP20ORLATER: // REMOVE + int y = 0; // REMOVE + // CHECK-FIXES-CPP20ORLATER: // REMOVE std::tie(x, y) = getPair(); // CHECK-MESSAGES-CPP20ORLATER: :[[@LINE-1]]:3: warning: use structured binding to decompose a pair [modernize-use-structured-binding] // CHECK-FIXES-CPP20ORLATER: auto [x, y] = getPair(); From fbcb0999bc232c79a23a4370222560928af8f1e9 Mon Sep 17 00:00:00 2001 From: flovent Date: Sun, 14 Sep 2025 18:39:02 +0800 Subject: [PATCH 12/44] [NFC] 80-character for check doc --- .../checks/modernize/use-structured-binding.rst | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/clang-tools-extra/docs/clang-tidy/checks/modernize/use-structured-binding.rst b/clang-tools-extra/docs/clang-tidy/checks/modernize/use-structured-binding.rst index 0d227b75968a7..637f8bcee666f 100644 --- a/clang-tools-extra/docs/clang-tidy/checks/modernize/use-structured-binding.rst +++ b/clang-tools-extra/docs/clang-tidy/checks/modernize/use-structured-binding.rst @@ -6,9 +6,11 @@ modernize-use-structured-binding Finds places where structured bindings could be used to decompose pairs and suggests replacing them. -This check finds three code patterns and recommends using structured bindings for clearer, more idiomatic C++17 code. +This check finds three code patterns and recommends using structured bindings +for clearer, more idiomatic C++17 code. -1. Decompose a pair variable by assigning its members to separate variables right after its definition: +1. Decompose a pair variable by assigning its members to separate variables +right after its definition: .. code-block:: c++ @@ -32,7 +34,8 @@ This check finds three code patterns and recommends using structured bindings fo auto [a, b] = getPair(); -3. Manually decompose a pair by assigning to its members to local variables in a range-based for loop: +3. Manually decompose a pair by assigning to its members to local variables +in a range-based for loop: .. code-block:: c++ @@ -48,12 +51,14 @@ This check finds three code patterns and recommends using structured bindings fo // use x and y } -The check also supports custom pair-like types via the :option:`PairTypes` option. +The check also supports custom pair-like types via the :option:`PairTypes` +option. Options ------- .. option:: PairTypes - A Semicolon-separated list of type names to be treated as pair-like for structured binding suggestions. - Example: `MyPairType;OtherPairType`. Default is `std::pair`. + A Semicolon-separated list of type names to be treated as pair-like for + structured binding suggestions. Example: `MyPairType;OtherPairType`. + Default is `std::pair`. From 3f95a697181f00e68971c26bccc18a9c33ba319c Mon Sep 17 00:00:00 2001 From: flovent Date: Sun, 14 Sep 2025 18:45:25 +0800 Subject: [PATCH 13/44] [NFC] Add limitations section to doc --- .../modernize/use-structured-binding.rst | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/clang-tools-extra/docs/clang-tidy/checks/modernize/use-structured-binding.rst b/clang-tools-extra/docs/clang-tidy/checks/modernize/use-structured-binding.rst index 637f8bcee666f..a635158420e21 100644 --- a/clang-tools-extra/docs/clang-tidy/checks/modernize/use-structured-binding.rst +++ b/clang-tools-extra/docs/clang-tidy/checks/modernize/use-structured-binding.rst @@ -54,6 +54,37 @@ in a range-based for loop: The check also supports custom pair-like types via the :option:`PairTypes` option. +Limitations +----------- + +The check currently ignores variables defined with attributes or qualifiers +except const and & since it's not very common: + +.. code-block:: c++ + + static auto pair = getPair(); + static int b = pair.first; + static int c = pair.second; + +The check doesn't check for some situations which could possibly transfered +to structured bnindings, for example: + +.. code-block:: c++ + + const auto& results = mapping.try_emplace("hello!"); + const iterator& it = results.first; + bool succeed = results.second; + // succeed is not changed in the following code + +and: + +.. code-block:: c++ + + const auto results = mapping.try_emplace("hello!"); + if (results.second) { + handle_inserted(results.first); + } + Options ------- From a5222a22546d1119896b22a58a1bc28914181e54 Mon Sep 17 00:00:00 2001 From: flovent Date: Sun, 14 Sep 2025 18:49:55 +0800 Subject: [PATCH 14/44] [NFC] Add some tests about two VarDecl in one DeclStmt. --- .../modernize/use-structured-binding.cpp | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/clang-tools-extra/test/clang-tidy/checkers/modernize/use-structured-binding.cpp b/clang-tools-extra/test/clang-tidy/checkers/modernize/use-structured-binding.cpp index 55bdda5150d0f..bef93027f72fb 100644 --- a/clang-tools-extra/test/clang-tidy/checkers/modernize/use-structured-binding.cpp +++ b/clang-tools-extra/test/clang-tidy/checkers/modernize/use-structured-binding.cpp @@ -24,6 +24,14 @@ void DecomposeByAssignWarnCases() { // CHECK-FIXES-ALL: // REMOVE } + { + auto P = getPair(); + // CHECK-MESSAGES-ALL: :[[@LINE-1]]:5: warning: use structured binding to decompose a pair [modernize-use-structured-binding] + // CHECK-FIXES-ALL: auto [x, y] = getPair(); + int x = P.first, y = P.second; // REMOVE + // CHECK-FIXES-ALL: // REMOVE + } + { auto P = getPair(); // CHECK-MESSAGES-ALL: :[[@LINE-1]]:5: warning: use structured binding to decompose a pair [modernize-use-structured-binding] @@ -78,6 +86,13 @@ void forRangeWarnCases() { // CHECK-FIXES-ALL: // REMOVE } + for (auto P : Pairs) { + // CHECK-MESSAGES-ALL: :[[@LINE-1]]:8: warning: use structured binding to decompose a pair [modernize-use-structured-binding] + // CHECK-FIXES-ALL: for (auto [x, y] : Pairs) { + int x = P.first, y = P.second; // REMOVE + // CHECK-FIXES-ALL: // REMOVE + } + for (const auto P : Pairs) { // CHECK-MESSAGES-ALL: :[[@LINE-1]]:8: warning: use structured binding to decompose a pair [modernize-use-structured-binding] // CHECK-FIXES-ALL: for (const auto [x, y] : Pairs) { @@ -169,6 +184,12 @@ void stdTieWarnCases() { // CHECK-MESSAGES-ALL: :[[@LINE-1]]:3: warning: use structured binding to decompose a pair [modernize-use-structured-binding] // CHECK-FIXES-ALL: auto [a, b] = getPair(); + int x = 0, y = 0; // REMOVE + // CHECK-FIXES-ALL: // REMOVE + std::tie(x, y) = getPair(); + // CHECK-MESSAGES-ALL: :[[@LINE-1]]:3: warning: use structured binding to decompose a pair [modernize-use-structured-binding] + // CHECK-FIXES-ALL: auto [x, y] = getPair(); + int* pa = nullptr; // REMOVE // CHECK-FIXES-ALL: // REMOVE int* pb = nullptr; // REMOVE From 8c423b34ee9f6cd3103001b20e63d3ecee2875f4 Mon Sep 17 00:00:00 2001 From: flovent Date: Sun, 14 Sep 2025 21:35:53 +0800 Subject: [PATCH 15/44] Update clang-tools-extra/clang-tidy/modernize/UseStructuredBindingCheck.cpp Co-authored-by: EugeneZelenko --- .../clang-tidy/modernize/UseStructuredBindingCheck.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clang-tools-extra/clang-tidy/modernize/UseStructuredBindingCheck.cpp b/clang-tools-extra/clang-tidy/modernize/UseStructuredBindingCheck.cpp index fab4e8dc16cf1..f9d700f5e849c 100644 --- a/clang-tools-extra/clang-tidy/modernize/UseStructuredBindingCheck.cpp +++ b/clang-tools-extra/clang-tidy/modernize/UseStructuredBindingCheck.cpp @@ -1,4 +1,4 @@ -//===--- UseStructuredBindingCheck.cpp - clang-tidy -----------------------===// +//===----------------------------------------------------------------------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. From 42a4e5b381fdb3a525159f2c8e558c9bf036b42f Mon Sep 17 00:00:00 2001 From: flovent Date: Sun, 14 Sep 2025 21:36:13 +0800 Subject: [PATCH 16/44] Update clang-tools-extra/clang-tidy/modernize/UseStructuredBindingCheck.h Co-authored-by: EugeneZelenko --- .../clang-tidy/modernize/UseStructuredBindingCheck.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clang-tools-extra/clang-tidy/modernize/UseStructuredBindingCheck.h b/clang-tools-extra/clang-tidy/modernize/UseStructuredBindingCheck.h index 63bc0a8c3da45..83d262a5db3cd 100644 --- a/clang-tools-extra/clang-tidy/modernize/UseStructuredBindingCheck.h +++ b/clang-tools-extra/clang-tidy/modernize/UseStructuredBindingCheck.h @@ -1,4 +1,4 @@ -//===--- UseStructuredBindingCheck.h - clang-tidy ---------------*- C++ -*-===// +//===----------------------------------------------------------------------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. From 8919fdb385b2a69704c4348fecb5f99b22bea7d7 Mon Sep 17 00:00:00 2001 From: flovent Date: Sun, 14 Sep 2025 21:36:37 +0800 Subject: [PATCH 17/44] Update clang-tools-extra/clang-tidy/modernize/UseStructuredBindingCheck.cpp Co-authored-by: EugeneZelenko --- .../clang-tidy/modernize/UseStructuredBindingCheck.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clang-tools-extra/clang-tidy/modernize/UseStructuredBindingCheck.cpp b/clang-tools-extra/clang-tidy/modernize/UseStructuredBindingCheck.cpp index f9d700f5e849c..78582afe706e6 100644 --- a/clang-tools-extra/clang-tidy/modernize/UseStructuredBindingCheck.cpp +++ b/clang-tools-extra/clang-tidy/modernize/UseStructuredBindingCheck.cpp @@ -125,7 +125,7 @@ AST_MATCHER_P2(Stmt, hasPreTwoVarDecl, ast_matchers::internal::Matcher, AST_MATCHER_P2(Stmt, hasNextTwoVarDecl, ast_matchers::internal::Matcher, InnerMatcher1, ast_matchers::internal::Matcher, InnerMatcher2) { - DynTypedNodeList Parents = Finder->getASTContext().getParents(Node); + const DynTypedNodeList Parents = Finder->getASTContext().getParents(Node); if (Parents.size() != 1) return false; From 88a243efa5243d7a846c820aeda1fcb79ba29eaf Mon Sep 17 00:00:00 2001 From: flovent Date: Sun, 14 Sep 2025 21:36:47 +0800 Subject: [PATCH 18/44] Update clang-tools-extra/clang-tidy/modernize/UseStructuredBindingCheck.cpp Co-authored-by: EugeneZelenko --- .../clang-tidy/modernize/UseStructuredBindingCheck.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clang-tools-extra/clang-tidy/modernize/UseStructuredBindingCheck.cpp b/clang-tools-extra/clang-tidy/modernize/UseStructuredBindingCheck.cpp index 78582afe706e6..99cef95aa0251 100644 --- a/clang-tools-extra/clang-tidy/modernize/UseStructuredBindingCheck.cpp +++ b/clang-tools-extra/clang-tidy/modernize/UseStructuredBindingCheck.cpp @@ -386,7 +386,7 @@ void UseStructuredBindingCheck::check(const MatchFinder::MatchResult &Result) { !Res.empty()) InitE = Res[0].getNodeAs("init_expr"); - std::optional PairCaptureType = + const std::optional PairCaptureType = getTransferType(*Result.Context, PairVar->getType(), InitE->getType()); std::optional FirstVarCaptureType = getTransferType(*Result.Context, FirstVar->getType(), From 8fa2edcce1f8ee83aeb7e3764ebf0ac19bcdc972 Mon Sep 17 00:00:00 2001 From: flovent Date: Sun, 14 Sep 2025 21:36:55 +0800 Subject: [PATCH 19/44] Update clang-tools-extra/clang-tidy/modernize/UseStructuredBindingCheck.cpp Co-authored-by: EugeneZelenko --- .../clang-tidy/modernize/UseStructuredBindingCheck.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clang-tools-extra/clang-tidy/modernize/UseStructuredBindingCheck.cpp b/clang-tools-extra/clang-tidy/modernize/UseStructuredBindingCheck.cpp index 99cef95aa0251..b8b1540e69864 100644 --- a/clang-tools-extra/clang-tidy/modernize/UseStructuredBindingCheck.cpp +++ b/clang-tools-extra/clang-tidy/modernize/UseStructuredBindingCheck.cpp @@ -388,7 +388,7 @@ void UseStructuredBindingCheck::check(const MatchFinder::MatchResult &Result) { const std::optional PairCaptureType = getTransferType(*Result.Context, PairVar->getType(), InitE->getType()); - std::optional FirstVarCaptureType = + const std::optional FirstVarCaptureType = getTransferType(*Result.Context, FirstVar->getType(), *Result.Nodes.getNodeAs(FirstTypeName)); std::optional SecondVarCaptureType = From 2c980656ab7ea781fcb7c7dc6f140d56ab432384 Mon Sep 17 00:00:00 2001 From: flovent Date: Sun, 14 Sep 2025 21:37:09 +0800 Subject: [PATCH 20/44] Update clang-tools-extra/clang-tidy/modernize/UseStructuredBindingCheck.cpp Co-authored-by: EugeneZelenko --- .../clang-tidy/modernize/UseStructuredBindingCheck.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clang-tools-extra/clang-tidy/modernize/UseStructuredBindingCheck.cpp b/clang-tools-extra/clang-tidy/modernize/UseStructuredBindingCheck.cpp index b8b1540e69864..f15ce7ca082ea 100644 --- a/clang-tools-extra/clang-tidy/modernize/UseStructuredBindingCheck.cpp +++ b/clang-tools-extra/clang-tidy/modernize/UseStructuredBindingCheck.cpp @@ -391,7 +391,7 @@ void UseStructuredBindingCheck::check(const MatchFinder::MatchResult &Result) { const std::optional FirstVarCaptureType = getTransferType(*Result.Context, FirstVar->getType(), *Result.Nodes.getNodeAs(FirstTypeName)); - std::optional SecondVarCaptureType = + const std::optional SecondVarCaptureType = getTransferType(*Result.Context, SecondVar->getType(), *Result.Nodes.getNodeAs(SecondTypeName)); if (!PairCaptureType || !FirstVarCaptureType || !SecondVarCaptureType || From 48b747ec2fb81b69a8084f561b0dbf36f3201c7e Mon Sep 17 00:00:00 2001 From: flovent Date: Tue, 16 Sep 2025 20:01:01 +0800 Subject: [PATCH 21/44] [NFC] Correct comment words --- .../clang-tidy/modernize/UseStructuredBindingCheck.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/clang-tools-extra/clang-tidy/modernize/UseStructuredBindingCheck.cpp b/clang-tools-extra/clang-tidy/modernize/UseStructuredBindingCheck.cpp index f15ce7ca082ea..b8d9ac964961b 100644 --- a/clang-tools-extra/clang-tidy/modernize/UseStructuredBindingCheck.cpp +++ b/clang-tools-extra/clang-tidy/modernize/UseStructuredBindingCheck.cpp @@ -149,8 +149,8 @@ AST_MATCHER_P2(Stmt, hasNextTwoVarDecl, return false; } -/// Matches a Stmt whose parent is a CompoundStmt, and there a two VarDecls -/// matching the inner matcher in the beginning of CompoundStmt. +/// Matches a CompoundStmt which has two VarDecls +/// matching the inner matcher in the beginning. AST_MATCHER_P2(CompoundStmt, hasFirstTwoVarDecl, ast_matchers::internal::Matcher, InnerMatcher1, ast_matchers::internal::Matcher, InnerMatcher2) { From a46277ea8aac25188ee15c2a6841f258b1abad88 Mon Sep 17 00:00:00 2001 From: flovent Date: Tue, 16 Sep 2025 23:14:36 +0800 Subject: [PATCH 22/44] Update clang-tools-extra/docs/clang-tidy/checks/modernize/use-structured-binding.rst Co-authored-by: EugeneZelenko --- .../docs/clang-tidy/checks/modernize/use-structured-binding.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clang-tools-extra/docs/clang-tidy/checks/modernize/use-structured-binding.rst b/clang-tools-extra/docs/clang-tidy/checks/modernize/use-structured-binding.rst index a635158420e21..a935516ed4307 100644 --- a/clang-tools-extra/docs/clang-tidy/checks/modernize/use-structured-binding.rst +++ b/clang-tools-extra/docs/clang-tidy/checks/modernize/use-structured-binding.rst @@ -90,6 +90,6 @@ Options .. option:: PairTypes - A Semicolon-separated list of type names to be treated as pair-like for + A semicolon-separated list of type names to be treated as pair-like for structured binding suggestions. Example: `MyPairType;OtherPairType`. Default is `std::pair`. From 36011dd2fb1b1f225d15ed12e1f66e9f3c896c80 Mon Sep 17 00:00:00 2001 From: flovent Date: Tue, 16 Sep 2025 23:15:49 +0800 Subject: [PATCH 23/44] [NFC] 80 chars limit for comment --- .../clang-tidy/modernize/UseStructuredBindingCheck.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/clang-tools-extra/clang-tidy/modernize/UseStructuredBindingCheck.cpp b/clang-tools-extra/clang-tidy/modernize/UseStructuredBindingCheck.cpp index b8d9ac964961b..7ecfeefc6f8df 100644 --- a/clang-tools-extra/clang-tidy/modernize/UseStructuredBindingCheck.cpp +++ b/clang-tools-extra/clang-tidy/modernize/UseStructuredBindingCheck.cpp @@ -149,8 +149,8 @@ AST_MATCHER_P2(Stmt, hasNextTwoVarDecl, return false; } -/// Matches a CompoundStmt which has two VarDecls -/// matching the inner matcher in the beginning. +/// Matches a CompoundStmt which has two VarDecls matching the inner matcher in +/// the beginning. AST_MATCHER_P2(CompoundStmt, hasFirstTwoVarDecl, ast_matchers::internal::Matcher, InnerMatcher1, ast_matchers::internal::Matcher, InnerMatcher2) { @@ -164,8 +164,8 @@ AST_MATCHER_P2(CompoundStmt, hasFirstTwoVarDecl, InnerMatcher1, InnerMatcher2, Finder, Builder); } -/// It's not very common to have specifiers for variables used to decompose -/// a pair, so we ignore these cases. +/// It's not very common to have specifiers for variables used to decompose a +/// pair, so we ignore these cases. AST_MATCHER(VarDecl, hasAnySpecifiersShouldBeIgnored) { return Node.isStaticLocal() || Node.isConstexpr() || Node.hasAttrs() || Node.isInlineSpecified(); From c5c673b602db51cb93cda6666fed4d22240a9dcc Mon Sep 17 00:00:00 2001 From: flovent Date: Wed, 17 Sep 2025 20:23:29 +0800 Subject: [PATCH 24/44] Update clang-tools-extra/docs/clang-tidy/checks/modernize/use-structured-binding.rst Co-authored-by: Congcong Cai --- .../docs/clang-tidy/checks/modernize/use-structured-binding.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clang-tools-extra/docs/clang-tidy/checks/modernize/use-structured-binding.rst b/clang-tools-extra/docs/clang-tidy/checks/modernize/use-structured-binding.rst index a935516ed4307..0d02e6761cdd4 100644 --- a/clang-tools-extra/docs/clang-tidy/checks/modernize/use-structured-binding.rst +++ b/clang-tools-extra/docs/clang-tidy/checks/modernize/use-structured-binding.rst @@ -39,7 +39,7 @@ in a range-based for loop: .. code-block:: c++ - for (autop : vecOfPairs) { + for (auto p : vecOfPairs) { int x = p.first; int y = p.second; // ... From d92f76849cad72881a87e0c36172ecaa670966dd Mon Sep 17 00:00:00 2001 From: flovent Date: Wed, 17 Sep 2025 21:25:00 +0800 Subject: [PATCH 25/44] [NFC] prefer static function --- .../modernize/UseStructuredBindingCheck.cpp | 56 ++++++++++--------- 1 file changed, 29 insertions(+), 27 deletions(-) diff --git a/clang-tools-extra/clang-tidy/modernize/UseStructuredBindingCheck.cpp b/clang-tools-extra/clang-tidy/modernize/UseStructuredBindingCheck.cpp index 7ecfeefc6f8df..f04923f01a459 100644 --- a/clang-tools-extra/clang-tidy/modernize/UseStructuredBindingCheck.cpp +++ b/clang-tools-extra/clang-tidy/modernize/UseStructuredBindingCheck.cpp @@ -14,37 +14,29 @@ using namespace clang::ast_matchers; namespace clang::tidy::modernize { -namespace { -constexpr const char *DefaultPairTypes = "std::pair"; -constexpr llvm::StringLiteral PairDeclName = "PairVarD"; -constexpr llvm::StringLiteral PairVarTypeName = "PairVarType"; -constexpr llvm::StringLiteral FirstVarDeclName = "FirstVarDecl"; -constexpr llvm::StringLiteral SecondVarDeclName = "SecondVarDecl"; -constexpr llvm::StringLiteral FirstDeclStmtName = "FirstDeclStmt"; -constexpr llvm::StringLiteral SecondDeclStmtName = "SecondDeclStmt"; -constexpr llvm::StringLiteral FirstTypeName = "FirstType"; -constexpr llvm::StringLiteral SecondTypeName = "SecondType"; -constexpr llvm::StringLiteral ScopeBlockName = "ScopeBlock"; -constexpr llvm::StringLiteral StdTieAssignStmtName = "StdTieAssign"; -constexpr llvm::StringLiteral StdTieExprName = "StdTieExpr"; -constexpr llvm::StringLiteral ForRangeStmtName = "ForRangeStmt"; -/// 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 -}; +static constexpr const char *DefaultPairTypes = "std::pair"; +static constexpr llvm::StringLiteral PairDeclName = "PairVarD"; +static constexpr llvm::StringLiteral PairVarTypeName = "PairVarType"; +static constexpr llvm::StringLiteral FirstVarDeclName = "FirstVarDecl"; +static constexpr llvm::StringLiteral SecondVarDeclName = "SecondVarDecl"; +static constexpr llvm::StringLiteral FirstDeclStmtName = "FirstDeclStmt"; +static constexpr llvm::StringLiteral SecondDeclStmtName = "SecondDeclStmt"; +static constexpr llvm::StringLiteral FirstTypeName = "FirstType"; +static constexpr llvm::StringLiteral SecondTypeName = "SecondType"; +static constexpr llvm::StringLiteral ScopeBlockName = "ScopeBlock"; +static constexpr llvm::StringLiteral StdTieAssignStmtName = "StdTieAssign"; +static constexpr llvm::StringLiteral StdTieExprName = "StdTieExpr"; +static constexpr llvm::StringLiteral ForRangeStmtName = "ForRangeStmt"; /// Try to match exactly two VarDecl inside two DeclStmts, and set binding for /// the used DeclStmts. -bool matchTwoVarDecl(const DeclStmt *DS1, const DeclStmt *DS2, - ast_matchers::internal::Matcher InnerMatcher1, - ast_matchers::internal::Matcher InnerMatcher2, - internal::ASTMatchFinder *Finder, - internal::BoundNodesTreeBuilder *Builder) { +static bool +matchTwoVarDecl(const DeclStmt *DS1, const DeclStmt *DS2, + ast_matchers::internal::Matcher InnerMatcher1, + ast_matchers::internal::Matcher InnerMatcher2, + internal::ASTMatchFinder *Finder, + internal::BoundNodesTreeBuilder *Builder) { SmallVector, 2> Vars; auto CollectVarsInDeclStmt = [&Vars](const DeclStmt *DS) -> bool { if (!DS) @@ -82,6 +74,16 @@ bool matchTwoVarDecl(const DeclStmt *DS1, const DeclStmt *DS2, 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, at the same time set /// binding for the CompoundStmt. From e4ffb48e8c9cb84b9b3c7b14f2e1a32c00228905 Mon Sep 17 00:00:00 2001 From: flovent Date: Wed, 17 Sep 2025 21:25:44 +0800 Subject: [PATCH 26/44] [NFC] Explicit capture in lambda --- .../clang-tidy/modernize/UseStructuredBindingCheck.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/clang-tools-extra/clang-tidy/modernize/UseStructuredBindingCheck.cpp b/clang-tools-extra/clang-tidy/modernize/UseStructuredBindingCheck.cpp index f04923f01a459..c7ae24fb0c840 100644 --- a/clang-tools-extra/clang-tidy/modernize/UseStructuredBindingCheck.cpp +++ b/clang-tools-extra/clang-tidy/modernize/UseStructuredBindingCheck.cpp @@ -339,8 +339,9 @@ void UseStructuredBindingCheck::check(const MatchFinder::MatchResult &Result) { } const auto *CFRS = Result.Nodes.getNodeAs(ForRangeStmtName); - auto DiagAndFix = [&](SourceLocation DiagLoc, SourceRange ReplaceRange, - TransferType TT = TT_ByVal) { + auto DiagAndFix = [&DS1, &DS2, &FirstVar, &SecondVar, &CFRS, + this](SourceLocation DiagLoc, SourceRange ReplaceRange, + TransferType TT = TT_ByVal) { StringRef Prefix; switch (TT) { case TT_ByVal: From 73231c8de9e38686b03cbf4d7044172eece9fb93 Mon Sep 17 00:00:00 2001 From: flovent Date: Wed, 17 Sep 2025 21:26:27 +0800 Subject: [PATCH 27/44] [NFC] Use llvm::twine to concat string --- .../clang-tidy/modernize/UseStructuredBindingCheck.cpp | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/clang-tools-extra/clang-tidy/modernize/UseStructuredBindingCheck.cpp b/clang-tools-extra/clang-tidy/modernize/UseStructuredBindingCheck.cpp index c7ae24fb0c840..8ca088ecf1405 100644 --- a/clang-tools-extra/clang-tidy/modernize/UseStructuredBindingCheck.cpp +++ b/clang-tools-extra/clang-tidy/modernize/UseStructuredBindingCheck.cpp @@ -363,11 +363,10 @@ void UseStructuredBindingCheck::check(const MatchFinder::MatchResult &Result) { if (DS2) Hints.emplace_back(FixItHint::CreateRemoval(DS2->getSourceRange())); - std::string ReplacementText = Prefix.str() + " [" + - FirstVar->getNameAsString() + ", " + - SecondVar->getNameAsString() + "]"; - if (CFRS) - ReplacementText += " :"; + std::string ReplacementText = + (Twine(Prefix) + " [" + FirstVar->getNameAsString() + ", " + + SecondVar->getNameAsString() + "]" + (CFRS ? " :" : "")) + .str(); diag(DiagLoc, "use structured binding to decompose a pair") << FixItHint::CreateReplacement(ReplaceRange, ReplacementText) << Hints; }; From 7ac2a3ed82e78e18c545a066929b53ac297560b6 Mon Sep 17 00:00:00 2001 From: flovent Date: Wed, 17 Sep 2025 21:38:55 +0800 Subject: [PATCH 28/44] [NFC] Avoid some duplications --- .../modernize/UseStructuredBindingCheck.cpp | 51 +++++++++---------- 1 file changed, 24 insertions(+), 27 deletions(-) diff --git a/clang-tools-extra/clang-tidy/modernize/UseStructuredBindingCheck.cpp b/clang-tools-extra/clang-tidy/modernize/UseStructuredBindingCheck.cpp index 8ca088ecf1405..24e59e3044d1d 100644 --- a/clang-tools-extra/clang-tidy/modernize/UseStructuredBindingCheck.cpp +++ b/clang-tools-extra/clang-tidy/modernize/UseStructuredBindingCheck.cpp @@ -202,12 +202,12 @@ UseStructuredBindingCheck::UseStructuredBindingCheck(StringRef Name, ; } -static auto getVarInitWithMemberMatcher(StringRef PairName, - StringRef MemberName, - StringRef TypeName, - StringRef BindingName) { +static auto getVarInitWithMemberMatcher( + StringRef PairName, StringRef MemberName, StringRef TypeName, + StringRef BindingName, + ast_matchers::internal::Matcher ExtraMatcher) { return varDecl( - unless(hasAnySpecifiersShouldBeIgnored()), unless(isInMarco()), + ExtraMatcher, hasInitializer( ignoringImpCasts(ignoringCopyCtorAndImplicitCast(memberExpr( hasObjectExpression(ignoringImpCasts(declRefExpr( @@ -223,10 +223,20 @@ void UseStructuredBindingCheck::registerMatchers(MatchFinder *Finder) { hasUnqualifiedDesugaredType(recordType( hasDeclaration(cxxRecordDecl(hasAnyName(PairTypes)))))); - auto VarInitWithFirstMember = getVarInitWithMemberMatcher( - PairDeclName, "first", FirstTypeName, FirstVarDeclName); - auto VarInitWithSecondMember = getVarInitWithMemberMatcher( - PairDeclName, "second", SecondTypeName, SecondVarDeclName); + auto UnlessShouldBeIgnored = + unless(anyOf(hasAnySpecifiersShouldBeIgnored(), isInMarco())); + + auto VarInitWithFirstMember = + getVarInitWithMemberMatcher(PairDeclName, "first", FirstTypeName, + FirstVarDeclName, UnlessShouldBeIgnored); + auto VarInitWithSecondMember = + getVarInitWithMemberMatcher(PairDeclName, "second", SecondTypeName, + SecondVarDeclName, UnlessShouldBeIgnored); + + auto RefToBindName = [&UnlessShouldBeIgnored](const llvm::StringLiteral &Name) + -> ast_matchers::internal::BindableMatcher { + return declRefExpr(to(varDecl(UnlessShouldBeIgnored).bind(Name))); + }; // X x; // Y y; @@ -237,23 +247,10 @@ void UseStructuredBindingCheck::registerMatchers(MatchFinder *Finder) { has(cxxOperatorCallExpr( hasOverloadedOperatorName("="), hasLHS(ignoringImplicit( - callExpr( - callee( - functionDecl(isInStdNamespace(), hasName("tie"))), - hasArgument( - 0, - declRefExpr(to( - varDecl( - unless(hasAnySpecifiersShouldBeIgnored()), - unless(isInMarco())) - .bind(FirstVarDeclName)))), - hasArgument( - 1, - declRefExpr(to( - varDecl( - unless(hasAnySpecifiersShouldBeIgnored()), - unless(isInMarco())) - .bind(SecondVarDeclName))))) + callExpr(callee(functionDecl(isInStdNamespace(), + hasName("tie"))), + hasArgument(0, RefToBindName(FirstVarDeclName)), + hasArgument(1, RefToBindName(SecondVarDeclName))) .bind(StdTieExprName))), hasRHS(expr(hasType(PairType)))) .bind(StdTieAssignStmtName)), @@ -269,7 +266,7 @@ void UseStructuredBindingCheck::registerMatchers(MatchFinder *Finder) { declStmt( unless(isInMarco()), hasSingleDecl( - varDecl(unless(hasAnySpecifiersShouldBeIgnored()), + varDecl(UnlessShouldBeIgnored, hasType(qualType(anyOf(PairType, lValueReferenceType( pointee(PairType)))) .bind(PairVarTypeName)), From 00285d9e49358ce8a83090b7f684183823973990 Mon Sep 17 00:00:00 2001 From: flovent Date: Wed, 17 Sep 2025 21:47:01 +0800 Subject: [PATCH 29/44] Stop collecting when we got two VarDecls from DS1 --- .../modernize/UseStructuredBindingCheck.cpp | 7 +++---- .../modernize/use-structured-binding.cpp | 17 +++++++++++++++++ 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/clang-tools-extra/clang-tidy/modernize/UseStructuredBindingCheck.cpp b/clang-tools-extra/clang-tidy/modernize/UseStructuredBindingCheck.cpp index 24e59e3044d1d..43431721e439e 100644 --- a/clang-tools-extra/clang-tidy/modernize/UseStructuredBindingCheck.cpp +++ b/clang-tools-extra/clang-tidy/modernize/UseStructuredBindingCheck.cpp @@ -55,10 +55,9 @@ matchTwoVarDecl(const DeclStmt *DS1, const DeclStmt *DS2, return true; }; - if (!CollectVarsInDeclStmt(DS1) || !CollectVarsInDeclStmt(DS2)) - return false; - - if (Vars.size() != 2) + // If we already get two VarDecls, then don't need to collect from DS2. + if (!CollectVarsInDeclStmt(DS1) || + (Vars.size() != 2 && !CollectVarsInDeclStmt(DS2)) || Vars.size() != 2) return false; if (InnerMatcher1.matches(*Vars[0].first, Finder, Builder) && diff --git a/clang-tools-extra/test/clang-tidy/checkers/modernize/use-structured-binding.cpp b/clang-tools-extra/test/clang-tidy/checkers/modernize/use-structured-binding.cpp index bef93027f72fb..33de71762ba87 100644 --- a/clang-tools-extra/test/clang-tidy/checkers/modernize/use-structured-binding.cpp +++ b/clang-tools-extra/test/clang-tidy/checkers/modernize/use-structured-binding.cpp @@ -32,6 +32,15 @@ void DecomposeByAssignWarnCases() { // CHECK-FIXES-ALL: // REMOVE } + { + auto P = getPair(); + // CHECK-MESSAGES-ALL: :[[@LINE-1]]:5: warning: use structured binding to decompose a pair [modernize-use-structured-binding] + // CHECK-FIXES-ALL: auto [x, y] = getPair(); + int x = P.first, y = P.second; // REMOVE + // CHECK-FIXES-ALL: // REMOVE + int z; + } + { auto P = getPair(); // CHECK-MESSAGES-ALL: :[[@LINE-1]]:5: warning: use structured binding to decompose a pair [modernize-use-structured-binding] @@ -93,6 +102,14 @@ void forRangeWarnCases() { // CHECK-FIXES-ALL: // REMOVE } + for (auto P : Pairs) { + // CHECK-MESSAGES-ALL: :[[@LINE-1]]:8: warning: use structured binding to decompose a pair [modernize-use-structured-binding] + // CHECK-FIXES-ALL: for (auto [x, y] : Pairs) { + int x = P.first, y = P.second; // REMOVE + // CHECK-FIXES-ALL: // REMOVE + int z; + } + for (const auto P : Pairs) { // CHECK-MESSAGES-ALL: :[[@LINE-1]]:8: warning: use structured binding to decompose a pair [modernize-use-structured-binding] // CHECK-FIXES-ALL: for (const auto [x, y] : Pairs) { From 242316ead2d67cd2ecbfab8c4344c63373f79f8c Mon Sep 17 00:00:00 2001 From: flovent Date: Wed, 17 Sep 2025 23:29:07 +0800 Subject: [PATCH 30/44] Exit for std::tie case when DS1 has multi child decl --- .../clang-tidy/modernize/UseStructuredBindingCheck.cpp | 2 ++ .../clang-tidy/checkers/modernize/use-structured-binding.cpp | 4 ++++ 2 files changed, 6 insertions(+) diff --git a/clang-tools-extra/clang-tidy/modernize/UseStructuredBindingCheck.cpp b/clang-tools-extra/clang-tidy/modernize/UseStructuredBindingCheck.cpp index 43431721e439e..c471436c0c88a 100644 --- a/clang-tools-extra/clang-tidy/modernize/UseStructuredBindingCheck.cpp +++ b/clang-tools-extra/clang-tidy/modernize/UseStructuredBindingCheck.cpp @@ -110,6 +110,8 @@ AST_MATCHER_P2(Stmt, hasPreTwoVarDecl, ast_matchers::internal::Matcher, const DeclStmt *DS1 = (!DS2->isSingleDecl() || ((I + 2) == C->body_rend()) ? nullptr : dyn_cast(*(I + 2))); + if (DS1 && !DS1->isSingleDecl()) + return false; if (matchTwoVarDecl(DS1, DS2, InnerMatcher1, InnerMatcher2, Finder, Builder)) { diff --git a/clang-tools-extra/test/clang-tidy/checkers/modernize/use-structured-binding.cpp b/clang-tools-extra/test/clang-tidy/checkers/modernize/use-structured-binding.cpp index 33de71762ba87..be3418a2ac224 100644 --- a/clang-tools-extra/test/clang-tidy/checkers/modernize/use-structured-binding.cpp +++ b/clang-tools-extra/test/clang-tidy/checkers/modernize/use-structured-binding.cpp @@ -230,6 +230,10 @@ void stdTieNotWarnCases() { a = 4; std::tie(a, b) = getPair(); // no warning + int c = 0, d = 0; + int e = 0; + std::tie(a, b) = getPair(); // no warning + int* pa = nullptr; int* pb = nullptr; MarkUsed(pa); From d2eb66b5fd5309a6eea401b139d43787b71209b0 Mon Sep 17 00:00:00 2001 From: flovent Date: Fri, 19 Sep 2025 11:59:12 +0800 Subject: [PATCH 31/44] [NFC] use llvm::reverse --- .../clang-tidy/modernize/UseStructuredBindingCheck.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/clang-tools-extra/clang-tidy/modernize/UseStructuredBindingCheck.cpp b/clang-tools-extra/clang-tidy/modernize/UseStructuredBindingCheck.cpp index c471436c0c88a..b0230d0819420 100644 --- a/clang-tools-extra/clang-tidy/modernize/UseStructuredBindingCheck.cpp +++ b/clang-tools-extra/clang-tidy/modernize/UseStructuredBindingCheck.cpp @@ -97,8 +97,7 @@ AST_MATCHER_P2(Stmt, hasPreTwoVarDecl, ast_matchers::internal::Matcher, if (!C) return false; - const auto I = - llvm::find(llvm::make_range(C->body_rbegin(), C->body_rend()), &Node); + const auto I = llvm::find(llvm::reverse(C->body()), &Node); assert(I != C->body_rend() && "C is parent of Node"); if ((I + 1) == C->body_rend()) return false; From c6f68854b1f946953ad4357841858a9d8d5f72f6 Mon Sep 17 00:00:00 2001 From: flovent Date: Sun, 21 Sep 2025 13:45:50 +0800 Subject: [PATCH 32/44] Refactor the way how we match Two VarDecls --- .../modernize/UseStructuredBindingCheck.cpp | 200 +++++++++--------- .../modernize/use-structured-binding.cpp | 60 ++---- 2 files changed, 115 insertions(+), 145 deletions(-) diff --git a/clang-tools-extra/clang-tidy/modernize/UseStructuredBindingCheck.cpp b/clang-tools-extra/clang-tidy/modernize/UseStructuredBindingCheck.cpp index b0230d0819420..6530fce2f6484 100644 --- a/clang-tools-extra/clang-tidy/modernize/UseStructuredBindingCheck.cpp +++ b/clang-tools-extra/clang-tidy/modernize/UseStructuredBindingCheck.cpp @@ -20,8 +20,8 @@ static constexpr llvm::StringLiteral PairDeclName = "PairVarD"; static constexpr llvm::StringLiteral PairVarTypeName = "PairVarType"; static constexpr llvm::StringLiteral FirstVarDeclName = "FirstVarDecl"; static constexpr llvm::StringLiteral SecondVarDeclName = "SecondVarDecl"; -static constexpr llvm::StringLiteral FirstDeclStmtName = "FirstDeclStmt"; -static constexpr llvm::StringLiteral SecondDeclStmtName = "SecondDeclStmt"; +static constexpr llvm::StringLiteral BeginDeclStmtName = "BeginDeclStmt"; +static constexpr llvm::StringLiteral EndDeclStmtName = "EndDeclStmt"; static constexpr llvm::StringLiteral FirstTypeName = "FirstType"; static constexpr llvm::StringLiteral SecondTypeName = "SecondType"; static constexpr llvm::StringLiteral ScopeBlockName = "ScopeBlock"; @@ -29,45 +29,65 @@ static constexpr llvm::StringLiteral StdTieAssignStmtName = "StdTieAssign"; static constexpr llvm::StringLiteral StdTieExprName = "StdTieExpr"; static constexpr llvm::StringLiteral ForRangeStmtName = "ForRangeStmt"; -/// Try to match exactly two VarDecl inside two DeclStmts, and set binding for -/// the used DeclStmts. -static bool -matchTwoVarDecl(const DeclStmt *DS1, const DeclStmt *DS2, - ast_matchers::internal::Matcher InnerMatcher1, - ast_matchers::internal::Matcher InnerMatcher2, - internal::ASTMatchFinder *Finder, - internal::BoundNodesTreeBuilder *Builder) { - SmallVector, 2> Vars; - auto CollectVarsInDeclStmt = [&Vars](const DeclStmt *DS) -> bool { - if (!DS) - return true; +/// 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 +static bool matchNVarDeclStartingWith( + Iterator Iter, Iterator EndIter, + ArrayRef> InnerMatchers, + ast_matchers::internal::ASTMatchFinder *Finder, + ast_matchers::internal::BoundNodesTreeBuilder *Builder, + bool Backwards = false) { + const DeclStmt *BeginDS = nullptr; + const DeclStmt *EndDS = nullptr; + size_t N = InnerMatchers.size(); + size_t Count = 0; + for (; Iter != EndIter; ++Iter) { + EndDS = dyn_cast(*Iter); + if (!EndDS) + break; - for (const auto *VD : DS->decls()) { - if (Vars.size() == 2) - return false; + if (!BeginDS) + BeginDS = EndDS; - if (const auto *Var = dyn_cast(VD)) - Vars.emplace_back(Var, DS); - else + auto Matches = [&](const Decl *VD) { + if (Count == N) return false; - } - return true; - }; - - // If we already get two VarDecls, then don't need to collect from DS2. - if (!CollectVarsInDeclStmt(DS1) || - (Vars.size() != 2 && !CollectVarsInDeclStmt(DS2)) || Vars.size() != 2) - return false; + if (const auto *Var = dyn_cast(VD); + Var && InnerMatchers[Backwards ? N - Count - 1 : Count].matches( + *Var, Finder, Builder)) { + ++Count; + return true; + } + + return false; + }; + + if (Backwards) { + for (const auto *VD : llvm::reverse(EndDS->decls())) { + if (!Matches(VD)) + return false; + } + } else { + for (const auto *VD : EndDS->decls()) { + if (!Matches(VD)) + return false; + } + } - if (InnerMatcher1.matches(*Vars[0].first, Finder, Builder) && - InnerMatcher2.matches(*Vars[1].first, Finder, Builder)) { - Builder->setBinding(FirstDeclStmtName, - clang::DynTypedNode::create(*Vars[0].second)); - if (Vars[0].second != Vars[1].second) - Builder->setBinding(SecondDeclStmtName, - clang::DynTypedNode::create(*Vars[1].second)); - return true; + // All the matchers is satisfied in those DeclStmts. + if (Count == N) { + Builder->setBinding( + BeginDeclStmtName, + clang::DynTypedNode::create(Backwards ? *EndDS : *BeginDS)); + Builder->setBinding(EndDeclStmtName, clang::DynTypedNode::create( + Backwards ? *BeginDS : *EndDS)); + return true; + } } return false; @@ -84,12 +104,11 @@ enum TransferType : uint8_t { }; /// Matches a Stmt whose parent is a CompoundStmt, and which is directly -/// following two VarDecls matching the inner matcher, at the same time set -/// binding for the CompoundStmt. -AST_MATCHER_P2(Stmt, hasPreTwoVarDecl, ast_matchers::internal::Matcher, - InnerMatcher1, ast_matchers::internal::Matcher, - InnerMatcher2) { - DynTypedNodeList Parents = Finder->getASTContext().getParents(Node); +/// following two VarDecls matching the inner matcher. +AST_MATCHER_P(Stmt, hasPreTwoVarDecl, + llvm::SmallVector>, + InnerMatchers) { + const DynTypedNodeList Parents = Finder->getASTContext().getParents(Node); if (Parents.size() != 1) return false; @@ -99,34 +118,16 @@ AST_MATCHER_P2(Stmt, hasPreTwoVarDecl, ast_matchers::internal::Matcher, const auto I = llvm::find(llvm::reverse(C->body()), &Node); assert(I != C->body_rend() && "C is parent of Node"); - if ((I + 1) == C->body_rend()) - return false; - - const auto *DS2 = dyn_cast(*(I + 1)); - if (!DS2) - return false; - - const DeclStmt *DS1 = (!DS2->isSingleDecl() || ((I + 2) == C->body_rend()) - ? nullptr - : dyn_cast(*(I + 2))); - if (DS1 && !DS1->isSingleDecl()) - return false; - - if (matchTwoVarDecl(DS1, DS2, InnerMatcher1, InnerMatcher2, Finder, - Builder)) { - Builder->setBinding(ScopeBlockName, clang::DynTypedNode::create(*C)); - return true; - } - - return false; + return matchNVarDeclStartingWith(I + 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, at the same time set -/// binding for the CompoundStmt. -AST_MATCHER_P2(Stmt, hasNextTwoVarDecl, - ast_matchers::internal::Matcher, InnerMatcher1, - ast_matchers::internal::Matcher, InnerMatcher2) { +/// followed by two VarDecls matching the inner matcher. +AST_MATCHER_P(Stmt, hasNextTwoVarDecl, + llvm::SmallVector>, + InnerMatchers) { + const DynTypedNodeList Parents = Finder->getASTContext().getParents(Node); if (Parents.size() != 1) return false; @@ -137,33 +138,17 @@ AST_MATCHER_P2(Stmt, hasNextTwoVarDecl, const auto *I = llvm::find(C->body(), &Node); assert(I != C->body_end() && "C is parent of Node"); - if ((I + 1) == C->body_end()) - return false; - - if (matchTwoVarDecl( - dyn_cast(*(I + 1)), - ((I + 2) == C->body_end() ? nullptr : dyn_cast(*(I + 2))), - InnerMatcher1, InnerMatcher2, Finder, Builder)) { - Builder->setBinding(ScopeBlockName, clang::DynTypedNode::create(*C)); - return true; - } - - return false; + return matchNVarDeclStartingWith(I + 1, C->body_end(), InnerMatchers, Finder, + Builder); } /// Matches a CompoundStmt which has two VarDecls matching the inner matcher in /// the beginning. -AST_MATCHER_P2(CompoundStmt, hasFirstTwoVarDecl, - ast_matchers::internal::Matcher, InnerMatcher1, - ast_matchers::internal::Matcher, InnerMatcher2) { - const auto *I = Node.body_begin(); - if ((I) == Node.body_end()) - return false; - - return matchTwoVarDecl( - dyn_cast(*(I)), - ((I + 1) == Node.body_end() ? nullptr : dyn_cast(*(I + 1))), - InnerMatcher1, InnerMatcher2, Finder, Builder); +AST_MATCHER_P(CompoundStmt, hasFirstTwoVarDecl, + llvm::SmallVector>, + 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 @@ -255,8 +240,10 @@ void UseStructuredBindingCheck::registerMatchers(MatchFinder *Finder) { hasRHS(expr(hasType(PairType)))) .bind(StdTieAssignStmtName)), hasPreTwoVarDecl( - varDecl(equalsBoundNode(std::string(FirstVarDeclName))), - varDecl(equalsBoundNode(std::string(SecondVarDeclName))))), + llvm::SmallVector>{ + varDecl(equalsBoundNode(std::string(FirstVarDeclName))), + varDecl(equalsBoundNode(std::string(SecondVarDeclName)))}), + hasParent(compoundStmt().bind(ScopeBlockName))), this); // pair p = ...; @@ -272,7 +259,10 @@ void UseStructuredBindingCheck::registerMatchers(MatchFinder *Finder) { .bind(PairVarTypeName)), hasInitializer(expr())) .bind(PairDeclName)), - hasNextTwoVarDecl(VarInitWithFirstMember, VarInitWithSecondMember)), + hasNextTwoVarDecl( + llvm::SmallVector>{ + VarInitWithFirstMember, VarInitWithSecondMember}), + hasParent(compoundStmt().bind(ScopeBlockName))), this); // for (pair p : map) { @@ -288,9 +278,12 @@ void UseStructuredBindingCheck::registerMatchers(MatchFinder *Finder) { .bind(PairVarTypeName)), hasInitializer(expr())) .bind(PairDeclName)), - hasBody(compoundStmt(hasFirstTwoVarDecl(VarInitWithFirstMember, - VarInitWithSecondMember)) - .bind(ScopeBlockName))) + hasBody( + compoundStmt( + hasFirstTwoVarDecl(llvm::SmallVector< + ast_matchers::internal::Matcher>{ + VarInitWithFirstMember, VarInitWithSecondMember})) + .bind(ScopeBlockName))) .bind(ForRangeStmtName), this); } @@ -320,8 +313,8 @@ void UseStructuredBindingCheck::check(const MatchFinder::MatchResult &Result) { const auto *FirstVar = Result.Nodes.getNodeAs(FirstVarDeclName); const auto *SecondVar = Result.Nodes.getNodeAs(SecondVarDeclName); - const auto *DS1 = Result.Nodes.getNodeAs(FirstDeclStmtName); - const auto *DS2 = Result.Nodes.getNodeAs(SecondDeclStmtName); + const auto *BeginDS = Result.Nodes.getNodeAs(BeginDeclStmtName); + const auto *EndDS = Result.Nodes.getNodeAs(EndDeclStmtName); const auto *ScopeBlock = Result.Nodes.getNodeAs(ScopeBlockName); // Captured structured bindings are a C++20 extension @@ -336,7 +329,7 @@ void UseStructuredBindingCheck::check(const MatchFinder::MatchResult &Result) { } const auto *CFRS = Result.Nodes.getNodeAs(ForRangeStmtName); - auto DiagAndFix = [&DS1, &DS2, &FirstVar, &SecondVar, &CFRS, + auto DiagAndFix = [&BeginDS, &EndDS, &FirstVar, &SecondVar, &CFRS, this](SourceLocation DiagLoc, SourceRange ReplaceRange, TransferType TT = TT_ByVal) { StringRef Prefix; @@ -354,18 +347,15 @@ void UseStructuredBindingCheck::check(const MatchFinder::MatchResult &Result) { Prefix = "const auto&"; break; } - std::vector Hints; - if (DS1) - Hints.emplace_back(FixItHint::CreateRemoval(DS1->getSourceRange())); - if (DS2) - Hints.emplace_back(FixItHint::CreateRemoval(DS2->getSourceRange())); std::string ReplacementText = (Twine(Prefix) + " [" + FirstVar->getNameAsString() + ", " + SecondVar->getNameAsString() + "]" + (CFRS ? " :" : "")) .str(); diag(DiagLoc, "use structured binding to decompose a pair") - << FixItHint::CreateReplacement(ReplaceRange, ReplacementText) << Hints; + << FixItHint::CreateReplacement(ReplaceRange, ReplacementText) + << FixItHint::CreateRemoval( + SourceRange{BeginDS->getBeginLoc(), EndDS->getEndLoc()}); }; if (const auto *COCE = diff --git a/clang-tools-extra/test/clang-tidy/checkers/modernize/use-structured-binding.cpp b/clang-tools-extra/test/clang-tidy/checkers/modernize/use-structured-binding.cpp index be3418a2ac224..48d48ec285575 100644 --- a/clang-tools-extra/test/clang-tidy/checkers/modernize/use-structured-binding.cpp +++ b/clang-tools-extra/test/clang-tidy/checkers/modernize/use-structured-binding.cpp @@ -18,8 +18,7 @@ void DecomposeByAssignWarnCases() { auto P = getPair(); // CHECK-MESSAGES-ALL: :[[@LINE-1]]:5: warning: use structured binding to decompose a pair [modernize-use-structured-binding] // CHECK-FIXES-ALL: auto [x, y] = getPair(); - int x = P.first; // REMOVE - // CHECK-FIXES-ALL: // REMOVE + int x = P.first; int y = P.second; // REMOVE // CHECK-FIXES-ALL: // REMOVE } @@ -45,8 +44,7 @@ void DecomposeByAssignWarnCases() { auto P = getPair(); // CHECK-MESSAGES-ALL: :[[@LINE-1]]:5: warning: use structured binding to decompose a pair [modernize-use-structured-binding] // CHECK-FIXES-ALL: auto [x, y] = getPair(); - int x = P.first; // REMOVE - // CHECK-FIXES-ALL: // REMOVE + int x = P.first; auto y = P.second; // REMOVE // CHECK-FIXES-ALL: // REMOVE } @@ -55,8 +53,7 @@ void DecomposeByAssignWarnCases() { const auto P = getPair(); // CHECK-MESSAGES-ALL: :[[@LINE-1]]:5: warning: use structured binding to decompose a pair [modernize-use-structured-binding] // CHECK-FIXES-ALL: const auto [x, y] = getPair(); - const int x = P.first; // REMOVE - // CHECK-FIXES-ALL: // REMOVE + const int x = P.first; const auto y = P.second; // REMOVE // CHECK-FIXES-ALL: // REMOVE } @@ -66,8 +63,7 @@ void DecomposeByAssignWarnCases() { auto& P = otherP; // CHECK-MESSAGES-ALL: :[[@LINE-1]]:5: warning: use structured binding to decompose a pair [modernize-use-structured-binding] // CHECK-FIXES-ALL: auto& [x, y] = otherP; - int& x = P.first; // REMOVE - // CHECK-FIXES-ALL: // REMOVE + int& x = P.first; auto& y = P.second; // REMOVE // CHECK-FIXES-ALL: // REMOVE } @@ -77,8 +73,7 @@ void DecomposeByAssignWarnCases() { const auto& P = otherP; // CHECK-MESSAGES-ALL: :[[@LINE-1]]:5: warning: use structured binding to decompose a pair [modernize-use-structured-binding] // CHECK-FIXES-ALL: const auto& [x, y] = otherP; - const int& x = P.first; // REMOVE - // CHECK-FIXES-ALL: // REMOVE + const int& x = P.first; const auto& y = P.second; // REMOVE // CHECK-FIXES-ALL: // REMOVE } @@ -89,8 +84,7 @@ void forRangeWarnCases() { for (auto P : Pairs) { // CHECK-MESSAGES-ALL: :[[@LINE-1]]:8: warning: use structured binding to decompose a pair [modernize-use-structured-binding] // CHECK-FIXES-ALL: for (auto [x, y] : Pairs) { - int x = P.first; // REMOVE - // CHECK-FIXES-ALL: // REMOVE + int x = P.first; int y = P.second; // REMOVE // CHECK-FIXES-ALL: // REMOVE } @@ -113,8 +107,7 @@ void forRangeWarnCases() { for (const auto P : Pairs) { // CHECK-MESSAGES-ALL: :[[@LINE-1]]:8: warning: use structured binding to decompose a pair [modernize-use-structured-binding] // CHECK-FIXES-ALL: for (const auto [x, y] : Pairs) { - const int x = P.first; // REMOVE - // CHECK-FIXES-ALL: // REMOVE + const int x = P.first; const int y = P.second; // REMOVE // CHECK-FIXES-ALL: // REMOVE } @@ -122,8 +115,7 @@ void forRangeWarnCases() { for (auto& P : Pairs) { // CHECK-MESSAGES-ALL: :[[@LINE-1]]:8: warning: use structured binding to decompose a pair [modernize-use-structured-binding] // CHECK-FIXES-ALL: for (auto& [x, y] : Pairs) { - int& x = P.first; // REMOVE - // CHECK-FIXES-ALL: // REMOVE + int& x = P.first; int& y = P.second; // REMOVE // CHECK-FIXES-ALL: // REMOVE } @@ -131,8 +123,7 @@ void forRangeWarnCases() { for (const auto& P : Pairs) { // CHECK-MESSAGES-ALL: :[[@LINE-1]]:8: warning: use structured binding to decompose a pair [modernize-use-structured-binding] // CHECK-FIXES-ALL: for (const auto& [x, y] : Pairs) { - const int& x = P.first; // REMOVE - // CHECK-FIXES-ALL: // REMOVE + const int& x = P.first; const int& y = P.second; // REMOVE // CHECK-FIXES-ALL: // REMOVE } @@ -141,8 +132,7 @@ void forRangeWarnCases() { for (auto P : ClassPairs) { // CHECK-MESSAGES-ALL: :[[@LINE-1]]:8: warning: use structured binding to decompose a pair [modernize-use-structured-binding] // CHECK-FIXES-ALL: for (auto [c1, c2] : ClassPairs) { - TestClass c1 = P.first; // REMOVE - // CHECK-FIXES-ALL: // REMOVE + TestClass c1 = P.first; TestClass c2 = P.second; // REMOVE // CHECK-FIXES-ALL: // REMOVE } @@ -150,8 +140,7 @@ void forRangeWarnCases() { for (const auto P : ClassPairs) { // CHECK-MESSAGES-ALL: :[[@LINE-1]]:8: warning: use structured binding to decompose a pair [modernize-use-structured-binding] // CHECK-FIXES-ALL: for (const auto [c1, c2] : ClassPairs) { - const TestClass c1 = P.first; // REMOVE - // CHECK-FIXES-ALL: // REMOVE + const TestClass c1 = P.first; const TestClass c2 = P.second; // REMOVE // CHECK-FIXES-ALL: // REMOVE } @@ -193,8 +182,7 @@ void forRangeNotWarnCases() { } void stdTieWarnCases() { - int a = 0; // REMOVE - // CHECK-FIXES-ALL: // REMOVE + int a = 0; int b = 0; // REMOVE // CHECK-FIXES-ALL: // REMOVE std::tie(a, b) = getPair(); @@ -207,16 +195,14 @@ void stdTieWarnCases() { // CHECK-MESSAGES-ALL: :[[@LINE-1]]:3: warning: use structured binding to decompose a pair [modernize-use-structured-binding] // CHECK-FIXES-ALL: auto [x, y] = getPair(); - int* pa = nullptr; // REMOVE - // CHECK-FIXES-ALL: // REMOVE + int* pa = nullptr; int* pb = nullptr; // REMOVE // CHECK-FIXES-ALL: // REMOVE std::tie(pa, pb) = getPair(); // CHECK-MESSAGES-ALL: :[[@LINE-1]]:3: warning: use structured binding to decompose a pair [modernize-use-structured-binding] // CHECK-FIXES-ALL: auto [pa, pb] = getPair(); - TestClass c1 (1, 2); // REMOVE - // CHECK-FIXES-ALL: // REMOVE + TestClass c1 (1, 2); TestClass c2 = TestClass {3, 4}; // REMOVE // CHECK-FIXES-ALL: // REMOVE std::tie(c1, c2) = getPair(); @@ -325,8 +311,7 @@ void captureByVal() { auto P = getPair(); // CHECK-MESSAGES-CPP20ORLATER: :[[@LINE-1]]:3: warning: use structured binding to decompose a pair [modernize-use-structured-binding] // CHECK-FIXES-CPP20ORLATER: auto [x, y] = getPair(); - int x = P.first; // REMOVE - // CHECK-FIXES-CPP20ORLATER: // REMOVE + int x = P.first; int y = P.second; // REMOVE // CHECK-FIXES-CPP20ORLATER: // REMOVE @@ -339,8 +324,7 @@ void captureByRef() { auto P = getPair(); // CHECK-MESSAGES-CPP20ORLATER: :[[@LINE-1]]:3: warning: use structured binding to decompose a pair [modernize-use-structured-binding] // CHECK-FIXES-CPP20ORLATER: auto [x, y] = getPair(); - int x = P.first; // REMOVE - // CHECK-FIXES-CPP20ORLATER: // REMOVE + int x = P.first; int y = P.second; // REMOVE // CHECK-FIXES-CPP20ORLATER: // REMOVE @@ -353,8 +337,7 @@ void captureByAllRef() { auto P = getPair(); // CHECK-MESSAGES-CPP20ORLATER: :[[@LINE-1]]:3: warning: use structured binding to decompose a pair [modernize-use-structured-binding] // CHECK-FIXES-CPP20ORLATER: auto [x, y] = getPair(); - int x = P.first; // REMOVE - // CHECK-FIXES-CPP20ORLATER: // REMOVE + int x = P.first; int y = P.second; // REMOVE // CHECK-FIXES-CPP20ORLATER: // REMOVE @@ -367,8 +350,7 @@ void deepLambda() { auto P = getPair(); // CHECK-MESSAGES-CPP20ORLATER: :[[@LINE-1]]:3: warning: use structured binding to decompose a pair [modernize-use-structured-binding] // CHECK-FIXES-CPP20ORLATER: auto [x, y] = getPair(); - int x = P.first; // REMOVE - // CHECK-FIXES-CPP20ORLATER: // REMOVE + int x = P.first; int y = P.second; // REMOVE // CHECK-FIXES-CPP20ORLATER: // REMOVE @@ -384,8 +366,7 @@ void forRangeNotWarn() { for (auto P : Pairs) { // CHECK-MESSAGES-CPP20ORLATER: :[[@LINE-1]]:8: warning: use structured binding to decompose a pair [modernize-use-structured-binding] // CHECK-FIXES-CPP20ORLATER: for (auto [x, y] : Pairs) { - int x = P.first; // REMOVE - // CHECK-FIXES-CPP20ORLATER: // REMOVE + int x = P.first; int y = P.second; // REMOVE // CHECK-FIXES-CPP20ORLATER: // REMOVE @@ -396,8 +377,7 @@ void forRangeNotWarn() { } void stdTieNotWarn() { - int x = 0; // REMOVE - // CHECK-FIXES-CPP20ORLATER: // REMOVE + int x = 0; int y = 0; // REMOVE // CHECK-FIXES-CPP20ORLATER: // REMOVE std::tie(x, y) = getPair(); From 2ebab5724bf2d1732a0785be692f82951f640c68 Mon Sep 17 00:00:00 2001 From: flovent Date: Sun, 21 Sep 2025 14:08:15 +0800 Subject: [PATCH 33/44] [NFC] Place lambda match operation to `registerMatchers` --- .../modernize/UseStructuredBindingCheck.cpp | 35 +++++++++++-------- 1 file changed, 21 insertions(+), 14 deletions(-) diff --git a/clang-tools-extra/clang-tidy/modernize/UseStructuredBindingCheck.cpp b/clang-tools-extra/clang-tidy/modernize/UseStructuredBindingCheck.cpp index 6530fce2f6484..5796be4685110 100644 --- a/clang-tools-extra/clang-tidy/modernize/UseStructuredBindingCheck.cpp +++ b/clang-tools-extra/clang-tidy/modernize/UseStructuredBindingCheck.cpp @@ -223,6 +223,21 @@ void UseStructuredBindingCheck::registerMatchers(MatchFinder *Finder) { return declRefExpr(to(varDecl(UnlessShouldBeIgnored).bind(Name))); }; + auto HasAnyLambdaCaptureThisVar = + [](ast_matchers::internal::Matcher VDMatcher) + -> ast_matchers::internal::BindableMatcher { + return compoundStmt(hasDescendant( + lambdaExpr(hasAnyCapture(capturesVar(varDecl(VDMatcher)))))); + }; + + // Captured structured bindings are a C++20 extension + auto UnlessFirstVarOrSecondVarIsCapturedByLambda = + getLangOpts().CPlusPlus20 + ? compoundStmt() + : compoundStmt(unless(HasAnyLambdaCaptureThisVar( + anyOf(equalsBoundNode(std::string(FirstVarDeclName)), + equalsBoundNode(std::string(SecondVarDeclName)))))); + // X x; // Y y; // std::tie(x, y) = ...; @@ -243,7 +258,8 @@ void UseStructuredBindingCheck::registerMatchers(MatchFinder *Finder) { llvm::SmallVector>{ varDecl(equalsBoundNode(std::string(FirstVarDeclName))), varDecl(equalsBoundNode(std::string(SecondVarDeclName)))}), - hasParent(compoundStmt().bind(ScopeBlockName))), + hasParent(compoundStmt(UnlessFirstVarOrSecondVarIsCapturedByLambda) + .bind(ScopeBlockName))), this); // pair p = ...; @@ -262,7 +278,8 @@ void UseStructuredBindingCheck::registerMatchers(MatchFinder *Finder) { hasNextTwoVarDecl( llvm::SmallVector>{ VarInitWithFirstMember, VarInitWithSecondMember}), - hasParent(compoundStmt().bind(ScopeBlockName))), + hasParent(compoundStmt(UnlessFirstVarOrSecondVarIsCapturedByLambda) + .bind(ScopeBlockName))), this); // for (pair p : map) { @@ -282,7 +299,8 @@ void UseStructuredBindingCheck::registerMatchers(MatchFinder *Finder) { compoundStmt( hasFirstTwoVarDecl(llvm::SmallVector< ast_matchers::internal::Matcher>{ - VarInitWithFirstMember, VarInitWithSecondMember})) + VarInitWithFirstMember, VarInitWithSecondMember}), + UnlessFirstVarOrSecondVarIsCapturedByLambda) .bind(ScopeBlockName))) .bind(ForRangeStmtName), this); @@ -317,17 +335,6 @@ void UseStructuredBindingCheck::check(const MatchFinder::MatchResult &Result) { const auto *EndDS = Result.Nodes.getNodeAs(EndDeclStmtName); const auto *ScopeBlock = Result.Nodes.getNodeAs(ScopeBlockName); - // Captured structured bindings are a C++20 extension - if (!Result.Context->getLangOpts().CPlusPlus20) { - if (auto Matchers = match( - compoundStmt( - hasDescendant(lambdaExpr(hasAnyCapture(capturesVar(varDecl( - anyOf(equalsNode(FirstVar), equalsNode(SecondVar)))))))), - *ScopeBlock, *Result.Context); - !Matchers.empty()) - return; - } - const auto *CFRS = Result.Nodes.getNodeAs(ForRangeStmtName); auto DiagAndFix = [&BeginDS, &EndDS, &FirstVar, &SecondVar, &CFRS, this](SourceLocation DiagLoc, SourceRange ReplaceRange, From 5bdf20655f83f91d02cb67d586490e9d2a31b28a Mon Sep 17 00:00:00 2001 From: flovent Date: Sun, 21 Sep 2025 14:10:24 +0800 Subject: [PATCH 34/44] [NFC] Doc typo fix --- .../clang-tidy/checks/modernize/use-structured-binding.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/clang-tools-extra/docs/clang-tidy/checks/modernize/use-structured-binding.rst b/clang-tools-extra/docs/clang-tidy/checks/modernize/use-structured-binding.rst index 0d02e6761cdd4..2526725c53ff9 100644 --- a/clang-tools-extra/docs/clang-tidy/checks/modernize/use-structured-binding.rst +++ b/clang-tools-extra/docs/clang-tidy/checks/modernize/use-structured-binding.rst @@ -66,8 +66,8 @@ except const and & since it's not very common: static int b = pair.first; static int c = pair.second; -The check doesn't check for some situations which could possibly transfered -to structured bnindings, for example: +The check doesn't check for some situations which could possibly be transferred +to structured bindings, for example: .. code-block:: c++ From edac2d43b7a2ff0442d1ad58134ba3c8edc5cbca Mon Sep 17 00:00:00 2001 From: flovent Date: Sun, 21 Sep 2025 15:05:58 +0800 Subject: [PATCH 35/44] [NFC] Move init expression logic to `registerMatchers` --- .../modernize/UseStructuredBindingCheck.cpp | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/clang-tools-extra/clang-tidy/modernize/UseStructuredBindingCheck.cpp b/clang-tools-extra/clang-tidy/modernize/UseStructuredBindingCheck.cpp index 5796be4685110..e92af08007db9 100644 --- a/clang-tools-extra/clang-tidy/modernize/UseStructuredBindingCheck.cpp +++ b/clang-tools-extra/clang-tidy/modernize/UseStructuredBindingCheck.cpp @@ -28,6 +28,7 @@ static constexpr llvm::StringLiteral ScopeBlockName = "ScopeBlock"; static constexpr llvm::StringLiteral StdTieAssignStmtName = "StdTieAssign"; static constexpr llvm::StringLiteral StdTieExprName = "StdTieExpr"; static constexpr llvm::StringLiteral ForRangeStmtName = "ForRangeStmt"; +static constexpr llvm::StringLiteral 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 @@ -273,7 +274,7 @@ void UseStructuredBindingCheck::registerMatchers(MatchFinder *Finder) { hasType(qualType(anyOf(PairType, lValueReferenceType( pointee(PairType)))) .bind(PairVarTypeName)), - hasInitializer(expr())) + hasInitializer(expr(ignoringCopyCtorAndImplicitCast(expr().bind(InitExprName))))) .bind(PairDeclName)), hasNextTwoVarDecl( llvm::SmallVector>{ @@ -293,7 +294,7 @@ void UseStructuredBindingCheck::registerMatchers(MatchFinder *Finder) { varDecl(hasType(qualType(anyOf(PairType, lValueReferenceType( pointee(PairType)))) .bind(PairVarTypeName)), - hasInitializer(expr())) + hasInitializer(expr(ignoringCopyCtorAndImplicitCast(expr().bind(InitExprName))))) .bind(PairDeclName)), hasBody( compoundStmt( @@ -375,12 +376,7 @@ void UseStructuredBindingCheck::check(const MatchFinder::MatchResult &Result) { // Check whether PairVar, FirstVar and SecondVar have the same transfer type, // so they can be combined to structured binding. const auto *PairVar = Result.Nodes.getNodeAs(PairDeclName); - const Expr *InitE = PairVar->getInit(); - if (auto Res = - match(expr(ignoringCopyCtorAndImplicitCast(expr().bind("init_expr"))), - *InitE, *Result.Context); - !Res.empty()) - InitE = Res[0].getNodeAs("init_expr"); + const Expr *InitE = Result.Nodes.getNodeAs(InitExprName); const std::optional PairCaptureType = getTransferType(*Result.Context, PairVar->getType(), InitE->getType()); From 0b007e85679a06a4156f8597f64fe5bc0dd6834f Mon Sep 17 00:00:00 2001 From: flovent Date: Sun, 21 Sep 2025 15:09:01 +0800 Subject: [PATCH 36/44] [NFC] Rename warning message to `use a structured binding...` --- .../modernize/UseStructuredBindingCheck.cpp | 2 +- .../use-structured-binding-custom.cpp | 4 +- .../modernize/use-structured-binding.cpp | 50 +++++++++---------- 3 files changed, 28 insertions(+), 28 deletions(-) diff --git a/clang-tools-extra/clang-tidy/modernize/UseStructuredBindingCheck.cpp b/clang-tools-extra/clang-tidy/modernize/UseStructuredBindingCheck.cpp index e92af08007db9..07a622d2938be 100644 --- a/clang-tools-extra/clang-tidy/modernize/UseStructuredBindingCheck.cpp +++ b/clang-tools-extra/clang-tidy/modernize/UseStructuredBindingCheck.cpp @@ -360,7 +360,7 @@ void UseStructuredBindingCheck::check(const MatchFinder::MatchResult &Result) { (Twine(Prefix) + " [" + FirstVar->getNameAsString() + ", " + SecondVar->getNameAsString() + "]" + (CFRS ? " :" : "")) .str(); - diag(DiagLoc, "use structured binding to decompose a pair") + diag(DiagLoc, "use a structured binding to decompose a pair") << FixItHint::CreateReplacement(ReplaceRange, ReplacementText) << FixItHint::CreateRemoval( SourceRange{BeginDS->getBeginLoc(), EndDS->getEndLoc()}); diff --git a/clang-tools-extra/test/clang-tidy/checkers/modernize/use-structured-binding-custom.cpp b/clang-tools-extra/test/clang-tidy/checkers/modernize/use-structured-binding-custom.cpp index bd335e1edaf6e..d8f9bdc924d94 100644 --- a/clang-tools-extra/test/clang-tidy/checkers/modernize/use-structured-binding-custom.cpp +++ b/clang-tools-extra/test/clang-tidy/checkers/modernize/use-structured-binding-custom.cpp @@ -16,7 +16,7 @@ struct otherPair { void OptionTest() { { auto P = custom::pair(); - // CHECK-MESSAGES: :[[@LINE-1]]:5: warning: use structured binding to decompose a pair [modernize-use-structured-binding] + // CHECK-MESSAGES: :[[@LINE-1]]:5: warning: use a structured binding to decompose a pair [modernize-use-structured-binding] // CHECK-FIXES: auto [x, y] = custom::pair(); int x = P.first; int y = P.second; @@ -24,7 +24,7 @@ void OptionTest() { { auto P = otherPair(); - // CHECK-MESSAGES: :[[@LINE-1]]:5: warning: use structured binding to decompose a pair [modernize-use-structured-binding] + // CHECK-MESSAGES: :[[@LINE-1]]:5: warning: use a structured binding to decompose a pair [modernize-use-structured-binding] // CHECK-FIXES: auto [x, y] = otherPair(); int x = P.first; int y = P.second; diff --git a/clang-tools-extra/test/clang-tidy/checkers/modernize/use-structured-binding.cpp b/clang-tools-extra/test/clang-tidy/checkers/modernize/use-structured-binding.cpp index 48d48ec285575..8a511f954e97e 100644 --- a/clang-tools-extra/test/clang-tidy/checkers/modernize/use-structured-binding.cpp +++ b/clang-tools-extra/test/clang-tidy/checkers/modernize/use-structured-binding.cpp @@ -16,7 +16,7 @@ struct TestClass { void DecomposeByAssignWarnCases() { { auto P = getPair(); - // CHECK-MESSAGES-ALL: :[[@LINE-1]]:5: warning: use structured binding to decompose a pair [modernize-use-structured-binding] + // CHECK-MESSAGES-ALL: :[[@LINE-1]]:5: warning: use a structured binding to decompose a pair [modernize-use-structured-binding] // CHECK-FIXES-ALL: auto [x, y] = getPair(); int x = P.first; int y = P.second; // REMOVE @@ -25,7 +25,7 @@ void DecomposeByAssignWarnCases() { { auto P = getPair(); - // CHECK-MESSAGES-ALL: :[[@LINE-1]]:5: warning: use structured binding to decompose a pair [modernize-use-structured-binding] + // CHECK-MESSAGES-ALL: :[[@LINE-1]]:5: warning: use a structured binding to decompose a pair [modernize-use-structured-binding] // CHECK-FIXES-ALL: auto [x, y] = getPair(); int x = P.first, y = P.second; // REMOVE // CHECK-FIXES-ALL: // REMOVE @@ -33,7 +33,7 @@ void DecomposeByAssignWarnCases() { { auto P = getPair(); - // CHECK-MESSAGES-ALL: :[[@LINE-1]]:5: warning: use structured binding to decompose a pair [modernize-use-structured-binding] + // CHECK-MESSAGES-ALL: :[[@LINE-1]]:5: warning: use a structured binding to decompose a pair [modernize-use-structured-binding] // CHECK-FIXES-ALL: auto [x, y] = getPair(); int x = P.first, y = P.second; // REMOVE // CHECK-FIXES-ALL: // REMOVE @@ -42,7 +42,7 @@ void DecomposeByAssignWarnCases() { { auto P = getPair(); - // CHECK-MESSAGES-ALL: :[[@LINE-1]]:5: warning: use structured binding to decompose a pair [modernize-use-structured-binding] + // CHECK-MESSAGES-ALL: :[[@LINE-1]]:5: warning: use a structured binding to decompose a pair [modernize-use-structured-binding] // CHECK-FIXES-ALL: auto [x, y] = getPair(); int x = P.first; auto y = P.second; // REMOVE @@ -51,7 +51,7 @@ void DecomposeByAssignWarnCases() { { const auto P = getPair(); - // CHECK-MESSAGES-ALL: :[[@LINE-1]]:5: warning: use structured binding to decompose a pair [modernize-use-structured-binding] + // CHECK-MESSAGES-ALL: :[[@LINE-1]]:5: warning: use a structured binding to decompose a pair [modernize-use-structured-binding] // CHECK-FIXES-ALL: const auto [x, y] = getPair(); const int x = P.first; const auto y = P.second; // REMOVE @@ -61,7 +61,7 @@ void DecomposeByAssignWarnCases() { { std::pair otherP; auto& P = otherP; - // CHECK-MESSAGES-ALL: :[[@LINE-1]]:5: warning: use structured binding to decompose a pair [modernize-use-structured-binding] + // CHECK-MESSAGES-ALL: :[[@LINE-1]]:5: warning: use a structured binding to decompose a pair [modernize-use-structured-binding] // CHECK-FIXES-ALL: auto& [x, y] = otherP; int& x = P.first; auto& y = P.second; // REMOVE @@ -71,7 +71,7 @@ void DecomposeByAssignWarnCases() { { std::pair otherP; const auto& P = otherP; - // CHECK-MESSAGES-ALL: :[[@LINE-1]]:5: warning: use structured binding to decompose a pair [modernize-use-structured-binding] + // CHECK-MESSAGES-ALL: :[[@LINE-1]]:5: warning: use a structured binding to decompose a pair [modernize-use-structured-binding] // CHECK-FIXES-ALL: const auto& [x, y] = otherP; const int& x = P.first; const auto& y = P.second; // REMOVE @@ -82,7 +82,7 @@ void DecomposeByAssignWarnCases() { void forRangeWarnCases() { std::pair Pairs[10]; for (auto P : Pairs) { - // CHECK-MESSAGES-ALL: :[[@LINE-1]]:8: warning: use structured binding to decompose a pair [modernize-use-structured-binding] + // CHECK-MESSAGES-ALL: :[[@LINE-1]]:8: warning: use a structured binding to decompose a pair [modernize-use-structured-binding] // CHECK-FIXES-ALL: for (auto [x, y] : Pairs) { int x = P.first; int y = P.second; // REMOVE @@ -90,14 +90,14 @@ void forRangeWarnCases() { } for (auto P : Pairs) { - // CHECK-MESSAGES-ALL: :[[@LINE-1]]:8: warning: use structured binding to decompose a pair [modernize-use-structured-binding] + // CHECK-MESSAGES-ALL: :[[@LINE-1]]:8: warning: use a structured binding to decompose a pair [modernize-use-structured-binding] // CHECK-FIXES-ALL: for (auto [x, y] : Pairs) { int x = P.first, y = P.second; // REMOVE // CHECK-FIXES-ALL: // REMOVE } for (auto P : Pairs) { - // CHECK-MESSAGES-ALL: :[[@LINE-1]]:8: warning: use structured binding to decompose a pair [modernize-use-structured-binding] + // CHECK-MESSAGES-ALL: :[[@LINE-1]]:8: warning: use a structured binding to decompose a pair [modernize-use-structured-binding] // CHECK-FIXES-ALL: for (auto [x, y] : Pairs) { int x = P.first, y = P.second; // REMOVE // CHECK-FIXES-ALL: // REMOVE @@ -105,7 +105,7 @@ void forRangeWarnCases() { } for (const auto P : Pairs) { - // CHECK-MESSAGES-ALL: :[[@LINE-1]]:8: warning: use structured binding to decompose a pair [modernize-use-structured-binding] + // CHECK-MESSAGES-ALL: :[[@LINE-1]]:8: warning: use a structured binding to decompose a pair [modernize-use-structured-binding] // CHECK-FIXES-ALL: for (const auto [x, y] : Pairs) { const int x = P.first; const int y = P.second; // REMOVE @@ -113,7 +113,7 @@ void forRangeWarnCases() { } for (auto& P : Pairs) { - // CHECK-MESSAGES-ALL: :[[@LINE-1]]:8: warning: use structured binding to decompose a pair [modernize-use-structured-binding] + // CHECK-MESSAGES-ALL: :[[@LINE-1]]:8: warning: use a structured binding to decompose a pair [modernize-use-structured-binding] // CHECK-FIXES-ALL: for (auto& [x, y] : Pairs) { int& x = P.first; int& y = P.second; // REMOVE @@ -121,7 +121,7 @@ void forRangeWarnCases() { } for (const auto& P : Pairs) { - // CHECK-MESSAGES-ALL: :[[@LINE-1]]:8: warning: use structured binding to decompose a pair [modernize-use-structured-binding] + // CHECK-MESSAGES-ALL: :[[@LINE-1]]:8: warning: use a structured binding to decompose a pair [modernize-use-structured-binding] // CHECK-FIXES-ALL: for (const auto& [x, y] : Pairs) { const int& x = P.first; const int& y = P.second; // REMOVE @@ -130,7 +130,7 @@ void forRangeWarnCases() { std::pair ClassPairs[10]; for (auto P : ClassPairs) { - // CHECK-MESSAGES-ALL: :[[@LINE-1]]:8: warning: use structured binding to decompose a pair [modernize-use-structured-binding] + // CHECK-MESSAGES-ALL: :[[@LINE-1]]:8: warning: use a structured binding to decompose a pair [modernize-use-structured-binding] // CHECK-FIXES-ALL: for (auto [c1, c2] : ClassPairs) { TestClass c1 = P.first; TestClass c2 = P.second; // REMOVE @@ -138,7 +138,7 @@ void forRangeWarnCases() { } for (const auto P : ClassPairs) { - // CHECK-MESSAGES-ALL: :[[@LINE-1]]:8: warning: use structured binding to decompose a pair [modernize-use-structured-binding] + // CHECK-MESSAGES-ALL: :[[@LINE-1]]:8: warning: use a structured binding to decompose a pair [modernize-use-structured-binding] // CHECK-FIXES-ALL: for (const auto [c1, c2] : ClassPairs) { const TestClass c1 = P.first; const TestClass c2 = P.second; // REMOVE @@ -186,27 +186,27 @@ void stdTieWarnCases() { int b = 0; // REMOVE // CHECK-FIXES-ALL: // REMOVE std::tie(a, b) = getPair(); - // CHECK-MESSAGES-ALL: :[[@LINE-1]]:3: warning: use structured binding to decompose a pair [modernize-use-structured-binding] + // CHECK-MESSAGES-ALL: :[[@LINE-1]]:3: warning: use a structured binding to decompose a pair [modernize-use-structured-binding] // CHECK-FIXES-ALL: auto [a, b] = getPair(); int x = 0, y = 0; // REMOVE // CHECK-FIXES-ALL: // REMOVE std::tie(x, y) = getPair(); - // CHECK-MESSAGES-ALL: :[[@LINE-1]]:3: warning: use structured binding to decompose a pair [modernize-use-structured-binding] + // CHECK-MESSAGES-ALL: :[[@LINE-1]]:3: warning: use a structured binding to decompose a pair [modernize-use-structured-binding] // CHECK-FIXES-ALL: auto [x, y] = getPair(); int* pa = nullptr; int* pb = nullptr; // REMOVE // CHECK-FIXES-ALL: // REMOVE std::tie(pa, pb) = getPair(); - // CHECK-MESSAGES-ALL: :[[@LINE-1]]:3: warning: use structured binding to decompose a pair [modernize-use-structured-binding] + // CHECK-MESSAGES-ALL: :[[@LINE-1]]:3: warning: use a structured binding to decompose a pair [modernize-use-structured-binding] // CHECK-FIXES-ALL: auto [pa, pb] = getPair(); TestClass c1 (1, 2); TestClass c2 = TestClass {3, 4}; // REMOVE // CHECK-FIXES-ALL: // REMOVE std::tie(c1, c2) = getPair(); - // CHECK-MESSAGES-ALL: :[[@LINE-1]]:3: warning: use structured binding to decompose a pair [modernize-use-structured-binding] + // CHECK-MESSAGES-ALL: :[[@LINE-1]]:3: warning: use a structured binding to decompose a pair [modernize-use-structured-binding] // CHECK-FIXES-ALL: auto [c1, c2] = getPair(); } @@ -309,7 +309,7 @@ void NotWarnForMacro2() { void captureByVal() { auto P = getPair(); - // CHECK-MESSAGES-CPP20ORLATER: :[[@LINE-1]]:3: warning: use structured binding to decompose a pair [modernize-use-structured-binding] + // CHECK-MESSAGES-CPP20ORLATER: :[[@LINE-1]]:3: warning: use a structured binding to decompose a pair [modernize-use-structured-binding] // CHECK-FIXES-CPP20ORLATER: auto [x, y] = getPair(); int x = P.first; int y = P.second; // REMOVE @@ -322,7 +322,7 @@ void captureByVal() { void captureByRef() { auto P = getPair(); - // CHECK-MESSAGES-CPP20ORLATER: :[[@LINE-1]]:3: warning: use structured binding to decompose a pair [modernize-use-structured-binding] + // CHECK-MESSAGES-CPP20ORLATER: :[[@LINE-1]]:3: warning: use a structured binding to decompose a pair [modernize-use-structured-binding] // CHECK-FIXES-CPP20ORLATER: auto [x, y] = getPair(); int x = P.first; int y = P.second; // REMOVE @@ -335,7 +335,7 @@ void captureByRef() { void captureByAllRef() { auto P = getPair(); - // CHECK-MESSAGES-CPP20ORLATER: :[[@LINE-1]]:3: warning: use structured binding to decompose a pair [modernize-use-structured-binding] + // CHECK-MESSAGES-CPP20ORLATER: :[[@LINE-1]]:3: warning: use a structured binding to decompose a pair [modernize-use-structured-binding] // CHECK-FIXES-CPP20ORLATER: auto [x, y] = getPair(); int x = P.first; int y = P.second; // REMOVE @@ -348,7 +348,7 @@ void captureByAllRef() { void deepLambda() { auto P = getPair(); - // CHECK-MESSAGES-CPP20ORLATER: :[[@LINE-1]]:3: warning: use structured binding to decompose a pair [modernize-use-structured-binding] + // CHECK-MESSAGES-CPP20ORLATER: :[[@LINE-1]]:3: warning: use a structured binding to decompose a pair [modernize-use-structured-binding] // CHECK-FIXES-CPP20ORLATER: auto [x, y] = getPair(); int x = P.first; int y = P.second; // REMOVE @@ -364,7 +364,7 @@ void deepLambda() { void forRangeNotWarn() { std::pair Pairs[10]; for (auto P : Pairs) { - // CHECK-MESSAGES-CPP20ORLATER: :[[@LINE-1]]:8: warning: use structured binding to decompose a pair [modernize-use-structured-binding] + // CHECK-MESSAGES-CPP20ORLATER: :[[@LINE-1]]:8: warning: use a structured binding to decompose a pair [modernize-use-structured-binding] // CHECK-FIXES-CPP20ORLATER: for (auto [x, y] : Pairs) { int x = P.first; int y = P.second; // REMOVE @@ -381,7 +381,7 @@ void stdTieNotWarn() { int y = 0; // REMOVE // CHECK-FIXES-CPP20ORLATER: // REMOVE std::tie(x, y) = getPair(); - // CHECK-MESSAGES-CPP20ORLATER: :[[@LINE-1]]:3: warning: use structured binding to decompose a pair [modernize-use-structured-binding] + // CHECK-MESSAGES-CPP20ORLATER: :[[@LINE-1]]:3: warning: use a structured binding to decompose a pair [modernize-use-structured-binding] // CHECK-FIXES-CPP20ORLATER: auto [x, y] = getPair(); auto lambda = [&x]() { From 5a3799e9bad64e9bdf5ad3f291d47a59384ea968 Mon Sep 17 00:00:00 2001 From: flovent Date: Sun, 21 Sep 2025 18:30:44 +0800 Subject: [PATCH 37/44] [NFC] cleanup --- .../modernize/UseStructuredBindingCheck.cpp | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/clang-tools-extra/clang-tidy/modernize/UseStructuredBindingCheck.cpp b/clang-tools-extra/clang-tidy/modernize/UseStructuredBindingCheck.cpp index 07a622d2938be..cbd0cf4e2c362 100644 --- a/clang-tools-extra/clang-tidy/modernize/UseStructuredBindingCheck.cpp +++ b/clang-tools-extra/clang-tidy/modernize/UseStructuredBindingCheck.cpp @@ -55,6 +55,7 @@ static bool matchNVarDeclStartingWith( BeginDS = EndDS; auto Matches = [&](const Decl *VD) { + // We don't want redundant decls in DeclStmt. if (Count == N) return false; @@ -113,7 +114,7 @@ AST_MATCHER_P(Stmt, hasPreTwoVarDecl, if (Parents.size() != 1) return false; - auto *C = Parents[0].get(); + const auto *C = Parents[0].get(); if (!C) return false; @@ -128,12 +129,11 @@ AST_MATCHER_P(Stmt, hasPreTwoVarDecl, AST_MATCHER_P(Stmt, hasNextTwoVarDecl, llvm::SmallVector>, InnerMatchers) { - const DynTypedNodeList Parents = Finder->getASTContext().getParents(Node); if (Parents.size() != 1) return false; - auto *C = Parents[0].get(); + const auto *C = Parents[0].get(); if (!C) return false; @@ -274,7 +274,8 @@ void UseStructuredBindingCheck::registerMatchers(MatchFinder *Finder) { hasType(qualType(anyOf(PairType, lValueReferenceType( pointee(PairType)))) .bind(PairVarTypeName)), - hasInitializer(expr(ignoringCopyCtorAndImplicitCast(expr().bind(InitExprName))))) + hasInitializer(expr(ignoringCopyCtorAndImplicitCast( + expr().bind(InitExprName))))) .bind(PairDeclName)), hasNextTwoVarDecl( llvm::SmallVector>{ @@ -294,7 +295,8 @@ void UseStructuredBindingCheck::registerMatchers(MatchFinder *Finder) { varDecl(hasType(qualType(anyOf(PairType, lValueReferenceType( pointee(PairType)))) .bind(PairVarTypeName)), - hasInitializer(expr(ignoringCopyCtorAndImplicitCast(expr().bind(InitExprName))))) + hasInitializer(expr(ignoringCopyCtorAndImplicitCast( + expr().bind(InitExprName))))) .bind(PairDeclName)), hasBody( compoundStmt( @@ -376,10 +378,10 @@ void UseStructuredBindingCheck::check(const MatchFinder::MatchResult &Result) { // Check whether PairVar, FirstVar and SecondVar have the same transfer type, // so they can be combined to structured binding. const auto *PairVar = Result.Nodes.getNodeAs(PairDeclName); - const Expr *InitE = Result.Nodes.getNodeAs(InitExprName); const std::optional PairCaptureType = - getTransferType(*Result.Context, PairVar->getType(), InitE->getType()); + getTransferType(*Result.Context, PairVar->getType(), + Result.Nodes.getNodeAs(InitExprName)->getType()); const std::optional FirstVarCaptureType = getTransferType(*Result.Context, FirstVar->getType(), *Result.Nodes.getNodeAs(FirstTypeName)); From c10eeeada8026e9363dc7b8504f1cad0dcb7cbd4 Mon Sep 17 00:00:00 2001 From: flovent Date: Sun, 21 Sep 2025 19:12:23 +0800 Subject: [PATCH 38/44] Use duck-typing to get pair type --- .../modernize/UseStructuredBindingCheck.cpp | 24 +++++------ .../modernize/UseStructuredBindingCheck.h | 6 +-- .../modernize/use-structured-binding.rst | 9 ---- .../use-structured-binding-custom.cpp | 32 -------------- .../modernize/use-structured-binding.cpp | 43 +++++++++++++++++++ 5 files changed, 55 insertions(+), 59 deletions(-) delete mode 100644 clang-tools-extra/test/clang-tidy/checkers/modernize/use-structured-binding-custom.cpp diff --git a/clang-tools-extra/clang-tidy/modernize/UseStructuredBindingCheck.cpp b/clang-tools-extra/clang-tidy/modernize/UseStructuredBindingCheck.cpp index cbd0cf4e2c362..3a36ec7aa79fd 100644 --- a/clang-tools-extra/clang-tidy/modernize/UseStructuredBindingCheck.cpp +++ b/clang-tools-extra/clang-tidy/modernize/UseStructuredBindingCheck.cpp @@ -8,14 +8,12 @@ #include "UseStructuredBindingCheck.h" #include "../utils/DeclRefExprUtils.h" -#include "../utils/OptionsUtils.h" #include "clang/Lex/Lexer.h" using namespace clang::ast_matchers; namespace clang::tidy::modernize { -static constexpr const char *DefaultPairTypes = "std::pair"; static constexpr llvm::StringLiteral PairDeclName = "PairVarD"; static constexpr llvm::StringLiteral PairVarTypeName = "PairVarType"; static constexpr llvm::StringLiteral FirstVarDeclName = "FirstVarDecl"; @@ -178,16 +176,15 @@ AST_MATCHER_P(Expr, ignoringCopyCtorAndImplicitCast, return InnerMatcher.matches(*Node.IgnoreImpCasts(), Finder, Builder); } -} // namespace - -UseStructuredBindingCheck::UseStructuredBindingCheck(StringRef Name, - ClangTidyContext *Context) - : ClangTidyCheck(Name, Context), - PairTypes(utils::options::parseStringList( - Options.get("PairTypes", DefaultPairTypes))) { - ; +AST_MATCHER(CXXRecordDecl, isPairType) { + return llvm::all_of(Node.fields(), [](const FieldDecl *FD) { + return FD->getAccess() == AS_public && + (FD->getName() == "first" || FD->getName() == "second"); + }); } +} // namespace + static auto getVarInitWithMemberMatcher( StringRef PairName, StringRef MemberName, StringRef TypeName, StringRef BindingName, @@ -204,10 +201,9 @@ static auto getVarInitWithMemberMatcher( } void UseStructuredBindingCheck::registerMatchers(MatchFinder *Finder) { - auto PairType = - qualType(unless(isVolatileQualified()), - hasUnqualifiedDesugaredType(recordType( - hasDeclaration(cxxRecordDecl(hasAnyName(PairTypes)))))); + auto PairType = qualType(unless(isVolatileQualified()), + hasUnqualifiedDesugaredType(recordType( + hasDeclaration(cxxRecordDecl(isPairType()))))); auto UnlessShouldBeIgnored = unless(anyOf(hasAnySpecifiersShouldBeIgnored(), isInMarco())); diff --git a/clang-tools-extra/clang-tidy/modernize/UseStructuredBindingCheck.h b/clang-tools-extra/clang-tidy/modernize/UseStructuredBindingCheck.h index 83d262a5db3cd..f8a4452890ecf 100644 --- a/clang-tools-extra/clang-tidy/modernize/UseStructuredBindingCheck.h +++ b/clang-tools-extra/clang-tidy/modernize/UseStructuredBindingCheck.h @@ -20,15 +20,13 @@ namespace clang::tidy::modernize { /// http://clang.llvm.org/extra/clang-tidy/checks/modernize/use-structured-binding.html class UseStructuredBindingCheck : public ClangTidyCheck { public: - UseStructuredBindingCheck(StringRef Name, ClangTidyContext *Context); + UseStructuredBindingCheck(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.CPlusPlus17; } - -private: - const std::vector PairTypes; }; } // namespace clang::tidy::modernize diff --git a/clang-tools-extra/docs/clang-tidy/checks/modernize/use-structured-binding.rst b/clang-tools-extra/docs/clang-tidy/checks/modernize/use-structured-binding.rst index 2526725c53ff9..8222b67fbe65a 100644 --- a/clang-tools-extra/docs/clang-tidy/checks/modernize/use-structured-binding.rst +++ b/clang-tools-extra/docs/clang-tidy/checks/modernize/use-structured-binding.rst @@ -84,12 +84,3 @@ and: if (results.second) { handle_inserted(results.first); } - -Options -------- - -.. option:: PairTypes - - A semicolon-separated list of type names to be treated as pair-like for - structured binding suggestions. Example: `MyPairType;OtherPairType`. - Default is `std::pair`. diff --git a/clang-tools-extra/test/clang-tidy/checkers/modernize/use-structured-binding-custom.cpp b/clang-tools-extra/test/clang-tidy/checkers/modernize/use-structured-binding-custom.cpp deleted file mode 100644 index d8f9bdc924d94..0000000000000 --- a/clang-tools-extra/test/clang-tidy/checkers/modernize/use-structured-binding-custom.cpp +++ /dev/null @@ -1,32 +0,0 @@ -// RUN: %check_clang_tidy -std=c++17-or-later %s modernize-use-structured-binding %t \ -// RUN: -config="{CheckOptions: {modernize-use-structured-binding.PairTypes: 'custom::pair; otherPair'}}" - -namespace custom { - struct pair { - int first; - int second; - }; -} - -struct otherPair { - int first; - int second; -}; - -void OptionTest() { - { - auto P = custom::pair(); - // CHECK-MESSAGES: :[[@LINE-1]]:5: warning: use a structured binding to decompose a pair [modernize-use-structured-binding] - // CHECK-FIXES: auto [x, y] = custom::pair(); - int x = P.first; - int y = P.second; - } - - { - auto P = otherPair(); - // CHECK-MESSAGES: :[[@LINE-1]]:5: warning: use a structured binding to decompose a pair [modernize-use-structured-binding] - // CHECK-FIXES: auto [x, y] = otherPair(); - int x = P.first; - int y = P.second; - } -} diff --git a/clang-tools-extra/test/clang-tidy/checkers/modernize/use-structured-binding.cpp b/clang-tools-extra/test/clang-tidy/checkers/modernize/use-structured-binding.cpp index 8a511f954e97e..be9cae8980c75 100644 --- a/clang-tools-extra/test/clang-tidy/checkers/modernize/use-structured-binding.cpp +++ b/clang-tools-extra/test/clang-tidy/checkers/modernize/use-structured-binding.cpp @@ -388,3 +388,46 @@ void stdTieNotWarn() { x = 1; }; } + +struct otherPair { + int first; + int second; +}; + +void OtherPairTest() { + { + auto P = otherPair(); + // CHECK-MESSAGES-ALL: :[[@LINE-1]]:5: warning: use a structured binding to decompose a pair [modernize-use-structured-binding] + // CHECK-FIXES-ALL: auto [x, y] = otherPair(); + int x = P.first; + int y = P.second; + } +} + +struct otherNonPair1 { + int first; + int second; + +private: + int third; +}; + +struct otherNonPair2 { + int first; + int second; + int third; +}; + +void OtherNonPairTest() { + { + auto P = otherNonPair1(); + int x = P.first; + int y = P.second; + } + + { + auto P = otherNonPair2(); + int x = P.first; + int y = P.second; + } +} From 48b7134dd5ed28c8928d71bc5da7699045084d5a Mon Sep 17 00:00:00 2001 From: flovent Date: Sun, 21 Sep 2025 19:18:00 +0800 Subject: [PATCH 39/44] [NFC] Fix doc --- .../clang-tidy/checks/modernize/use-structured-binding.rst | 3 --- 1 file changed, 3 deletions(-) diff --git a/clang-tools-extra/docs/clang-tidy/checks/modernize/use-structured-binding.rst b/clang-tools-extra/docs/clang-tidy/checks/modernize/use-structured-binding.rst index 8222b67fbe65a..f68041e266231 100644 --- a/clang-tools-extra/docs/clang-tidy/checks/modernize/use-structured-binding.rst +++ b/clang-tools-extra/docs/clang-tidy/checks/modernize/use-structured-binding.rst @@ -51,9 +51,6 @@ in a range-based for loop: // use x and y } -The check also supports custom pair-like types via the :option:`PairTypes` -option. - Limitations ----------- From 06079985e59470c50335b0ccadbf8801901baf6b Mon Sep 17 00:00:00 2001 From: flovent Date: Sun, 21 Sep 2025 20:44:30 +0800 Subject: [PATCH 40/44] [NFC] Should not use auto here --- .../clang-tidy/modernize/UseStructuredBindingCheck.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clang-tools-extra/clang-tidy/modernize/UseStructuredBindingCheck.cpp b/clang-tools-extra/clang-tidy/modernize/UseStructuredBindingCheck.cpp index 3a36ec7aa79fd..11e198b60ffb5 100644 --- a/clang-tools-extra/clang-tidy/modernize/UseStructuredBindingCheck.cpp +++ b/clang-tools-extra/clang-tidy/modernize/UseStructuredBindingCheck.cpp @@ -166,7 +166,7 @@ AST_POLYMORPHIC_MATCHER(isInMarco, AST_MATCHER_P(Expr, ignoringCopyCtorAndImplicitCast, ast_matchers::internal::Matcher, InnerMatcher) { if (const auto *CtorE = dyn_cast(&Node)) { - if (const auto *CtorD = CtorE->getConstructor(); + if (const CXXConstructorDecl *CtorD = CtorE->getConstructor(); CtorD->isCopyConstructor() && CtorE->getNumArgs() == 1) { return InnerMatcher.matches(*CtorE->getArg(0)->IgnoreImpCasts(), Finder, Builder); From 7b7ba725314ac7560f129e21ea61c29492290956 Mon Sep 17 00:00:00 2001 From: flovent Date: Sun, 21 Sep 2025 20:51:32 +0800 Subject: [PATCH 41/44] [NFC] Add more tests for custom pair type --- .../modernize/use-structured-binding.cpp | 55 +++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/clang-tools-extra/test/clang-tidy/checkers/modernize/use-structured-binding.cpp b/clang-tools-extra/test/clang-tidy/checkers/modernize/use-structured-binding.cpp index be9cae8980c75..b398e035e35c3 100644 --- a/clang-tools-extra/test/clang-tidy/checkers/modernize/use-structured-binding.cpp +++ b/clang-tools-extra/test/clang-tidy/checkers/modernize/use-structured-binding.cpp @@ -402,6 +402,61 @@ void OtherPairTest() { int x = P.first; int y = P.second; } + + { + const auto P = otherPair(); + // CHECK-MESSAGES-ALL: :[[@LINE-1]]:5: warning: use a structured binding to decompose a pair [modernize-use-structured-binding] + // CHECK-FIXES-ALL: const auto [x, y] = otherPair(); + const int x = P.first; + const auto y = P.second; // REMOVE + // CHECK-FIXES-ALL: // REMOVE + } + + { + otherPair otherP; + auto& P = otherP; + // CHECK-MESSAGES-ALL: :[[@LINE-1]]:5: warning: use a structured binding to decompose a pair [modernize-use-structured-binding] + // CHECK-FIXES-ALL: auto& [x, y] = otherP; + int& x = P.first; + auto& y = P.second; // REMOVE + // CHECK-FIXES-ALL: // REMOVE + } + + { + std::pair otherP; + const auto& P = otherP; + // CHECK-MESSAGES-ALL: :[[@LINE-1]]:5: warning: use a structured binding to decompose a pair [modernize-use-structured-binding] + // CHECK-FIXES-ALL: const auto& [x, y] = otherP; + const int& x = P.first; + const auto& y = P.second; // REMOVE + // CHECK-FIXES-ALL: // REMOVE + } +} + +void OtherPairNotWarnCases() { + { + auto P = otherPair(); + const int x = P.first; + int y = P.second; + } + + { + auto P = otherPair(); + volatile int x = P.first; + int y = P.second; + } + + { + auto P = otherPair(); + int x = P.first; + [[maybe_unused]] int y = P.second; + } + + { + static auto P = getPair(); + int x = P.first; + int y = P.second; + } } struct otherNonPair1 { From 52bc67c81e4847cdf68558f73d9169b4967e3d7a Mon Sep 17 00:00:00 2001 From: flovent Date: Sun, 21 Sep 2025 20:59:33 +0800 Subject: [PATCH 42/44] [NFC] Place lambda out of loop --- .../modernize/UseStructuredBindingCheck.cpp | 31 ++++++++++--------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/clang-tools-extra/clang-tidy/modernize/UseStructuredBindingCheck.cpp b/clang-tools-extra/clang-tidy/modernize/UseStructuredBindingCheck.cpp index 11e198b60ffb5..501939b4c5607 100644 --- a/clang-tools-extra/clang-tidy/modernize/UseStructuredBindingCheck.cpp +++ b/clang-tools-extra/clang-tidy/modernize/UseStructuredBindingCheck.cpp @@ -44,6 +44,22 @@ static bool matchNVarDeclStartingWith( const DeclStmt *EndDS = nullptr; 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(VD); + Var && InnerMatchers[Backwards ? N - Count - 1 : Count].matches( + *Var, Finder, Builder)) { + ++Count; + return true; + } + + return false; + }; + for (; Iter != EndIter; ++Iter) { EndDS = dyn_cast(*Iter); if (!EndDS) @@ -52,21 +68,6 @@ static bool matchNVarDeclStartingWith( if (!BeginDS) BeginDS = EndDS; - auto Matches = [&](const Decl *VD) { - // We don't want redundant decls in DeclStmt. - if (Count == N) - return false; - - if (const auto *Var = dyn_cast(VD); - Var && InnerMatchers[Backwards ? N - Count - 1 : Count].matches( - *Var, Finder, Builder)) { - ++Count; - return true; - } - - return false; - }; - if (Backwards) { for (const auto *VD : llvm::reverse(EndDS->decls())) { if (!Matches(VD)) From d091ff4557f5625f1398fe94c665c2fd0ef4b71e Mon Sep 17 00:00:00 2001 From: flovent Date: Sun, 21 Sep 2025 21:06:10 +0800 Subject: [PATCH 43/44] [NFC] Iterator's name from `I` to `It` --- .../modernize/UseStructuredBindingCheck.cpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/clang-tools-extra/clang-tidy/modernize/UseStructuredBindingCheck.cpp b/clang-tools-extra/clang-tidy/modernize/UseStructuredBindingCheck.cpp index 501939b4c5607..16e2922d25d50 100644 --- a/clang-tools-extra/clang-tidy/modernize/UseStructuredBindingCheck.cpp +++ b/clang-tools-extra/clang-tidy/modernize/UseStructuredBindingCheck.cpp @@ -117,10 +117,10 @@ AST_MATCHER_P(Stmt, hasPreTwoVarDecl, if (!C) return false; - const auto I = llvm::find(llvm::reverse(C->body()), &Node); - assert(I != C->body_rend() && "C is parent of Node"); - return matchNVarDeclStartingWith(I + 1, C->body_rend(), InnerMatchers, Finder, - Builder, true); + 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 @@ -136,9 +136,9 @@ AST_MATCHER_P(Stmt, hasNextTwoVarDecl, if (!C) return false; - const auto *I = llvm::find(C->body(), &Node); - assert(I != C->body_end() && "C is parent of Node"); - return matchNVarDeclStartingWith(I + 1, C->body_end(), InnerMatchers, Finder, + 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); } From 6d44ea0706b9d69f1d09d9adb10d74deeaaf4108 Mon Sep 17 00:00:00 2001 From: flovent Date: Sun, 21 Sep 2025 22:35:37 +0800 Subject: [PATCH 44/44] [NFC] Add test for multi checked cases in one scope --- .../modernize/use-structured-binding.cpp | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/clang-tools-extra/test/clang-tidy/checkers/modernize/use-structured-binding.cpp b/clang-tools-extra/test/clang-tidy/checkers/modernize/use-structured-binding.cpp index b398e035e35c3..1a359c23a016f 100644 --- a/clang-tools-extra/test/clang-tidy/checkers/modernize/use-structured-binding.cpp +++ b/clang-tools-extra/test/clang-tidy/checkers/modernize/use-structured-binding.cpp @@ -77,6 +77,23 @@ void DecomposeByAssignWarnCases() { const auto& y = P.second; // REMOVE // CHECK-FIXES-ALL: // REMOVE } + + { + auto P = getPair(); // match + // CHECK-MESSAGES-ALL: :[[@LINE-1]]:5: warning: use a structured binding to decompose a pair [modernize-use-structured-binding] + // CHECK-FIXES-ALL: auto [x, y] = getPair(); // match + int x = P.first; + int y = P.second; // REMOVE + // CHECK-FIXES-ALL: // REMOVE + + // maybe match with `hasParent` but NOT with `has(declStmt())` + auto another_p = getPair(); + // CHECK-MESSAGES-ALL: :[[@LINE-1]]:5: warning: use a structured binding to decompose a pair [modernize-use-structured-binding] + // CHECK-FIXES-ALL: auto [another_x, another_y] = getPair(); + int another_x = another_p.first; + int another_y = another_p.second; // REMOVE + // CHECK-FIXES-ALL: // REMOVE + } } void forRangeWarnCases() {