Skip to content

Commit efde1cf

Browse files
committed
[clang-tidy] Add support for Initialization Forwarding in Nested Objects within modernize-use-emplace
1 parent 60b1d44 commit efde1cf

File tree

3 files changed

+129
-24
lines changed

3 files changed

+129
-24
lines changed

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

Lines changed: 86 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,34 @@ void UseEmplaceCheck::registerMatchers(MatchFinder *Finder) {
305320
this);
306321
}
307322

323+
static const CXXConstructExpr *unwrapToConstructorExpr(const Expr *E) {
324+
while (E) {
325+
if (const auto *ConstructorExpr = llvm::dyn_cast<CXXConstructExpr>(E)) {
326+
return ConstructorExpr;
327+
}
328+
329+
if (const auto *BindTemp = llvm::dyn_cast<CXXBindTemporaryExpr>(E)) {
330+
E = BindTemp->getSubExpr();
331+
continue;
332+
}
333+
334+
if (const auto *MaterialTemp =
335+
llvm::dyn_cast<MaterializeTemporaryExpr>(E)) {
336+
E = MaterialTemp->getSubExpr();
337+
continue;
338+
}
339+
340+
if (const auto *Cast = llvm::dyn_cast<ImplicitCastExpr>(E)) {
341+
E = Cast->getSubExpr();
342+
continue;
343+
}
344+
345+
break;
346+
}
347+
348+
return nullptr; // No relevant sub-expression found
349+
}
350+
308351
void UseEmplaceCheck::check(const MatchFinder::MatchResult &Result) {
309352
const auto *PushBackCall =
310353
Result.Nodes.getNodeAs<CXXMemberCallExpr>("push_back_call");
@@ -313,7 +356,8 @@ void UseEmplaceCheck::check(const MatchFinder::MatchResult &Result) {
313356
Result.Nodes.getNodeAs<CXXMemberCallExpr>("push_front_call");
314357
const auto *EmplacyCall =
315358
Result.Nodes.getNodeAs<CXXMemberCallExpr>("emplacy_call");
316-
const auto *CtorCall = Result.Nodes.getNodeAs<CXXConstructExpr>("ctor");
359+
auto *CtorCall = Result.Nodes.getNodeAs<CXXConstructExpr>("ctor");
360+
auto *AggInitCall = Result.Nodes.getNodeAs<InitListExpr>("agg_init");
317361
const auto *MakeCall = Result.Nodes.getNodeAs<CallExpr>("make");
318362
const auto *TemporaryExpr =
319363
Result.Nodes.getNodeAs<MaterializeTemporaryExpr>("temporary_expr");
@@ -332,19 +376,42 @@ void UseEmplaceCheck::check(const MatchFinder::MatchResult &Result) {
332376
}();
333377

334378
assert(Call && "No call matched");
335-
assert((CtorCall || MakeCall) && "No push_back parameter matched");
379+
assert((CtorCall || MakeCall || AggInitCall) &&
380+
"No push_back parameter matched");
336381

337382
if (IgnoreImplicitConstructors && CtorCall && CtorCall->getNumArgs() >= 1 &&
338383
CtorCall->getArg(0)->getSourceRange() == CtorCall->getSourceRange())
339384
return;
340385

386+
if (IgnoreImplicitConstructors && AggInitCall &&
387+
AggInitCall->getNumInits() >= 1 &&
388+
AggInitCall->getInit(0)->getSourceRange() ==
389+
AggInitCall->getSourceRange())
390+
return;
391+
392+
if (getLangOpts().LangStd >= LangStandard::lang_cxx20 && AggInitCall) {
393+
for (const auto *Init : AggInitCall->inits()) {
394+
if (const auto *InnerConstructorExpr = unwrapToConstructorExpr(Init)) {
395+
// consume all args if it's an empty constructor call so that we can ->
396+
// T{} -> emplace_back()
397+
if (InnerConstructorExpr && InnerConstructorExpr->getNumArgs() == 0) {
398+
399+
CtorCall = InnerConstructorExpr;
400+
AggInitCall = nullptr;
401+
break;
402+
}
403+
}
404+
}
405+
}
406+
341407
const auto FunctionNameSourceRange = CharSourceRange::getCharRange(
342408
Call->getExprLoc(), Call->getArg(0)->getExprLoc());
343409

344410
auto Diag =
345411
EmplacyCall
346412
? diag(TemporaryExpr ? TemporaryExpr->getBeginLoc()
347413
: CtorCall ? CtorCall->getBeginLoc()
414+
: AggInitCall ? AggInitCall->getBeginLoc()
348415
: MakeCall->getBeginLoc(),
349416
"unnecessary temporary object created while calling %0")
350417
: diag(Call->getExprLoc(), "use emplace%select{|_back|_front}0 "
@@ -376,9 +443,10 @@ void UseEmplaceCheck::check(const MatchFinder::MatchResult &Result) {
376443
}
377444

378445
const SourceRange CallParensRange =
379-
MakeCall ? SourceRange(MakeCall->getCallee()->getEndLoc(),
380-
MakeCall->getRParenLoc())
381-
: CtorCall->getParenOrBraceRange();
446+
MakeCall ? SourceRange(MakeCall->getCallee()->getEndLoc(),
447+
MakeCall->getRParenLoc())
448+
: CtorCall ? CtorCall->getParenOrBraceRange()
449+
: AggInitCall->getSourceRange();
382450

383451
// Finish if there is no explicit constructor call.
384452
if (CallParensRange.getBegin().isInvalid())
@@ -387,6 +455,7 @@ void UseEmplaceCheck::check(const MatchFinder::MatchResult &Result) {
387455
// FIXME: Will there ever be a CtorCall, if there is no TemporaryExpr?
388456
const SourceLocation ExprBegin = TemporaryExpr ? TemporaryExpr->getExprLoc()
389457
: CtorCall ? CtorCall->getExprLoc()
458+
: AggInitCall ? AggInitCall->getExprLoc()
390459
: MakeCall->getExprLoc();
391460

392461
// 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
@@ -182,6 +182,10 @@ Changes in existing checks
182182
``constexpr`` and ``static``` values on member initialization and by detecting
183183
explicit casting of built-in types within member list initialization.
184184

185+
- Improved :doc:`modernize-use-emplace
186+
<clang-tidy/checks/modernize/use-emplace>` check by adding support for C++20
187+
Argument forwarding inside ``struct`` type objects.
188+
185189
- Improved :doc:`modernize-use-ranges
186190
<clang-tidy/checks/modernize/use-ranges>` check by updating suppress
187191
warnings logic for ``nullptr`` in ``std::find``.

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

Lines changed: 39 additions & 7 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', \
@@ -1237,10 +1245,6 @@ void testBracedInitTemporaries() {
12371245

12381246
// These should not be noticed or fixed; after the correction, the code won't
12391247
// compile.
1240-
v1.push_back(NonTrivialNoCtor{""});
1241-
v1.push_back({""});
1242-
v1.push_back(NonTrivialNoCtor{InnerType{""}});
1243-
v1.push_back({InnerType{""}});
12441248
v1.emplace_back(NonTrivialNoCtor{""});
12451249

12461250
std::vector<NonTrivialWithVector> v2;
@@ -1288,8 +1292,6 @@ void testBracedInitTemporaries() {
12881292
// compile.
12891293
v2.push_back(NonTrivialWithVector{{0}});
12901294
v2.push_back({{0}});
1291-
v2.push_back(NonTrivialWithVector{std::vector<int>{0}});
1292-
v2.push_back({std::vector<int>{0}});
12931295
v2.emplace_back(NonTrivialWithVector{std::vector<int>{0}});
12941296

12951297
std::vector<NonTrivialWithCtor> v3;
@@ -1434,3 +1436,33 @@ void testWithPointerTypes() {
14341436
// CHECK-MESSAGES: :[[@LINE-1]]:15: warning: unnecessary temporary object created while calling emplace
14351437
// CHECK-FIXES: sp->emplace();
14361438
}
1439+
1440+
namespace GH1225740 {
1441+
1442+
void CXX20testBracedInitTemporaries(){
1443+
1444+
std::vector<NonTrivialNoCtor> v1;
1445+
1446+
v1.push_back(NonTrivialNoCtor{""});
1447+
// CHECK-MESSAGES-CPP20: :[[@LINE-1]]:6: warning: use emplace_back instead of push_back
1448+
// CHECK-FIXES-CPP20: v1.emplace_back("");
1449+
v1.push_back({""});
1450+
// CHECK-MESSAGES-CPP20: :[[@LINE-1]]:6: warning: use emplace_back instead of push_back
1451+
// CHECK-FIXES-CPP20: v1.emplace_back("");
1452+
v1.push_back(NonTrivialNoCtor{InnerType{""}});
1453+
// CHECK-MESSAGES-CPP20: :[[@LINE-1]]:6: warning: use emplace_back instead of push_back
1454+
// CHECK-FIXES-CPP20: v1.emplace_back(InnerType{""});
1455+
v1.push_back({InnerType{""}});
1456+
// CHECK-MESSAGES-CPP20: :[[@LINE-1]]:6: warning: use emplace_back instead of push_back
1457+
// CHECK-FIXES-CPP20: v1.emplace_back(InnerType{""});
1458+
1459+
std::vector<NonTrivialWithVector> v2;
1460+
1461+
v2.push_back(NonTrivialWithVector{std::vector<int>{0}});
1462+
// CHECK-MESSAGES-CPP20: :[[@LINE-1]]:6: warning: use emplace_back instead of push_back
1463+
// CHECK-FIXES-CPP20: v2.emplace_back(std::vector<int>{0});
1464+
v2.push_back({std::vector<int>{0}});
1465+
// CHECK-MESSAGES-CPP20: :[[@LINE-1]]:6: warning: use emplace_back instead of push_back
1466+
// CHECK-FIXES-CPP20: v2.emplace_back(std::vector<int>{0});
1467+
}
1468+
}

0 commit comments

Comments
 (0)