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
406 lines
16 KiB
C++
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
|