================ @@ -0,0 +1,419 @@ +//===----------------------------------------------------------------------===// +// +// 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<VarDecl> InnerMatcher1, + ast_matchers::internal::Matcher<VarDecl> InnerMatcher2, + internal::ASTMatchFinder *Finder, + internal::BoundNodesTreeBuilder *Builder) { + SmallVector<std::pair<const VarDecl *, const DeclStmt *>, 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<VarDecl>(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<VarDecl>, + InnerMatcher1, ast_matchers::internal::Matcher<VarDecl>, + InnerMatcher2) { + DynTypedNodeList Parents = Finder->getASTContext().getParents(Node); + if (Parents.size() != 1) + return false; + + auto *C = Parents[0].get<CompoundStmt>(); + 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<DeclStmt>(*(I + 1)); + if (!DS2) + return false; + + const DeclStmt *DS1 = (!DS2->isSingleDecl() || ((I + 2) == C->body_rend()) + ? nullptr + : dyn_cast<DeclStmt>(*(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<VarDecl>, InnerMatcher1, + ast_matchers::internal::Matcher<VarDecl>, InnerMatcher2) { + const DynTypedNodeList Parents = Finder->getASTContext().getParents(Node); + if (Parents.size() != 1) + return false; + + auto *C = Parents[0].get<CompoundStmt>(); + 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<DeclStmt>(*(I + 1)), + ((I + 2) == C->body_end() ? nullptr : dyn_cast<DeclStmt>(*(I + 2))), + InnerMatcher1, InnerMatcher2, Finder, Builder)) { + Builder->setBinding(ScopeBlockName, clang::DynTypedNode::create(*C)); + return true; + } + + return false; +} + +/// Matches a CompoundStmt which has two VarDecls +/// matching the inner matcher in the beginning. +AST_MATCHER_P2(CompoundStmt, hasFirstTwoVarDecl, + ast_matchers::internal::Matcher<VarDecl>, InnerMatcher1, + ast_matchers::internal::Matcher<VarDecl>, InnerMatcher2) { + const auto *I = Node.body_begin(); + if ((I) == Node.body_end()) + return false; + + return matchTwoVarDecl( + dyn_cast<DeclStmt>(*(I)), + ((I + 1) == Node.body_end() ? nullptr : dyn_cast<DeclStmt>(*(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<Expr>, InnerMatcher) { + if (const auto *CtorE = dyn_cast<CXXConstructExpr>(&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)))))); ---------------- flovent wrote:
Combining all the review, I am trying to make a `matchNVarDecl` and related content now, so we can match the exact number of `VarDecl` we want, for tuple-like, array, and structs, and get rid of the limit of `first` and `second`. It might be a big change to add support for them (check codes, test cases and docs), so should i do it in this PR, or open another one after this one is finished (limit it to pair types, more easier for review). For tuple-like class, i think it's a little different because we don't know this tuple's size in static analysis (We might can know size from `std::tuple`'s template argument size? But it may not applies to other tuple-like class.), so we will not know if user used all elements and whether they can be combined to structured binding. I think maybe we can only match `std::tie` case for tuple-like class. https://github.com/llvm/llvm-project/pull/158462 _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits