clang-tools 24.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 const 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 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 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 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 const auto PairType =
208 qualType(unless(isVolatileQualified()),
209 hasUnqualifiedDesugaredType(
210 recordType(hasDeclaration(cxxRecordDecl(isPairType())))));
211
212 auto UnlessShouldBeIgnored =
213 unless(anyOf(hasAnySpecifiersShouldBeIgnored(), isInMacro()));
214
215 const auto VarInitWithFirstMember =
217 FirstVarDeclName, UnlessShouldBeIgnored);
218 const auto VarInitWithSecondMember =
220 SecondVarDeclName, UnlessShouldBeIgnored);
221
222 const auto RefToBindName = [&UnlessShouldBeIgnored](const StringRef &Name) {
223 return declRefExpr(to(varDecl(UnlessShouldBeIgnored).bind(Name)));
224 };
225
226 const auto HasAnyLambdaCaptureThisVar =
227 [](const ast_matchers::internal::Matcher<VarDecl> &VDMatcher) {
228 return compoundStmt(hasDescendant(
229 lambdaExpr(hasAnyCapture(capturesVar(varDecl(VDMatcher))))));
230 };
231
232 // Captured structured bindings are a C++20 extension
233 const auto UnlessFirstVarOrSecondVarIsCapturedByLambda =
234 getLangOpts().CPlusPlus20
235 ? compoundStmt()
236 : compoundStmt(unless(HasAnyLambdaCaptureThisVar(
237 anyOf(equalsBoundNode(std::string(FirstVarDeclName)),
238 equalsBoundNode(std::string(SecondVarDeclName))))));
239
240 // X x;
241 // Y y;
242 // std::tie(x, y) = ...;
243 Finder->addMatcher(
244 exprWithCleanups(
245 unless(isInMacro()),
246 has(cxxOperatorCallExpr(
247 hasOverloadedOperatorName("="),
248 hasLHS(ignoringImplicit(
249 callExpr(callee(functionDecl(isInStdNamespace(),
250 hasName("tie"))),
251 hasArgument(0, RefToBindName(FirstVarDeclName)),
252 hasArgument(1, RefToBindName(SecondVarDeclName)))
253 .bind(StdTieExprName))),
254 hasRHS(expr(hasType(PairType))))
255 .bind(StdTieAssignStmtName)),
256 hasPreTwoVarDecl(
257 SmallVector<ast_matchers::internal::Matcher<VarDecl>>{
258 varDecl(equalsBoundNode(std::string(FirstVarDeclName))),
259 varDecl(equalsBoundNode(std::string(SecondVarDeclName)))}),
260 hasParent(compoundStmt(UnlessFirstVarOrSecondVarIsCapturedByLambda)
261 .bind(ScopeBlockName))),
262 this);
263
264 // pair<X, Y> p = ...;
265 // X x = p.first;
266 // Y y = p.second;
267 Finder->addMatcher(
268 declStmt(
269 unless(isInMacro()),
270 hasSingleDecl(varDecl(UnlessShouldBeIgnored,
271 unless(isDirectInitialization()),
272 hasType(typeOrLValueReferenceTo(PairType).bind(
274 hasInitializer(ignoringCopyCtorAndImplicitCast(
275 expr().bind(InitExprName))))
276 .bind(PairDeclName)),
277 hasNextTwoVarDecl(
278 SmallVector<ast_matchers::internal::Matcher<VarDecl>>{
279 VarInitWithFirstMember, VarInitWithSecondMember}),
280 hasParent(compoundStmt(UnlessFirstVarOrSecondVarIsCapturedByLambda)
281 .bind(ScopeBlockName))),
282 this);
283
284 // for (pair<X, Y> p : map) {
285 // X x = p.first;
286 // Y y = p.second;
287 // }
288 Finder->addMatcher(
289 cxxForRangeStmt(
290 unless(isInMacro()),
291 hasLoopVariable(
292 varDecl(hasType(typeOrLValueReferenceTo(PairType).bind(
294 hasInitializer(ignoringCopyCtorAndImplicitCast(
295 expr().bind(InitExprName))))
296 .bind(PairDeclName)),
297 hasBody(compoundStmt(
298 hasFirstTwoVarDecl(
299 SmallVector<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 const auto DiagAndFix = [&BeginDS, &EndDS, &FirstVar, &SecondVar, &CFRS,
338 this](SourceLocation DiagLoc,
339 SourceRange ReplaceRange,
340 TransferType TT = TT_ByVal) {
341 const auto Prefix = [&TT]() -> StringRef {
342 switch (TT) {
343 case TT_ByVal:
344 return "auto";
345 case TT_ByConstVal:
346 return "const auto";
347 case TT_ByRef:
348 return "auto&";
349 case TT_ByConstRef:
350 return "const auto&";
351 }
352 llvm_unreachable("Unhandled TransferType enum");
353 }();
354
355 const std::string ReplacementText =
356 (Twine(Prefix) + " [" + FirstVar->getNameAsString() + ", " +
357 SecondVar->getNameAsString() + "]" + (CFRS ? " :" : ""))
358 .str();
359 diag(DiagLoc, "use a structured binding to decompose a pair")
360 << FixItHint::CreateReplacement(ReplaceRange, ReplacementText)
361 << FixItHint::CreateRemoval(
362 SourceRange{BeginDS->getBeginLoc(), EndDS->getEndLoc()});
363 };
364
365 if (const auto *COCE =
366 Result.Nodes.getNodeAs<CXXOperatorCallExpr>(StdTieAssignStmtName)) {
367 DiagAndFix(COCE->getBeginLoc(),
368 Result.Nodes.getNodeAs<Expr>(StdTieExprName)->getSourceRange());
369 return;
370 }
371
372 // Check whether PairVar, FirstVar and SecondVar have the same transfer type,
373 // so they can be combined to structured binding.
374 const auto *PairVar = Result.Nodes.getNodeAs<VarDecl>(PairDeclName);
375
376 const std::optional<TransferType> PairCaptureType =
377 getTransferType(*Result.Context, PairVar->getType(),
378 Result.Nodes.getNodeAs<Expr>(InitExprName)->getType());
379 const std::optional<TransferType> FirstVarCaptureType =
380 getTransferType(*Result.Context, FirstVar->getType(),
381 *Result.Nodes.getNodeAs<QualType>(FirstTypeName));
382 const std::optional<TransferType> SecondVarCaptureType =
383 getTransferType(*Result.Context, SecondVar->getType(),
384 *Result.Nodes.getNodeAs<QualType>(SecondTypeName));
385 if (!PairCaptureType || !FirstVarCaptureType || !SecondVarCaptureType ||
386 *PairCaptureType != *FirstVarCaptureType ||
387 *FirstVarCaptureType != *SecondVarCaptureType)
388 return;
389
390 // Check PairVar is not used except for assignment members to firstVar and
391 // SecondVar.
392 if (const auto AllRef = utils::decl_ref_expr::allDeclRefExprs(
393 *PairVar, *ScopeBlock, *Result.Context);
394 AllRef.size() != 2)
395 return;
396
397 DiagAndFix(PairVar->getBeginLoc(),
398 CFRS ? PairVar->getSourceRange()
399 : SourceRange(PairVar->getBeginLoc(),
400 Lexer::getLocForEndOfToken(
401 PairVar->getLocation(), 0,
402 Result.Context->getSourceManager(),
403 Result.Context->getLangOpts())),
404 *PairCaptureType);
405}
406
407} // 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 ValueDecl &ValueDecl, const Stmt &Stmt, ASTContext &Context)
Returns set of all DeclRefExprs to ValueDecl within Stmt.