1 | //===--- UseEmplaceCheck.cpp - clang-tidy----------------------------------===// |
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 | |
9 | #include "UseEmplaceCheck.h" |
10 | #include "../utils/OptionsUtils.h" |
11 | using namespace clang::ast_matchers; |
12 | |
13 | namespace clang::tidy::modernize { |
14 | |
15 | namespace { |
16 | AST_MATCHER_P(InitListExpr, initCountLeq, unsigned, N) { |
17 | return Node.getNumInits() <= N; |
18 | } |
19 | |
20 | // Identical to hasAnyName, except it does not take template specifiers into |
21 | // account. This is used to match the functions names as in |
22 | // DefaultEmplacyFunctions below without caring about the template types of the |
23 | // containers. |
24 | AST_MATCHER_P(NamedDecl, hasAnyNameIgnoringTemplates, std::vector<StringRef>, |
25 | Names) { |
26 | const std::string FullName = "::" + Node.getQualifiedNameAsString(); |
27 | |
28 | // This loop removes template specifiers by only keeping characters not within |
29 | // template brackets. We keep a depth count to handle nested templates. For |
30 | // example, it'll transform a::b<c<d>>::e<f> to simply a::b::e. |
31 | std::string FullNameTrimmed; |
32 | int Depth = 0; |
33 | for (const auto &Character : FullName) { |
34 | if (Character == '<') { |
35 | ++Depth; |
36 | } else if (Character == '>') { |
37 | --Depth; |
38 | } else if (Depth == 0) { |
39 | FullNameTrimmed.append(n: 1, c: Character); |
40 | } |
41 | } |
42 | |
43 | // This loop is taken from HasNameMatcher::matchesNodeFullSlow in |
44 | // clang/lib/ASTMatchers/ASTMatchersInternal.cpp and checks whether |
45 | // FullNameTrimmed matches any of the given Names. |
46 | const StringRef FullNameTrimmedRef = FullNameTrimmed; |
47 | for (const StringRef Pattern : Names) { |
48 | if (Pattern.starts_with(Prefix: "::" )) { |
49 | if (FullNameTrimmed == Pattern) |
50 | return true; |
51 | } else if (FullNameTrimmedRef.ends_with(Suffix: Pattern) && |
52 | FullNameTrimmedRef.drop_back(N: Pattern.size()).ends_with(Suffix: "::" )) { |
53 | return true; |
54 | } |
55 | } |
56 | |
57 | return false; |
58 | } |
59 | |
60 | // Checks if the given matcher is the last argument of the given CallExpr. |
61 | AST_MATCHER_P(CallExpr, hasLastArgument, |
62 | clang::ast_matchers::internal::Matcher<Expr>, InnerMatcher) { |
63 | if (Node.getNumArgs() == 0) |
64 | return false; |
65 | |
66 | return InnerMatcher.matches(Node: *Node.getArg(Arg: Node.getNumArgs() - 1), Finder, |
67 | Builder); |
68 | } |
69 | |
70 | // Checks if the given member call has the same number of arguments as the |
71 | // function had parameters defined (this is useful to check if there is only one |
72 | // variadic argument). |
73 | AST_MATCHER(CXXMemberCallExpr, ) { |
74 | if (const FunctionTemplateDecl *Primary = |
75 | Node.getMethodDecl()->getPrimaryTemplate()) |
76 | return Node.getNumArgs() == Primary->getTemplatedDecl()->getNumParams(); |
77 | |
78 | return Node.getNumArgs() == Node.getMethodDecl()->getNumParams(); |
79 | } |
80 | |
81 | AST_MATCHER(DeclRefExpr, hasExplicitTemplateArgs) { |
82 | return Node.hasExplicitTemplateArgs(); |
83 | } |
84 | |
85 | // Helper Matcher which applies the given QualType Matcher either directly or by |
86 | // resolving a pointer type to its pointee. Used to match v.push_back() as well |
87 | // as p->push_back(). |
88 | auto hasTypeOrPointeeType( |
89 | const ast_matchers::internal::Matcher<QualType> &TypeMatcher) { |
90 | return anyOf(hasType(InnerMatcher: TypeMatcher), |
91 | hasType(InnerMatcher: pointerType(pointee(TypeMatcher)))); |
92 | } |
93 | |
94 | // Matches if the node has canonical type matching any of the given names. |
95 | auto hasWantedType(llvm::ArrayRef<StringRef> TypeNames) { |
96 | return hasCanonicalType(InnerMatcher: hasDeclaration(InnerMatcher: cxxRecordDecl(hasAnyName(TypeNames)))); |
97 | } |
98 | |
99 | // Matches member call expressions of the named method on the listed container |
100 | // types. |
101 | auto cxxMemberCallExprOnContainer( |
102 | StringRef MethodName, llvm::ArrayRef<StringRef> ContainerNames) { |
103 | return cxxMemberCallExpr( |
104 | hasDeclaration(InnerMatcher: functionDecl(hasName(Name: MethodName))), |
105 | on(InnerMatcher: hasTypeOrPointeeType(TypeMatcher: hasWantedType(TypeNames: ContainerNames)))); |
106 | } |
107 | |
108 | const auto DefaultContainersWithPushBack = |
109 | "::std::vector; ::std::list; ::std::deque" ; |
110 | const auto DefaultContainersWithPush = |
111 | "::std::stack; ::std::queue; ::std::priority_queue" ; |
112 | const auto DefaultContainersWithPushFront = |
113 | "::std::forward_list; ::std::list; ::std::deque" ; |
114 | const auto DefaultSmartPointers = |
115 | "::std::shared_ptr; ::std::unique_ptr; ::std::auto_ptr; ::std::weak_ptr" ; |
116 | const auto DefaultTupleTypes = "::std::pair; ::std::tuple" ; |
117 | const auto DefaultTupleMakeFunctions = "::std::make_pair; ::std::make_tuple" ; |
118 | const auto DefaultEmplacyFunctions = |
119 | "vector::emplace_back; vector::emplace;" |
120 | "deque::emplace; deque::emplace_front; deque::emplace_back;" |
121 | "forward_list::emplace_after; forward_list::emplace_front;" |
122 | "list::emplace; list::emplace_back; list::emplace_front;" |
123 | "set::emplace; set::emplace_hint;" |
124 | "map::emplace; map::emplace_hint;" |
125 | "multiset::emplace; multiset::emplace_hint;" |
126 | "multimap::emplace; multimap::emplace_hint;" |
127 | "unordered_set::emplace; unordered_set::emplace_hint;" |
128 | "unordered_map::emplace; unordered_map::emplace_hint;" |
129 | "unordered_multiset::emplace; unordered_multiset::emplace_hint;" |
130 | "unordered_multimap::emplace; unordered_multimap::emplace_hint;" |
131 | "stack::emplace; queue::emplace; priority_queue::emplace" ; |
132 | } // namespace |
133 | |
134 | UseEmplaceCheck::UseEmplaceCheck(StringRef Name, ClangTidyContext *Context) |
135 | : ClangTidyCheck(Name, Context), IgnoreImplicitConstructors(Options.get( |
136 | LocalName: "IgnoreImplicitConstructors" , Default: false)), |
137 | ContainersWithPushBack(utils::options::parseStringList(Option: Options.get( |
138 | LocalName: "ContainersWithPushBack" , Default: DefaultContainersWithPushBack))), |
139 | ContainersWithPush(utils::options::parseStringList( |
140 | Option: Options.get(LocalName: "ContainersWithPush" , Default: DefaultContainersWithPush))), |
141 | ContainersWithPushFront(utils::options::parseStringList(Option: Options.get( |
142 | LocalName: "ContainersWithPushFront" , Default: DefaultContainersWithPushFront))), |
143 | SmartPointers(utils::options::parseStringList( |
144 | Option: Options.get(LocalName: "SmartPointers" , Default: DefaultSmartPointers))), |
145 | TupleTypes(utils::options::parseStringList( |
146 | Option: Options.get(LocalName: "TupleTypes" , Default: DefaultTupleTypes))), |
147 | TupleMakeFunctions(utils::options::parseStringList( |
148 | Option: Options.get(LocalName: "TupleMakeFunctions" , Default: DefaultTupleMakeFunctions))), |
149 | EmplacyFunctions(utils::options::parseStringList( |
150 | Option: Options.get(LocalName: "EmplacyFunctions" , Default: DefaultEmplacyFunctions))) {} |
151 | |
152 | void UseEmplaceCheck::registerMatchers(MatchFinder *Finder) { |
153 | // FIXME: Bunch of functionality that could be easily added: |
154 | // + add handling of `insert` for stl associative container, but be careful |
155 | // because this requires special treatment (it could cause performance |
156 | // regression) |
157 | // + match for emplace calls that should be replaced with insertion |
158 | auto CallPushBack = |
159 | cxxMemberCallExprOnContainer(MethodName: "push_back" , ContainerNames: ContainersWithPushBack); |
160 | auto CallPush = cxxMemberCallExprOnContainer(MethodName: "push" , ContainerNames: ContainersWithPush); |
161 | auto CallPushFront = |
162 | cxxMemberCallExprOnContainer(MethodName: "push_front" , ContainerNames: ContainersWithPushFront); |
163 | |
164 | auto CallEmplacy = cxxMemberCallExpr( |
165 | hasDeclaration( |
166 | InnerMatcher: functionDecl(hasAnyNameIgnoringTemplates(Names: EmplacyFunctions))), |
167 | on(InnerMatcher: hasTypeOrPointeeType(TypeMatcher: hasCanonicalType(InnerMatcher: hasDeclaration( |
168 | InnerMatcher: has(typedefNameDecl(hasName(Name: "value_type" ), |
169 | hasType(InnerMatcher: type(hasUnqualifiedDesugaredType( |
170 | InnerMatcher: recordType().bind(ID: "value_type" ))))))))))); |
171 | |
172 | // We can't replace push_backs of smart pointer because |
173 | // if emplacement fails (f.e. bad_alloc in vector) we will have leak of |
174 | // passed pointer because smart pointer won't be constructed |
175 | // (and destructed) as in push_back case. |
176 | auto IsCtorOfSmartPtr = |
177 | hasDeclaration(InnerMatcher: cxxConstructorDecl(ofClass(InnerMatcher: hasAnyName(SmartPointers)))); |
178 | |
179 | // Bitfields binds only to consts and emplace_back take it by universal ref. |
180 | auto BitFieldAsArgument = hasAnyArgument( |
181 | InnerMatcher: ignoringImplicit(InnerMatcher: memberExpr(hasDeclaration(InnerMatcher: fieldDecl(isBitField()))))); |
182 | |
183 | // Initializer list can't be passed to universal reference. |
184 | auto InitializerListAsArgument = hasAnyArgument( |
185 | InnerMatcher: ignoringImplicit(InnerMatcher: allOf(cxxConstructExpr(isListInitialization()), |
186 | unless(cxxTemporaryObjectExpr())))); |
187 | |
188 | // We could have leak of resource. |
189 | auto NewExprAsArgument = hasAnyArgument(InnerMatcher: ignoringImplicit(InnerMatcher: cxxNewExpr())); |
190 | // We would call another constructor. |
191 | auto ConstructingDerived = |
192 | hasParent(implicitCastExpr(hasCastKind(Kind: CastKind::CK_DerivedToBase))); |
193 | |
194 | // emplace_back can't access private or protected constructors. |
195 | auto IsPrivateOrProtectedCtor = |
196 | hasDeclaration(InnerMatcher: cxxConstructorDecl(anyOf(isPrivate(), isProtected()))); |
197 | |
198 | auto HasInitList = anyOf(has(ignoringImplicit(InnerMatcher: initListExpr())), |
199 | has(cxxStdInitializerListExpr())); |
200 | |
201 | // FIXME: Discard 0/NULL (as nullptr), static inline const data members, |
202 | // overloaded functions and template names. |
203 | auto SoughtConstructExpr = |
204 | cxxConstructExpr( |
205 | unless(anyOf(IsCtorOfSmartPtr, HasInitList, BitFieldAsArgument, |
206 | InitializerListAsArgument, NewExprAsArgument, |
207 | ConstructingDerived, IsPrivateOrProtectedCtor))) |
208 | .bind(ID: "ctor" ); |
209 | auto HasConstructExpr = has(ignoringImplicit(InnerMatcher: SoughtConstructExpr)); |
210 | |
211 | // allow for T{} to be replaced, even if no CTOR is declared |
212 | auto HasConstructInitListExpr = has(initListExpr( |
213 | initCountLeq(N: 1), anyOf(allOf(has(SoughtConstructExpr), |
214 | has(cxxConstructExpr(argumentCountIs(N: 0)))), |
215 | has(cxxBindTemporaryExpr( |
216 | has(SoughtConstructExpr), |
217 | has(cxxConstructExpr(argumentCountIs(N: 0)))))))); |
218 | auto HasBracedInitListExpr = |
219 | anyOf(has(cxxBindTemporaryExpr(HasConstructInitListExpr)), |
220 | HasConstructInitListExpr); |
221 | |
222 | auto MakeTuple = ignoringImplicit( |
223 | InnerMatcher: callExpr(callee(InnerMatcher: expr(ignoringImplicit(InnerMatcher: declRefExpr( |
224 | unless(hasExplicitTemplateArgs()), |
225 | to(InnerMatcher: functionDecl(hasAnyName(TupleMakeFunctions)))))))) |
226 | .bind(ID: "make" )); |
227 | |
228 | // make_something can return type convertible to container's element type. |
229 | // Allow the conversion only on containers of pairs. |
230 | auto MakeTupleCtor = ignoringImplicit(InnerMatcher: cxxConstructExpr( |
231 | has(materializeTemporaryExpr(MakeTuple)), |
232 | hasDeclaration(InnerMatcher: cxxConstructorDecl(ofClass(InnerMatcher: hasAnyName(TupleTypes)))))); |
233 | |
234 | auto SoughtParam = |
235 | materializeTemporaryExpr( |
236 | anyOf(has(MakeTuple), has(MakeTupleCtor), HasConstructExpr, |
237 | HasBracedInitListExpr, |
238 | has(cxxFunctionalCastExpr(HasConstructExpr)), |
239 | has(cxxFunctionalCastExpr(HasBracedInitListExpr)))) |
240 | .bind(ID: "temporary_expr" ); |
241 | |
242 | auto HasConstructExprWithValueTypeType = |
243 | has(ignoringImplicit(InnerMatcher: cxxConstructExpr( |
244 | SoughtConstructExpr, hasType(InnerMatcher: type(hasUnqualifiedDesugaredType( |
245 | InnerMatcher: type(equalsBoundNode(ID: "value_type" )))))))); |
246 | |
247 | auto HasBracedInitListWithValueTypeType = |
248 | anyOf(allOf(HasConstructInitListExpr, |
249 | has(initListExpr(hasType(InnerMatcher: type(hasUnqualifiedDesugaredType( |
250 | InnerMatcher: type(equalsBoundNode(ID: "value_type" )))))))), |
251 | has(cxxBindTemporaryExpr( |
252 | HasConstructInitListExpr, |
253 | has(initListExpr(hasType(InnerMatcher: type(hasUnqualifiedDesugaredType( |
254 | InnerMatcher: type(equalsBoundNode(ID: "value_type" )))))))))); |
255 | |
256 | auto HasConstructExprWithValueTypeTypeAsLastArgument = hasLastArgument( |
257 | InnerMatcher: materializeTemporaryExpr( |
258 | anyOf(HasConstructExprWithValueTypeType, |
259 | HasBracedInitListWithValueTypeType, |
260 | has(cxxFunctionalCastExpr(HasConstructExprWithValueTypeType)), |
261 | has(cxxFunctionalCastExpr(HasBracedInitListWithValueTypeType)))) |
262 | .bind(ID: "temporary_expr" )); |
263 | |
264 | Finder->addMatcher( |
265 | NodeMatch: traverse(TK: TK_AsIs, InnerMatcher: cxxMemberCallExpr(CallPushBack, has(SoughtParam), |
266 | unless(isInTemplateInstantiation())) |
267 | .bind(ID: "push_back_call" )), |
268 | Action: this); |
269 | |
270 | Finder->addMatcher( |
271 | NodeMatch: traverse(TK: TK_AsIs, InnerMatcher: cxxMemberCallExpr(CallPush, has(SoughtParam), |
272 | unless(isInTemplateInstantiation())) |
273 | .bind(ID: "push_call" )), |
274 | Action: this); |
275 | |
276 | Finder->addMatcher( |
277 | NodeMatch: traverse(TK: TK_AsIs, InnerMatcher: cxxMemberCallExpr(CallPushFront, has(SoughtParam), |
278 | unless(isInTemplateInstantiation())) |
279 | .bind(ID: "push_front_call" )), |
280 | Action: this); |
281 | |
282 | Finder->addMatcher( |
283 | NodeMatch: traverse(TK: TK_AsIs, |
284 | InnerMatcher: cxxMemberCallExpr( |
285 | CallEmplacy, HasConstructExprWithValueTypeTypeAsLastArgument, |
286 | hasSameNumArgsAsDeclNumParams(), |
287 | unless(isInTemplateInstantiation())) |
288 | .bind(ID: "emplacy_call" )), |
289 | Action: this); |
290 | |
291 | Finder->addMatcher( |
292 | NodeMatch: traverse( |
293 | TK: TK_AsIs, |
294 | InnerMatcher: cxxMemberCallExpr( |
295 | CallEmplacy, |
296 | on(InnerMatcher: hasType(InnerMatcher: cxxRecordDecl(has(typedefNameDecl( |
297 | hasName(Name: "value_type" ), |
298 | hasType(InnerMatcher: type( |
299 | hasUnqualifiedDesugaredType(InnerMatcher: recordType(hasDeclaration( |
300 | InnerMatcher: cxxRecordDecl(hasAnyName(SmallVector<StringRef, 2>( |
301 | TupleTypes.begin(), TupleTypes.end()))))))))))))), |
302 | has(MakeTuple), hasSameNumArgsAsDeclNumParams(), |
303 | unless(isInTemplateInstantiation())) |
304 | .bind(ID: "emplacy_call" )), |
305 | Action: this); |
306 | } |
307 | |
308 | void UseEmplaceCheck::check(const MatchFinder::MatchResult &Result) { |
309 | const auto *PushBackCall = |
310 | Result.Nodes.getNodeAs<CXXMemberCallExpr>(ID: "push_back_call" ); |
311 | const auto *PushCall = Result.Nodes.getNodeAs<CXXMemberCallExpr>(ID: "push_call" ); |
312 | const auto *PushFrontCall = |
313 | Result.Nodes.getNodeAs<CXXMemberCallExpr>(ID: "push_front_call" ); |
314 | const auto *EmplacyCall = |
315 | Result.Nodes.getNodeAs<CXXMemberCallExpr>(ID: "emplacy_call" ); |
316 | const auto *CtorCall = Result.Nodes.getNodeAs<CXXConstructExpr>(ID: "ctor" ); |
317 | const auto *MakeCall = Result.Nodes.getNodeAs<CallExpr>(ID: "make" ); |
318 | const auto *TemporaryExpr = |
319 | Result.Nodes.getNodeAs<MaterializeTemporaryExpr>(ID: "temporary_expr" ); |
320 | |
321 | const CXXMemberCallExpr *Call = [&]() { |
322 | if (PushBackCall) { |
323 | return PushBackCall; |
324 | } |
325 | if (PushCall) { |
326 | return PushCall; |
327 | } |
328 | if (PushFrontCall) { |
329 | return PushFrontCall; |
330 | } |
331 | return EmplacyCall; |
332 | }(); |
333 | |
334 | assert(Call && "No call matched" ); |
335 | assert((CtorCall || MakeCall) && "No push_back parameter matched" ); |
336 | |
337 | if (IgnoreImplicitConstructors && CtorCall && CtorCall->getNumArgs() >= 1 && |
338 | CtorCall->getArg(Arg: 0)->getSourceRange() == CtorCall->getSourceRange()) |
339 | return; |
340 | |
341 | const auto FunctionNameSourceRange = CharSourceRange::getCharRange( |
342 | Call->getExprLoc(), Call->getArg(0)->getExprLoc()); |
343 | |
344 | auto Diag = |
345 | EmplacyCall |
346 | ? diag(Loc: TemporaryExpr ? TemporaryExpr->getBeginLoc() |
347 | : CtorCall ? CtorCall->getBeginLoc() |
348 | : MakeCall->getBeginLoc(), |
349 | Description: "unnecessary temporary object created while calling %0" ) |
350 | : diag(Loc: Call->getExprLoc(), Description: "use emplace%select{|_back|_front}0 " |
351 | "instead of push%select{|_back|_front}0" ); |
352 | if (EmplacyCall) |
353 | Diag << Call->getMethodDecl()->getName(); |
354 | else if (PushCall) |
355 | Diag << 0; |
356 | else if (PushBackCall) |
357 | Diag << 1; |
358 | else |
359 | Diag << 2; |
360 | |
361 | if (FunctionNameSourceRange.getBegin().isMacroID()) |
362 | return; |
363 | |
364 | if (PushBackCall) { |
365 | const char *EmplacePrefix = MakeCall ? "emplace_back" : "emplace_back(" ; |
366 | Diag << FixItHint::CreateReplacement(FunctionNameSourceRange, |
367 | EmplacePrefix); |
368 | } else if (PushCall) { |
369 | const char *EmplacePrefix = MakeCall ? "emplace" : "emplace(" ; |
370 | Diag << FixItHint::CreateReplacement(FunctionNameSourceRange, |
371 | EmplacePrefix); |
372 | } else if (PushFrontCall) { |
373 | const char *EmplacePrefix = MakeCall ? "emplace_front" : "emplace_front(" ; |
374 | Diag << FixItHint::CreateReplacement(FunctionNameSourceRange, |
375 | EmplacePrefix); |
376 | } |
377 | |
378 | const SourceRange CallParensRange = |
379 | MakeCall ? SourceRange(MakeCall->getCallee()->getEndLoc(), |
380 | MakeCall->getRParenLoc()) |
381 | : CtorCall->getParenOrBraceRange(); |
382 | |
383 | // Finish if there is no explicit constructor call. |
384 | if (CallParensRange.getBegin().isInvalid()) |
385 | return; |
386 | |
387 | // FIXME: Will there ever be a CtorCall, if there is no TemporaryExpr? |
388 | const SourceLocation ExprBegin = TemporaryExpr ? TemporaryExpr->getExprLoc() |
389 | : CtorCall ? CtorCall->getExprLoc() |
390 | : MakeCall->getExprLoc(); |
391 | |
392 | // Range for constructor name and opening brace. |
393 | const auto ParamCallSourceRange = |
394 | CharSourceRange::getTokenRange(B: ExprBegin, E: CallParensRange.getBegin()); |
395 | |
396 | // Range for constructor closing brace and end of temporary expr. |
397 | const auto EndCallSourceRange = CharSourceRange::getTokenRange( |
398 | B: CallParensRange.getEnd(), |
399 | E: TemporaryExpr ? TemporaryExpr->getEndLoc() : CallParensRange.getEnd()); |
400 | |
401 | Diag << FixItHint::CreateRemoval(ParamCallSourceRange) |
402 | << FixItHint::CreateRemoval(EndCallSourceRange); |
403 | |
404 | if (MakeCall && EmplacyCall) { |
405 | // Remove extra left parenthesis |
406 | Diag << FixItHint::CreateRemoval( |
407 | CharSourceRange::getCharRange(MakeCall->getCallee()->getEndLoc(), |
408 | MakeCall->getArg(Arg: 0)->getBeginLoc())); |
409 | } |
410 | } |
411 | |
412 | void UseEmplaceCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { |
413 | Options.store(Options&: Opts, LocalName: "IgnoreImplicitConstructors" , Value: IgnoreImplicitConstructors); |
414 | Options.store(Options&: Opts, LocalName: "ContainersWithPushBack" , |
415 | Value: utils::options::serializeStringList(Strings: ContainersWithPushBack)); |
416 | Options.store(Options&: Opts, LocalName: "ContainersWithPush" , |
417 | Value: utils::options::serializeStringList(Strings: ContainersWithPush)); |
418 | Options.store(Options&: Opts, LocalName: "ContainersWithPushFront" , |
419 | Value: utils::options::serializeStringList(Strings: ContainersWithPushFront)); |
420 | Options.store(Options&: Opts, LocalName: "SmartPointers" , |
421 | Value: utils::options::serializeStringList(Strings: SmartPointers)); |
422 | Options.store(Options&: Opts, LocalName: "TupleTypes" , |
423 | Value: utils::options::serializeStringList(Strings: TupleTypes)); |
424 | Options.store(Options&: Opts, LocalName: "TupleMakeFunctions" , |
425 | Value: utils::options::serializeStringList(Strings: TupleMakeFunctions)); |
426 | Options.store(Options&: Opts, LocalName: "EmplacyFunctions" , |
427 | Value: utils::options::serializeStringList(Strings: EmplacyFunctions)); |
428 | } |
429 | |
430 | } // namespace clang::tidy::modernize |
431 | |