Skip to content

Commit 30678d7

Browse files
committed
[clang-tidy] Add support for Initialization Forwarding in Nested Objects within modernize-use-emplace
1 parent 5ac680c commit 30678d7

File tree

3 files changed

+119
-20
lines changed

3 files changed

+119
-20
lines changed

clang-tools-extra/clang-tidy/modernize/UseEmplaceCheck.cpp

Lines changed: 92 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -98,8 +98,8 @@ auto hasWantedType(llvm::ArrayRef<StringRef> TypeNames) {
9898

9999
// Matches member call expressions of the named method on the listed container
100100
// types.
101-
auto cxxMemberCallExprOnContainer(
102-
StringRef MethodName, llvm::ArrayRef<StringRef> ContainerNames) {
101+
auto cxxMemberCallExprOnContainer(StringRef MethodName,
102+
llvm::ArrayRef<StringRef> ContainerNames) {
103103
return cxxMemberCallExpr(
104104
hasDeclaration(functionDecl(hasName(MethodName))),
105105
on(hasTypeOrPointeeType(hasWantedType(ContainerNames))));
@@ -174,19 +174,19 @@ void UseEmplaceCheck::registerMatchers(MatchFinder *Finder) {
174174
// passed pointer because smart pointer won't be constructed
175175
// (and destructed) as in push_back case.
176176
auto IsCtorOfSmartPtr =
177-
hasDeclaration(cxxConstructorDecl(ofClass(hasAnyName(SmartPointers))));
177+
cxxConstructorDecl(ofClass(hasAnyName(SmartPointers)));
178178

179179
// Bitfields binds only to consts and emplace_back take it by universal ref.
180-
auto BitFieldAsArgument = hasAnyArgument(
181-
ignoringImplicit(memberExpr(hasDeclaration(fieldDecl(isBitField())))));
180+
auto BitFieldAsArgument =
181+
ignoringImplicit(memberExpr(hasDeclaration(fieldDecl(isBitField()))));
182182

183183
// Initializer list can't be passed to universal reference.
184-
auto InitializerListAsArgument = hasAnyArgument(
184+
auto InitializerListAsArgument =
185185
ignoringImplicit(allOf(cxxConstructExpr(isListInitialization()),
186-
unless(cxxTemporaryObjectExpr()))));
186+
unless(cxxTemporaryObjectExpr())));
187187

188188
// We could have leak of resource.
189-
auto NewExprAsArgument = hasAnyArgument(ignoringImplicit(cxxNewExpr()));
189+
auto NewExprAsArgument = ignoringImplicit(cxxNewExpr());
190190
// We would call another constructor.
191191
auto ConstructingDerived =
192192
hasParent(implicitCastExpr(hasCastKind(CastKind::CK_DerivedToBase)));
@@ -202,11 +202,26 @@ void UseEmplaceCheck::registerMatchers(MatchFinder *Finder) {
202202
// overloaded functions and template names.
203203
auto SoughtConstructExpr =
204204
cxxConstructExpr(
205-
unless(anyOf(IsCtorOfSmartPtr, HasInitList, BitFieldAsArgument,
206-
InitializerListAsArgument, NewExprAsArgument,
207-
ConstructingDerived, IsPrivateOrProtectedCtor)))
205+
unless(anyOf(hasDeclaration(IsCtorOfSmartPtr), HasInitList,
206+
hasAnyArgument(BitFieldAsArgument),
207+
hasAnyArgument(InitializerListAsArgument),
208+
hasAnyArgument(NewExprAsArgument), ConstructingDerived,
209+
IsPrivateOrProtectedCtor)))
208210
.bind("ctor");
209-
auto HasConstructExpr = has(ignoringImplicit(SoughtConstructExpr));
211+
212+
auto IsPrimitiveType = hasType(builtinType());
213+
214+
auto AggregateInitExpr =
215+
getLangOpts().CPlusPlus20
216+
? initListExpr(unless(anyOf(HasInitList, has(IsCtorOfSmartPtr),
217+
has(BitFieldAsArgument),
218+
has(InitializerListAsArgument),
219+
has(NewExprAsArgument), IsPrimitiveType)))
220+
.bind("agg_init")
221+
: unless(anything());
222+
223+
auto HasConstructExpr =
224+
has(ignoringImplicit(anyOf(SoughtConstructExpr, AggregateInitExpr)));
210225

211226
// allow for T{} to be replaced, even if no CTOR is declared
212227
auto HasConstructInitListExpr = has(initListExpr(
@@ -305,6 +320,38 @@ void UseEmplaceCheck::registerMatchers(MatchFinder *Finder) {
305320
this);
306321
}
307322

323+
const Expr *unwrapInnerExpression(const Expr *E) {
324+
325+
while (true) {
326+
if (!E)
327+
break;
328+
329+
if (llvm::isa<CXXConstructExpr>(E)) {
330+
return E;
331+
}
332+
333+
if (const auto *BindTemp = llvm::dyn_cast<CXXBindTemporaryExpr>(E)) {
334+
E = BindTemp->getSubExpr();
335+
continue;
336+
}
337+
338+
if (const auto *MaterialTemp =
339+
llvm::dyn_cast<MaterializeTemporaryExpr>(E)) {
340+
E = MaterialTemp->getSubExpr();
341+
continue;
342+
}
343+
344+
if (const auto *Cast = llvm::dyn_cast<ImplicitCastExpr>(E)) {
345+
E = Cast->getSubExpr();
346+
continue;
347+
}
348+
349+
break;
350+
}
351+
352+
return nullptr; // No relevant sub-expression found
353+
}
354+
308355
void UseEmplaceCheck::check(const MatchFinder::MatchResult &Result) {
309356
const auto *PushBackCall =
310357
Result.Nodes.getNodeAs<CXXMemberCallExpr>("push_back_call");
@@ -313,7 +360,8 @@ void UseEmplaceCheck::check(const MatchFinder::MatchResult &Result) {
313360
Result.Nodes.getNodeAs<CXXMemberCallExpr>("push_front_call");
314361
const auto *EmplacyCall =
315362
Result.Nodes.getNodeAs<CXXMemberCallExpr>("emplacy_call");
316-
const auto *CtorCall = Result.Nodes.getNodeAs<CXXConstructExpr>("ctor");
363+
auto *CtorCall = Result.Nodes.getNodeAs<CXXConstructExpr>("ctor");
364+
auto *AggInitCall = Result.Nodes.getNodeAs<InitListExpr>("agg_init");
317365
const auto *MakeCall = Result.Nodes.getNodeAs<CallExpr>("make");
318366
const auto *TemporaryExpr =
319367
Result.Nodes.getNodeAs<MaterializeTemporaryExpr>("temporary_expr");
@@ -332,19 +380,44 @@ void UseEmplaceCheck::check(const MatchFinder::MatchResult &Result) {
332380
}();
333381

334382
assert(Call && "No call matched");
335-
assert((CtorCall || MakeCall) && "No push_back parameter matched");
383+
assert((CtorCall || MakeCall || AggInitCall) &&
384+
"No push_back parameter matched");
336385

337386
if (IgnoreImplicitConstructors && CtorCall && CtorCall->getNumArgs() >= 1 &&
338387
CtorCall->getArg(0)->getSourceRange() == CtorCall->getSourceRange())
339388
return;
340389

390+
if (IgnoreImplicitConstructors && AggInitCall &&
391+
AggInitCall->getNumInits() >= 1 &&
392+
AggInitCall->getInit(0)->getSourceRange() ==
393+
AggInitCall->getSourceRange())
394+
return;
395+
396+
if (getLangOpts().LangStd >= LangStandard::lang_cxx20 && AggInitCall) {
397+
for (const auto *Init : AggInitCall->inits()) {
398+
if (const auto *InnermostInit = unwrapInnerExpression(Init)) {
399+
// consume all args if it's an empty constructor call so that we can ->
400+
// T{} -> emplace_back()
401+
if (const auto *CtorExpr =
402+
llvm::dyn_cast<CXXConstructExpr>(InnermostInit);
403+
CtorExpr && CtorExpr->getNumArgs() == 0) {
404+
405+
CtorCall = CtorExpr;
406+
AggInitCall = nullptr;
407+
break;
408+
}
409+
}
410+
}
411+
}
412+
341413
const auto FunctionNameSourceRange = CharSourceRange::getCharRange(
342414
Call->getExprLoc(), Call->getArg(0)->getExprLoc());
343415

344416
auto Diag =
345417
EmplacyCall
346418
? diag(TemporaryExpr ? TemporaryExpr->getBeginLoc()
347419
: CtorCall ? CtorCall->getBeginLoc()
420+
: AggInitCall ? AggInitCall->getBeginLoc()
348421
: MakeCall->getBeginLoc(),
349422
"unnecessary temporary object created while calling %0")
350423
: diag(Call->getExprLoc(), "use emplace%select{|_back|_front}0 "
@@ -376,9 +449,10 @@ void UseEmplaceCheck::check(const MatchFinder::MatchResult &Result) {
376449
}
377450

378451
const SourceRange CallParensRange =
379-
MakeCall ? SourceRange(MakeCall->getCallee()->getEndLoc(),
380-
MakeCall->getRParenLoc())
381-
: CtorCall->getParenOrBraceRange();
452+
MakeCall ? SourceRange(MakeCall->getCallee()->getEndLoc(),
453+
MakeCall->getRParenLoc())
454+
: CtorCall ? CtorCall->getParenOrBraceRange()
455+
: AggInitCall->getSourceRange();
382456

383457
// Finish if there is no explicit constructor call.
384458
if (CallParensRange.getBegin().isInvalid())
@@ -387,6 +461,7 @@ void UseEmplaceCheck::check(const MatchFinder::MatchResult &Result) {
387461
// FIXME: Will there ever be a CtorCall, if there is no TemporaryExpr?
388462
const SourceLocation ExprBegin = TemporaryExpr ? TemporaryExpr->getExprLoc()
389463
: CtorCall ? CtorCall->getExprLoc()
464+
: AggInitCall ? AggInitCall->getExprLoc()
390465
: MakeCall->getExprLoc();
391466

392467
// Range for constructor name and opening brace.

clang-tools-extra/docs/ReleaseNotes.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,10 @@ Changes in existing checks
171171
``constexpr`` and ``static``` values on member initialization and by detecting
172172
explicit casting of built-in types within member list initialization.
173173

174+
- Improved :doc:`modernize-use-emplace
175+
<clang-tidy/checks/modernize/use-emplace>` check by adding support for C++20
176+
Argument forwarding inside ``struct`` type objects.
177+
174178
- Improved :doc:`modernize-use-ranges
175179
<clang-tidy/checks/modernize/use-ranges>` check by updating suppress
176180
warnings logic for ``nullptr`` in ``std::find``.

clang-tools-extra/test/clang-tidy/checkers/modernize/use-emplace.cpp

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,12 @@
1-
// RUN: %check_clang_tidy %s modernize-use-emplace %t -- \
1+
// RUN: %check_clang_tidy %s -std=c++11 modernize-use-emplace %t -- \
2+
// RUN: -config="{CheckOptions: \
3+
// RUN: {modernize-use-emplace.ContainersWithPushBack: \
4+
// RUN: '::std::vector; ::std::list; ::std::deque; llvm::LikeASmallVector', \
5+
// RUN: modernize-use-emplace.TupleTypes: \
6+
// RUN: '::std::pair; std::tuple; ::test::Single', \
7+
// RUN: modernize-use-emplace.TupleMakeFunctions: \
8+
// RUN: '::std::make_pair; ::std::make_tuple; ::test::MakeSingle'}}"
9+
// RUN: %check_clang_tidy -std=c++20 %s modernize-use-emplace -check-suffixes=,CPP20 %t -- \
210
// RUN: -config="{CheckOptions: \
311
// RUN: {modernize-use-emplace.ContainersWithPushBack: \
412
// RUN: '::std::vector; ::std::list; ::std::deque; llvm::LikeASmallVector', \
@@ -1236,12 +1244,20 @@ void testBracedInitTemporaries() {
12361244
// CHECK-FIXES: v1.emplace_back();
12371245

12381246
// These should not be noticed or fixed; after the correction, the code won't
1239-
// compile.
1247+
// compile in version previous to C++20.
12401248
v1.push_back(NonTrivialNoCtor{""});
1249+
// CHECK-MESSAGES-CPP20: :[[@LINE-1]]:6: warning: use emplace_back instead of push_back
1250+
// CHECK-FIXES-CPP20: v1.emplace_back("");
12411251
v1.push_back({""});
1252+
// CHECK-MESSAGES-CPP20: :[[@LINE-1]]:6: warning: use emplace_back instead of push_back
1253+
// CHECK-FIXES-CPP20: v1.emplace_back("");
12421254
v1.push_back(NonTrivialNoCtor{InnerType{""}});
1255+
// CHECK-MESSAGES-CPP20: :[[@LINE-1]]:6: warning: use emplace_back instead of push_back
1256+
// CHECK-FIXES-CPP20: v1.emplace_back(InnerType{""});
12431257
v1.push_back({InnerType{""}});
1244-
v1.emplace_back(NonTrivialNoCtor{""});
1258+
// CHECK-MESSAGES-CPP20: :[[@LINE-1]]:6: warning: use emplace_back instead of push_back
1259+
// CHECK-FIXES-CPP20: v1.emplace_back(InnerType{""});
1260+
v1.emplace_back(NonTrivialNoCtor{""}); //TODO: Unsure if this should be covered in cxx20
12451261

12461262
std::vector<NonTrivialWithVector> v2;
12471263

@@ -1289,7 +1305,11 @@ void testBracedInitTemporaries() {
12891305
v2.push_back(NonTrivialWithVector{{0}});
12901306
v2.push_back({{0}});
12911307
v2.push_back(NonTrivialWithVector{std::vector<int>{0}});
1308+
// CHECK-MESSAGES-CPP20: :[[@LINE-1]]:6: warning: use emplace_back instead of push_back
1309+
// CHECK-FIXES-CPP20: v2.emplace_back(std::vector<int>{0});
12921310
v2.push_back({std::vector<int>{0}});
1311+
// CHECK-MESSAGES-CPP20: :[[@LINE-1]]:6: warning: use emplace_back instead of push_back
1312+
// CHECK-FIXES-CPP20: v2.emplace_back(std::vector<int>{0});
12931313
v2.emplace_back(NonTrivialWithVector{std::vector<int>{0}});
12941314

12951315
std::vector<NonTrivialWithCtor> v3;

0 commit comments

Comments
 (0)