Files
TheRock/compiler/amd-llvm/clang-tools-extra/clang-tidy/modernize/UseStructuredBindingCheck.cpp
T
derek.holloway 7502bf64a4
CodeQL / Analyze (actions) (push) Canceled after 0s
CodeQL / Analyze (python) (push) Canceled after 0s
pre-commit / pre-commit (push) Canceled after 0s
Multi-Arch CI / setup (push) Canceled after 0s
Publish build_manylinux_x86_64 images / publish_build_manylinux_x86_64 (push) Canceled after 0s
Setup (Multi-Arch) / setup (push) Canceled after 0s
Multi-Arch CI / Linux::${{ fromJSON(needs.setup.outputs.linux_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubi10 image / publish_no_rocm_image_ubi10 (push) Canceled after 0s
Multi-Arch CI / Windows::${{ fromJSON(needs.setup.outputs.windows_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Multi-Arch CI / Manifest Diff (push) Canceled after 0s
TheRock Manifest Diff Report / Generate Manifest Diff Report (push) Canceled after 0s
Multi-Arch CI / CI Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_ocl_rt images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_rocgdb images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Unit Tests / Unit Tests :: ubuntu-24.04 (push) Canceled after 0s
Unit Tests / Unit Tests :: windows-2022 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_media (push) Canceled after 0s
Unit Tests / Unit Tests Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_openmpi (push) Canceled after 0s
Flatten subprojects into a single base monorepo
2026-09-02 16:34:47 -07:00

406 lines
16 KiB
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
//
//===----------------------------------------------------------------------===//
#include "UseStructuredBindingCheck.h"
#include "../utils/DeclRefExprUtils.h"
#include "clang/Lex/Lexer.h"
using namespace clang::ast_matchers;
namespace clang::tidy::modernize {
static constexpr StringRef PairDeclName = "PairVarD";
static constexpr StringRef PairVarTypeName = "PairVarType";
static constexpr StringRef FirstVarDeclName = "FirstVarDecl";
static constexpr StringRef SecondVarDeclName = "SecondVarDecl";
static constexpr StringRef BeginDeclStmtName = "BeginDeclStmt";
static constexpr StringRef EndDeclStmtName = "EndDeclStmt";
static constexpr StringRef FirstTypeName = "FirstType";
static constexpr StringRef SecondTypeName = "SecondType";
static constexpr StringRef ScopeBlockName = "ScopeBlock";
static constexpr StringRef StdTieAssignStmtName = "StdTieAssign";
static constexpr StringRef StdTieExprName = "StdTieExpr";
static constexpr StringRef ForRangeStmtName = "ForRangeStmt";
static constexpr StringRef InitExprName = "init_expr";
/// Matches a sequence of VarDecls matching the inner matchers, starting from
/// the \p Iter to \p EndIter and set bindings for the first DeclStmt and the
/// last DeclStmt if matched.
///
/// \p Backwards indicates whether to match the VarDecls in reverse order.
template <typename Iterator>
static bool matchNVarDeclStartingWith(
Iterator Iter, const Iterator &EndIter,
ArrayRef<ast_matchers::internal::Matcher<VarDecl>> InnerMatchers,
ast_matchers::internal::ASTMatchFinder *Finder,
ast_matchers::internal::BoundNodesTreeBuilder *Builder,
bool Backwards = false) {
const DeclStmt *BeginDS = nullptr;
const DeclStmt *EndDS = nullptr;
const size_t N = InnerMatchers.size();
size_t Count = 0;
auto Matches = [&](const Decl *VD) {
// We don't want redundant decls in DeclStmt.
if (Count == N)
return false;
if (const auto *Var = dyn_cast<VarDecl>(VD);
Var && InnerMatchers[Backwards ? N - Count - 1 : Count].matches(
*Var, Finder, Builder)) {
++Count;
return true;
}
return false;
};
for (; Iter != EndIter; ++Iter) {
EndDS = dyn_cast<DeclStmt>(*Iter);
if (!EndDS)
break;
if (!BeginDS)
BeginDS = EndDS;
for (const auto *VD :
llvm::reverse_conditionally(EndDS->decls(), Backwards)) {
if (!Matches(VD))
return false;
}
// All the matchers is satisfied in those DeclStmts.
if (Count == N) {
if (Backwards)
std::swap(BeginDS, EndDS);
Builder->setBinding(BeginDeclStmtName, DynTypedNode::create(*BeginDS));
Builder->setBinding(EndDeclStmtName, DynTypedNode::create(*EndDS));
return true;
}
}
return false;
}
namespace {
/// What qualifiers and specifiers are used to create structured binding
/// declaration, it only supports the following four cases now.
enum TransferType : uint8_t {
TT_ByVal,
TT_ByConstVal,
TT_ByRef,
TT_ByConstRef
};
/// Matches a Stmt whose parent is a CompoundStmt, and which is directly
/// following two VarDecls matching the inner matcher.
AST_MATCHER_P(Stmt, hasPreTwoVarDecl,
SmallVector<ast_matchers::internal::Matcher<VarDecl>>,
InnerMatchers) {
const DynTypedNodeList Parents = Finder->getASTContext().getParents(Node);
if (Parents.size() != 1)
return false;
const auto *C = Parents[0].get<CompoundStmt>();
if (!C)
return false;
const auto It = llvm::find(llvm::reverse(C->body()), &Node);
assert(It != C->body_rend() && "C is parent of Node");
return matchNVarDeclStartingWith(It + 1, C->body_rend(), InnerMatchers,
Finder, Builder, true);
}
/// Matches a Stmt whose parent is a CompoundStmt, and which is directly
/// followed by two VarDecls matching the inner matcher.
AST_MATCHER_P(Stmt, hasNextTwoVarDecl,
SmallVector<ast_matchers::internal::Matcher<VarDecl>>,
InnerMatchers) {
const DynTypedNodeList Parents = Finder->getASTContext().getParents(Node);
if (Parents.size() != 1)
return false;
const auto *C = Parents[0].get<CompoundStmt>();
if (!C)
return false;
const auto *It = llvm::find(C->body(), &Node);
assert(It != C->body_end() && "C is parent of Node");
return matchNVarDeclStartingWith(It + 1, C->body_end(), InnerMatchers, Finder,
Builder);
}
/// Matches a CompoundStmt which has two VarDecls matching the inner matcher in
/// the beginning.
AST_MATCHER_P(CompoundStmt, hasFirstTwoVarDecl,
SmallVector<ast_matchers::internal::Matcher<VarDecl>>,
InnerMatchers) {
return matchNVarDeclStartingWith(Node.body_begin(), Node.body_end(),
InnerMatchers, Finder, Builder);
}
/// It's not very common to have specifiers for variables used to decompose a
/// pair, so we ignore these cases.
AST_MATCHER(VarDecl, hasAnySpecifiersShouldBeIgnored) {
return Node.isStaticLocal() || Node.isConstexpr() || Node.hasAttrs() ||
Node.isInlineSpecified() || Node.getStorageClass() != SC_None ||
Node.getTSCSpec() != TSCS_unspecified;
}
// Ignore nodes inside macros.
AST_POLYMORPHIC_MATCHER(isInMacro,
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 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,
const ast_matchers::internal::Matcher<VarDecl> &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);
}
static auto typeOrLValueReferenceTo(
const ast_matchers::internal::Matcher<QualType> &TypeMatcher) {
return qualType(
anyOf(TypeMatcher, lValueReferenceType(pointee(TypeMatcher))));
}
void UseStructuredBindingCheck::registerMatchers(MatchFinder *Finder) {
auto PairType = qualType(unless(isVolatileQualified()),
hasUnqualifiedDesugaredType(recordType(
hasDeclaration(cxxRecordDecl(isPairType())))));
auto UnlessShouldBeIgnored =
unless(anyOf(hasAnySpecifiersShouldBeIgnored(), isInMacro()));
auto VarInitWithFirstMember =
getVarInitWithMemberMatcher(PairDeclName, "first", FirstTypeName,
FirstVarDeclName, UnlessShouldBeIgnored);
auto VarInitWithSecondMember =
getVarInitWithMemberMatcher(PairDeclName, "second", SecondTypeName,
SecondVarDeclName, UnlessShouldBeIgnored);
auto RefToBindName = [&UnlessShouldBeIgnored](const StringRef &Name) {
return declRefExpr(to(varDecl(UnlessShouldBeIgnored).bind(Name)));
};
auto HasAnyLambdaCaptureThisVar =
[](const ast_matchers::internal::Matcher<VarDecl> &VDMatcher) {
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(isInMacro()),
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(
SmallVector<ast_matchers::internal::Matcher<VarDecl>>{
varDecl(equalsBoundNode(std::string(FirstVarDeclName))),
varDecl(equalsBoundNode(std::string(SecondVarDeclName)))}),
hasParent(compoundStmt(UnlessFirstVarOrSecondVarIsCapturedByLambda)
.bind(ScopeBlockName))),
this);
// pair<X, Y> p = ...;
// X x = p.first;
// Y y = p.second;
Finder->addMatcher(
declStmt(
unless(isInMacro()),
hasSingleDecl(varDecl(UnlessShouldBeIgnored,
unless(isDirectInitialization()),
hasType(typeOrLValueReferenceTo(PairType).bind(
PairVarTypeName)),
hasInitializer(ignoringCopyCtorAndImplicitCast(
expr().bind(InitExprName))))
.bind(PairDeclName)),
hasNextTwoVarDecl(
SmallVector<ast_matchers::internal::Matcher<VarDecl>>{
VarInitWithFirstMember, VarInitWithSecondMember}),
hasParent(compoundStmt(UnlessFirstVarOrSecondVarIsCapturedByLambda)
.bind(ScopeBlockName))),
this);
// for (pair<X, Y> p : map) {
// X x = p.first;
// Y y = p.second;
// }
Finder->addMatcher(
cxxForRangeStmt(
unless(isInMacro()),
hasLoopVariable(
varDecl(hasType(typeOrLValueReferenceTo(PairType).bind(
PairVarTypeName)),
hasInitializer(ignoringCopyCtorAndImplicitCast(
expr().bind(InitExprName))))
.bind(PairDeclName)),
hasBody(compoundStmt(
hasFirstTwoVarDecl(
SmallVector<ast_matchers::internal::Matcher<VarDecl>>{
VarInitWithFirstMember, VarInitWithSecondMember}),
UnlessFirstVarOrSecondVarIsCapturedByLambda)
.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 *BeginDS = Result.Nodes.getNodeAs<DeclStmt>(BeginDeclStmtName);
const auto *EndDS = Result.Nodes.getNodeAs<DeclStmt>(EndDeclStmtName);
const auto *ScopeBlock = Result.Nodes.getNodeAs<CompoundStmt>(ScopeBlockName);
const auto *CFRS = Result.Nodes.getNodeAs<CXXForRangeStmt>(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&";
}
llvm_unreachable("Unhandled TransferType enum");
}();
const 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<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 std::optional<TransferType> PairCaptureType =
getTransferType(*Result.Context, PairVar->getType(),
Result.Nodes.getNodeAs<Expr>(InitExprName)->getType());
const std::optional<TransferType> FirstVarCaptureType =
getTransferType(*Result.Context, FirstVar->getType(),
*Result.Nodes.getNodeAs<QualType>(FirstTypeName));
const 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