Skip to content

Commit cff4446

Browse files
committed
[Clang] Implement P2718R0 "Lifetime extension in range-based for loops"
Implement P2718R0 "Lifetime extension in range-based for loops" (https://wg21.link/P2718R0) Differential Revision: https://reviews.llvm.org/D153701
1 parent faf555f commit cff4446

File tree

16 files changed

+913
-87
lines changed

16 files changed

+913
-87
lines changed

clang/docs/ReleaseNotes.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,9 @@ C++20 Feature Support
6565
C++23 Feature Support
6666
^^^^^^^^^^^^^^^^^^^^^
6767

68+
- Implemented `P2718R0: Lifetime extension in range-based for loops <https://wg21.link/P2718R0>`_. Also
69+
materialize temporary object which is a prvalue in discarded-value expression.
70+
6871
C++2c Feature Support
6972
^^^^^^^^^^^^^^^^^^^^^
7073

clang/include/clang/Parse/Parser.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2402,7 +2402,7 @@ class Parser : public CodeCompletionHandler {
24022402
struct ForRangeInit {
24032403
SourceLocation ColonLoc;
24042404
ExprResult RangeExpr;
2405-
2405+
SmallVector<MaterializeTemporaryExpr *, 8> LifetimeExtendTemps;
24062406
bool ParsedForRangeDecl() { return !ColonLoc.isInvalid(); }
24072407
};
24082408
struct ForRangeInfo : ForRangeInit {

clang/include/clang/Sema/Sema.h

Lines changed: 88 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1342,6 +1342,12 @@ class Sema final {
13421342
/// context not already known to be immediately invoked.
13431343
llvm::SmallPtrSet<DeclRefExpr *, 4> ReferenceToConsteval;
13441344

1345+
/// P2718R0 - Lifetime extension in range-based for loops.
1346+
/// MaterializeTemporaryExprs in for-range-init expressions which need to
1347+
/// extend lifetime. Add MaterializeTemporaryExpr* if the value of
1348+
/// InLifetimeExtendingContext is true.
1349+
SmallVector<MaterializeTemporaryExpr *, 8> ForRangeLifetimeExtendTemps;
1350+
13451351
/// \brief Describes whether we are in an expression constext which we have
13461352
/// to handle differently.
13471353
enum ExpressionKind {
@@ -1361,6 +1367,39 @@ class Sema final {
13611367
// VLAs).
13621368
bool InConditionallyConstantEvaluateContext = false;
13631369

1370+
/// Whether we are currently in a context in which all temporaries must be
1371+
/// lifetime-extended, even if they're not bound to a reference (for
1372+
/// example, in a for-range initializer).
1373+
bool InLifetimeExtendingContext = false;
1374+
1375+
/// Whether we are currently in a context in which all temporaries must be
1376+
/// materialized.
1377+
///
1378+
/// [class.temporary]/p2:
1379+
/// The materialization of a temporary object is generally delayed as long
1380+
/// as possible in order to avoid creating unnecessary temporary objects.
1381+
///
1382+
/// Temporary objects are materialized:
1383+
/// (2.1) when binding a reference to a prvalue ([dcl.init.ref],
1384+
/// [expr.type.conv], [expr.dynamic.cast], [expr.static.cast],
1385+
/// [expr.const.cast], [expr.cast]),
1386+
///
1387+
/// (2.2) when performing member access on a class prvalue ([expr.ref],
1388+
/// [expr.mptr.oper]),
1389+
///
1390+
/// (2.3) when performing an array-to-pointer conversion or subscripting
1391+
/// on an array prvalue ([conv.array], [expr.sub]),
1392+
///
1393+
/// (2.4) when initializing an object of type
1394+
/// std​::​initializer_list<T> from a braced-init-list
1395+
/// ([dcl.init.list]),
1396+
///
1397+
/// (2.5) for certain unevaluated operands ([expr.typeid], [expr.sizeof])
1398+
///
1399+
/// (2.6) when a prvalue that has type other than cv void appears as a
1400+
/// discarded-value expression ([expr.context]).
1401+
bool InMaterializeTemporaryObjectContext = false;
1402+
13641403
// When evaluating immediate functions in the initializer of a default
13651404
// argument or default member initializer, this is the declaration whose
13661405
// default initializer is being evaluated and the location of the call
@@ -5253,22 +5292,17 @@ class Sema final {
52535292
BFRK_Check
52545293
};
52555294

5256-
StmtResult ActOnCXXForRangeStmt(Scope *S, SourceLocation ForLoc,
5257-
SourceLocation CoawaitLoc,
5258-
Stmt *InitStmt,
5259-
Stmt *LoopVar,
5260-
SourceLocation ColonLoc, Expr *Collection,
5261-
SourceLocation RParenLoc,
5262-
BuildForRangeKind Kind);
5263-
StmtResult BuildCXXForRangeStmt(SourceLocation ForLoc,
5264-
SourceLocation CoawaitLoc,
5265-
Stmt *InitStmt,
5266-
SourceLocation ColonLoc,
5267-
Stmt *RangeDecl, Stmt *Begin, Stmt *End,
5268-
Expr *Cond, Expr *Inc,
5269-
Stmt *LoopVarDecl,
5270-
SourceLocation RParenLoc,
5271-
BuildForRangeKind Kind);
5295+
StmtResult ActOnCXXForRangeStmt(
5296+
Scope *S, SourceLocation ForLoc, SourceLocation CoawaitLoc,
5297+
Stmt *InitStmt, Stmt *LoopVar, SourceLocation ColonLoc, Expr *Collection,
5298+
SourceLocation RParenLoc, BuildForRangeKind Kind,
5299+
ArrayRef<MaterializeTemporaryExpr *> LifetimeExtendTemps = {});
5300+
StmtResult BuildCXXForRangeStmt(
5301+
SourceLocation ForLoc, SourceLocation CoawaitLoc, Stmt *InitStmt,
5302+
SourceLocation ColonLoc, Stmt *RangeDecl, Stmt *Begin, Stmt *End,
5303+
Expr *Cond, Expr *Inc, Stmt *LoopVarDecl, SourceLocation RParenLoc,
5304+
BuildForRangeKind Kind,
5305+
ArrayRef<MaterializeTemporaryExpr *> LifetimeExtendTemps = {});
52725306
StmtResult FinishCXXForRangeStmt(Stmt *ForRange, Stmt *Body);
52735307

52745308
StmtResult ActOnGotoStmt(SourceLocation GotoLoc,
@@ -10010,6 +10044,18 @@ class Sema final {
1001010044
return currentEvaluationContext().isImmediateFunctionContext();
1001110045
}
1001210046

10047+
bool isInLifetimeExtendingContext() const {
10048+
assert(!ExprEvalContexts.empty() &&
10049+
"Must be in an expression evaluation context");
10050+
return ExprEvalContexts.back().InLifetimeExtendingContext;
10051+
}
10052+
10053+
bool isInMaterializeTemporaryObjectContext() const {
10054+
assert(!ExprEvalContexts.empty() &&
10055+
"Must be in an expression evaluation context");
10056+
return ExprEvalContexts.back().InMaterializeTemporaryObjectContext;
10057+
}
10058+
1001310059
bool isCheckingDefaultArgumentOrInitializer() const {
1001410060
const ExpressionEvaluationContextRecord &Ctx = currentEvaluationContext();
1001510061
return (Ctx.Context ==
@@ -10049,6 +10095,32 @@ class Sema final {
1004910095
return Res;
1005010096
}
1005110097

10098+
/// keepInLifetimeExtendingContext - Pull down InLifetimeExtendingContext
10099+
/// flag from previous context.
10100+
void keepInLifetimeExtendingContext() {
10101+
if (ExprEvalContexts.size() > 2 &&
10102+
ExprEvalContexts[ExprEvalContexts.size() - 2]
10103+
.InLifetimeExtendingContext) {
10104+
auto &LastRecord = ExprEvalContexts.back();
10105+
auto &PrevRecord = ExprEvalContexts[ExprEvalContexts.size() - 2];
10106+
LastRecord.InLifetimeExtendingContext =
10107+
PrevRecord.InLifetimeExtendingContext;
10108+
}
10109+
}
10110+
10111+
/// keepInMaterializeTemporaryObjectContext - Pull down
10112+
/// InMaterializeTemporaryObjectContext flag from previous context.
10113+
void keepInMaterializeTemporaryObjectContext() {
10114+
if (ExprEvalContexts.size() > 2 &&
10115+
ExprEvalContexts[ExprEvalContexts.size() - 2]
10116+
.InMaterializeTemporaryObjectContext) {
10117+
auto &LastRecord = ExprEvalContexts.back();
10118+
auto &PrevRecord = ExprEvalContexts[ExprEvalContexts.size() - 2];
10119+
LastRecord.InMaterializeTemporaryObjectContext =
10120+
PrevRecord.InMaterializeTemporaryObjectContext;
10121+
}
10122+
}
10123+
1005210124
/// RAII class used to determine whether SFINAE has
1005310125
/// trapped any errors that occur during template argument
1005410126
/// deduction.

clang/lib/Frontend/InitPreprocessor.cpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -643,7 +643,9 @@ static void InitializeCPlusPlusFeatureTestMacros(const LangOptions &LangOpts,
643643
: "200704");
644644
Builder.defineMacro("__cpp_constexpr_in_decltype", "201711L");
645645
Builder.defineMacro("__cpp_range_based_for",
646-
LangOpts.CPlusPlus17 ? "201603L" : "200907");
646+
LangOpts.CPlusPlus23 ? "202211L"
647+
: LangOpts.CPlusPlus17 ? "201603L"
648+
: "200907");
647649
Builder.defineMacro("__cpp_static_assert", LangOpts.CPlusPlus26 ? "202306L"
648650
: LangOpts.CPlusPlus17
649651
? "201411L"

clang/lib/Parse/ParseDecl.cpp

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2312,12 +2312,38 @@ Parser::DeclGroupPtrTy Parser::ParseDeclGroup(ParsingDeclSpec &DS,
23122312
bool IsForRangeLoop = false;
23132313
if (TryConsumeToken(tok::colon, FRI->ColonLoc)) {
23142314
IsForRangeLoop = true;
2315+
EnterExpressionEvaluationContext ForRangeInitContext(
2316+
Actions, Sema::ExpressionEvaluationContext::PotentiallyEvaluated,
2317+
/*LambdaContextDecl=*/nullptr,
2318+
Sema::ExpressionEvaluationContextRecord::EK_Other,
2319+
getLangOpts().CPlusPlus23);
2320+
2321+
// P2718R0 - Lifetime extension in range-based for loops.
2322+
if (getLangOpts().CPlusPlus23) {
2323+
auto &LastRecord = Actions.ExprEvalContexts.back();
2324+
LastRecord.InLifetimeExtendingContext = true;
2325+
2326+
// Materialize non-`cv void` prvalue temporaries in discarded
2327+
// expressions. These materialized temporaries may be lifetime-extented.
2328+
LastRecord.InMaterializeTemporaryObjectContext = true;
2329+
}
2330+
23152331
if (getLangOpts().OpenMP)
23162332
Actions.startOpenMPCXXRangeFor();
23172333
if (Tok.is(tok::l_brace))
23182334
FRI->RangeExpr = ParseBraceInitializer();
23192335
else
23202336
FRI->RangeExpr = ParseExpression();
2337+
2338+
// Before c++23, ForRangeLifetimeExtendTemps should be empty.
2339+
assert(
2340+
getLangOpts().CPlusPlus23 ||
2341+
Actions.ExprEvalContexts.back().ForRangeLifetimeExtendTemps.empty());
2342+
2343+
// Move the collected materialized temporaries into ForRangeInit before
2344+
// ForRangeInitContext exit.
2345+
FRI->LifetimeExtendTemps = std::move(
2346+
Actions.ExprEvalContexts.back().ForRangeLifetimeExtendTemps);
23212347
}
23222348

23232349
Decl *ThisDecl = Actions.ActOnDeclarator(getCurScope(), D);

clang/lib/Parse/ParseStmt.cpp

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2288,11 +2288,11 @@ StmtResult Parser::ParseForStatement(SourceLocation *TrailingElseLoc) {
22882288
ForRangeStmt = Actions.ActOnCXXForRangeStmt(
22892289
getCurScope(), ForLoc, CoawaitLoc, FirstPart.get(),
22902290
ForRangeInfo.LoopVar.get(), ForRangeInfo.ColonLoc, CorrectedRange.get(),
2291-
T.getCloseLocation(), Sema::BFRK_Build);
2292-
2293-
// Similarly, we need to do the semantic analysis for a for-range
2294-
// statement immediately in order to close over temporaries correctly.
2291+
T.getCloseLocation(), Sema::BFRK_Build,
2292+
ForRangeInfo.LifetimeExtendTemps);
22952293
} else if (ForEach) {
2294+
// Similarly, we need to do the semantic analysis for a for-range
2295+
// statement immediately in order to close over temporaries correctly.
22962296
ForEachStmt = Actions.ActOnObjCForCollectionStmt(ForLoc,
22972297
FirstPart.get(),
22982298
Collection.get(),

clang/lib/Sema/SemaExpr.cpp

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6259,7 +6259,7 @@ ExprResult Sema::BuildCXXDefaultArgExpr(SourceLocation CallLoc,
62596259
assert(Param->hasDefaultArg() && "can't build nonexistent default arg");
62606260

62616261
bool NestedDefaultChecking = isCheckingDefaultArgumentOrInitializer();
6262-
6262+
bool InLifetimeExtendingContext = isInLifetimeExtendingContext();
62636263
std::optional<ExpressionEvaluationContextRecord::InitializationContext>
62646264
InitializationContext =
62656265
OutermostDeclarationWithDelayedImmediateInvocations();
@@ -6292,9 +6292,17 @@ ExprResult Sema::BuildCXXDefaultArgExpr(SourceLocation CallLoc,
62926292
ImmediateCallVisitor V(getASTContext());
62936293
if (!NestedDefaultChecking)
62946294
V.TraverseDecl(Param);
6295-
if (V.HasImmediateCalls) {
6296-
ExprEvalContexts.back().DelayedDefaultInitializationContext = {
6297-
CallLoc, Param, CurContext};
6295+
6296+
// Rewrite the call argument that was created from the corresponding
6297+
// parameter's default argument.
6298+
if (V.HasImmediateCalls || InLifetimeExtendingContext) {
6299+
if (V.HasImmediateCalls)
6300+
ExprEvalContexts.back().DelayedDefaultInitializationContext = {
6301+
CallLoc, Param, CurContext};
6302+
// Pass down lifetime extending flag, and collect temporaries in
6303+
// CreateMaterializeTemporaryExpr when we rewrite the call argument.
6304+
keepInLifetimeExtendingContext();
6305+
keepInMaterializeTemporaryObjectContext();
62986306
EnsureImmediateInvocationInDefaultArgs Immediate(*this);
62996307
ExprResult Res;
63006308
runWithSufficientStackSpace(CallLoc, [&] {
@@ -6340,7 +6348,7 @@ ExprResult Sema::BuildCXXDefaultInitExpr(SourceLocation Loc, FieldDecl *Field) {
63406348
Expr *Init = nullptr;
63416349

63426350
bool NestedDefaultChecking = isCheckingDefaultArgumentOrInitializer();
6343-
6351+
bool InLifetimeExtendingContext = isInLifetimeExtendingContext();
63446352
EnterExpressionEvaluationContext EvalContext(
63456353
*this, ExpressionEvaluationContext::PotentiallyEvaluated, Field);
63466354

@@ -6375,12 +6383,16 @@ ExprResult Sema::BuildCXXDefaultInitExpr(SourceLocation Loc, FieldDecl *Field) {
63756383
ImmediateCallVisitor V(getASTContext());
63766384
if (!NestedDefaultChecking)
63776385
V.TraverseDecl(Field);
6378-
if (V.HasImmediateCalls) {
6386+
if (V.HasImmediateCalls || InLifetimeExtendingContext) {
63796387
ExprEvalContexts.back().DelayedDefaultInitializationContext = {Loc, Field,
63806388
CurContext};
63816389
ExprEvalContexts.back().IsCurrentlyCheckingDefaultArgumentOrInitializer =
63826390
NestedDefaultChecking;
63836391

6392+
// Pass down lifetime extending flag, and collect temporaries in
6393+
// CreateMaterializeTemporaryExpr when we rewrite the call argument.
6394+
keepInLifetimeExtendingContext();
6395+
keepInMaterializeTemporaryObjectContext();
63846396
EnsureImmediateInvocationInDefaultArgs Immediate(*this);
63856397
ExprResult Res;
63866398
runWithSufficientStackSpace(Loc, [&] {
@@ -18655,6 +18667,16 @@ void Sema::PopExpressionEvaluationContext() {
1865518667
}
1865618668
}
1865718669

18670+
// Append the collected materialized temporaries into previous context before
18671+
// exit if the previous also is a lifetime extending context.
18672+
auto &PrevRecord = ExprEvalContexts[ExprEvalContexts.size() - 2];
18673+
if (getLangOpts().CPlusPlus23 && isInLifetimeExtendingContext() &&
18674+
PrevRecord.InLifetimeExtendingContext && !ExprEvalContexts.empty()) {
18675+
auto &PrevRecord = ExprEvalContexts[ExprEvalContexts.size() - 2];
18676+
PrevRecord.ForRangeLifetimeExtendTemps.append(
18677+
Rec.ForRangeLifetimeExtendTemps);
18678+
}
18679+
1865818680
WarnOnPendingNoDerefs(Rec);
1865918681
HandleImmediateInvocations(*this, Rec);
1866018682

clang/lib/Sema/SemaExprCXX.cpp

Lines changed: 26 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -8222,21 +8222,6 @@ ExprResult Sema::IgnoredValueConversions(Expr *E) {
82228222
E = result.get();
82238223
}
82248224

8225-
// C99 6.3.2.1:
8226-
// [Except in specific positions,] an lvalue that does not have
8227-
// array type is converted to the value stored in the
8228-
// designated object (and is no longer an lvalue).
8229-
if (E->isPRValue()) {
8230-
// In C, function designators (i.e. expressions of function type)
8231-
// are r-values, but we still want to do function-to-pointer decay
8232-
// on them. This is both technically correct and convenient for
8233-
// some clients.
8234-
if (!getLangOpts().CPlusPlus && E->getType()->isFunctionType())
8235-
return DefaultFunctionArrayConversion(E);
8236-
8237-
return E;
8238-
}
8239-
82408225
if (getLangOpts().CPlusPlus) {
82418226
// The C++11 standard defines the notion of a discarded-value expression;
82428227
// normally, we don't need to do anything to handle it, but if it is a
@@ -8257,11 +8242,32 @@ ExprResult Sema::IgnoredValueConversions(Expr *E) {
82578242
// If the expression is a prvalue after this optional conversion, the
82588243
// temporary materialization conversion is applied.
82598244
//
8260-
// We skip this step: IR generation is able to synthesize the storage for
8261-
// itself in the aggregate case, and adding the extra node to the AST is
8262-
// just clutter.
8263-
// FIXME: We don't emit lifetime markers for the temporaries due to this.
8264-
// FIXME: Do any other AST consumers care about this?
8245+
// We do not materialize temporaries by default in order to avoid creating
8246+
// unnecessary temporary objects. If we skip this step, IR generation is
8247+
// able to synthesize the storage for itself in the aggregate case, and
8248+
// adding the extra node to the AST is just clutter.
8249+
if (isInMaterializeTemporaryObjectContext() && getLangOpts().CPlusPlus17 &&
8250+
E->isPRValue() && !E->getType()->isVoidType()) {
8251+
ExprResult Res = TemporaryMaterializationConversion(E);
8252+
if (Res.isInvalid())
8253+
return E;
8254+
E = Res.get();
8255+
}
8256+
return E;
8257+
}
8258+
8259+
// C99 6.3.2.1:
8260+
// [Except in specific positions,] an lvalue that does not have
8261+
// array type is converted to the value stored in the
8262+
// designated object (and is no longer an lvalue).
8263+
if (E->isPRValue()) {
8264+
// In C, function designators (i.e. expressions of function type)
8265+
// are r-values, but we still want to do function-to-pointer decay
8266+
// on them. This is both technically correct and convenient for
8267+
// some clients.
8268+
if (!getLangOpts().CPlusPlus && E->getType()->isFunctionType())
8269+
return DefaultFunctionArrayConversion(E);
8270+
82658271
return E;
82668272
}
82678273

clang/lib/Sema/SemaInit.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8491,6 +8491,10 @@ Sema::CreateMaterializeTemporaryExpr(QualType T, Expr *Temporary,
84918491
// are done in both CreateMaterializeTemporaryExpr and MaybeBindToTemporary,
84928492
// but there may be a chance to merge them.
84938493
Cleanup.setExprNeedsCleanups(false);
8494+
if (isInLifetimeExtendingContext()) {
8495+
auto &Record = ExprEvalContexts.back();
8496+
Record.ForRangeLifetimeExtendTemps.push_back(MTE);
8497+
}
84948498
return MTE;
84958499
}
84968500

0 commit comments

Comments
 (0)