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/3] [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/3] [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/3] [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());
 

_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to