https://github.com/akash-manna-sky updated https://github.com/llvm/llvm-project/pull/218710
>From f967cb3987c7690d338c9f046e0bfbaff82cc79d Mon Sep 17 00:00:00 2001 From: Akash Manna <[email protected]> Date: Tue, 25 Aug 2026 20:49:51 +0530 Subject: [PATCH 1/4] [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 a49971adef86ff..16285462c17043 100644 --- a/clang/docs/ReleaseNotes.md +++ b/clang/docs/ReleaseNotes.md @@ -646,6 +646,12 @@ features cannot lower the translation-unit ABI level; - Fixed a crash when a coroutine keyword appeared inside a mem-initializer on a function that is not a constructor. (#GH194298) +- 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 5e67cd551bff8b..6d3898d31dba98 100644 --- a/clang/lib/Parse/ParseStmt.cpp +++ b/clang/lib/Parse/ParseStmt.cpp @@ -1955,9 +1955,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 a1d89f9842f933..6bbc48f1e66c61 100644 --- a/clang/lib/Sema/SemaExpand.cpp +++ b/clang/lib/Sema/SemaExpand.cpp @@ -591,8 +591,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 00000000000000..aab9d9d7fd5ade --- /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 36d53726b6b300e0da40ed2a0ab643e4a62076a1 Mon Sep 17 00:00:00 2001 From: Akash Manna <[email protected]> Date: Fri, 28 Aug 2026 12:35:13 +0530 Subject: [PATCH 2/4] [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 16285462c17043..5e1c231df7d318 100644 --- a/clang/docs/ReleaseNotes.md +++ b/clang/docs/ReleaseNotes.md @@ -185,6 +185,10 @@ features cannot lower the translation-unit ABI level; them to an enumeration type with a fixed `bool` underlying type. This resolves [CWG1094](https://wg21.link/cwg1094). +- 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 @@ -650,7 +654,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 6913c42884a367..2ab942dae930f6 100644 --- a/clang/include/clang/Parse/Parser.h +++ b/clang/include/clang/Parse/Parser.h @@ -4239,8 +4239,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. @@ -5327,7 +5326,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 4ff4c669a6b703..26284d550eeb33 100644 --- a/clang/include/clang/Sema/Sema.h +++ b/clang/include/clang/Sema/Sema.h @@ -15873,6 +15873,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 fd6b290d9b6739..ba6b0ce6afbaaf 100644 --- a/clang/lib/Parse/ParseExpr.cpp +++ b/clang/lib/Parse/ParseExpr.cpp @@ -3214,8 +3214,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) @@ -3244,11 +3243,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()); } @@ -3258,11 +3253,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 40d78b5d3d2a68..b797e4326edb66 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 6d3898d31dba98..87b81aaac871d8 100644 --- a/clang/lib/Parse/ParseStmt.cpp +++ b/clang/lib/Parse/ParseStmt.cpp @@ -1955,14 +1955,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 6bbc48f1e66c61..1f226e4a62ce91 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(); @@ -576,10 +583,38 @@ StmtResult Sema::FinishCXXExpansionStmt(Stmt *Exp, Stmt *Body) { if (Inst.isInvalid()) return StmtError(); - 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( @@ -591,12 +626,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 c8458fda58a88e..d9d212d34dc4da 100644 --- a/clang/lib/Sema/TreeTransform.h +++ b/clang/lib/Sema/TreeTransform.h @@ -9723,8 +9723,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; @@ -9737,16 +9765,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 00000000000000..6ee28093c16b87 --- /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 dd450a8f1b76c5..fd87a027c1a63f 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> >From 77fd9656bfd0f18168d5cfb5fdf43d56d864ab8b Mon Sep 17 00:00:00 2001 From: Akash Manna <[email protected]> Date: Mon, 7 Sep 2026 22:44:14 +0530 Subject: [PATCH 3/4] [Clang] Parse each expansion-init-list element in its own evaluation context Addresses review feedback: push an expression evaluation context per element and finish the element inside it, instead of finishing all elements within the shared range-init context. --- clang/lib/Parse/ParseInit.cpp | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/clang/lib/Parse/ParseInit.cpp b/clang/lib/Parse/ParseInit.cpp index b797e4326edb66..b7231cd24f2647 100644 --- a/clang/lib/Parse/ParseInit.cpp +++ b/clang/lib/Parse/ParseInit.cpp @@ -523,8 +523,15 @@ ExprResult Parser::ParseExpansionInitList() { ExprVector InitExprs; bool SawError = false; while (Tok.isNot(tok::r_brace)) { - ExprResult Elem = Tok.is(tok::l_brace) ? ParseBraceInitializer() - : ParseAssignmentExpression(); + ExprResult Elem; + { + // Each element is a full-expression of its own. + EnterExpressionEvaluationContext ElemCtx( + Actions, Actions.currentEvaluationContext().Context); + Elem = Tok.is(tok::l_brace) ? ParseBraceInitializer() + : ParseAssignmentExpression(); + Elem = Actions.MaybeCreateExprWithCleanups(Elem); + } if (Tok.is(tok::code_completion)) { cutOffParsing(); @@ -532,8 +539,6 @@ ExprResult Parser::ParseExpansionInitList() { break; } - // Each element is a full-expression of its own. - Elem = Actions.MaybeCreateExprWithCleanups(Elem); if (Tok.is(tok::ellipsis)) Elem = Actions.ActOnPackExpansion(Elem.get(), ConsumeToken()); >From c47a3c5e64825225a02c6c1a1711694da246d7e8 Mon Sep 17 00:00:00 2001 From: Akash Manna <[email protected]> Date: Fri, 18 Sep 2026 18:50:16 +0530 Subject: [PATCH 4/4] Reposition the release notes to avoid the conflicts --- clang/docs/ReleaseNotes.md | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/clang/docs/ReleaseNotes.md b/clang/docs/ReleaseNotes.md index 06173da631ea93..15786f6dda6782 100644 --- a/clang/docs/ReleaseNotes.md +++ b/clang/docs/ReleaseNotes.md @@ -187,6 +187,10 @@ features cannot lower the translation-unit ABI level; #### Resolutions to C++ Defect Reports +- 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. + - Clang now falls back to alignment-aware allocation functions for non-overaligned types, implementing [CWG2282](https://wg21.link/cwg2282). @@ -194,10 +198,6 @@ features cannot lower the translation-unit ABI level; them to an enumeration type with a fixed `bool` underlying type. This resolves [CWG1094](https://wg21.link/cwg1094). -- 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 @@ -653,6 +653,12 @@ features cannot lower the translation-unit ABI level; libstdc++15 has been extended to support preprocessed input. Previously, splitting the preprocessing and compilation step would result in the fix not being applied. (#GH160314) +- 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. + Each element is now a full-expression of its own. (#GH212630) + - A defaulted copy or move assignment operator for a union was left with an empty body and copied nothing when the operator was actually called, for example through a pointer to member. Clang now synthesizes a whole-object @@ -728,12 +734,6 @@ features cannot lower the translation-unit ABI level; - Fixed an issue where an explicit specialization of a constexpr variable would result in a link error. (#GH219796) -- 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. - Each element is now a full-expression of its own. (#GH212630) - #### Bug Fixes to AST Handling - Fixed a non-deterministic ordering of unused local typedefs that made _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
