https://github.com/akash-manna-sky updated https://github.com/llvm/llvm-project/pull/218710
>From 5c5c2d0f21935270897454527af98be6506df75a Mon Sep 17 00:00:00 2001 From: Akash Manna <[email protected]> Date: Tue, 25 Aug 2026 20:49:51 +0530 Subject: [PATCH 1/2] [Clang] Fix crash on expansion-init-list elements that need cleanups The parser wrapped the syntactic expansion-init-list in an ExprWithCleanups whenever an element needed cleanups (e.g. a temporary bound to a reference parameter). The list has no type, so the wrapper had none either, and ActOnCXXExpansionStmtPattern no longer recognised it as an init list and dereferenced the null type. Discard those cleanups instead: the elements are only evaluated as the initializer of the expansion variable in each expansion, where they are rebuilt anyway. Do the same when building the dependent CXXExpansionSelectExpr so it can't get wrapped during instantiation either, which HasDependentSize/ComputeExpansionSize don't expect. Fixes #212630 --- clang/docs/ReleaseNotes.md | 6 ++++ clang/lib/Parse/ParseStmt.cpp | 11 +++++-- clang/lib/Sema/SemaExpand.cpp | 6 +++- clang/test/SemaTemplate/GH212630.cpp | 49 ++++++++++++++++++++++++++++ 4 files changed, 68 insertions(+), 4 deletions(-) create mode 100644 clang/test/SemaTemplate/GH212630.cpp diff --git a/clang/docs/ReleaseNotes.md b/clang/docs/ReleaseNotes.md index 3c6694f510952..c49428a2a95d0 100644 --- a/clang/docs/ReleaseNotes.md +++ b/clang/docs/ReleaseNotes.md @@ -531,6 +531,12 @@ features cannot lower the translation-unit ABI level; parameter that follows a parameter pack (e.g. `template <typename... T> S::S(T..., int = 10) {}`). (#GH216211) +- Fixed an assertion failure in an enumerating expansion statement + (`template for`) when an element of the expansion-init-list needed cleanups, + e.g. a temporary bound to a reference parameter such as `{g(1), g(2)}` with + `int g(const int&)`, or a temporary of a type with a non-trivial destructor. + (#GH212630) + #### Bug Fixes to AST Handling - Fixed a non-deterministic ordering of unused local typedefs that made diff --git a/clang/lib/Parse/ParseStmt.cpp b/clang/lib/Parse/ParseStmt.cpp index 219bcd980e860..9f5a37e840c1b 100644 --- a/clang/lib/Parse/ParseStmt.cpp +++ b/clang/lib/Parse/ParseStmt.cpp @@ -1965,9 +1965,14 @@ void Parser::ParseForRangeInitializerAfterColon(ForRangeInit &FRI, assert(Actions.CurContext->isExpansionStmt()); Sema::ContextRAII CtxGuard(Actions, Actions.CurContext->getParent(), /*NewThis=*/false); - FRI.RangeExpr = - Tok.is(tok::l_brace) ? ParseExpansionInitList() : ParseExpression(); - FRI.RangeExpr = Actions.MaybeCreateExprWithCleanups(FRI.RangeExpr); + if (Tok.is(tok::l_brace)) { + // The elements are only evaluated as the initializer of the expansion + // variable in each expansion, so their cleanups belong there. + FRI.RangeExpr = ParseExpansionInitList(); + Actions.DiscardCleanupsInEvaluationContext(); + } else { + FRI.RangeExpr = Actions.MaybeCreateExprWithCleanups(ParseExpression()); + } } else if (Tok.is(tok::l_brace)) { FRI.RangeExpr = ParseBraceInitializer(); } else { diff --git a/clang/lib/Sema/SemaExpand.cpp b/clang/lib/Sema/SemaExpand.cpp index 779b7add08344..77e7cb282c5a0 100644 --- a/clang/lib/Sema/SemaExpand.cpp +++ b/clang/lib/Sema/SemaExpand.cpp @@ -589,8 +589,12 @@ StmtResult Sema::FinishCXXExpansionStmt(Stmt *Exp, Stmt *Body) { } ExprResult Sema::BuildCXXExpansionSelectExpr(InitListExpr *Range, Expr *Idx) { - if (Idx->isValueDependent() || InitListContainsPack(Range)) + if (Idx->isValueDependent() || InitListContainsPack(Range)) { + // The elements are only evaluated by the expansion that selects them, so + // their cleanups must not wrap this expression. + DiscardCleanupsInEvaluationContext(); return new (Context) CXXExpansionSelectExpr(Context, Range, Idx); + } // The index is a DRE to a template parameter; we should never // fail to evaluate it. diff --git a/clang/test/SemaTemplate/GH212630.cpp b/clang/test/SemaTemplate/GH212630.cpp new file mode 100644 index 0000000000000..aab9d9d7fd5ad --- /dev/null +++ b/clang/test/SemaTemplate/GH212630.cpp @@ -0,0 +1,49 @@ +// RUN: %clang_cc1 -std=c++26 -fsyntax-only -verify %s +// expected-no-diagnostics + +namespace GH212630 { + +void f(int g(const int&)) { + template for (auto x : {g(1), g(2), g(3)}) + g(0); +} + +struct M { + int m(const int &x) const { return x; } +}; + +int overloaded(const int &); +long overloaded(const long &); + +void related(int (*fp)(const int &), int (&fr)(const int &), M m) { + template for (auto x : {fp(1), fr(2), m.m(3), overloaded(4), overloaded(5L)}) {} +} + +constexpr int h(const int &x) { return x * 2; } + +struct S { + int v; + constexpr S(int v) : v(v) {} + constexpr ~S() {} +}; + +constexpr int direct() { + int sum = 0; + template for (auto x : {h(1), h(2), h(3)}) { sum += x; } + template for (constexpr auto x : {h(1), h(2), h(3)}) { sum += x; } + template for (auto s : {S(1), S(2)}) { sum += s.v; } + return sum; +} +static_assert(direct() == 27); + +// With a pack, the elements are rebuilt when the template is instantiated. +template <typename... Ts> +constexpr int pack(Ts... ts) { + int sum = 0; + template for (auto x : {h(1), h(ts)...}) { sum += x; } + template for (auto s : {S(ts)...}) { sum += s.v; } + return sum; +} +static_assert(pack(2, 3) == 17); + +} // namespace GH212630 >From 9e2625031a63d111a906d9ad8c89261748a3d4c4 Mon Sep 17 00:00:00 2001 From: Akash Manna <[email protected]> Date: Fri, 28 Aug 2026 12:35:13 +0530 Subject: [PATCH 2/2] [Clang] Implement CWG3043 and fix assertion on expansion-init-list cleanups Each element of an expansion-init-list is now a full-expression of its own: ParseExpansionInitList() has its own loop again (undoing the ParseExpressionList() changes) and finishes every element with MaybeCreateExprWithCleanups, so no cleanups are left pending after it. Previously the whole (typeless) init list was wrapped in an ExprWithCleanups, which made ActOnCXXExpansionStmtPattern dereference a null type. When the pattern is instantiated, its elements are rebuilt and finished the same way. Per CWG3043, temporaries in an element persist for the lifetime of the expansion variable initialized from it. When an expansion is built, only the selected element is instantiated, in a lifetime-extending context, and its temporaries are extended to the variable; the same is redone when an already-expanded statement is instantiated again. Fixes #212630 --- clang/docs/ReleaseNotes.md | 6 +- clang/include/clang/Parse/Parser.h | 6 +- clang/include/clang/Sema/Sema.h | 6 ++ clang/lib/Parse/ParseExpr.cpp | 14 +--- clang/lib/Parse/ParseInit.cpp | 36 ++++++-- clang/lib/Parse/ParseStmt.cpp | 13 ++- clang/lib/Sema/SemaExpand.cpp | 69 ++++++++++----- clang/lib/Sema/TreeTransform.h | 84 +++++++++++++++++-- ...2c-enumerating-expansion-stmt-lifetime.cpp | 79 +++++++++++++++++ clang/test/SemaCXX/cxx2c-expansion-stmts.cpp | 62 +++++++++++++- 10 files changed, 315 insertions(+), 60 deletions(-) create mode 100644 clang/test/CodeGenCXX/cxx2c-enumerating-expansion-stmt-lifetime.cpp diff --git a/clang/docs/ReleaseNotes.md b/clang/docs/ReleaseNotes.md index c49428a2a95d0..d9ac19c4d12b5 100644 --- a/clang/docs/ReleaseNotes.md +++ b/clang/docs/ReleaseNotes.md @@ -154,6 +154,10 @@ features cannot lower the translation-unit ABI level; - Clang now falls back to alignment-aware allocation functions for non-overaligned types, implementing [CWG2282](https://wg21.link/cwg2282). +- Implemented [CWG3043](https://wg21.link/cwg3043): temporaries in an element + of the expansion-init-list of an enumerating expansion statement now persist + for the lifetime of the expansion variable initialized from that element. + ### C Language Changes #### C2y Feature Support @@ -535,7 +539,7 @@ features cannot lower the translation-unit ABI level; (`template for`) when an element of the expansion-init-list needed cleanups, e.g. a temporary bound to a reference parameter such as `{g(1), g(2)}` with `int g(const int&)`, or a temporary of a type with a non-trivial destructor. - (#GH212630) + Each element is now a full-expression of its own. (#GH212630) #### Bug Fixes to AST Handling diff --git a/clang/include/clang/Parse/Parser.h b/clang/include/clang/Parse/Parser.h index 163aa483a84e3..d63f633c30dc8 100644 --- a/clang/include/clang/Parse/Parser.h +++ b/clang/include/clang/Parse/Parser.h @@ -4232,8 +4232,7 @@ class Parser : public CodeCompletionHandler { bool ParseExpressionList(SmallVectorImpl<Expr *> &Exprs, llvm::function_ref<void()> ExpressionStarts = llvm::function_ref<void()>(), - bool FailImmediatelyOnInvalidExpr = false, - bool ParsingExpansionStmtInitList = false); + bool FailImmediatelyOnInvalidExpr = false); /// ParseSimpleExpressionList - A simple comma-separated list of expressions, /// used for misc language extensions. @@ -5320,7 +5319,8 @@ class Parser : public CodeCompletionHandler { ExprResult ParseBraceInitializer(); /// ParseExpansionInitList - Called when the initializer of an expansion - /// statement starts with an open brace. + /// statement starts with an open brace. Each element of the list is a + /// full-expression of its own. /// /// \verbatim /// expansion-init-list: [C++26 [stmt.expand]] diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h index 418f9b38fa11f..720073f401edc 100644 --- a/clang/include/clang/Sema/Sema.h +++ b/clang/include/clang/Sema/Sema.h @@ -15837,6 +15837,12 @@ class Sema final : public SemaBase { StmtResult FinishCXXExpansionStmt(Stmt *Expansion, Stmt *Body); + /// Build the expansion variable of an enumerating expansion statement for + /// one expansion using \p Build, extending the lifetime of the temporaries + /// in its initializer to the variable. + StmtResult + BuildEnumeratingExpansionVar(llvm::function_ref<StmtResult()> Build); + StmtResult BuildCXXEnumeratingExpansionStmtPattern(Decl *ESD, Stmt *Init, Stmt *ExpansionVar, SourceLocation LParenLoc, diff --git a/clang/lib/Parse/ParseExpr.cpp b/clang/lib/Parse/ParseExpr.cpp index 87cd7a01451cf..0f0502ac0e253 100644 --- a/clang/lib/Parse/ParseExpr.cpp +++ b/clang/lib/Parse/ParseExpr.cpp @@ -3215,8 +3215,7 @@ void Parser::injectEmbedTokens() { bool Parser::ParseExpressionList(SmallVectorImpl<Expr *> &Exprs, llvm::function_ref<void()> ExpressionStarts, - bool FailImmediatelyOnInvalidExpr, - bool ParsingExpansionStmtInitList) { + bool FailImmediatelyOnInvalidExpr) { bool SawError = false; while (true) { if (ExpressionStarts) @@ -3245,11 +3244,7 @@ bool Parser::ParseExpressionList(SmallVectorImpl<Expr *> &Exprs, SawError = true; if (FailImmediatelyOnInvalidExpr) break; - - // We expect '}' rather than ')' at the end of an expansion-init-list. - SkipUntil(tok::comma, - ParsingExpansionStmtInitList ? tok::r_brace : tok::r_paren, - StopAtSemi | StopBeforeMatch); + SkipUntil(tok::comma, tok::r_paren, StopAtSemi | StopBeforeMatch); } else { Exprs.push_back(Expr.get()); } @@ -3259,11 +3254,6 @@ bool Parser::ParseExpressionList(SmallVectorImpl<Expr *> &Exprs, // Move to the next argument, remember where the comma was. Token Comma = Tok; ConsumeToken(); - - // CWG 3061: Trailing commas are allowed in expansion-init-lists. - if (ParsingExpansionStmtInitList && Tok.is(tok::r_brace)) - break; - checkPotentialAngleBracketDelimiter(Comma); } return SawError; diff --git a/clang/lib/Parse/ParseInit.cpp b/clang/lib/Parse/ParseInit.cpp index 40d78b5d3d2a6..b797e4326edb6 100644 --- a/clang/lib/Parse/ParseInit.cpp +++ b/clang/lib/Parse/ParseInit.cpp @@ -521,16 +521,40 @@ ExprResult Parser::ParseExpansionInitList() { T.consumeOpen(); ExprVector InitExprs; + bool SawError = false; + while (Tok.isNot(tok::r_brace)) { + ExprResult Elem = Tok.is(tok::l_brace) ? ParseBraceInitializer() + : ParseAssignmentExpression(); + + if (Tok.is(tok::code_completion)) { + cutOffParsing(); + SawError = true; + break; + } - if (!Tok.is(tok::r_brace) && - ParseExpressionList(InitExprs, /*ExpressionStarts=*/{}, - /*FailImmediatelyOnInvalidExpr=*/false, - /*ParsingExpansionStmtInitList=*/true)) { - T.consumeClose(); - return ExprError(); + // Each element is a full-expression of its own. + Elem = Actions.MaybeCreateExprWithCleanups(Elem); + if (Tok.is(tok::ellipsis)) + Elem = Actions.ActOnPackExpansion(Elem.get(), ConsumeToken()); + + if (Elem.isInvalid()) { + SawError = true; + SkipUntil(tok::comma, tok::r_brace, StopAtSemi | StopBeforeMatch); + } else { + InitExprs.push_back(Elem.get()); + } + + if (Tok.isNot(tok::comma)) + break; + + // CWG 3061: A trailing comma is allowed. + ConsumeToken(); } T.consumeClose(); + if (SawError) + return ExprError(); + return Actions.ActOnCXXExpansionInitList(InitExprs, T.getOpenLocation(), T.getCloseLocation()); } diff --git a/clang/lib/Parse/ParseStmt.cpp b/clang/lib/Parse/ParseStmt.cpp index 9f5a37e840c1b..583d420d1def5 100644 --- a/clang/lib/Parse/ParseStmt.cpp +++ b/clang/lib/Parse/ParseStmt.cpp @@ -1965,14 +1965,11 @@ void Parser::ParseForRangeInitializerAfterColon(ForRangeInit &FRI, assert(Actions.CurContext->isExpansionStmt()); Sema::ContextRAII CtxGuard(Actions, Actions.CurContext->getParent(), /*NewThis=*/false); - if (Tok.is(tok::l_brace)) { - // The elements are only evaluated as the initializer of the expansion - // variable in each expansion, so their cleanups belong there. - FRI.RangeExpr = ParseExpansionInitList(); - Actions.DiscardCleanupsInEvaluationContext(); - } else { - FRI.RangeExpr = Actions.MaybeCreateExprWithCleanups(ParseExpression()); - } + // The elements of an expansion-init-list are already full-expressions. + FRI.RangeExpr = + Tok.is(tok::l_brace) + ? ParseExpansionInitList() + : Actions.MaybeCreateExprWithCleanups(ParseExpression()); } else if (Tok.is(tok::l_brace)) { FRI.RangeExpr = ParseBraceInitializer(); } else { diff --git a/clang/lib/Sema/SemaExpand.cpp b/clang/lib/Sema/SemaExpand.cpp index 77e7cb282c5a0..cb24fe1b46e81 100644 --- a/clang/lib/Sema/SemaExpand.cpp +++ b/clang/lib/Sema/SemaExpand.cpp @@ -339,7 +339,6 @@ StmtResult Sema::ActOnCXXExpansionStmtPattern( if (FinalizeExpansionVar(*this, ExpansionVar, Initializer)) return StmtError(); - // TODO: CWG3043 (lifetime extension in enumerating expansion statements). return BuildCXXEnumeratingExpansionStmtPattern(ESD, Init, DS, LParenLoc, ColonLoc, RParenLoc); } @@ -493,6 +492,25 @@ StmtResult Sema::BuildNonEnumeratingCXXExpansionStmtPattern( Context, ESD, Init, ExpansionVarStmt, DS, LParenLoc, ColonLoc, RParenLoc); } +StmtResult +Sema::BuildEnumeratingExpansionVar(llvm::function_ref<StmtResult()> Build) { + // CWG3043: Temporaries in the element persist for the lifetime of the + // expansion variable. + EnterExpressionEvaluationContext Ctx(*this, + currentEvaluationContext().Context); + currentEvaluationContext().InLifetimeExtendingContext = true; + currentEvaluationContext().RebuildDefaultArgOrDefaultInit = true; + + StmtResult Var = Build(); + if (Var.isInvalid()) + return StmtError(); + + ApplyForRangeOrExpansionStatementLifetimeExtension( + cast<VarDecl>(cast<DeclStmt>(Var.get())->getSingleDecl()), + currentEvaluationContext().ForRangeLifetimeExtendTemps); + return Var; +} + StmtResult Sema::FinishCXXExpansionStmt(Stmt *Exp, Stmt *Body) { if (!Exp || !Body) return StmtError(); @@ -544,17 +562,6 @@ StmtResult Sema::FinishCXXExpansionStmt(Stmt *Exp, Stmt *Body) { return Expansion; } - // Create a compound statement binding the expansion variable and body, - // as well as the 'iter' variable if this is an iterating expansion statement. - SmallVector<Stmt *, 3> StmtsToInstantiate; - if (Expansion->isIterating()) - StmtsToInstantiate.push_back(Expansion->getIterVarStmt()); - StmtsToInstantiate.push_back(Expansion->getExpansionVarStmt()); - StmtsToInstantiate.push_back(Body); - Stmt *CombinedBody = - CompoundStmt::Create(Context, StmtsToInstantiate, FPOptionsOverride(), - Body->getBeginLoc(), Body->getEndLoc()); - // Expand the body for each instantiation. SmallVector<Stmt *, 4> Instantiations; CXXExpansionStmtDecl *ESD = Expansion->getDecl(); @@ -574,10 +581,38 @@ StmtResult Sema::FinishCXXExpansionStmt(Stmt *Exp, Stmt *Body) { InstantiatingTemplate Inst(*this, Body->getBeginLoc(), Expansion, Arg, Body->getSourceRange()); - StmtResult Instantiation = SubstStmt(CombinedBody, MTArgList); + // Create a compound statement binding the expansion variable and body, + // as well as the 'iter' variable if this is an iterating expansion + // statement. + CompoundScopeRAII CompoundScope(*this); + SmallVector<Stmt *, 3> Stmts; + if (Expansion->isIterating()) { + StmtResult Iter = SubstStmt(Expansion->getIterVarStmt(), MTArgList); + if (Iter.isInvalid()) + return StmtError(); + Stmts.push_back(Iter.get()); + } + + auto SubstExpansionVar = [&] { + return SubstStmt(Expansion->getExpansionVarStmt(), MTArgList); + }; + StmtResult ExpansionVar = + Expansion->isEnumerating() + ? BuildEnumeratingExpansionVar(SubstExpansionVar) + : SubstExpansionVar(); + if (ExpansionVar.isInvalid()) + return StmtError(); + Stmts.push_back(ExpansionVar.get()); + + StmtResult Instantiation = SubstStmt(Body, MTArgList); if (Instantiation.isInvalid()) return StmtError(); - Instantiations.push_back(Instantiation.get()); + Stmts.push_back(Instantiation.get()); + + Instantiations.push_back(ActOnCompoundStmt(Body->getBeginLoc(), + Body->getEndLoc(), Stmts, + /*isStmtExpr=*/false) + .get()); } auto *InstantiationsStmt = CXXExpansionStmtInstantiation::Create( @@ -589,12 +624,8 @@ StmtResult Sema::FinishCXXExpansionStmt(Stmt *Exp, Stmt *Body) { } ExprResult Sema::BuildCXXExpansionSelectExpr(InitListExpr *Range, Expr *Idx) { - if (Idx->isValueDependent() || InitListContainsPack(Range)) { - // The elements are only evaluated by the expansion that selects them, so - // their cleanups must not wrap this expression. - DiscardCleanupsInEvaluationContext(); + if (Idx->isValueDependent() || InitListContainsPack(Range)) return new (Context) CXXExpansionSelectExpr(Context, Range, Idx); - } // The index is a DRE to a template parameter; we should never // fail to evaluate it. diff --git a/clang/lib/Sema/TreeTransform.h b/clang/lib/Sema/TreeTransform.h index 4799f72dd6177..2ad6013441359 100644 --- a/clang/lib/Sema/TreeTransform.h +++ b/clang/lib/Sema/TreeTransform.h @@ -9602,8 +9602,36 @@ StmtResult TreeTransform<Derived>::TransformCXXExpansionStmtInstantiation( } } - if (TransformStmts(Instantiations, S->getInstantiations())) - return StmtError(); + // The expansion variable's initializer is rebuilt, so redo its lifetime + // extension. + bool IsEnumerating = S->getParent()->getExpansionPattern()->isEnumerating(); + for (Stmt *OldInst : S->getInstantiations()) { + StmtResult NewInst; + if (IsEnumerating) { + auto *CS = cast<CompoundStmt>(OldInst); + Sema::CompoundScopeRAII CompoundScope(SemaRef); + SmallVector<Stmt *, 2> Stmts; + for (Stmt *Sub : CS->body()) { + StmtResult R = Sub == CS->body_front() + ? SemaRef.BuildEnumeratingExpansionVar([&] { + return getDerived().TransformStmt(Sub); + }) + : getDerived().TransformStmt(Sub); + if (R.isInvalid()) + return StmtError(); + Stmts.push_back(R.get()); + } + NewInst = getDerived().RebuildCompoundStmt( + CS->getLBracLoc(), Stmts, CS->getRBracLoc(), /*IsStmtExpr=*/false); + } else { + NewInst = getDerived().TransformStmt(OldInst); + } + if (NewInst.isInvalid()) + return StmtError(); + + SubStmtChanged |= NewInst.get() != OldInst; + Instantiations.push_back(NewInst.get()); + } if (!getDerived().AlwaysRebuild() && !SubStmtChanged) return S; @@ -9616,16 +9644,56 @@ StmtResult TreeTransform<Derived>::TransformCXXExpansionStmtInstantiation( template <typename Derived> ExprResult TreeTransform<Derived>::TransformCXXExpansionSelectExpr( CXXExpansionSelectExpr *E) { - ExprResult Range = getDerived().TransformExpr(E->getRangeExpr()); ExprResult Idx = getDerived().TransformExpr(E->getIndexExpr()); - if (Range.isInvalid() || Idx.isInvalid()) + if (Idx.isInvalid()) return ExprError(); - if (!getDerived().AlwaysRebuild() && Range.get() == E->getRangeExpr() && - Idx.get() == E->getIndexExpr()) - return E; + InitListExpr *Range = E->getRangeExpr(); + + // A known index means we're expanding; only the selected element is needed. + if (!Idx.get()->isValueDependent()) { + assert(llvm::none_of(Range->inits(), llvm::IsaPred<PackExpansionExpr>) && + "expanding an expansion-init-list that still contains packs"); + uint64_t I = + Idx.get()->EvaluateKnownConstInt(SemaRef.Context).getZExtValue(); + return getDerived().TransformInitializer(Range->getInit(I), + /*NotCopyInit=*/false); + } + + // Otherwise, rebuild the list. Each element is a full-expression of its own + // (the expansions of one pack share an evaluation context). + SmallVector<Expr *, 4> Inits; + for (Expr *Init : Range->inits()) { + EnterExpressionEvaluationContext Ctx( + SemaRef, SemaRef.currentEvaluationContext().Context); + SmallVector<Expr *, 2> Outputs; + if (getDerived().TransformExprs(&Init, 1, /*IsCall=*/false, Outputs)) + return ExprError(); + + for (Expr *Out : Outputs) { + // Keep a pack expansion outermost by finishing its pattern instead. + if (auto *PE = dyn_cast<PackExpansionExpr>(Out)) { + Expr *Pattern = SemaRef.MaybeCreateExprWithCleanups(PE->getPattern()); + if (Pattern != PE->getPattern()) { + ExprResult Res = getDerived().RebuildPackExpansion( + Pattern, PE->getEllipsisLoc(), PE->getNumExpansions()); + if (Res.isInvalid()) + return ExprError(); + Out = Res.get(); + } + } else { + Out = SemaRef.MaybeCreateExprWithCleanups(Out); + } + Inits.push_back(Out); + } + } + + ExprResult NewRange = SemaRef.ActOnCXXExpansionInitList( + Inits, Range->getLBraceLoc(), Range->getRBraceLoc()); + if (NewRange.isInvalid()) + return ExprError(); - return SemaRef.BuildCXXExpansionSelectExpr(Range.getAs<InitListExpr>(), + return SemaRef.BuildCXXExpansionSelectExpr(cast<InitListExpr>(NewRange.get()), Idx.get()); } diff --git a/clang/test/CodeGenCXX/cxx2c-enumerating-expansion-stmt-lifetime.cpp b/clang/test/CodeGenCXX/cxx2c-enumerating-expansion-stmt-lifetime.cpp new file mode 100644 index 0000000000000..6ee28093c16b8 --- /dev/null +++ b/clang/test/CodeGenCXX/cxx2c-enumerating-expansion-stmt-lifetime.cpp @@ -0,0 +1,79 @@ +// RUN: %clang_cc1 -std=c++2c -triple x86_64-unknown-linux-gnu -emit-llvm -o - %s | FileCheck %s + +// CWG 3043: The temporary bound to f's parameter lives as long as the +// expansion variable, so it must be destroyed after the body. + +struct T { + int& x; + T(int& x) noexcept : x(x) {} + ~T() noexcept { x = 42; } +}; + +const T& f(const T& t) noexcept { return t; } +T g(int& x) noexcept { return T(x); } +void body(int); + +int lifetime_extension() { + int x = 5; + template for (auto&& e : {f(g(x)), f(g(x))}) { + body(e.x); + } + return x; +} + +template <typename U> +int lifetime_extension_instantiate_expansions() { + int x = 5; + template for (U e : {f(g(x))}) { + body(e.x); + } + return x; +} + +template <typename... Ts> +int lifetime_extension_pack(Ts... ts) { + int x = 5; + template for (auto&& e : {f(g(x)), f(g(ts))...}) { + body(e.x); + } + return x; +} + +void instantiate() { + lifetime_extension_instantiate_expansions<const T&>(); + lifetime_extension_pack(1); +} + +// CHECK-LABEL: define {{.*}} i32 @_Z18lifetime_extensionv() +// CHECK: call void @_Z1gRi(ptr {{.*}}sret{{.*}} %[[TMP0:[^ ,]+]], ptr {{.*}} %x) +// CHECK-NEXT: call {{.*}} ptr @_Z1fRK1T(ptr {{.*}} %[[TMP0]]) +// CHECK-NOT: call void @_ZN1TD1Ev +// CHECK: call void @_Z4bodyi( +// CHECK: call void @_ZN1TD1Ev(ptr {{.*}} %[[TMP0]]) +// CHECK: call void @_Z1gRi(ptr {{.*}}sret{{.*}} %[[TMP1:[^ ,]+]], ptr {{.*}} %x) +// CHECK-NEXT: call {{.*}} ptr @_Z1fRK1T(ptr {{.*}} %[[TMP1]]) +// CHECK-NOT: call void @_ZN1TD1Ev +// CHECK: call void @_Z4bodyi( +// CHECK: call void @_ZN1TD1Ev(ptr {{.*}} %[[TMP1]]) +// CHECK: ret i32 + +// CHECK-LABEL: define {{.*}} i32 @_Z41lifetime_extension_instantiate_expansionsIRK1TEiv() +// CHECK: call void @_Z1gRi(ptr {{.*}}sret{{.*}} %[[TMP2:[^ ,]+]], ptr {{.*}} %x) +// CHECK-NEXT: call {{.*}} ptr @_Z1fRK1T(ptr {{.*}} %[[TMP2]]) +// CHECK-NOT: call void @_ZN1TD1Ev +// CHECK: call void @_Z4bodyi( +// CHECK: call void @_ZN1TD1Ev(ptr {{.*}} %[[TMP2]]) +// CHECK: ret i32 + +// CHECK-LABEL: define {{.*}} i32 @_Z23lifetime_extension_packIJiEEiDpT_( +// CHECK: call void @_Z1gRi(ptr {{.*}}sret{{.*}} %[[TMP3:[^ ,]+]], ptr {{.*}} %x) +// CHECK-NEXT: call {{.*}} ptr @_Z1fRK1T(ptr {{.*}} %[[TMP3]]) +// CHECK-NOT: call void @_ZN1TD1Ev +// CHECK: call void @_Z4bodyi( +// CHECK: call void @_ZN1TD1Ev(ptr {{.*}} %[[TMP3]]) +// CHECK: call void @_Z1gRi(ptr {{.*}}sret{{.*}} %[[TMP4:[^ ,]+]], ptr {{.*}} %ts +// CHECK-NEXT: call {{.*}} ptr @_Z1fRK1T(ptr {{.*}} %[[TMP4]]) +// CHECK-NOT: call void @_ZN1TD1Ev +// CHECK: call void @_Z4bodyi( +// CHECK: call void @_ZN1TD1Ev(ptr {{.*}} %[[TMP4]]) +// CHECK: ret i32 diff --git a/clang/test/SemaCXX/cxx2c-expansion-stmts.cpp b/clang/test/SemaCXX/cxx2c-expansion-stmts.cpp index dd450a8f1b76c..fd87a027c1a63 100644 --- a/clang/test/SemaCXX/cxx2c-expansion-stmts.cpp +++ b/clang/test/SemaCXX/cxx2c-expansion-stmts.cpp @@ -927,9 +927,9 @@ constexpr T g(int& x) noexcept { return T(x); } // CWG 3043: // -// Lifetime extension only applies to destructuring expansion statements -// (enumerating statements don't have a range variable, and the range variable -// of iterating statements is constexpr). +// Temporaries in the expansion-initializer of a destructuring expansion +// statement persist for the lifetime of the reference initialized by it (the +// range variable of iterating statements is constexpr). constexpr int lifetime_extension() { int x = 5; int sum = 0; @@ -976,6 +976,62 @@ static_assert(lifetime_extension() == 47); static_assert(lifetime_extension_instantiate_expansions<int>() == 47); static_assert(lifetime_extension_dependent_expansion_stmt<int>() == 47); static_assert(foo<int>().lifetime_extension_multiple_instantiations<int>() == 47); + +// Temporaries in an element of an expansion-init-list persist for the lifetime +// of the expansion variable initialized from it. +constexpr int lifetime_extension_enumerating() { + int x = 5; + int sum = 0; + template for (auto e : {f(g(x))}) { + sum += x; + } + return sum + x; +} + +constexpr int lifetime_extension_enumerating_ref() { + int x = 5; + int sum = 0; + template for (auto&& e : {f(g(x))}) { + sum += e.x; + } + return sum + x; +} + +template <typename U> +constexpr int lifetime_extension_enumerating_instantiate_expansions() { + int x = 5; + int sum = 0; + template for (U e : {f(g(x))}) { + sum += e.x; + } + return sum + x; +} + +template <typename U> +constexpr int lifetime_extension_enumerating_dependent_element() { + int x = 5; + int sum = 0; + template for (auto&& e : {f(g((U&)x))}) { + sum += e.x; + } + return sum + x; +} + +template <typename... Ts> +constexpr int lifetime_extension_enumerating_pack(Ts... ts) { + int x = 5; + int sum = 0; + template for (auto&& e : {f(g(x)), f(g(ts))...}) { + sum += e.x; + } + return sum + x; +} + +static_assert(lifetime_extension_enumerating() == 47); +static_assert(lifetime_extension_enumerating_ref() == 47); +static_assert(lifetime_extension_enumerating_instantiate_expansions<const T&>() == 47); +static_assert(lifetime_extension_enumerating_dependent_element<int>() == 47); +static_assert(lifetime_extension_enumerating_pack(1, 2) == 50); } template <typename... Ts> _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
