-
Notifications
You must be signed in to change notification settings - Fork 15.2k
[clang-tidy] Add new check modernize-use-structured-binding
#158462
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
@llvm/pr-subscribers-clang-tools-extra @llvm/pr-subscribers-clang-tidy Author: None (flovent) ChangesThis check detect code which can be transfered to c++17 structured binding, and provides fix-it. Limitations:
(1)
In theory this can be transfered to structured binding:
But it's needed to check whether
That's not checked too. It's coming from #138735, but leaves some unhanlded cases mentioned above. Patch is 33.29 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/158462.diff 11 Files Affected:
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<UseNoexceptCheck>("modernize-use-noexcept");
CheckFactories.registerCheck<UseNullptrCheck>("modernize-use-nullptr");
CheckFactories.registerCheck<UseOverrideCheck>("modernize-use-override");
+ CheckFactories.registerCheck<UseStructuredBindingCheck>(
+ "modernize-use-structured-binding");
CheckFactories.registerCheck<UseTrailingReturnTypeCheck>(
"modernize-use-trailing-return-type");
CheckFactories.registerCheck<UseTransparentFunctorsCheck>(
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<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) {
+ 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 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<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))))));
+
+ 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<X, Y> 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<X, Y> 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<TransferType> 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<VarDecl>(FirstVarDeclName);
+ const auto *SecondVar = Result.Nodes.getNodeAs<VarDecl>(SecondVarDeclName);
+
+ const auto *DS1 = Result.Nodes.getNodeAs<DeclStmt>(FirstDeclStmtName);
+ const auto *DS2 = Result.Nodes.getNodeAs<DeclStmt>(SecondDeclStmtName);
+ const auto *ScopeBlock = Result.Nodes.getNodeAs<CompoundStmt>(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<CXXForRangeStmt>(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<FixItHint> 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<CXXOperatorCallExpr>(StdTieAssignStmtName)) {
+ DiagAndFix(COCE->getBeginLoc(),
+ Result.Nodes.getNodeAs<Expr>(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<VarDecl>(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<Expr>("init_expr");
+
+ std::optional<TransferType> PairCaptureType =
+ getTransferType(*Result.Context, PairVar->getType(), InitE->getType());
+ std::optional<TransferType> FirstVarCaptureType =
+ getTransferType(*Result.Context, FirstVar->getType(),
+ *Result.Nodes.getNodeAs<QualType>(FirstTypeName));
+ std::optional<TransferType> SecondVarCaptureType =
+ getTransferType(*Result.Context, SecondVar->getType(),
+ *Result.Nodes.getNodeAs<QualType>(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<StringRef> 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...
[truncated]
|
It's tested in llvm codebase with It provides ~431 warnings and fixits with check option See apply modernize-structured-binding commit based on trunk ad9d551 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Very interesting check, thank you for working on it!
For now, looked only at tests/docs
clang-tools-extra/docs/clang-tidy/checks/modernize/use-structured-binding.rst
Outdated
Show resolved
Hide resolved
clang-tools-extra/docs/clang-tidy/checks/modernize/use-structured-binding.rst
Outdated
Show resolved
Hide resolved
clang-tools-extra/clang-tidy/modernize/UseStructuredBindingCheck.cpp
Outdated
Show resolved
Hide resolved
clang-tools-extra/docs/clang-tidy/checks/modernize/use-structured-binding.rst
Show resolved
Hide resolved
clang-tools-extra/docs/clang-tidy/checks/modernize/use-structured-binding.rst
Outdated
Show resolved
Hide resolved
clang-tools-extra/docs/clang-tidy/checks/modernize/use-structured-binding.rst
Outdated
Show resolved
Hide resolved
...a/test/clang-tidy/checkers/modernize/use-structured-binding-skip-lambda-capture-in-cxx17.cpp
Outdated
Show resolved
Hide resolved
clang-tools-extra/test/clang-tidy/checkers/modernize/use-structured-binding.cpp
Outdated
Show resolved
Hide resolved
clang-tools-extra/test/clang-tidy/checkers/modernize/use-structured-binding.cpp
Show resolved
Hide resolved
clang-tools-extra/test/clang-tidy/checkers/modernize/use-structured-binding.cpp
Outdated
Show resolved
Hide resolved
…red-binding.rst Co-authored-by: Baranov Victor <[email protected]>
…red-binding.rst Co-authored-by: Baranov Victor <[email protected]>
…red-binding.rst Co-authored-by: Baranov Victor <[email protected]>
…red-binding.rst Co-authored-by: Baranov Victor <[email protected]>
clang-tools-extra/clang-tidy/modernize/UseStructuredBindingCheck.h
Outdated
Show resolved
Hide resolved
clang-tools-extra/clang-tidy/modernize/UseStructuredBindingCheck.cpp
Outdated
Show resolved
Hide resolved
clang-tools-extra/clang-tidy/modernize/UseStructuredBindingCheck.cpp
Outdated
Show resolved
Hide resolved
clang-tools-extra/clang-tidy/modernize/UseStructuredBindingCheck.cpp
Outdated
Show resolved
Hide resolved
clang-tools-extra/clang-tidy/modernize/UseStructuredBindingCheck.cpp
Outdated
Show resolved
Hide resolved
clang-tools-extra/clang-tidy/modernize/UseStructuredBindingCheck.cpp
Outdated
Show resolved
Hide resolved
…ck.cpp Co-authored-by: EugeneZelenko <[email protected]>
…ck.h Co-authored-by: EugeneZelenko <[email protected]>
…ck.cpp Co-authored-by: EugeneZelenko <[email protected]>
…ck.cpp Co-authored-by: EugeneZelenko <[email protected]>
|
||
/// Matches a Stmt whose parent is a CompoundStmt, and which is directly | ||
/// following two VarDecls matching the inner matcher. | ||
AST_MATCHER_P(Stmt, hasPreTwoVarDecl, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see that we generally check for CompoundStmt
2 times: one in this matcher, other in registerMatchers
with compoundStmt
. I think we can restructure this a bit to check of CompoundStmt
only one time:
First make Finder->addMatcher(compoundStmt(declStmt(...), ...).bind(stmt))
this will avoid hasParent
in matchers which will make everything faster.
Then, in hasPreTwoVarDecl
pass ID (stmt
) of matched compoundStmt
and retrieve it with Nodes.getNodeAs<CompoundStmt>(ID)
instead of doing Finder->getASTContext().getParents(Node)
. You can see this trick in modernize\UseStartsEndsWithCheck.cpp
with NotLengthExprForStringNode
class.
It may not be a trivial rewrite, so I don't enforce it to do right away, but first part seem not very hard and Finder->addMatcher
would look more natural and faster
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
First make Finder->addMatcher(compoundStmt(declStmt(...), ...).bind(stmt)) this will avoid hasParent in matchers which will make everything faster.
Doesn't hasDescendant
needed here? It will match all child nodes of CompoundStmt
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can use has
to match only nodes are direct children of CompoundStmt
. declStmt(hasParent(compoundStmt()))
should have the same semantics as compoundStmt(has(declStmt()))
, but thinking about it again, maybe it would not match if there are multiple decompositions:
{
auto P = getPair<int, int>(); // match
int x = P.first;
int y = P.second;
// maybe match with `hasParent` but NOT with `has(declStmt())`
auto another_p = getPair<int, int>();
int another_x = another_p.first;
int another_y = another_p.second;
}
Could you add this test?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added in 6d44ea0
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I tried to get ScopeBlock directly in hasPreTwoVarDecl
after it's binded by current hasParent(compoundStmt
, but it may need to scan all the bindings node by calling Builder::visitMatches
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd remove my comments // match
and // maybe match with hasParent but NOT with has(declStmt())
. I just placed them for current discussion.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I tried to get ScopeBlock directly in
hasPreTwoVarDecl
after it's binded by currenthasParent(compoundStmt
, but it may need to scan all the bindings node by callingBuilder::visitMatches
.
Maybe I don't see bigger picture here, but it should only be one call to Builder->removeBindings
and then Nodes.getNode(this->BindingID)
inside callback, like in "cppcoreguidelines/MissingStdForwardCheck.cpp" hasSameNameAsBoundNode
.
Since we don't have any established benchmarks for checks anyway, I'd happy to accept current working matchers.
I personally like "Make It Work Make It Right Make It Fast" strategy, and if we'd be lacking performance in the future we could try rewriting matchers different way.
clang-tools-extra/clang-tidy/modernize/UseStructuredBindingCheck.cpp
Outdated
Show resolved
Hide resolved
clang-tools-extra/clang-tidy/modernize/UseStructuredBindingCheck.cpp
Outdated
Show resolved
Hide resolved
clang-tools-extra/clang-tidy/modernize/UseStructuredBindingCheck.cpp
Outdated
Show resolved
Hide resolved
clang-tools-extra/clang-tidy/modernize/UseStructuredBindingCheck.cpp
Outdated
Show resolved
Hide resolved
clang-tools-extra/test/clang-tidy/checkers/modernize/use-structured-binding.cpp
Show resolved
Hide resolved
I just find out we should do some special handing for direct inited pair type, like:
We will give a warning and fix-it, but obviously it will break compilation because we don't have pair type. Any ideas what we should do about them?
This pattern doesn't seems common in real world and the pair should not be created in the first place, so i prefer to ignore them and avoid further matching work. |
clang-tools-extra/clang-tidy/modernize/UseStructuredBindingCheck.cpp
Outdated
Show resolved
Hide resolved
clang-tools-extra/clang-tidy/modernize/UseStructuredBindingCheck.h
Outdated
Show resolved
Hide resolved
…ck.h Co-authored-by: Victor Chernyakin <[email protected]>
CI failure seems irrelevant. |
ping. And any opinions for #158462 (comment)? |
I'd go for what's easier for now (simply ignoring). AFAIK, this case simply don't match right now? Could you add these examples in tests P.S. I hope to do another round of review in upcoming days |
Sadly it matches, but it can be ignored without much work, just need to check
Thanks! |
@@ -0,0 +1,622 @@ | |||
// 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/ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: IIRC you can omit "ALL" suffix here and in first RUN write -check-suffix=,CPP20ORLATER
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wonderful! ALL
suffix is learned from other testfiles, that saves many times and makes it look simpler.
fbbbdd5
int x = P.first; | ||
int y = P.second; // REMOVE |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don't we need to write // REMOVE1
and // REMOVE2
to check that both lines are deleted?
Maybe we should use https://llvm.org/docs/CommandGuide/FileCheck.html#the-check-next-directive
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added in 8d3054c
Because of refatoring job of matchTwoVarDecl
, right now we remove continuous DeclStmt
s, so i only check for // REMOVE
in next line here.
TestClass& operator++(); | ||
TestClass(int x, int y) : a(x), b(y) {} | ||
}; | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could we add a test when we decompose pair with operator*()
, e.g.:
std::unordered_map::iterator it;
auto [key, value] = *it;
Good to also check iteration over containers (with involving iterators):
for (auto [k, v] : my_map);
It's a popular pattern IMO.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I add testcase for for (auto [k, v] : my_map);
in 4da01f9,
For the iterator case:
std::unordered_map::iterator it;
auto key = it->first;
auto value = it->second;
->
should be operator->
too? it's not supported because we check MemberExpr
to VarDecl
now and it might has side effects for operator->
(We don't know whether it just get it's member).
AST_MATCHER(CXXRecordDecl, isPairType) { | ||
return llvm::all_of(Node.fields(), [](const FieldDecl *FD) { | ||
return FD->getAccess() == AS_public && | ||
(FD->getName() == "first" || FD->getName() == "second"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we can allow fields to have different names as long as there are two of them?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We need those members' name or FieldDecl
bindings for further matching, so it's needed to setBinding for them.
It might be much work, i am planning to expand this after this PR is finished.
return Node.getBeginLoc().isMacroID() || Node.getEndLoc().isMacroID(); | ||
} | ||
|
||
AST_MATCHER_P(Expr, ignoringCopyCtorAndImplicitCast, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So we essentially do InnerMatcher(CtorE->getArg(0))
, could we make it into classname?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what does classname
meaning here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I meant name of the matcher.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am still confused, changing current name of matcher? how to make it into name.
return varDecl( | ||
ExtraMatcher, | ||
hasInitializer( | ||
ignoringImpCasts(ignoringCopyCtorAndImplicitCast(memberExpr( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we need one more ignoringImpCasts
here if ignoringCopyCtorAndImplicitCast
already handles it?
This check detect code which can be transfered to c++17 structured binding, and provides fix-it.
Limitations:
const
and&
since it's not very common:(1)
In theory this can be transfered to structured binding:
But it's needed to check whether
succeed
is changed after definition.(2)
That's not checked too.
It's coming from #138735, but leaves some unhanlded cases mentioned above.