clang-tools 23.0.0git
UseStructuredBindingCheck.cpp
Go to the documentation of this file.
1//===----------------------------------------------------------------------===//
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8
11#include "clang/Lex/Lexer.h"
12
13using namespace clang::ast_matchers;
14
15namespace clang::tidy::modernize {
16
17static constexpr StringRef PairDeclName = "PairVarD";
18static constexpr StringRef PairVarTypeName = "PairVarType";
19static constexpr StringRef FirstVarDeclName = "FirstVarDecl";
20static constexpr StringRef SecondVarDeclName = "SecondVarDecl";
21static constexpr StringRef BeginDeclStmtName = "BeginDeclStmt";
22static constexpr StringRef EndDeclStmtName = "EndDeclStmt";
23static constexpr StringRef FirstTypeName = "FirstType";
24static constexpr StringRef SecondTypeName = "SecondType";
25static constexpr StringRef ScopeBlockName = "ScopeBlock";
26static constexpr StringRef StdTieAssignStmtName = "StdTieAssign";
27static constexpr StringRef StdTieExprName = "StdTieExpr";
28static constexpr StringRef ForRangeStmtName = "ForRangeStmt";
29static constexpr StringRef InitExprName = "init_expr";
30
31/// Matches a sequence of VarDecls matching the inner matchers, starting from
32/// the \p Iter to \p EndIter and set bindings for the first DeclStmt and the
33/// last DeclStmt if matched.
34///
35/// \p Backwards indicates whether to match the VarDecls in reverse order.
36template <typename Iterator>
38 Iterator Iter, const Iterator &EndIter,
39 ArrayRef<ast_matchers::internal::Matcher<VarDecl>> InnerMatchers,
40 ast_matchers::internal::ASTMatchFinder *Finder,
41 ast_matchers::internal::BoundNodesTreeBuilder *Builder,
42 bool Backwards = false) {
43 const DeclStmt *BeginDS = nullptr;
44 const DeclStmt *EndDS = nullptr;
45 const size_t N = InnerMatchers.size();
46 size_t Count = 0;
47
48 auto Matches = [&](const Decl *VD) {
49 // We don't want redundant decls in DeclStmt.
50 if (Count == N)
51 return false;
52
53 if (const auto *Var = dyn_cast<VarDecl>(VD);
54 Var && InnerMatchers[Backwards ? N - Count - 1 : Count].matches(
55 *Var, Finder, Builder)) {
56 ++Count;
57 return true;
58 }
59
60 return false;
61 };
62
63 for (; Iter != EndIter; ++Iter) {
64 EndDS = dyn_cast<DeclStmt>(*Iter);
65 if (!EndDS)
66 break;
67
68 if (!BeginDS)
69 BeginDS = EndDS;
70
71 for (const auto *VD :
72 llvm::reverse_conditionally(EndDS->decls(), Backwards)) {
73 if (!Matches(VD))
74 return false;
75 }
76
77 // All the matchers is satisfied in those DeclStmts.
78 if (Count == N) {
79 if (Backwards)
80 std::swap(BeginDS, EndDS);
81 Builder->setBinding(BeginDeclStmtName, DynTypedNode::create(*BeginDS));
82 Builder->setBinding(EndDeclStmtName, DynTypedNode::create(*EndDS));
83 return true;
84 }
85 }
86
87 return false;
88}
89
90namespace {
91/// What qualifiers and specifiers are used to create structured binding
92/// declaration, it only supports the following four cases now.
93enum TransferType : uint8_t {
94 TT_ByVal,
95 TT_ByConstVal,
96 TT_ByRef,
97 TT_ByConstRef
98};
99
100/// Matches a Stmt whose parent is a CompoundStmt, and which is directly
101/// following two VarDecls matching the inner matcher.
102AST_MATCHER_P(Stmt, hasPreTwoVarDecl,
103 llvm::SmallVector<ast_matchers::internal::Matcher<VarDecl>>,
104 InnerMatchers) {
105 const DynTypedNodeList Parents = Finder->getASTContext().getParents(Node);
106 if (Parents.size() != 1)
107 return false;
108
109 const auto *C = Parents[0].get<CompoundStmt>();
110 if (!C)
111 return false;
112
113 const auto It = llvm::find(llvm::reverse(C->body()), &Node);
114 assert(It != C->body_rend() && "C is parent of Node");
115 return matchNVarDeclStartingWith(It + 1, C->body_rend(), InnerMatchers,
116 Finder, Builder, true);
117}
118
119/// Matches a Stmt whose parent is a CompoundStmt, and which is directly
120/// followed by two VarDecls matching the inner matcher.
121AST_MATCHER_P(Stmt, hasNextTwoVarDecl,
122 llvm::SmallVector<ast_matchers::internal::Matcher<VarDecl>>,
123 InnerMatchers) {
124 const DynTypedNodeList Parents = Finder->getASTContext().getParents(Node);
125 if (Parents.size() != 1)
126 return false;
127
128 const auto *C = Parents[0].get<CompoundStmt>();
129 if (!C)
130 return false;
131
132 const auto *It = llvm::find(C->body(), &Node);
133 assert(It != C->body_end() && "C is parent of Node");
134 return matchNVarDeclStartingWith(It + 1, C->body_end(), InnerMatchers, Finder,
135 Builder);
136}
137
138/// Matches a CompoundStmt which has two VarDecls matching the inner matcher in
139/// the beginning.
140AST_MATCHER_P(CompoundStmt, hasFirstTwoVarDecl,
141 llvm::SmallVector<ast_matchers::internal::Matcher<VarDecl>>,
142 InnerMatchers) {
143 return matchNVarDeclStartingWith(Node.body_begin(), Node.body_end(),
144 InnerMatchers, Finder, Builder);
145}
146
147/// It's not very common to have specifiers for variables used to decompose a
148/// pair, so we ignore these cases.
149AST_MATCHER(VarDecl, hasAnySpecifiersShouldBeIgnored) {
150 return Node.isStaticLocal() || Node.isConstexpr() || Node.hasAttrs() ||
151 Node.isInlineSpecified() || Node.getStorageClass() != SC_None ||
152 Node.getTSCSpec() != TSCS_unspecified;
153}
154
155// Ignore nodes inside macros.
157 AST_POLYMORPHIC_SUPPORTED_TYPES(Stmt, Decl)) {
158 return Node.getBeginLoc().isMacroID() || Node.getEndLoc().isMacroID();
159}
160
161AST_MATCHER_P(Expr, ignoringCopyCtorAndImplicitCast,
162 ast_matchers::internal::Matcher<Expr>, InnerMatcher) {
163 if (const auto *CtorE = dyn_cast<CXXConstructExpr>(&Node)) {
164 if (const CXXConstructorDecl *CtorD = CtorE->getConstructor();
165 CtorD->isCopyConstructor() && CtorE->getNumArgs() == 1) {
166 return InnerMatcher.matches(*CtorE->getArg(0)->IgnoreImpCasts(), Finder,
167 Builder);
168 }
169 }
170
171 return InnerMatcher.matches(*Node.IgnoreImpCasts(), Finder, Builder);
172}
173
174AST_MATCHER(CXXRecordDecl, isPairType) {
175 return llvm::all_of(Node.fields(), [](const FieldDecl *FD) {
176 return FD->getAccess() == AS_public &&
177 (FD->getName() == "first" || FD->getName() == "second");
178 });
179}
180
181AST_MATCHER(VarDecl, isDirectInitialization) {
182 return Node.getInitStyle() != VarDecl::InitializationStyle::CInit;
183}
184
185} // namespace
186
188 StringRef PairName, StringRef MemberName, StringRef TypeName,
189 StringRef BindingName,
190 const ast_matchers::internal::Matcher<VarDecl> &ExtraMatcher) {
191 return varDecl(ExtraMatcher,
192 hasInitializer(ignoringCopyCtorAndImplicitCast(memberExpr(
193 hasObjectExpression(ignoringImpCasts(declRefExpr(
194 to(equalsBoundNode(std::string(PairName)))))),
195 member(fieldDecl(hasName(MemberName),
196 hasType(qualType().bind(TypeName))))))))
197 .bind(BindingName);
198}
199
201 const ast_matchers::internal::Matcher<QualType> &TypeMatcher) {
202 return qualType(
203 anyOf(TypeMatcher, lValueReferenceType(pointee(TypeMatcher))));
204}
205
207 auto PairType = qualType(unless(isVolatileQualified()),
208 hasUnqualifiedDesugaredType(recordType(
209 hasDeclaration(cxxRecordDecl(isPairType())))));
210
211 auto UnlessShouldBeIgnored =
212 unless(anyOf(hasAnySpecifiersShouldBeIgnored(), isInMacro()));
213
214 auto VarInitWithFirstMember =
216 FirstVarDeclName, UnlessShouldBeIgnored);
217 auto VarInitWithSecondMember =
219 SecondVarDeclName, UnlessShouldBeIgnored);
220
221 auto RefToBindName = [&UnlessShouldBeIgnored](const StringRef &Name) {
222 return declRefExpr(to(varDecl(UnlessShouldBeIgnored).bind(Name)));
223 };
224
225 auto HasAnyLambdaCaptureThisVar =
226 [](const ast_matchers::internal::Matcher<VarDecl> &VDMatcher) {
227 return compoundStmt(hasDescendant(
228 lambdaExpr(hasAnyCapture(capturesVar(varDecl(VDMatcher))))));
229 };
230
231 // Captured structured bindings are a C++20 extension
232 auto UnlessFirstVarOrSecondVarIsCapturedByLambda =
233 getLangOpts().CPlusPlus20
234 ? compoundStmt()
235 : compoundStmt(unless(HasAnyLambdaCaptureThisVar(
236 anyOf(equalsBoundNode(std::string(FirstVarDeclName)),
237 equalsBoundNode(std::string(SecondVarDeclName))))));
238
239 // X x;
240 // Y y;
241 // std::tie(x, y) = ...;
242 Finder->addMatcher(
243 exprWithCleanups(
244 unless(isInMacro()),
245 has(cxxOperatorCallExpr(
246 hasOverloadedOperatorName("="),
247 hasLHS(ignoringImplicit(
248 callExpr(callee(functionDecl(isInStdNamespace(),
249 hasName("tie"))),
250 hasArgument(0, RefToBindName(FirstVarDeclName)),
251 hasArgument(1, RefToBindName(SecondVarDeclName)))
252 .bind(StdTieExprName))),
253 hasRHS(expr(hasType(PairType))))
254 .bind(StdTieAssignStmtName)),
255 hasPreTwoVarDecl(
256 llvm::SmallVector<ast_matchers::internal::Matcher<VarDecl>>{
257 varDecl(equalsBoundNode(std::string(FirstVarDeclName))),
258 varDecl(equalsBoundNode(std::string(SecondVarDeclName)))}),
259 hasParent(compoundStmt(UnlessFirstVarOrSecondVarIsCapturedByLambda)
260 .bind(ScopeBlockName))),
261 this);
262
263 // pair<X, Y> p = ...;
264 // X x = p.first;
265 // Y y = p.second;
266 Finder->addMatcher(
267 declStmt(
268 unless(isInMacro()),
269 hasSingleDecl(varDecl(UnlessShouldBeIgnored,
270 unless(isDirectInitialization()),
271 hasType(typeOrLValueReferenceTo(PairType).bind(
273 hasInitializer(ignoringCopyCtorAndImplicitCast(
274 expr().bind(InitExprName))))
275 .bind(PairDeclName)),
276 hasNextTwoVarDecl(
277 llvm::SmallVector<ast_matchers::internal::Matcher<VarDecl>>{
278 VarInitWithFirstMember, VarInitWithSecondMember}),
279 hasParent(compoundStmt(UnlessFirstVarOrSecondVarIsCapturedByLambda)
280 .bind(ScopeBlockName))),
281 this);
282
283 // for (pair<X, Y> p : map) {
284 // X x = p.first;
285 // Y y = p.second;
286 // }
287 Finder->addMatcher(
288 cxxForRangeStmt(
289 unless(isInMacro()),
290 hasLoopVariable(
291 varDecl(hasType(typeOrLValueReferenceTo(PairType).bind(
293 hasInitializer(ignoringCopyCtorAndImplicitCast(
294 expr().bind(InitExprName))))
295 .bind(PairDeclName)),
296 hasBody(
297 compoundStmt(
298 hasFirstTwoVarDecl(llvm::SmallVector<
299 ast_matchers::internal::Matcher<VarDecl>>{
300 VarInitWithFirstMember, VarInitWithSecondMember}),
301 UnlessFirstVarOrSecondVarIsCapturedByLambda)
302 .bind(ScopeBlockName)))
303 .bind(ForRangeStmtName),
304 this);
305}
306
307static std::optional<TransferType> getTransferType(const ASTContext &Ctx,
308 QualType ResultType,
309 QualType OriginType) {
310 ResultType = ResultType.getCanonicalType();
311 OriginType = OriginType.getCanonicalType();
312
313 if (ResultType == Ctx.getLValueReferenceType(OriginType.withConst()))
314 return TT_ByConstRef;
315
316 if (ResultType == Ctx.getLValueReferenceType(OriginType))
317 return TT_ByRef;
318
319 if (ResultType == OriginType.withConst())
320 return TT_ByConstVal;
321
322 if (ResultType == OriginType)
323 return TT_ByVal;
324
325 return std::nullopt;
326}
327
328void UseStructuredBindingCheck::check(const MatchFinder::MatchResult &Result) {
329 const auto *FirstVar = Result.Nodes.getNodeAs<VarDecl>(FirstVarDeclName);
330 const auto *SecondVar = Result.Nodes.getNodeAs<VarDecl>(SecondVarDeclName);
331
332 const auto *BeginDS = Result.Nodes.getNodeAs<DeclStmt>(BeginDeclStmtName);
333 const auto *EndDS = Result.Nodes.getNodeAs<DeclStmt>(EndDeclStmtName);
334 const auto *ScopeBlock = Result.Nodes.getNodeAs<CompoundStmt>(ScopeBlockName);
335
336 const auto *CFRS = Result.Nodes.getNodeAs<CXXForRangeStmt>(ForRangeStmtName);
337 auto DiagAndFix = [&BeginDS, &EndDS, &FirstVar, &SecondVar, &CFRS,
338 this](SourceLocation DiagLoc, SourceRange ReplaceRange,
339 TransferType TT = TT_ByVal) {
340 const auto Prefix = [&TT]() -> StringRef {
341 switch (TT) {
342 case TT_ByVal:
343 return "auto";
344 case TT_ByConstVal:
345 return "const auto";
346 case TT_ByRef:
347 return "auto&";
348 case TT_ByConstRef:
349 return "const auto&";
350 }
351 }();
352
353 const std::string ReplacementText =
354 (Twine(Prefix) + " [" + FirstVar->getNameAsString() + ", " +
355 SecondVar->getNameAsString() + "]" + (CFRS ? " :" : ""))
356 .str();
357 diag(DiagLoc, "use a structured binding to decompose a pair")
358 << FixItHint::CreateReplacement(ReplaceRange, ReplacementText)
359 << FixItHint::CreateRemoval(
360 SourceRange{BeginDS->getBeginLoc(), EndDS->getEndLoc()});
361 };
362
363 if (const auto *COCE =
364 Result.Nodes.getNodeAs<CXXOperatorCallExpr>(StdTieAssignStmtName)) {
365 DiagAndFix(COCE->getBeginLoc(),
366 Result.Nodes.getNodeAs<Expr>(StdTieExprName)->getSourceRange());
367 return;
368 }
369
370 // Check whether PairVar, FirstVar and SecondVar have the same transfer type,
371 // so they can be combined to structured binding.
372 const auto *PairVar = Result.Nodes.getNodeAs<VarDecl>(PairDeclName);
373
374 const std::optional<TransferType> PairCaptureType =
375 getTransferType(*Result.Context, PairVar->getType(),
376 Result.Nodes.getNodeAs<Expr>(InitExprName)->getType());
377 const std::optional<TransferType> FirstVarCaptureType =
378 getTransferType(*Result.Context, FirstVar->getType(),
379 *Result.Nodes.getNodeAs<QualType>(FirstTypeName));
380 const std::optional<TransferType> SecondVarCaptureType =
381 getTransferType(*Result.Context, SecondVar->getType(),
382 *Result.Nodes.getNodeAs<QualType>(SecondTypeName));
383 if (!PairCaptureType || !FirstVarCaptureType || !SecondVarCaptureType ||
384 *PairCaptureType != *FirstVarCaptureType ||
385 *FirstVarCaptureType != *SecondVarCaptureType)
386 return;
387
388 // Check PairVar is not used except for assignment members to firstVar and
389 // SecondVar.
390 if (auto AllRef = utils::decl_ref_expr::allDeclRefExprs(*PairVar, *ScopeBlock,
391 *Result.Context);
392 AllRef.size() != 2)
393 return;
394
395 DiagAndFix(PairVar->getBeginLoc(),
396 CFRS ? PairVar->getSourceRange()
397 : SourceRange(PairVar->getBeginLoc(),
398 Lexer::getLocForEndOfToken(
399 PairVar->getLocation(), 0,
400 Result.Context->getSourceManager(),
401 Result.Context->getLangOpts())),
402 *PairCaptureType);
403}
404
405} // namespace clang::tidy::modernize
void check(const ast_matchers::MatchFinder::MatchResult &Result) override
void registerMatchers(ast_matchers::MatchFinder *Finder) override
AST_POLYMORPHIC_MATCHER(isInAbseilFile, AST_POLYMORPHIC_SUPPORTED_TYPES(Decl, Stmt, TypeLoc, NestedNameSpecifierLoc))
Matches AST nodes that were found within Abseil files.
AST_MATCHER_P(Stmt, isStatementIdenticalToBoundNode, std::string, ID)
AST_MATCHER(BinaryOperator, isRelationalOperator)
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)
Matches a sequence of VarDecls matching the inner matchers, starting from the Iter to EndIter and set...
static constexpr StringRef PairVarTypeName
static constexpr StringRef InitExprName
static constexpr StringRef StdTieAssignStmtName
static std::optional< TransferType > getTransferType(const ASTContext &Ctx, QualType ResultType, QualType OriginType)
static constexpr StringRef SecondTypeName
static constexpr StringRef BeginDeclStmtName
static constexpr StringRef ForRangeStmtName
static constexpr StringRef SecondVarDeclName
static constexpr StringRef StdTieExprName
static auto getVarInitWithMemberMatcher(StringRef PairName, StringRef MemberName, StringRef TypeName, StringRef BindingName, const ast_matchers::internal::Matcher< VarDecl > &ExtraMatcher)
static constexpr StringRef EndDeclStmtName
static constexpr StringRef FirstVarDeclName
static auto typeOrLValueReferenceTo(const ast_matchers::internal::Matcher< QualType > &TypeMatcher)
static constexpr StringRef PairDeclName
static constexpr StringRef ScopeBlockName
static constexpr StringRef FirstTypeName
SmallPtrSet< const DeclRefExpr *, 16 > allDeclRefExprs(const VarDecl &VarDecl, const Stmt &Stmt, ASTContext &Context)
Returns set of all DeclRefExprs to VarDecl within Stmt.