diff --git a/clang-tools-extra/clang-tidy/modernize/CMakeLists.txt b/clang-tools-extra/clang-tidy/modernize/CMakeLists.txt index 882f2dc9fb4d8..8972cdd601185 100644 --- a/clang-tools-extra/clang-tidy/modernize/CMakeLists.txt +++ b/clang-tools-extra/clang-tidy/modernize/CMakeLists.txt @@ -49,6 +49,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 360e2b8434d0c..be25015ee506c 100644 --- a/clang-tools-extra/clang-tidy/modernize/ModernizeTidyModule.cpp +++ b/clang-tools-extra/clang-tidy/modernize/ModernizeTidyModule.cpp @@ -50,6 +50,7 @@ #include "UseStdFormatCheck.h" #include "UseStdNumbersCheck.h" #include "UseStdPrintCheck.h" +#include "UseStructuredBindingCheck.h" #include "UseTrailingReturnTypeCheck.h" #include "UseTransparentFunctorsCheck.h" #include "UseUncaughtExceptionsCheck.h" @@ -127,6 +128,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..2be721671b445 --- /dev/null +++ b/clang-tools-extra/clang-tidy/modernize/UseStructuredBindingCheck.cpp @@ -0,0 +1,409 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "UseStructuredBindingCheck.h" +#include "../utils/DeclRefExprUtils.h" +#include "clang/Lex/Lexer.h" + +using namespace clang::ast_matchers; + +namespace clang::tidy::modernize { + +static constexpr 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 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"; +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 +/// 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; + + 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) + break; + + if (!BeginDS) + BeginDS = EndDS; + + 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; + } + } + + // 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; +} + +namespace { +/// What qualifiers and specifiers are used to create structured binding +/// declaration, it only supports the following four cases now. +enum TransferType : uint8_t { + TT_ByVal, + TT_ByConstVal, + TT_ByRef, + TT_ByConstRef +}; + +/// Matches a Stmt whose parent is a CompoundStmt, and which is directly +/// following two VarDecls matching the inner matcher. +AST_MATCHER_P(Stmt, hasPreTwoVarDecl, + llvm::SmallVector>, + InnerMatchers) { + const DynTypedNodeList Parents = Finder->getASTContext().getParents(Node); + if (Parents.size() != 1) + return false; + + const auto *C = Parents[0].get(); + if (!C) + return false; + + const auto It = llvm::find(llvm::reverse(C->body()), &Node); + assert(It != C->body_rend() && "C is parent of Node"); + return matchNVarDeclStartingWith(It + 1, C->body_rend(), InnerMatchers, + Finder, Builder, true); +} + +/// Matches a Stmt whose parent is a CompoundStmt, and which is directly +/// followed by two VarDecls matching the inner matcher. +AST_MATCHER_P(Stmt, hasNextTwoVarDecl, + llvm::SmallVector>, + InnerMatchers) { + const DynTypedNodeList Parents = Finder->getASTContext().getParents(Node); + if (Parents.size() != 1) + return false; + + const auto *C = Parents[0].get(); + if (!C) + return false; + + const auto *It = llvm::find(C->body(), &Node); + assert(It != C->body_end() && "C is parent of Node"); + return matchNVarDeclStartingWith(It + 1, C->body_end(), InnerMatchers, Finder, + Builder); +} + +/// Matches a CompoundStmt which has two VarDecls matching the inner matcher in +/// the beginning. +AST_MATCHER_P(CompoundStmt, hasFirstTwoVarDecl, + llvm::SmallVector>, + InnerMatchers) { + return matchNVarDeclStartingWith(Node.body_begin(), Node.body_end(), + InnerMatchers, Finder, Builder); +} + +/// It's not very common to have specifiers for variables used to decompose a +/// pair, so we ignore these cases. +AST_MATCHER(VarDecl, hasAnySpecifiersShouldBeIgnored) { + 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 CXXConstructorDecl *CtorD = CtorE->getConstructor(); + CtorD->isCopyConstructor() && CtorE->getNumArgs() == 1) { + return InnerMatcher.matches(*CtorE->getArg(0)->IgnoreImpCasts(), Finder, + Builder); + } + } + + return InnerMatcher.matches(*Node.IgnoreImpCasts(), Finder, Builder); +} + +AST_MATCHER(CXXRecordDecl, isPairType) { + return llvm::all_of(Node.fields(), [](const FieldDecl *FD) { + return FD->getAccess() == AS_public && + (FD->getName() == "first" || FD->getName() == "second"); + }); +} + +AST_MATCHER(VarDecl, isDirectInitialization) { + return Node.getInitStyle() != VarDecl::InitializationStyle::CInit; +} + +} // namespace + +static auto getVarInitWithMemberMatcher( + StringRef PairName, StringRef MemberName, StringRef TypeName, + StringRef BindingName, + ast_matchers::internal::Matcher ExtraMatcher) { + return varDecl(ExtraMatcher, + hasInitializer(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(isPairType()))))); + + 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))); + }; + + 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) = ...; + Finder->addMatcher( + exprWithCleanups( + unless(isInMarco()), + has(cxxOperatorCallExpr( + hasOverloadedOperatorName("="), + hasLHS(ignoringImplicit( + callExpr(callee(functionDecl(isInStdNamespace(), + hasName("tie"))), + hasArgument(0, RefToBindName(FirstVarDeclName)), + hasArgument(1, RefToBindName(SecondVarDeclName))) + .bind(StdTieExprName))), + hasRHS(expr(hasType(PairType)))) + .bind(StdTieAssignStmtName)), + hasPreTwoVarDecl( + llvm::SmallVector>{ + varDecl(equalsBoundNode(std::string(FirstVarDeclName))), + varDecl(equalsBoundNode(std::string(SecondVarDeclName)))}), + hasParent(compoundStmt(UnlessFirstVarOrSecondVarIsCapturedByLambda) + .bind(ScopeBlockName))), + this); + + // pair p = ...; + // X x = p.first; + // Y y = p.second; + Finder->addMatcher( + declStmt( + unless(isInMarco()), + hasSingleDecl( + varDecl(UnlessShouldBeIgnored, unless(isDirectInitialization()), + hasType(qualType(anyOf(PairType, lValueReferenceType( + pointee(PairType)))) + .bind(PairVarTypeName)), + hasInitializer(ignoringCopyCtorAndImplicitCast( + expr().bind(InitExprName)))) + .bind(PairDeclName)), + hasNextTwoVarDecl( + llvm::SmallVector>{ + VarInitWithFirstMember, VarInitWithSecondMember}), + hasParent(compoundStmt(UnlessFirstVarOrSecondVarIsCapturedByLambda) + .bind(ScopeBlockName))), + 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(ignoringCopyCtorAndImplicitCast( + expr().bind(InitExprName)))) + .bind(PairDeclName)), + hasBody( + compoundStmt( + hasFirstTwoVarDecl(llvm::SmallVector< + ast_matchers::internal::Matcher>{ + VarInitWithFirstMember, VarInitWithSecondMember}), + UnlessFirstVarOrSecondVarIsCapturedByLambda) + .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 *BeginDS = Result.Nodes.getNodeAs(BeginDeclStmtName); + const auto *EndDS = Result.Nodes.getNodeAs(EndDeclStmtName); + const auto *ScopeBlock = Result.Nodes.getNodeAs(ScopeBlockName); + + const auto *CFRS = Result.Nodes.getNodeAs(ForRangeStmtName); + auto DiagAndFix = [&BeginDS, &EndDS, &FirstVar, &SecondVar, &CFRS, + this](SourceLocation DiagLoc, SourceRange ReplaceRange, + TransferType TT = TT_ByVal) { + const auto Prefix = [&TT]() -> StringRef { + switch (TT) { + case TT_ByVal: + return "auto"; + case TT_ByConstVal: + return "const auto"; + case TT_ByRef: + return "auto&"; + case TT_ByConstRef: + return "const auto&"; + } + }(); + + std::string ReplacementText = + (Twine(Prefix) + " [" + FirstVar->getNameAsString() + ", " + + SecondVar->getNameAsString() + "]" + (CFRS ? " :" : "")) + .str(); + diag(DiagLoc, "use a structured binding to decompose a pair") + << FixItHint::CreateReplacement(ReplaceRange, ReplacementText) + << FixItHint::CreateRemoval( + SourceRange{BeginDS->getBeginLoc(), EndDS->getEndLoc()}); + }; + + 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 std::optional PairCaptureType = + getTransferType(*Result.Context, PairVar->getType(), + Result.Nodes.getNodeAs(InitExprName)->getType()); + const std::optional FirstVarCaptureType = + getTransferType(*Result.Context, FirstVar->getType(), + *Result.Nodes.getNodeAs(FirstTypeName)); + const 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..0b11ce38c44db --- /dev/null +++ b/clang-tools-extra/clang-tidy/modernize/UseStructuredBindingCheck.h @@ -0,0 +1,34 @@ +//===----------------------------------------------------------------------===// +// +// 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: +/// https://clang.llvm.org/extra/clang-tidy/checks/modernize/use-structured-binding.html +class UseStructuredBindingCheck : public ClangTidyCheck { +public: + 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; + } +}; + +} // 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 c7ba1195f84f6..9a831f745326a 100644 --- a/clang-tools-extra/docs/ReleaseNotes.rst +++ b/clang-tools-extra/docs/ReleaseNotes.rst @@ -203,6 +203,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 :doc:`readability-redundant-parentheses ` check. diff --git a/clang-tools-extra/docs/clang-tidy/checks/list.rst b/clang-tools-extra/docs/clang-tidy/checks/list.rst index f94696d4ef9c7..2790d76f5bf94 100644 --- a/clang-tools-extra/docs/clang-tidy/checks/list.rst +++ b/clang-tools-extra/docs/clang-tidy/checks/list.rst @@ -325,6 +325,7 @@ Clang-Tidy Checks :doc:`modernize-use-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..f68041e266231 --- /dev/null +++ b/clang-tools-extra/docs/clang-tidy/checks/modernize/use-structured-binding.rst @@ -0,0 +1,83 @@ +.. title:: clang-tidy - modernize-use-structured-binding + +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. + +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 (auto p : vecOfPairs) { + int x = p.first; + int y = p.second; + // ... + } + + into: + + for (auto [x, y] : vecOfPairs) { + // use x and y + } + +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 be transferred +to structured bindings, 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); + } 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..f49cfd840e128 --- /dev/null +++ b/clang-tools-extra/test/clang-tidy/checkers/modernize/Inputs/use-structured-binding/fake_std_pair_tuple.h @@ -0,0 +1,44 @@ +namespace std { + template + struct pair { + T1 first; + T2 second; + + pair() = default; + pair(T1 first, T2 second) : first(first), second(second) {} + }; + + template + struct tuple { + tuple(Args&...) {} + + template + tuple operator=(const std::pair&); + }; + + template + tuple tie(Args&... args) { + return tuple(args...); + } + + template + class unordered_map { + public: + using value_type = pair; + + class iterator { + public: + iterator& operator++(); + bool operator!=(const iterator &other); + const value_type &operator*() const; + value_type operator*(); + const value_type* operator->() const; + }; + + iterator begin() const; + iterator end() const; + }; +} + +template +std::pair getPair(); 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..52098fbbce253 --- /dev/null +++ b/clang-tools-extra/test/clang-tidy/checkers/modernize/use-structured-binding.cpp @@ -0,0 +1,665 @@ +// RUN: %check_clang_tidy -check-suffix=,CPP20ORLATER -std=c++20-or-later %s modernize-use-structured-binding %t -- -- -I %S/Inputs/use-structured-binding/ +// 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" + +template +void MarkUsed(T x); + +struct TestClass { + int a; + int b; + TestClass() : a(0), b(0) {} + TestClass& operator++(); + TestClass(int x, int y) : a(x), b(y) {} +}; + +void DecomposeByAssignWarnCases() { + { + auto P = getPair(); + // CHECK-MESSAGES: :[[@LINE-1]]:5: warning: use a structured binding to decompose a pair [modernize-use-structured-binding] + // CHECK-FIXES: auto [x, y] = getPair(); + // CHECK-NEXT: // REMOVE + int x = P.first; + int y = P.second; // REMOVE + // CHECK-FIXES: // REMOVE + } + + { + auto P = getPair(); + // CHECK-MESSAGES: :[[@LINE-1]]:5: warning: use a structured binding to decompose a pair [modernize-use-structured-binding] + // CHECK-FIXES: auto [x, y] = getPair(); + int x = P.first, y = P.second; // REMOVE + // CHECK-FIXES: // REMOVE + } + + { + auto P = getPair(); + // CHECK-MESSAGES: :[[@LINE-1]]:5: warning: use a structured binding to decompose a pair [modernize-use-structured-binding] + // CHECK-FIXES: auto [x, y] = getPair(); + int x = P.first, y = P.second; // REMOVE + // CHECK-FIXES: // REMOVE + int z; + } + + { + auto P = getPair(); + // CHECK-MESSAGES: :[[@LINE-1]]:5: warning: use a structured binding to decompose a pair [modernize-use-structured-binding] + // CHECK-FIXES: auto [x, y] = getPair(); + // CHECK-NEXT: // REMOVE + int x = P.first; + auto y = P.second; // REMOVE + // CHECK-FIXES: // REMOVE + } + + { + const auto P = getPair(); + // CHECK-MESSAGES: :[[@LINE-1]]:5: warning: use a structured binding to decompose a pair [modernize-use-structured-binding] + // CHECK-FIXES: const auto [x, y] = getPair(); + // CHECK-NEXT: // REMOVE + const int x = P.first; + const auto y = P.second; // REMOVE + // CHECK-FIXES: // REMOVE + } + + { + std::pair otherP; + auto& P = otherP; + // CHECK-MESSAGES: :[[@LINE-1]]:5: warning: use a structured binding to decompose a pair [modernize-use-structured-binding] + // CHECK-FIXES: auto& [x, y] = otherP; + // CHECK-NEXT: // REMOVE + int& x = P.first; + auto& y = P.second; // REMOVE + // CHECK-FIXES: // REMOVE + } + + { + std::pair otherP; + const auto& P = otherP; + // CHECK-MESSAGES: :[[@LINE-1]]:5: warning: use a structured binding to decompose a pair [modernize-use-structured-binding] + // CHECK-FIXES: const auto& [x, y] = otherP; + // CHECK-NEXT: // REMOVE + const int& x = P.first; + const auto& y = P.second; // REMOVE + // CHECK-FIXES: // REMOVE + } + + { + auto P = getPair(); + // CHECK-MESSAGES: :[[@LINE-1]]:5: warning: use a structured binding to decompose a pair [modernize-use-structured-binding] + // CHECK-FIXES: auto [x, y] = getPair(); + // CHECK-NEXT: // REMOVE + int x = P.first; + int y = P.second; // REMOVE + // CHECK-FIXES: // REMOVE + + auto another_p = getPair(); + // CHECK-MESSAGES: :[[@LINE-1]]:5: warning: use a structured binding to decompose a pair [modernize-use-structured-binding] + // CHECK-FIXES: auto [another_x, another_y] = getPair(); + // CHECK-NEXT: // REMOVE + int another_x = another_p.first; + int another_y = another_p.second; // REMOVE + // CHECK-FIXES: // REMOVE + } +} + +void forRangeWarnCases() { + std::pair Pairs[10]; + for (auto P : Pairs) { + // CHECK-MESSAGES: :[[@LINE-1]]:8: warning: use a structured binding to decompose a pair [modernize-use-structured-binding] + // CHECK-FIXES: for (auto [x, y] : Pairs) { + // CHECK-NEXT: // REMOVE + int x = P.first; + int y = P.second; // REMOVE + // CHECK-FIXES: // REMOVE + } + + for (auto P : Pairs) { + // CHECK-MESSAGES: :[[@LINE-1]]:8: warning: use a structured binding to decompose a pair [modernize-use-structured-binding] + // CHECK-FIXES: for (auto [x, y] : Pairs) { + int x = P.first, y = P.second; // REMOVE + // CHECK-FIXES: // REMOVE + } + + for (auto P : Pairs) { + // CHECK-MESSAGES: :[[@LINE-1]]:8: warning: use a structured binding to decompose a pair [modernize-use-structured-binding] + // CHECK-FIXES: for (auto [x, y] : Pairs) { + int x = P.first, y = P.second; // REMOVE + // CHECK-FIXES: // REMOVE + int z; + } + + for (const auto P : Pairs) { + // CHECK-MESSAGES: :[[@LINE-1]]:8: warning: use a structured binding to decompose a pair [modernize-use-structured-binding] + // CHECK-FIXES: for (const auto [x, y] : Pairs) { + // CHECK-NEXT: // REMOVE + const int x = P.first; + const int y = P.second; // REMOVE + // CHECK-FIXES: // REMOVE + } + + for (auto& P : Pairs) { + // CHECK-MESSAGES: :[[@LINE-1]]:8: warning: use a structured binding to decompose a pair [modernize-use-structured-binding] + // CHECK-FIXES: for (auto& [x, y] : Pairs) { + // CHECK-NEXT: // REMOVE + int& x = P.first; + int& y = P.second; // REMOVE + // CHECK-FIXES: // REMOVE + } + + for (const auto& P : Pairs) { + // CHECK-MESSAGES: :[[@LINE-1]]:8: warning: use a structured binding to decompose a pair [modernize-use-structured-binding] + // CHECK-FIXES: for (const auto& [x, y] : Pairs) { + // CHECK-NEXT: // REMOVE + const int& x = P.first; + const int& y = P.second; // REMOVE + // CHECK-FIXES: // REMOVE + } + + std::pair ClassPairs[10]; + for (auto P : ClassPairs) { + // CHECK-MESSAGES: :[[@LINE-1]]:8: warning: use a structured binding to decompose a pair [modernize-use-structured-binding] + // CHECK-FIXES: for (auto [c1, c2] : ClassPairs) { + // CHECK-NEXT: // REMOVE + TestClass c1 = P.first; + TestClass c2 = P.second; // REMOVE + // CHECK-FIXES: // REMOVE + } + + for (const auto P : ClassPairs) { + // CHECK-MESSAGES: :[[@LINE-1]]:8: warning: use a structured binding to decompose a pair [modernize-use-structured-binding] + // CHECK-FIXES: for (const auto [c1, c2] : ClassPairs) { + // CHECK-NEXT: // REMOVE + const TestClass c1 = P.first; + const TestClass c2 = P.second; // REMOVE + // CHECK-FIXES: // REMOVE + } +} + +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]; + for (auto P : ClassPairs) { + TestClass c1 = P.first; + ++ c1 ; + TestClass c2 = P.second; + } + + int c; + for (auto P : ClassPairs) { + TestClass c1 = P.first; + c ++ ; + TestClass c2 = P.second; + } +} + +void stdTieWarnCases() { + // CHECK-NEXT: // REMOVE + int a = 0; + int b = 0; // REMOVE + // CHECK-FIXES: // REMOVE + std::tie(a, b) = getPair(); + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: use a structured binding to decompose a pair [modernize-use-structured-binding] + // CHECK-FIXES: auto [a, b] = getPair(); + + int x = 0, y = 0; // REMOVE + // CHECK-FIXES: // REMOVE + std::tie(x, y) = getPair(); + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: use a structured binding to decompose a pair [modernize-use-structured-binding] + // CHECK-FIXES: auto [x, y] = getPair(); + + // CHECK-NEXT: // REMOVE + int* pa = nullptr; + int* pb = nullptr; // REMOVE + // CHECK-FIXES: // REMOVE + std::tie(pa, pb) = getPair(); + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: use a structured binding to decompose a pair [modernize-use-structured-binding] + // CHECK-FIXES: auto [pa, pb] = getPair(); + + // CHECK-NEXT: // REMOVE + TestClass c1 (1, 2); + TestClass c2 = TestClass {3, 4}; // REMOVE + // CHECK-FIXES: // REMOVE + std::tie(c1, c2) = getPair(); + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: use a structured binding to decompose a 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 c = 0, d = 0; + int e = 0; + 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; +} + +void captureByVal() { + auto P = getPair(); + // 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(); + // CHECK-NEXT-CPP20ORLATER: // REMOVE + int x = P.first; + int y = P.second; // REMOVE + // CHECK-FIXES-CPP20ORLATER: // REMOVE + + auto lambda = [x]() { + int y = x; + }; +} + +void captureByRef() { + auto P = getPair(); + // 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(); + // CHECK-NEXT-CPP20ORLATER: // REMOVE + int x = P.first; + int y = P.second; // REMOVE + // CHECK-FIXES-CPP20ORLATER: // REMOVE + + auto lambda = [&x]() { + x = 1; + }; +} + +void captureByAllRef() { + auto P = getPair(); + // 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(); + // CHECK-NEXT-CPP20ORLATER: // REMOVE + int x = P.first; + int y = P.second; // REMOVE + // CHECK-FIXES-CPP20ORLATER: // REMOVE + + auto lambda = [&]() { + x = 1; + }; +} + +void deepLambda() { + auto P = getPair(); + // 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(); + // CHECK-NEXT-CPP20ORLATER: // REMOVE + int x = P.first; + int y = P.second; // REMOVE + // CHECK-FIXES-CPP20ORLATER: // REMOVE + + { + auto lambda = [x]() { + int y = x; + }; + } +} + +void forRangeNotWarn() { + std::pair Pairs[10]; + for (auto P : Pairs) { + // 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) { + // CHECK-NEXT-CPP20ORLATER: // REMOVE + int x = P.first; + int y = P.second; // REMOVE + // CHECK-FIXES-CPP20ORLATER: // REMOVE + + auto lambda = [&]() { + x = 1; + }; + } +} + +void stdTieNotWarn() { + // CHECK-NEXT-CPP20ORLATER: // REMOVE + int x = 0; + int y = 0; // REMOVE + // CHECK-FIXES-CPP20ORLATER: // REMOVE + std::tie(x, y) = getPair(); + // 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]() { + x = 1; + }; +} + +struct otherPair { + int first; + int second; +}; + +void OtherPairTest() { + { + 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(); + // CHECK-NEXT: // REMOVE + int x = P.first; + int y = P.second; // REMOVE + // CHECK-FIXES: // REMOVE + } + + { + const auto P = otherPair(); + // CHECK-MESSAGES: :[[@LINE-1]]:5: warning: use a structured binding to decompose a pair [modernize-use-structured-binding] + // CHECK-FIXES: const auto [x, y] = otherPair(); + // CHECK-NEXT: // REMOVE + const int x = P.first; + const auto y = P.second; // REMOVE + // CHECK-FIXES: // REMOVE + } + + { + otherPair otherP; + auto& P = otherP; + // CHECK-MESSAGES: :[[@LINE-1]]:5: warning: use a structured binding to decompose a pair [modernize-use-structured-binding] + // CHECK-FIXES: auto& [x, y] = otherP; + // CHECK-NEXT: // REMOVE + int& x = P.first; + auto& y = P.second; // REMOVE + // CHECK-FIXES: // REMOVE + } + + { + std::pair otherP; + const auto& P = otherP; + // CHECK-MESSAGES: :[[@LINE-1]]:5: warning: use a structured binding to decompose a pair [modernize-use-structured-binding] + // CHECK-FIXES: const auto& [x, y] = otherP; + // CHECK-NEXT: // REMOVE + const int& x = P.first; + const auto& y = P.second; // REMOVE + // CHECK-FIXES: // 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 { + 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; + } +} + +template +PairType getCertainPair(); + +struct ConstFieldPair { + const int first; + int second; +}; + +void ConstFieldPairTests() { + { + const ConstFieldPair P = getCertainPair(); + // CHECK-MESSAGES: :[[@LINE-1]]:5: warning: use a structured binding to decompose a pair [modernize-use-structured-binding] + // CHECK-FIXES: const auto [x, y] = getCertainPair(); + // CHECK-NEXT: // REMOVE + const int x = P.first; + const int y = P.second; // REMOVE + // CHECK-FIXES: // REMOVE + } + + { + const ConstFieldPair& P = getCertainPair(); + // CHECK-MESSAGES: :[[@LINE-1]]:5: warning: use a structured binding to decompose a pair [modernize-use-structured-binding] + // CHECK-FIXES: const auto& [x, y] = getCertainPair(); + // CHECK-NEXT: // REMOVE + const int& x = P.first; + const int& y = P.second; // REMOVE + // CHECK-FIXES: // REMOVE + } + + { + ConstFieldPair P = getCertainPair(); // no warning + int x = P.first; + int y = P.second; + } +} + +struct PointerFieldPair { + int* first; + int second; +}; + +void PointerFieldPairTests() { + { + PointerFieldPair P = getCertainPair(); + // CHECK-MESSAGES: :[[@LINE-1]]:5: warning: use a structured binding to decompose a pair [modernize-use-structured-binding] + // CHECK-FIXES: auto [x, y] = getCertainPair(); + // CHECK-NEXT: // REMOVE + int* x = P.first; + int y = P.second; // REMOVE + // CHECK-FIXES: // REMOVE + } + + { + PointerFieldPair P = getCertainPair(); // no warning + const int* x = P.first; + int y = P.second; + } +} + +struct ConstRefFieldPair { + const int& first; + int second; + ConstRefFieldPair(int& f, int s) : first(f), second(s) {} +}; + +void ConstRefFieldPairTests() { + { + ConstRefFieldPair P = getCertainPair(); + // CHECK-MESSAGES: :[[@LINE-1]]:5: warning: use a structured binding to decompose a pair [modernize-use-structured-binding] + // CHECK-FIXES: auto [x, y] = getCertainPair(); + // CHECK-NEXT: // REMOVE + const int& x = P.first; + int y = P.second; // REMOVE + // CHECK-FIXES: // REMOVE + } + + { + ConstRefFieldPair P = getCertainPair();; // no warning + int x = P.first; + int y = P.second; + } +} + +struct StaticFieldPair { + static int first; + int second; +}; + +void StaticFieldPairTests() { + { + StaticFieldPair P; // Should not warn + int x = P.first; + int y = P.second; + } + + { + StaticFieldPair P; // Should not warn + static int x = P.first; + int y = P.second; + } +} + +void IgnoreDirectInit() { + { + std::pair P{1, 1}; + int x = P.first; + int y = P.second; + } + + { + std::pair P(1, 1); + int x = P.first; + int y = P.second; + } + + { + std::pair P; + int x = P.first; + int y = P.second; + } +} + +void StdMapTestCases() { + for (auto p : std::unordered_map()) { + // CHECK-MESSAGES: :[[@LINE-1]]:8: warning: use a structured binding to decompose a pair [modernize-use-structured-binding] + // CHECK-FIXES: for (auto [x, y] : std::unordered_map()) { + // CHECK-NEXT: // REMOVE + int x = p.first; + int y = p.second; // REMOVE + // CHECK-FIXES: // REMOVE + } +} +