Skip to content

Commit c2d2299

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 6c2fbc3 commit c2d2299

File tree

11 files changed

+713
-41
lines changed

11 files changed

+713
-41
lines changed

clang/docs/ReleaseNotes.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,9 @@ C++23 Feature Support
169169
- Added a separate warning to warn the use of attributes on lambdas as a C++23 extension
170170
in previous language versions: ``-Wc++23-lambda-attributes``.
171171

172+
- Implemented `P2718R0: Lifetime extension in range-based for loops <https://wg21.link/P2718R0>`_. Also
173+
materialize temporary object which is a prvalue in discarded-value expression.
174+
172175
C++2c Feature Support
173176
^^^^^^^^^^^^^^^^^^^^^
174177

clang/include/clang/Parse/Parser.h

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

clang/include/clang/Sema/Sema.h

Lines changed: 36 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1338,6 +1338,12 @@ class Sema final {
13381338
/// context not already known to be immediately invoked.
13391339
llvm::SmallPtrSet<DeclRefExpr *, 4> ReferenceToConsteval;
13401340

1341+
/// P2718R0 - Lifetime extension in range-based for loops.
1342+
/// MaterializeTemporaryExprs in for-range-init expression which need to
1343+
/// extend lifetime. Add MaterializeTemporaryExpr* if the value of
1344+
/// IsInLifetimeExtendingContext is true.
1345+
SmallVector<MaterializeTemporaryExpr *, 8> ForRangeLifetimeExtendTemps;
1346+
13411347
/// \brief Describes whether we are in an expression constext which we have
13421348
/// to handle differently.
13431349
enum ExpressionKind {
@@ -1357,6 +1363,19 @@ class Sema final {
13571363
// VLAs).
13581364
bool InConditionallyConstantEvaluateContext = false;
13591365

1366+
/// Whether we are currently in a context in which temporaries must be
1367+
/// lifetime-extended (Eg. in a for-range initializer).
1368+
bool IsInLifetimeExtendingContext = false;
1369+
1370+
/// Whether we should materialize temporaries in discarded expressions.
1371+
///
1372+
/// [C++23][class.temporary]/p2.6 when a prvalue that has type other than cv
1373+
/// void appears as a discarded-value expression ([expr.context]).
1374+
///
1375+
/// We do not materialize temporaries by default in order to avoid creating
1376+
/// unnecessary temporary objects.
1377+
bool MaterializePRValueInDiscardedExpression = false;
1378+
13601379
// When evaluating immediate functions in the initializer of a default
13611380
// argument or default member initializer, this is the declaration whose
13621381
// default initializer is being evaluated and the location of the call
@@ -5211,13 +5230,11 @@ class Sema final {
52115230
BFRK_Check
52125231
};
52135232

5214-
StmtResult ActOnCXXForRangeStmt(Scope *S, SourceLocation ForLoc,
5215-
SourceLocation CoawaitLoc,
5216-
Stmt *InitStmt,
5217-
Stmt *LoopVar,
5218-
SourceLocation ColonLoc, Expr *Collection,
5219-
SourceLocation RParenLoc,
5220-
BuildForRangeKind Kind);
5233+
StmtResult ActOnCXXForRangeStmt(
5234+
Scope *S, SourceLocation ForLoc, SourceLocation CoawaitLoc,
5235+
Stmt *InitStmt, Stmt *LoopVar, SourceLocation ColonLoc, Expr *Collection,
5236+
SourceLocation RParenLoc, BuildForRangeKind Kind,
5237+
ArrayRef<MaterializeTemporaryExpr *> LifetimeExtendTemps = {});
52215238
StmtResult BuildCXXForRangeStmt(SourceLocation ForLoc,
52225239
SourceLocation CoawaitLoc,
52235240
Stmt *InitStmt,
@@ -9959,6 +9976,18 @@ class Sema final {
99599976
return currentEvaluationContext().isImmediateFunctionContext();
99609977
}
99619978

9979+
bool isInLifetimeExtendingContext() const {
9980+
assert(!ExprEvalContexts.empty() &&
9981+
"Must be in an expression evaluation context");
9982+
return ExprEvalContexts.back().IsInLifetimeExtendingContext;
9983+
}
9984+
9985+
bool ShouldMaterializePRValueInDiscardedExpression() const {
9986+
assert(!ExprEvalContexts.empty() &&
9987+
"Must be in an expression evaluation context");
9988+
return ExprEvalContexts.back().MaterializePRValueInDiscardedExpression;
9989+
}
9990+
99629991
bool isCheckingDefaultArgumentOrInitializer() const {
99639992
const ExpressionEvaluationContextRecord &Ctx = currentEvaluationContext();
99649993
return (Ctx.Context ==

clang/lib/Parse/ParseDecl.cpp

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2312,12 +2312,31 @@ 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.IsInLifetimeExtendingContext = true;
2325+
2326+
// Materialize non-`cv void` prvalue temporaries in discarded
2327+
// expressions. These materialized temporaries may be lifetime-extented.
2328+
LastRecord.MaterializePRValueInDiscardedExpression = 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+
if (getLangOpts().CPlusPlus23)
2338+
FRI->LifetimeExtendTemps = std::move(
2339+
Actions.ExprEvalContexts.back().ForRangeLifetimeExtendTemps);
23212340
}
23222341

23232342
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: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6258,7 +6258,7 @@ ExprResult Sema::BuildCXXDefaultArgExpr(SourceLocation CallLoc,
62586258
assert(Param->hasDefaultArg() && "can't build nonexistent default arg");
62596259

62606260
bool NestedDefaultChecking = isCheckingDefaultArgumentOrInitializer();
6261-
6261+
bool IsInLifetimeExtendingContext = isInLifetimeExtendingContext();
62626262
std::optional<ExpressionEvaluationContextRecord::InitializationContext>
62636263
InitializationContext =
62646264
OutermostDeclarationWithDelayedImmediateInvocations();
@@ -6291,9 +6291,19 @@ ExprResult Sema::BuildCXXDefaultArgExpr(SourceLocation CallLoc,
62916291
ImmediateCallVisitor V(getASTContext());
62926292
if (!NestedDefaultChecking)
62936293
V.TraverseDecl(Param);
6294-
if (V.HasImmediateCalls) {
6295-
ExprEvalContexts.back().DelayedDefaultInitializationContext = {
6296-
CallLoc, Param, CurContext};
6294+
6295+
// Rewrite the call argument that was created from the corresponding
6296+
// parameter's default argument.
6297+
if (V.HasImmediateCalls || IsInLifetimeExtendingContext) {
6298+
if (V.HasImmediateCalls)
6299+
ExprEvalContexts.back().DelayedDefaultInitializationContext = {
6300+
CallLoc, Param, CurContext};
6301+
// Pass down lifetime extending flag, and collect temporaries in
6302+
// CreateMaterializeTemporaryExpr when we rewrite the call argument.
6303+
auto &LastRecord = ExprEvalContexts.back();
6304+
auto &PrevRecord = ExprEvalContexts[ExprEvalContexts.size() - 2];
6305+
LastRecord.IsInLifetimeExtendingContext = IsInLifetimeExtendingContext;
6306+
62976307
EnsureImmediateInvocationInDefaultArgs Immediate(*this);
62986308
ExprResult Res;
62996309
runWithSufficientStackSpace(CallLoc, [&] {
@@ -6307,6 +6317,10 @@ ExprResult Sema::BuildCXXDefaultArgExpr(SourceLocation CallLoc,
63076317
if (Res.isInvalid())
63086318
return ExprError();
63096319
Init = Res.get();
6320+
6321+
// Collect MaterializeTemporaryExprs in the rewritten CXXDefaultArgExpr.
6322+
PrevRecord.ForRangeLifetimeExtendTemps.append(
6323+
LastRecord.ForRangeLifetimeExtendTemps);
63106324
}
63116325
}
63126326

clang/lib/Sema/SemaExprCXX.cpp

Lines changed: 27 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -8209,21 +8209,6 @@ ExprResult Sema::IgnoredValueConversions(Expr *E) {
82098209
E = result.get();
82108210
}
82118211

8212-
// C99 6.3.2.1:
8213-
// [Except in specific positions,] an lvalue that does not have
8214-
// array type is converted to the value stored in the
8215-
// designated object (and is no longer an lvalue).
8216-
if (E->isPRValue()) {
8217-
// In C, function designators (i.e. expressions of function type)
8218-
// are r-values, but we still want to do function-to-pointer decay
8219-
// on them. This is both technically correct and convenient for
8220-
// some clients.
8221-
if (!getLangOpts().CPlusPlus && E->getType()->isFunctionType())
8222-
return DefaultFunctionArrayConversion(E);
8223-
8224-
return E;
8225-
}
8226-
82278212
if (getLangOpts().CPlusPlus) {
82288213
// The C++11 standard defines the notion of a discarded-value expression;
82298214
// normally, we don't need to do anything to handle it, but if it is a
@@ -8244,11 +8229,33 @@ ExprResult Sema::IgnoredValueConversions(Expr *E) {
82448229
// If the expression is a prvalue after this optional conversion, the
82458230
// temporary materialization conversion is applied.
82468231
//
8247-
// We skip this step: IR generation is able to synthesize the storage for
8248-
// itself in the aggregate case, and adding the extra node to the AST is
8249-
// just clutter.
8250-
// FIXME: We don't emit lifetime markers for the temporaries due to this.
8251-
// FIXME: Do any other AST consumers care about this?
8232+
// We do not materialize temporaries by default in order to avoid creating
8233+
// unnecessary temporary objects. If we skip this step, IR generation is
8234+
// able to synthesize the storage for itself in the aggregate case, and
8235+
// adding the extra node to the AST is just clutter.
8236+
if (ShouldMaterializePRValueInDiscardedExpression() &&
8237+
getLangOpts().CPlusPlus17 && E->isPRValue() &&
8238+
!E->getType()->isVoidType()) {
8239+
ExprResult Res = TemporaryMaterializationConversion(E);
8240+
if (Res.isInvalid())
8241+
return E;
8242+
E = Res.get();
8243+
}
8244+
return E;
8245+
}
8246+
8247+
// C99 6.3.2.1:
8248+
// [Except in specific positions,] an lvalue that does not have
8249+
// array type is converted to the value stored in the
8250+
// designated object (and is no longer an lvalue).
8251+
if (E->isPRValue()) {
8252+
// In C, function designators (i.e. expressions of function type)
8253+
// are r-values, but we still want to do function-to-pointer decay
8254+
// on them. This is both technically correct and convenient for
8255+
// some clients.
8256+
if (!getLangOpts().CPlusPlus && E->getType()->isFunctionType())
8257+
return DefaultFunctionArrayConversion(E);
8258+
82528259
return E;
82538260
}
82548261

clang/lib/Sema/SemaInit.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8478,6 +8478,10 @@ Sema::CreateMaterializeTemporaryExpr(QualType T, Expr *Temporary,
84788478
// are done in both CreateMaterializeTemporaryExpr and MaybeBindToTemporary,
84798479
// but there may be a chance to merge them.
84808480
Cleanup.setExprNeedsCleanups(false);
8481+
if (isInLifetimeExtendingContext()) {
8482+
auto &Record = ExprEvalContexts.back();
8483+
Record.ForRangeLifetimeExtendTemps.push_back(MTE);
8484+
}
84818485
return MTE;
84828486
}
84838487

clang/lib/Sema/SemaStmt.cpp

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2483,11 +2483,11 @@ static bool ObjCEnumerationCollection(Expr *Collection) {
24832483
///
24842484
/// The body of the loop is not available yet, since it cannot be analysed until
24852485
/// we have determined the type of the for-range-declaration.
2486-
StmtResult Sema::ActOnCXXForRangeStmt(Scope *S, SourceLocation ForLoc,
2487-
SourceLocation CoawaitLoc, Stmt *InitStmt,
2488-
Stmt *First, SourceLocation ColonLoc,
2489-
Expr *Range, SourceLocation RParenLoc,
2490-
BuildForRangeKind Kind) {
2486+
StmtResult Sema::ActOnCXXForRangeStmt(
2487+
Scope *S, SourceLocation ForLoc, SourceLocation CoawaitLoc, Stmt *InitStmt,
2488+
Stmt *First, SourceLocation ColonLoc, Expr *Range, SourceLocation RParenLoc,
2489+
BuildForRangeKind Kind,
2490+
ArrayRef<MaterializeTemporaryExpr *> LifetimeExtendTemps) {
24912491
// FIXME: recover in order to allow the body to be parsed.
24922492
if (!First)
24932493
return StmtError();
@@ -2539,6 +2539,13 @@ StmtResult Sema::ActOnCXXForRangeStmt(Scope *S, SourceLocation ForLoc,
25392539
return StmtError();
25402540
}
25412541

2542+
// P2718R0 - Lifetime extension in range-based for loops.
2543+
if (getLangOpts().CPlusPlus23 && !LifetimeExtendTemps.empty()) {
2544+
InitializedEntity Entity = InitializedEntity::InitializeVariable(RangeVar);
2545+
for (auto *MTE : LifetimeExtendTemps)
2546+
MTE->setExtendingDecl(RangeVar, Entity.allocateManglingNumber());
2547+
}
2548+
25422549
// Claim the type doesn't contain auto: we've already done the checking.
25432550
DeclGroupPtrTy RangeGroup =
25442551
BuildDeclaratorGroup(MutableArrayRef<Decl *>((Decl **)&RangeVar, 1));

0 commit comments

Comments
 (0)