https://github.com/Sirraide updated 
https://github.com/llvm/llvm-project/pull/169684

>From d6c6a2889d8b88ef75244dda44ed43fb395b5051 Mon Sep 17 00:00:00 2001
From: Sirraide <[email protected]>
Date: Wed, 26 Nov 2025 16:18:02 +0100
Subject: [PATCH 1/9] [Clang] [C++26] Expansion Statements (Part 5)

---
 .../clang/Basic/DiagnosticSemaKinds.td        |   7 +
 clang/include/clang/Sema/Sema.h               |   6 +
 clang/lib/Sema/SemaExpand.cpp                 | 257 +++++++++++++++++-
 clang/lib/Sema/SemaStmt.cpp                   |  13 +-
 clang/lib/Sema/TreeTransform.h                |  14 +
 5 files changed, 291 insertions(+), 6 deletions(-)

diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td 
b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index d4783c1c9677d..96292d0a4e306 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -165,6 +165,10 @@ def err_ice_too_large : Error<
 def err_expr_not_string_literal : Error<"expression is not a string literal">;
 def note_constexpr_assert_failed : Note<
   "assertion failed during evaluation of constant expression">;
+def err_expansion_size_expr_not_ice : Error<
+  "expansion size is not a constant expression">;
+def err_expansion_size_negative : Error<
+  "expansion size must not be negative (was %0)">;
 
 // Semantic analysis of constant literals.
 def ext_predef_outside_function : Warning<
@@ -3698,6 +3702,9 @@ def err_conflicting_codeseg_attribute : Error<
 def warn_duplicate_codeseg_attribute : Warning<
   "duplicate code segment specifiers">, InGroup<Section>;
 
+def err_expansion_stmt_lambda : Error<
+  "cannot expand lambda closure type">;
+
 def err_attribute_patchable_function_entry_invalid_section
     : Error<"section argument to 'patchable_function_entry' attribute is not "
             "valid for this target: %0">;
diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index b86a9eaed305e..f5a36626a9dad 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -15704,6 +15704,12 @@ class Sema final : public SemaBase {
                                                      SourceLocation ColonLoc,
                                                      SourceLocation RParenLoc);
 
+  StmtResult BuildNonEnumeratingCXXExpansionStmtPattern(
+      CXXExpansionStmtDecl *ESD, Stmt *Init, DeclStmt *ExpansionVarStmt,
+      Expr *ExpansionInitializer, SourceLocation LParenLoc,
+      SourceLocation ColonLoc, SourceLocation RParenLoc,
+      ArrayRef<MaterializeTemporaryExpr *> LifetimeExtendTemps = {});
+
   ExprResult BuildCXXExpansionSelectExpr(InitListExpr *Range, Expr *Idx);
 
   std::optional<uint64_t>
diff --git a/clang/lib/Sema/SemaExpand.cpp b/clang/lib/Sema/SemaExpand.cpp
index a0f5e852ebdb1..4e392e33578e2 100644
--- a/clang/lib/Sema/SemaExpand.cpp
+++ b/clang/lib/Sema/SemaExpand.cpp
@@ -24,6 +24,25 @@
 using namespace clang;
 using namespace sema;
 
+namespace {
+struct IterableExpansionStmtData {
+  enum class State {
+    NotIterable,
+    Error,
+    Ok,
+  };
+
+  DeclStmt *RangeDecl = nullptr;
+  DeclStmt *BeginDecl = nullptr;
+  DeclStmt *EndDecl = nullptr;
+  Expr *Initializer = nullptr;
+  State TheState = State::NotIterable;
+
+  bool isIterable() const { return TheState == State::Ok; }
+  bool hasError() { return TheState == State::Error; }
+};
+} // namespace
+
 // Build a 'DeclRefExpr' designating the template parameter '__N'.
 static DeclRefExpr *BuildIndexDRE(Sema &S, CXXExpansionStmtDecl *ESD) {
   return S.BuildDeclRefExpr(ESD->getIndexTemplateParm(),
@@ -55,15 +74,134 @@ static bool HasDependentSize(const CXXExpansionStmtPattern 
*Pattern) {
     return InitListContainsPack(SelectExpr->getRangeExpr());
   }
 
-  case CXXExpansionStmtPattern::ExpansionStmtKind::Iterating:
-  case CXXExpansionStmtPattern::ExpansionStmtKind::Destructuring:
+  case CXXExpansionStmtPattern::ExpansionStmtKind::Iterating: {
+    const Expr *Begin = Pattern->getBeginVar()->getInit();
+    const Expr *End = Pattern->getEndVar()->getInit();
+    return Begin->isInstantiationDependent() || 
End->isInstantiationDependent();
+  }
+
   case CXXExpansionStmtPattern::ExpansionStmtKind::Dependent:
+    return true;
+
+  case CXXExpansionStmtPattern::ExpansionStmtKind::Destructuring:
     llvm_unreachable("TODO");
   }
 
   llvm_unreachable("invalid pattern kind");
 }
 
+static IterableExpansionStmtData
+TryBuildIterableExpansionStmtInitializer(Sema &S, Expr *ExpansionInitializer,
+                                         Expr *Index, SourceLocation ColonLoc,
+                                         bool VarIsConstexpr) {
+  IterableExpansionStmtData Data;
+
+  // C++26 [stmt.expand]p3: An expression is expansion-iterable if it does not
+  // have array type [...]
+  QualType Ty = ExpansionInitializer->getType().getNonReferenceType();
+  if (Ty->isArrayType())
+    return Data;
+
+  // Lookup member and ADL 'begin()'/'end()'. Only check if they exist; even if
+  // they're deleted, inaccessible, etc., this is still an iterating expansion
+  // statement, albeit an ill-formed one.
+  DeclarationNameInfo BeginName(&S.PP.getIdentifierTable().get("begin"),
+                                ColonLoc);
+  DeclarationNameInfo EndName(&S.PP.getIdentifierTable().get("end"), ColonLoc);
+
+  // Try member lookup first.
+  bool FoundBeginEnd = false;
+  if (auto *Record = Ty->getAsCXXRecordDecl()) {
+    LookupResult BeginLR(S, BeginName, Sema::LookupMemberName);
+    LookupResult EndLR(S, EndName, Sema::LookupMemberName);
+    FoundBeginEnd = S.LookupQualifiedName(BeginLR, Record) &&
+                    S.LookupQualifiedName(EndLR, Record);
+  }
+
+  // Try ADL.
+  //
+  // If overload resolution for 'begin()' *and* 'end()' succeeds (irrespective
+  // of whether it results in a usable candidate), then assume this is an
+  // iterating expansion statement.
+  auto HasADLCandidate = [&](DeclarationName Name) {
+    OverloadCandidateSet Candidates(ColonLoc, 
OverloadCandidateSet::CSK_Normal);
+    OverloadCandidateSet::iterator Best;
+
+    S.AddArgumentDependentLookupCandidates(Name, ColonLoc, 
ExpansionInitializer,
+                                           /*ExplicitTemplateArgs=*/nullptr,
+                                           Candidates);
+
+    return Candidates.BestViableFunction(S, ColonLoc, Best) !=
+           OR_No_Viable_Function;
+  };
+
+  if (!FoundBeginEnd && (!HasADLCandidate(BeginName.getName()) ||
+                         !HasADLCandidate(EndName.getName())))
+    return Data;
+
+  auto Ctx = Sema::ExpressionEvaluationContext::PotentiallyEvaluated;
+  if (VarIsConstexpr)
+    Ctx = Sema::ExpressionEvaluationContext::ImmediateFunctionContext;
+  EnterExpressionEvaluationContext ExprEvalCtx(S, Ctx);
+
+  // The declarations should be attached to the parent decl context.
+  Sema::ContextRAII CtxGuard(
+      S, S.CurContext->getEnclosingNonExpansionStatementContext(),
+      /*NewThis=*/false);
+
+  // Ok, we know that this is supposed to be an iterable expansion statement;
+  // delegate to the for-range code to build the range/begin/end variables.
+  //
+  // Any failure at this point is a hard error.
+  Data.TheState = IterableExpansionStmtData::State::Error;
+  Scope *Scope = S.getCurScope();
+
+  // TODO: CWG 3131 changes how this range is declared.
+  StmtResult Var = S.BuildCXXForRangeRangeVar(Scope, ExpansionInitializer,
+                                              /*ForExpansionStmt=*/true);
+  if (Var.isInvalid())
+    return Data;
+
+  auto *RangeVar = cast<DeclStmt>(Var.get());
+  Sema::ForRangeBeginEndInfo Info = S.BuildCXXForRangeBeginEndVars(
+      Scope, cast<VarDecl>(RangeVar->getSingleDecl()), ColonLoc,
+      /*CoawaitLoc=*/{},
+      /*LifetimeExtendTemps=*/{}, Sema::BFRK_Build, /*ForExpansionStmt=*/true);
+
+  if (!Info.isValid())
+    return Data;
+
+  StmtResult BeginStmt = S.ActOnDeclStmt(
+      S.ConvertDeclToDeclGroup(Info.BeginVar), ColonLoc, ColonLoc);
+  StmtResult EndStmt = S.ActOnDeclStmt(S.ConvertDeclToDeclGroup(Info.EndVar),
+                                       ColonLoc, ColonLoc);
+  if (BeginStmt.isInvalid() || EndStmt.isInvalid())
+    return Data;
+
+  // Build '*(begin + i)'.
+  DeclRefExpr *Begin = S.BuildDeclRefExpr(
+      Info.BeginVar, Info.BeginVar->getType().getNonReferenceType(), VK_LValue,
+      ColonLoc);
+
+  ExprResult BeginPlusI =
+      S.ActOnBinOp(Scope, ColonLoc, tok::plus, Begin, Index);
+  if (BeginPlusI.isInvalid())
+    return Data;
+
+  ExprResult Deref =
+      S.ActOnUnaryOp(Scope, ColonLoc, tok::star, BeginPlusI.get());
+  if (Deref.isInvalid())
+    return Data;
+
+  Deref = S.MaybeCreateExprWithCleanups(Deref.get());
+  Data.BeginDecl = BeginStmt.getAs<DeclStmt>();
+  Data.EndDecl = EndStmt.getAs<DeclStmt>();
+  Data.RangeDecl = RangeVar;
+  Data.Initializer = Deref.get();
+  Data.TheState = IterableExpansionStmtData::State::Ok;
+  return Data;
+}
+
 CXXExpansionStmtDecl *
 Sema::ActOnCXXExpansionStmtDecl(unsigned TemplateDepth,
                                 SourceLocation TemplateKWLoc) {
@@ -134,8 +272,26 @@ StmtResult Sema::ActOnCXXExpansionStmtPattern(
                                                    ColonLoc, RParenLoc);
   }
 
-  Diag(ESD->getLocation(), diag::err_expansion_statements_todo);
-  return StmtError();
+  if (ExpansionInitializer->hasPlaceholderType()) {
+    ExprResult R = CheckPlaceholderExpr(ExpansionInitializer);
+    if (R.isInvalid())
+      return StmtError();
+    ExpansionInitializer = R.get();
+  }
+
+  if (DiagnoseUnexpandedParameterPack(ExpansionInitializer))
+    return StmtError();
+
+  // Reject lambdas early.
+  if (auto *RD = ExpansionInitializer->getType()->getAsCXXRecordDecl();
+      RD && RD->isLambda()) {
+    Diag(ExpansionInitializer->getBeginLoc(), diag::err_expansion_stmt_lambda);
+    return StmtError();
+  }
+
+  return BuildNonEnumeratingCXXExpansionStmtPattern(
+      ESD, Init, DS, ExpansionInitializer, LParenLoc, ColonLoc, RParenLoc,
+      LifetimeExtendTemps);
 }
 
 StmtResult Sema::BuildCXXEnumeratingExpansionStmtPattern(
@@ -146,6 +302,43 @@ StmtResult Sema::BuildCXXEnumeratingExpansionStmtPattern(
       cast<DeclStmt>(ExpansionVar), LParenLoc, ColonLoc, RParenLoc);
 }
 
+StmtResult Sema::BuildNonEnumeratingCXXExpansionStmtPattern(
+    CXXExpansionStmtDecl *ESD, Stmt *Init, DeclStmt *ExpansionVarStmt,
+    Expr *ExpansionInitializer, SourceLocation LParenLoc,
+    SourceLocation ColonLoc, SourceLocation RParenLoc,
+    ArrayRef<MaterializeTemporaryExpr *> LifetimeExtendTemps) {
+  VarDecl *ExpansionVar = cast<VarDecl>(ExpansionVarStmt->getSingleDecl());
+
+  if (ExpansionInitializer->isTypeDependent()) {
+    ActOnDependentForRangeInitializer(ExpansionVar, BFRK_Build);
+    return new (Context) CXXDependentExpansionStmtPattern(
+        ESD, Init, ExpansionVarStmt, ExpansionInitializer, LParenLoc, ColonLoc,
+        RParenLoc);
+  }
+
+  // Otherwise, if it can be an iterating expansion statement, it is one.
+  DeclRefExpr *Index = BuildIndexDRE(*this, ESD);
+  IterableExpansionStmtData Data = TryBuildIterableExpansionStmtInitializer(
+      *this, ExpansionInitializer, Index, ColonLoc,
+      ExpansionVar->isConstexpr());
+  if (Data.hasError()) {
+    ActOnInitializerError(ExpansionVar);
+    return StmtError();
+  }
+
+  if (Data.isIterable()) {
+    if (FinaliseExpansionVar(*this, ExpansionVar, Data.Initializer))
+      return StmtError();
+
+    return new (Context) CXXIteratingExpansionStmtPattern(
+        ESD, Init, ExpansionVarStmt, Data.RangeDecl, Data.BeginDecl,
+        Data.EndDecl, LParenLoc, ColonLoc, RParenLoc);
+  }
+
+  Diag(ESD->getLocation(), diag::err_expansion_statements_todo);
+  return StmtError();
+}
+
 StmtResult Sema::FinishCXXExpansionStmt(Stmt *Exp, Stmt *Body) {
   if (!Exp || !Body)
     return StmtError();
@@ -168,7 +361,13 @@ StmtResult Sema::FinishCXXExpansionStmt(Stmt *Exp, Stmt 
*Body) {
   if (Expansion->getInit())
     Shared.push_back(Expansion->getInit());
 
-  assert(Expansion->isEnumerating() && "TODO");
+  if (Expansion->isIterating()) {
+    Shared.push_back(Expansion->getRangeVarStmt());
+    Shared.push_back(Expansion->getBeginVarStmt());
+    Shared.push_back(Expansion->getEndVarStmt());
+  } else {
+    assert(Expansion->isEnumerating() && "TODO");
+  }
 
   // Return an empty statement if the range is empty.
   if (*NumInstantiations == 0) {
@@ -243,5 +442,53 @@ Sema::ComputeExpansionSize(CXXExpansionStmtPattern 
*Expansion) {
         ->getRangeExpr()
         ->getNumInits();
 
+  // By [stmt.expand]5.2, N is the result of evaluating the expression
+  //
+  // [] consteval {
+  //    std::ptrdiff_t result = 0;
+  //    for (auto i = begin; i != end; ++i) ++result;
+  //    return result;
+  // }()
+  // TODO: CWG 3131 changes this lambda a bit.
+  if (auto *Iterating = dyn_cast<CXXIteratingExpansionStmtPattern>(Expansion)) 
{
+    EnterExpressionEvaluationContext ExprEvalCtx(
+        *this, ExpressionEvaluationContext::ConstantEvaluated);
+
+    // FIXME: Actually do that; unfortunately, conjuring a lambda out of thin
+    // air in Sema is a massive pain, so for now just cheat by computing
+    // 'end - begin'.
+    SourceLocation Loc = Iterating->getColonLoc();
+    DeclRefExpr *Begin = BuildDeclRefExpr(
+        Iterating->getBeginVar(),
+        Iterating->getBeginVar()->getType().getNonReferenceType(), VK_LValue,
+        Loc);
+
+    DeclRefExpr *End = BuildDeclRefExpr(
+        Iterating->getEndVar(),
+        Iterating->getEndVar()->getType().getNonReferenceType(), VK_LValue,
+        Loc);
+
+    ExprResult N = ActOnBinOp(getCurScope(), Loc, tok::minus, End, Begin);
+    if (N.isInvalid())
+      return std::nullopt;
+
+    Expr::EvalResult ER;
+    SmallVector<PartialDiagnosticAt, 4> Notes;
+    ER.Diag = &Notes;
+    if (!N.get()->EvaluateAsInt(ER, Context)) {
+      Diag(Loc, diag::err_expansion_size_expr_not_ice);
+      for (const auto &[Location, PDiag] : Notes)
+        Diag(Location, PDiag);
+      return std::nullopt;
+    }
+
+    if (ER.Val.getInt().isNegative()) {
+      Diag(Loc, diag::err_expansion_size_negative) << ER.Val.getInt();
+      return std::nullopt;
+    }
+
+    return ER.Val.getInt().getZExtValue();
+  }
+
   llvm_unreachable("TODO");
 }
diff --git a/clang/lib/Sema/SemaStmt.cpp b/clang/lib/Sema/SemaStmt.cpp
index da1005f8a6d80..415aa6b0b4120 100644
--- a/clang/lib/Sema/SemaStmt.cpp
+++ b/clang/lib/Sema/SemaStmt.cpp
@@ -2411,6 +2411,11 @@ void NoteForRangeBeginEndFunction(Sema &SemaRef, Expr *E,
 /// Build a variable declaration for a for-range statement.
 VarDecl *BuildForRangeVarDecl(Sema &SemaRef, SourceLocation Loc, QualType Type,
                               StringRef Name, bool ForExpansionStmt) {
+  // Making the variable constexpr doesn't automatically add 'const' to the
+  // type, so do that now.
+  if (ForExpansionStmt && !Type->isReferenceType())
+    Type = Type.withConst();
+
   DeclContext *DC = SemaRef.CurContext;
   IdentifierInfo *II = &SemaRef.PP.getIdentifierTable().get(Name);
   TypeSourceInfo *TInfo = SemaRef.Context.getTrivialTypeSourceInfo(Type, Loc);
@@ -2418,6 +2423,9 @@ VarDecl *BuildForRangeVarDecl(Sema &SemaRef, 
SourceLocation Loc, QualType Type,
                                   TInfo, SC_None);
   Decl->setImplicit();
   Decl->setCXXForRangeImplicitVar(true);
+  if (ForExpansionStmt)
+    // CWG 3044: Do not make the variable 'static'.
+    Decl->setConstexpr(true);
   return Decl;
 }
 }
@@ -2731,7 +2739,10 @@ Sema::ForRangeBeginEndInfo 
Sema::BuildCXXForRangeBeginEndVars(
     return {};
 
   // P2718R0 - Lifetime extension in range-based for loops.
-  if (getLangOpts().CPlusPlus23)
+  //
+  // CWG 3043 – Do not apply lifetime extension to iterating
+  // expansion statements.
+  if (getLangOpts().CPlusPlus23 && !ForExpansionStmt)
     ApplyForRangeOrExpansionStatementLifetimeExtension(RangeVar,
                                                        LifetimeExtendTemps);
 
diff --git a/clang/lib/Sema/TreeTransform.h b/clang/lib/Sema/TreeTransform.h
index fc646f5051905..82d67c32e17c9 100644
--- a/clang/lib/Sema/TreeTransform.h
+++ b/clang/lib/Sema/TreeTransform.h
@@ -9323,6 +9323,20 @@ StmtResult 
TreeTransform<Derived>::TransformCXXExpansionStmtPattern(
     NewPattern = CXXExpansionStmtPattern::CreateEnumerating(
         SemaRef.Context, NewESD, Init, ExpansionVarStmt, S->getLParenLoc(),
         S->getColonLoc(), S->getRParenLoc());
+  } else if (S->isIterating()) {
+    StmtResult Range = getDerived().TransformStmt(S->getRangeVarStmt());
+    if (Range.isInvalid())
+      return StmtError();
+
+    StmtResult Begin = getDerived().TransformStmt(S->getBeginVarStmt());
+    StmtResult End = getDerived().TransformStmt(S->getEndVarStmt());
+    if (Begin.isInvalid() || End.isInvalid())
+      return StmtError();
+
+    NewPattern = CXXExpansionStmtPattern::CreateIterating(
+        SemaRef.Context, NewESD, Init, ExpansionVarStmt,
+        Range.getAs<DeclStmt>(), Begin.getAs<DeclStmt>(), 
End.getAs<DeclStmt>(),
+        S->getLParenLoc(), S->getColonLoc(), S->getRParenLoc());
   } else {
     llvm_unreachable("TODO");
   }

>From baf1c8bba91ff6996f6d85d3aef3b63639c71175 Mon Sep 17 00:00:00 2001
From: Sirraide <[email protected]>
Date: Mon, 1 Dec 2025 19:12:52 +0100
Subject: [PATCH 2/9] Reject incomplete types and vlas

---
 clang/include/clang/Basic/DiagnosticSemaKinds.td |  4 ++++
 clang/lib/Sema/SemaExpand.cpp                    | 11 +++++++++++
 2 files changed, 15 insertions(+)

diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td 
b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index 96292d0a4e306..3d335549e1b99 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -3702,6 +3702,10 @@ def err_conflicting_codeseg_attribute : Error<
 def warn_duplicate_codeseg_attribute : Warning<
   "duplicate code segment specifiers">, InGroup<Section>;
 
+def err_expansion_stmt_vla : Error<
+  "cannot expand variable length array type %0">;
+def err_expansion_stmt_incomplete : Error<
+  "cannot expand expression of incomplete type %0">;
 def err_expansion_stmt_lambda : Error<
   "cannot expand lambda closure type">;
 
diff --git a/clang/lib/Sema/SemaExpand.cpp b/clang/lib/Sema/SemaExpand.cpp
index 4e392e33578e2..a8496720ad39e 100644
--- a/clang/lib/Sema/SemaExpand.cpp
+++ b/clang/lib/Sema/SemaExpand.cpp
@@ -316,6 +316,17 @@ StmtResult 
Sema::BuildNonEnumeratingCXXExpansionStmtPattern(
         RParenLoc);
   }
 
+  if (RequireCompleteType(ExpansionInitializer->getExprLoc(),
+                          ExpansionInitializer->getType(),
+                          diag::err_expansion_stmt_incomplete))
+    return StmtError();
+
+  if (ExpansionInitializer->getType()->isVariableArrayType()) {
+    Diag(ExpansionInitializer->getExprLoc(), diag::err_expansion_stmt_vla)
+        << ExpansionInitializer->getType();
+    return StmtError();
+  }
+
   // Otherwise, if it can be an iterating expansion statement, it is one.
   DeclRefExpr *Index = BuildIndexDRE(*this, ESD);
   IterableExpansionStmtData Data = TryBuildIterableExpansionStmtInitializer(

>From c415e6413cc8db5f231a243488ada683fd797c7b Mon Sep 17 00:00:00 2001
From: Sirraide <[email protected]>
Date: Tue, 2 Dec 2025 21:52:48 +0100
Subject: [PATCH 3/9] Reject lambdas in Build instead of ActOn

---
 clang/lib/Sema/SemaExpand.cpp | 14 +++++++-------
 1 file changed, 7 insertions(+), 7 deletions(-)

diff --git a/clang/lib/Sema/SemaExpand.cpp b/clang/lib/Sema/SemaExpand.cpp
index a8496720ad39e..bea0374b53f41 100644
--- a/clang/lib/Sema/SemaExpand.cpp
+++ b/clang/lib/Sema/SemaExpand.cpp
@@ -282,13 +282,6 @@ StmtResult Sema::ActOnCXXExpansionStmtPattern(
   if (DiagnoseUnexpandedParameterPack(ExpansionInitializer))
     return StmtError();
 
-  // Reject lambdas early.
-  if (auto *RD = ExpansionInitializer->getType()->getAsCXXRecordDecl();
-      RD && RD->isLambda()) {
-    Diag(ExpansionInitializer->getBeginLoc(), diag::err_expansion_stmt_lambda);
-    return StmtError();
-  }
-
   return BuildNonEnumeratingCXXExpansionStmtPattern(
       ESD, Init, DS, ExpansionInitializer, LParenLoc, ColonLoc, RParenLoc,
       LifetimeExtendTemps);
@@ -327,6 +320,13 @@ StmtResult 
Sema::BuildNonEnumeratingCXXExpansionStmtPattern(
     return StmtError();
   }
 
+  // Reject lambdas early.
+  if (auto *RD = ExpansionInitializer->getType()->getAsCXXRecordDecl();
+      RD && RD->isLambda()) {
+    Diag(ExpansionInitializer->getBeginLoc(), diag::err_expansion_stmt_lambda);
+    return StmtError();
+  }
+
   // Otherwise, if it can be an iterating expansion statement, it is one.
   DeclRefExpr *Index = BuildIndexDRE(*this, ESD);
   IterableExpansionStmtData Data = TryBuildIterableExpansionStmtInitializer(

>From f99e06481490f38583563f76e58b75dd82f07b5d Mon Sep 17 00:00:00 2001
From: Sirraide <[email protected]>
Date: Wed, 3 Dec 2025 01:50:44 +0100
Subject: [PATCH 4/9] Properly compute iterating expansion stmt size

---
 clang/lib/Sema/SemaExpand.cpp | 191 +++++++++++++++++++++++++++++-----
 1 file changed, 167 insertions(+), 24 deletions(-)

diff --git a/clang/lib/Sema/SemaExpand.cpp b/clang/lib/Sema/SemaExpand.cpp
index bea0374b53f41..be772a9ce7c6b 100644
--- a/clang/lib/Sema/SemaExpand.cpp
+++ b/clang/lib/Sema/SemaExpand.cpp
@@ -21,6 +21,8 @@
 #include "clang/Sema/Sema.h"
 #include "clang/Sema/Template.h"
 
+#include <llvm/ADT/ScopeExit.h>
+
 using namespace clang;
 using namespace sema;
 
@@ -302,6 +304,13 @@ StmtResult 
Sema::BuildNonEnumeratingCXXExpansionStmtPattern(
     ArrayRef<MaterializeTemporaryExpr *> LifetimeExtendTemps) {
   VarDecl *ExpansionVar = cast<VarDecl>(ExpansionVarStmt->getSingleDecl());
 
+  // Reject lambdas early.
+  if (auto *RD = ExpansionInitializer->getType()->getAsCXXRecordDecl();
+      RD && RD->isLambda()) {
+    Diag(ExpansionInitializer->getBeginLoc(), diag::err_expansion_stmt_lambda);
+    return StmtError();
+  }
+
   if (ExpansionInitializer->isTypeDependent()) {
     ActOnDependentForRangeInitializer(ExpansionVar, BFRK_Build);
     return new (Context) CXXDependentExpansionStmtPattern(
@@ -320,13 +329,6 @@ StmtResult 
Sema::BuildNonEnumeratingCXXExpansionStmtPattern(
     return StmtError();
   }
 
-  // Reject lambdas early.
-  if (auto *RD = ExpansionInitializer->getType()->getAsCXXRecordDecl();
-      RD && RD->isLambda()) {
-    Diag(ExpansionInitializer->getBeginLoc(), diag::err_expansion_stmt_lambda);
-    return StmtError();
-  }
-
   // Otherwise, if it can be an iterating expansion statement, it is one.
   DeclRefExpr *Index = BuildIndexDRE(*this, ESD);
   IterableExpansionStmtData Data = TryBuildIterableExpansionStmtInitializer(
@@ -362,6 +364,18 @@ StmtResult Sema::FinishCXXExpansionStmt(Stmt *Exp, Stmt 
*Body) {
   if (HasDependentSize(Expansion))
     return Expansion;
 
+  // Now that we're expanding this, exit the context of the expansion stmt
+  // so that we no longer treat this as dependent.
+  ContextRAII CtxGuard(*this, CurContext->getParent(),
+                       /*NewThis=*/false);
+
+  // Even if the size isn't technically dependent, delay expansion until
+  // we're no longer in a template if this is an iterating expansion statement
+  // since evaluating a lambda declared in a template doesn't work too well.
+  if (CurContext->isDependentContext() &&
+      isa<CXXIteratingExpansionStmtPattern>(Expansion))
+    return Expansion;
+
   // This can fail if this is an iterating expansion statement.
   std::optional<uint64_t> NumInstantiations = ComputeExpansionSize(Expansion);
   if (!NumInstantiations)
@@ -399,11 +413,6 @@ StmtResult Sema::FinishCXXExpansionStmt(Stmt *Exp, Stmt 
*Body) {
   SmallVector<Stmt *, 4> Instantiations;
   CXXExpansionStmtDecl *ESD = Expansion->getDecl();
   for (uint64_t I = 0; I < *NumInstantiations; ++I) {
-    // Now that we're expanding this, exit the context of the expansion stmt
-    // so that we no longer treat this as dependent.
-    ContextRAII CtxGuard(*this, CurContext->getParent(),
-                         /*NewThis=*/false);
-
     TemplateArgument Arg{Context, llvm::APSInt::get(I),
                          Context.getPointerDiffType()};
     MultiLevelTemplateArgumentList MTArgList(ESD, Arg, true);
@@ -462,42 +471,176 @@ Sema::ComputeExpansionSize(CXXExpansionStmtPattern 
*Expansion) {
   // }()
   // TODO: CWG 3131 changes this lambda a bit.
   if (auto *Iterating = dyn_cast<CXXIteratingExpansionStmtPattern>(Expansion)) 
{
+    SourceLocation Loc = Expansion->getColonLoc();
     EnterExpressionEvaluationContext ExprEvalCtx(
         *this, ExpressionEvaluationContext::ConstantEvaluated);
 
-    // FIXME: Actually do that; unfortunately, conjuring a lambda out of thin
-    // air in Sema is a massive pain, so for now just cheat by computing
-    // 'end - begin'.
-    SourceLocation Loc = Iterating->getColonLoc();
+    // This is mostly copied from ParseLambdaExpressionAfterIntroducer().
+    ParseScope LambdaScope(*this, Scope::LambdaScope | Scope::DeclScope |
+                                      Scope::FunctionDeclarationScope |
+                                      Scope::FunctionPrototypeScope);
+    AttributeFactory AttrFactory;
+    LambdaIntroducer Intro;
+    Intro.Range = SourceRange(Loc, Loc);
+    Intro.Default = LCD_ByRef; // CWG 3131
+    Intro.DefaultLoc = Loc;
+    DeclSpec DS(AttrFactory);
+    Declarator D(DS, ParsedAttributesView::none(),
+                 DeclaratorContext::LambdaExpr);
+    PushLambdaScope();
+    ActOnLambdaExpressionAfterIntroducer(Intro, getCurScope());
+
+    // Make the lambda 'consteval'.
+    {
+      ParseScope Prototype(*this, Scope::FunctionPrototypeScope |
+                                     Scope::FunctionDeclarationScope |
+                                     Scope::DeclScope);
+      const char* PrevSpec = nullptr;
+      unsigned DiagId = 0;
+      DS.SetConstexprSpec(ConstexprSpecKind::Consteval, Loc, PrevSpec, DiagId);
+      assert(DiagId == 0 && PrevSpec == nullptr);
+      ActOnLambdaClosureParameters(getCurScope(), /*ParamInfo=*/{});
+      ActOnLambdaClosureQualifiers(Intro, /*MutableLoc=*/SourceLocation());
+    }
+
+    ParseScope BodyScope(*this, Scope::BlockScope | Scope::FnScope |
+                                    Scope::DeclScope |
+                                    Scope::CompoundStmtScope);
+
+    ActOnStartOfLambdaDefinition(Intro, D, DS);
+
+    // Enter the compound statement that is the lambda body.
+    ActOnStartOfCompoundStmt(/*IsStmtExpr=*/false);
+    ActOnAfterCompoundStatementLeadingPragmas();
+    auto PopScopesOnReturn = llvm::make_scope_exit([&] {
+      ActOnFinishOfCompoundStmt();
+      ActOnLambdaError(Loc, getCurScope());
+    });
+
+    // std::ptrdiff_t result = 0;
+    QualType PtrDiffT = Context.getPointerDiffType();
+    VarDecl *ResultVar = VarDecl::Create(
+        Context, CurContext, Loc, Loc, 
&PP.getIdentifierTable().get("__result"),
+        PtrDiffT, Context.getTrivialTypeSourceInfo(PtrDiffT, Loc), SC_None);
+    Expr *Zero = ActOnIntegerConstant(Loc, 0).get();
+    AddInitializerToDecl(ResultVar, Zero, false);
+    StmtResult ResultVarStmt =
+        ActOnDeclStmt(ConvertDeclToDeclGroup(ResultVar), Loc, Loc);
+    if (ResultVarStmt.isInvalid() || ResultVar->isInvalidDecl())
+      return std::nullopt;
+
+    // Start the for loop.
+    ParseScope ForScope(*this, Scope::DeclScope | Scope::ControlScope);
+
+    // auto i = begin;
+    VarDecl *IterationVar = VarDecl::Create(
+        Context, CurContext, Loc, Loc, &PP.getIdentifierTable().get("__i"),
+        Context.getAutoDeductType(),
+        Context.getTrivialTypeSourceInfo(Context.getAutoDeductType(), Loc),
+        SC_None);
     DeclRefExpr *Begin = BuildDeclRefExpr(
         Iterating->getBeginVar(),
         Iterating->getBeginVar()->getType().getNonReferenceType(), VK_LValue,
         Loc);
+    AddInitializerToDecl(IterationVar, Begin, false);
+    StmtResult IterationVarStmt =
+        ActOnDeclStmt(ConvertDeclToDeclGroup(IterationVar), Loc, Loc);
+    if (IterationVarStmt.isInvalid() || IterationVar->isInvalidDecl())
+      return std::nullopt;
 
+    // i != end
+    DeclRefExpr *IterationVarDeclRef = BuildDeclRefExpr(
+        IterationVar, IterationVar->getType().getNonReferenceType(), VK_LValue,
+        Loc);
     DeclRefExpr *End = BuildDeclRefExpr(
         Iterating->getEndVar(),
         Iterating->getEndVar()->getType().getNonReferenceType(), VK_LValue,
         Loc);
+    ExprResult NotEqual = ActOnBinOp(getCurScope(), Loc, tok::exclaimequal,
+                                     IterationVarDeclRef, End);
+    if (NotEqual.isInvalid())
+      return std::nullopt;
+    ConditionResult Condition = ActOnCondition(
+        getCurScope(), Loc, NotEqual.get(), ConditionKind::Boolean,
+        /*MissingOk=*/false);
+    if (Condition.isInvalid())
+      return std::nullopt;
+
+    // ++i
+    IterationVarDeclRef = BuildDeclRefExpr(
+        IterationVar, IterationVar->getType().getNonReferenceType(), VK_LValue,
+        Loc);
+    ExprResult Increment =
+        ActOnUnaryOp(getCurScope(), Loc, tok::plusplus, IterationVarDeclRef);
+    if (Increment.isInvalid())
+      return std::nullopt;
+    FullExprArg ThirdPart = MakeFullDiscardedValueExpr(Increment.get());
+
+    // Enter the body of the for loop.
+    ParseScope InnerScope(*this, Scope::DeclScope);
+    getCurScope()->decrementMSManglingNumber();
+
+    // ++result;
+    DeclRefExpr *ResultDeclRef = BuildDeclRefExpr(
+        ResultVar, ResultVar->getType().getNonReferenceType(), VK_LValue, Loc);
+    ExprResult IncrementResult =
+        ActOnUnaryOp(getCurScope(), Loc, tok::plusplus, ResultDeclRef);
+    if (IncrementResult.isInvalid())
+      return std::nullopt;
+    StmtResult IncrementStmt = ActOnExprStmt(IncrementResult.get());
+    if (IncrementStmt.isInvalid())
+      return std::nullopt;
 
-    ExprResult N = ActOnBinOp(getCurScope(), Loc, tok::minus, End, Begin);
-    if (N.isInvalid())
+    // Exit the for loop.
+    InnerScope.Exit();
+    ForScope.Exit();
+    StmtResult ForLoop =
+        ActOnForStmt(Loc, Loc, IterationVarStmt.get(), Condition, ThirdPart,
+                     Loc, IncrementStmt.get());
+    if (ForLoop.isInvalid())
+      return std::nullopt;
+
+    // return result;
+    ResultDeclRef = BuildDeclRefExpr(
+        ResultVar, ResultVar->getType().getNonReferenceType(), VK_LValue, Loc);
+    StmtResult Return = ActOnReturnStmt(Loc, ResultDeclRef, getCurScope());
+    if (Return.isInvalid())
+      return std::nullopt;
+
+    // Finally, we can build the compound statement that is the lambda body.
+    StmtResult LambdaBody = ActOnCompoundStmt(
+        Loc, Loc, {ResultVarStmt.get(), ForLoop.get(), Return.get()},
+        /*isStmtExpr=*/false);
+    if (LambdaBody.isInvalid())
+      return std::nullopt;
+
+    ActOnFinishOfCompoundStmt();
+    BodyScope.Exit();
+    LambdaScope.Exit();
+    PopScopesOnReturn.release();
+    ExprResult Lambda = ActOnLambdaExpr(Loc, LambdaBody.get());
+    if (Lambda.isInvalid())
+      return std::nullopt;
+
+    // Invoke the lambda.
+    ExprResult Call =
+        ActOnCallExpr(getCurScope(), Lambda.get(), Loc, /*ArgExprs=*/{}, Loc);
+    if (Call.isInvalid())
       return std::nullopt;
 
     Expr::EvalResult ER;
     SmallVector<PartialDiagnosticAt, 4> Notes;
     ER.Diag = &Notes;
-    if (!N.get()->EvaluateAsInt(ER, Context)) {
+    if (!Call.get()->EvaluateAsInt(ER, Context)) {
       Diag(Loc, diag::err_expansion_size_expr_not_ice);
       for (const auto &[Location, PDiag] : Notes)
         Diag(Location, PDiag);
       return std::nullopt;
     }
 
-    if (ER.Val.getInt().isNegative()) {
-      Diag(Loc, diag::err_expansion_size_negative) << ER.Val.getInt();
-      return std::nullopt;
-    }
-
+    // It shouldn't be possible for this to be negative since we compute this
+    // via the built-in '++' on a ptrdiff_t.
+    assert(ER.Val.getInt().isNonNegative());
     return ER.Val.getInt().getZExtValue();
   }
 

>From c9f4cbca5d6103287f36bb4c9890554346b76253 Mon Sep 17 00:00:00 2001
From: Sirraide <[email protected]>
Date: Wed, 3 Dec 2025 01:52:54 +0100
Subject: [PATCH 5/9] Remove unused diagnostic

---
 clang/include/clang/Basic/DiagnosticSemaKinds.td | 2 --
 1 file changed, 2 deletions(-)

diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td 
b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index 3d335549e1b99..3c3589e0ae22a 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -167,8 +167,6 @@ def note_constexpr_assert_failed : Note<
   "assertion failed during evaluation of constant expression">;
 def err_expansion_size_expr_not_ice : Error<
   "expansion size is not a constant expression">;
-def err_expansion_size_negative : Error<
-  "expansion size must not be negative (was %0)">;
 
 // Semantic analysis of constant literals.
 def ext_predef_outside_function : Warning<

>From 8f0fb373336ca0f979a35959fbe1801114a98294 Mon Sep 17 00:00:00 2001
From: Sirraide <[email protected]>
Date: Wed, 3 Dec 2025 01:53:24 +0100
Subject: [PATCH 6/9] Formatting

---
 clang/lib/Sema/SemaExpand.cpp | 9 ++++-----
 1 file changed, 4 insertions(+), 5 deletions(-)

diff --git a/clang/lib/Sema/SemaExpand.cpp b/clang/lib/Sema/SemaExpand.cpp
index be772a9ce7c6b..76c562f538400 100644
--- a/clang/lib/Sema/SemaExpand.cpp
+++ b/clang/lib/Sema/SemaExpand.cpp
@@ -20,8 +20,7 @@
 #include "clang/Sema/Overload.h"
 #include "clang/Sema/Sema.h"
 #include "clang/Sema/Template.h"
-
-#include <llvm/ADT/ScopeExit.h>
+#include "llvm/ADT/ScopeExit.h"
 
 using namespace clang;
 using namespace sema;
@@ -493,9 +492,9 @@ Sema::ComputeExpansionSize(CXXExpansionStmtPattern 
*Expansion) {
     // Make the lambda 'consteval'.
     {
       ParseScope Prototype(*this, Scope::FunctionPrototypeScope |
-                                     Scope::FunctionDeclarationScope |
-                                     Scope::DeclScope);
-      const char* PrevSpec = nullptr;
+                                      Scope::FunctionDeclarationScope |
+                                      Scope::DeclScope);
+      const char *PrevSpec = nullptr;
       unsigned DiagId = 0;
       DS.SetConstexprSpec(ConstexprSpecKind::Consteval, Loc, PrevSpec, DiagId);
       assert(DiagId == 0 && PrevSpec == nullptr);

>From fda444a41343a66abfe86ef4428a67a1c33ebd08 Mon Sep 17 00:00:00 2001
From: Sirraide <[email protected]>
Date: Wed, 3 Dec 2025 03:19:19 +0100
Subject: [PATCH 7/9] CWG 3131

---
 clang/lib/Sema/SemaExpand.cpp | 107 ++++++++++++++++++++--------------
 clang/lib/Sema/SemaStmt.cpp   |  18 ++++--
 2 files changed, 78 insertions(+), 47 deletions(-)

diff --git a/clang/lib/Sema/SemaExpand.cpp b/clang/lib/Sema/SemaExpand.cpp
index 76c562f538400..13d981d7b426a 100644
--- a/clang/lib/Sema/SemaExpand.cpp
+++ b/clang/lib/Sema/SemaExpand.cpp
@@ -157,17 +157,34 @@ TryBuildIterableExpansionStmtInitializer(Sema &S, Expr 
*ExpansionInitializer,
   Data.TheState = IterableExpansionStmtData::State::Error;
   Scope *Scope = S.getCurScope();
 
-  // TODO: CWG 3131 changes how this range is declared.
-  StmtResult Var = S.BuildCXXForRangeRangeVar(Scope, ExpansionInitializer,
-                                              /*ForExpansionStmt=*/true);
+  // CWG 3131: The declaration of 'range' is of the form
+  //
+  //     constexpr[opt] decltype(auto) range = (expansion-initializer);
+  //
+  // where 'constexpr' is present iff the for-range-declaration is 'constexpr'.
+  StmtResult Var = S.BuildCXXForRangeRangeVar(
+      Scope, S.ActOnParenExpr(ColonLoc, ColonLoc, ExpansionInitializer).get(),
+      S.Context.getAutoType(QualType(), AutoTypeKeyword::DecltypeAuto,
+                            /*IsDependent*/ false),
+      VarIsConstexpr);
   if (Var.isInvalid())
     return Data;
 
+  // CWG 3131: Discussion around this core issue (though as of the time of
+  // writing not the resolution itself) suggests that the other variables we
+  // create here should likewise be 'constexpr' iff the range variable is
+  // declared 'constexpr'.
+  //
+  // FIXME: As of CWG 3131, 'end' is no longer used outside the lambda that
+  // performs the size calculation (despite that, CWG 3131 currently still
+  // lists it in the generated code, but this is likely an oversight). Ideally,
+  // we should only create 'begin' here instead, but that requires another
+  // substantial refactor of the for-range code.
   auto *RangeVar = cast<DeclStmt>(Var.get());
   Sema::ForRangeBeginEndInfo Info = S.BuildCXXForRangeBeginEndVars(
       Scope, cast<VarDecl>(RangeVar->getSingleDecl()), ColonLoc,
       /*CoawaitLoc=*/{},
-      /*LifetimeExtendTemps=*/{}, Sema::BFRK_Build, /*ForExpansionStmt=*/true);
+      /*LifetimeExtendTemps=*/{}, Sema::BFRK_Build, VarIsConstexpr);
 
   if (!Info.isValid())
     return Data;
@@ -269,6 +286,10 @@ StmtResult Sema::ActOnCXXExpansionStmtPattern(
     // Note that lifetime extension only applies to destructuring expansion
     // statements, so we just ignore 'LifetimeExtendedTemps' entirely for other
     // types of expansion statements (this is CWG 3043).
+    //
+    // TODO: CWG 3131 makes it so the 'range' variable of an iterating
+    // expansion statement need no longer be 'constexpr'... so do we want
+    // lifetime extension for iterating expansion statements after all?
     return BuildCXXEnumeratingExpansionStmtPattern(ESD, Init, DS, LParenLoc,
                                                    ColonLoc, RParenLoc);
   }
@@ -461,14 +482,15 @@ Sema::ComputeExpansionSize(CXXExpansionStmtPattern 
*Expansion) {
         ->getRangeExpr()
         ->getNumInits();
 
-  // By [stmt.expand]5.2, N is the result of evaluating the expression
+  // CWG 3131: N is the result of evaluating the expression
   //
-  // [] consteval {
+  // [&] consteval {
   //    std::ptrdiff_t result = 0;
-  //    for (auto i = begin; i != end; ++i) ++result;
+  //    auto b = begin-expr;
+  //    auto e = end-expr;
+  //    for (; b != e; ++b) ++result;
   //    return result;
   // }()
-  // TODO: CWG 3131 changes this lambda a bit.
   if (auto *Iterating = dyn_cast<CXXIteratingExpansionStmtPattern>(Expansion)) 
{
     SourceLocation Loc = Expansion->getColonLoc();
     EnterExpressionEvaluationContext ExprEvalCtx(
@@ -531,32 +553,32 @@ Sema::ComputeExpansionSize(CXXExpansionStmtPattern 
*Expansion) {
     // Start the for loop.
     ParseScope ForScope(*this, Scope::DeclScope | Scope::ControlScope);
 
-    // auto i = begin;
-    VarDecl *IterationVar = VarDecl::Create(
-        Context, CurContext, Loc, Loc, &PP.getIdentifierTable().get("__i"),
-        Context.getAutoDeductType(),
-        Context.getTrivialTypeSourceInfo(Context.getAutoDeductType(), Loc),
-        SC_None);
-    DeclRefExpr *Begin = BuildDeclRefExpr(
-        Iterating->getBeginVar(),
-        Iterating->getBeginVar()->getType().getNonReferenceType(), VK_LValue,
-        Loc);
-    AddInitializerToDecl(IterationVar, Begin, false);
-    StmtResult IterationVarStmt =
-        ActOnDeclStmt(ConvertDeclToDeclGroup(IterationVar), Loc, Loc);
-    if (IterationVarStmt.isInvalid() || IterationVar->isInvalidDecl())
+    // auto b = begin-expr;
+    // auto e = end-expr;
+    ForRangeBeginEndInfo Info = BuildCXXForRangeBeginEndVars(
+        getCurScope(), Iterating->getRangeVar(), Loc,
+        /*CoawaitLoc=*/{},
+        /*LifetimeExtendTemps=*/{}, BFRK_Build, /*Constexpr=*/false);
+    if (!Info.isValid())
       return std::nullopt;
 
-    // i != end
-    DeclRefExpr *IterationVarDeclRef = BuildDeclRefExpr(
-        IterationVar, IterationVar->getType().getNonReferenceType(), VK_LValue,
-        Loc);
-    DeclRefExpr *End = BuildDeclRefExpr(
-        Iterating->getEndVar(),
-        Iterating->getEndVar()->getType().getNonReferenceType(), VK_LValue,
-        Loc);
-    ExprResult NotEqual = ActOnBinOp(getCurScope(), Loc, tok::exclaimequal,
-                                     IterationVarDeclRef, End);
+    StmtResult BeginStmt =
+        ActOnDeclStmt(ConvertDeclToDeclGroup(Info.BeginVar), Loc, Loc);
+    StmtResult EndStmt =
+        ActOnDeclStmt(ConvertDeclToDeclGroup(Info.EndVar), Loc, Loc);
+    if (BeginStmt.isInvalid() || EndStmt.isInvalid())
+      return std::nullopt;
+
+    // b != e
+    auto GetDeclRef = [&](VarDecl *VD) -> DeclRefExpr * {
+      return BuildDeclRefExpr(VD, VD->getType().getNonReferenceType(),
+                              VK_LValue, Loc);
+    };
+
+    DeclRefExpr *Begin = GetDeclRef(Info.BeginVar);
+    DeclRefExpr *End = GetDeclRef(Info.EndVar);
+    ExprResult NotEqual =
+        ActOnBinOp(getCurScope(), Loc, tok::exclaimequal, Begin, End);
     if (NotEqual.isInvalid())
       return std::nullopt;
     ConditionResult Condition = ActOnCondition(
@@ -565,12 +587,10 @@ Sema::ComputeExpansionSize(CXXExpansionStmtPattern 
*Expansion) {
     if (Condition.isInvalid())
       return std::nullopt;
 
-    // ++i
-    IterationVarDeclRef = BuildDeclRefExpr(
-        IterationVar, IterationVar->getType().getNonReferenceType(), VK_LValue,
-        Loc);
+    // ++b
+    Begin = GetDeclRef(Info.BeginVar);
     ExprResult Increment =
-        ActOnUnaryOp(getCurScope(), Loc, tok::plusplus, IterationVarDeclRef);
+        ActOnUnaryOp(getCurScope(), Loc, tok::plusplus, Begin);
     if (Increment.isInvalid())
       return std::nullopt;
     FullExprArg ThirdPart = MakeFullDiscardedValueExpr(Increment.get());
@@ -593,9 +613,8 @@ Sema::ComputeExpansionSize(CXXExpansionStmtPattern 
*Expansion) {
     // Exit the for loop.
     InnerScope.Exit();
     ForScope.Exit();
-    StmtResult ForLoop =
-        ActOnForStmt(Loc, Loc, IterationVarStmt.get(), Condition, ThirdPart,
-                     Loc, IncrementStmt.get());
+    StmtResult ForLoop = ActOnForStmt(Loc, Loc, /*First=*/nullptr, Condition,
+                                      ThirdPart, Loc, IncrementStmt.get());
     if (ForLoop.isInvalid())
       return std::nullopt;
 
@@ -607,9 +626,11 @@ Sema::ComputeExpansionSize(CXXExpansionStmtPattern 
*Expansion) {
       return std::nullopt;
 
     // Finally, we can build the compound statement that is the lambda body.
-    StmtResult LambdaBody = ActOnCompoundStmt(
-        Loc, Loc, {ResultVarStmt.get(), ForLoop.get(), Return.get()},
-        /*isStmtExpr=*/false);
+    StmtResult LambdaBody =
+        ActOnCompoundStmt(Loc, Loc,
+                          {ResultVarStmt.get(), BeginStmt.get(), EndStmt.get(),
+                           ForLoop.get(), Return.get()},
+                          /*isStmtExpr=*/false);
     if (LambdaBody.isInvalid())
       return std::nullopt;
 
diff --git a/clang/lib/Sema/SemaStmt.cpp b/clang/lib/Sema/SemaStmt.cpp
index 415aa6b0b4120..1ff6b4c2a2619 100644
--- a/clang/lib/Sema/SemaStmt.cpp
+++ b/clang/lib/Sema/SemaStmt.cpp
@@ -2410,10 +2410,10 @@ void NoteForRangeBeginEndFunction(Sema &SemaRef, Expr 
*E,
 
 /// Build a variable declaration for a for-range statement.
 VarDecl *BuildForRangeVarDecl(Sema &SemaRef, SourceLocation Loc, QualType Type,
-                              StringRef Name, bool ForExpansionStmt) {
+                              StringRef Name, bool Constexpr) {
   // Making the variable constexpr doesn't automatically add 'const' to the
   // type, so do that now.
-  if (ForExpansionStmt && !Type->isReferenceType())
+  if (Constexpr && !Type->isReferenceType())
     Type = Type.withConst();
 
   DeclContext *DC = SemaRef.CurContext;
@@ -2423,7 +2423,7 @@ VarDecl *BuildForRangeVarDecl(Sema &SemaRef, 
SourceLocation Loc, QualType Type,
                                   TInfo, SC_None);
   Decl->setImplicit();
   Decl->setCXXForRangeImplicitVar(true);
-  if (ForExpansionStmt)
+  if (Constexpr)
     // CWG 3044: Do not make the variable 'static'.
     Decl->setConstexpr(true);
   return Decl;
@@ -2742,7 +2742,17 @@ Sema::ForRangeBeginEndInfo 
Sema::BuildCXXForRangeBeginEndVars(
   //
   // CWG 3043 – Do not apply lifetime extension to iterating
   // expansion statements.
-  if (getLangOpts().CPlusPlus23 && !ForExpansionStmt)
+  //
+  // Note: CWG 3131 makes it so the 'range' variable need not be
+  // constexpr anymore, which means that we probably *do* want
+  // lifetime extension in that case after all, contrary to what
+  // CWG 3043 currently states. This just works out naturally with
+  // this implementation at the moment, but wg21 insist on no lifetime
+  // extension for iterating expansion statements, then this instead
+  // needs to check whether we're building this for an expansion statement
+  // instead of just applying lifetime extension if the variable isn't
+  // constexpr (or we could pass in an empty range for 'LifetimeExtendTemps').
+  if (getLangOpts().CPlusPlus23 && !Constexpr)
     ApplyForRangeOrExpansionStatementLifetimeExtension(RangeVar,
                                                        LifetimeExtendTemps);
 

>From e66c77b0a73509d9e9c8723ba70fa3c1cb5b5238 Mon Sep 17 00:00:00 2001
From: Sirraide <[email protected]>
Date: Wed, 3 Dec 2025 20:32:53 +0100
Subject: [PATCH 8/9] Add code that was accidentally deleted in rebase

---
 clang/lib/Sema/TreeTransform.h | 15 +++++++++++++++
 1 file changed, 15 insertions(+)

diff --git a/clang/lib/Sema/TreeTransform.h b/clang/lib/Sema/TreeTransform.h
index 82d67c32e17c9..ec9a14c4b26c7 100644
--- a/clang/lib/Sema/TreeTransform.h
+++ b/clang/lib/Sema/TreeTransform.h
@@ -9337,6 +9337,21 @@ StmtResult 
TreeTransform<Derived>::TransformCXXExpansionStmtPattern(
         SemaRef.Context, NewESD, Init, ExpansionVarStmt,
         Range.getAs<DeclStmt>(), Begin.getAs<DeclStmt>(), 
End.getAs<DeclStmt>(),
         S->getLParenLoc(), S->getColonLoc(), S->getRParenLoc());
+  } else if (S->isDependent()) {
+    ExprResult ExpansionInitializer =
+        getDerived().TransformExpr(S->getExpansionInitializer());
+    if (ExpansionInitializer.isInvalid())
+      return StmtError();
+
+    StmtResult Res = SemaRef.BuildNonEnumeratingCXXExpansionStmtPattern(
+        NewESD, Init, ExpansionVarStmt, ExpansionInitializer.get(),
+        S->getLParenLoc(), S->getColonLoc(), S->getRParenLoc(),
+        /*LifetimeExtendTemps=*/{});
+
+    if (Res.isInvalid())
+      return StmtError();
+
+    NewPattern = cast<CXXExpansionStmtPattern>(Res.get());
   } else {
     llvm_unreachable("TODO");
   }

>From 97db6032ab67e52f83408d7f81f6914415778b87 Mon Sep 17 00:00:00 2001
From: Sirraide <[email protected]>
Date: Wed, 3 Dec 2025 20:43:08 +0100
Subject: [PATCH 9/9] Merge all pattern kinds into a single AST node

---
 clang/lib/Sema/SemaExpand.cpp | 17 ++++++++---------
 1 file changed, 8 insertions(+), 9 deletions(-)

diff --git a/clang/lib/Sema/SemaExpand.cpp b/clang/lib/Sema/SemaExpand.cpp
index 13d981d7b426a..40891e96e97de 100644
--- a/clang/lib/Sema/SemaExpand.cpp
+++ b/clang/lib/Sema/SemaExpand.cpp
@@ -333,9 +333,9 @@ StmtResult Sema::BuildNonEnumeratingCXXExpansionStmtPattern(
 
   if (ExpansionInitializer->isTypeDependent()) {
     ActOnDependentForRangeInitializer(ExpansionVar, BFRK_Build);
-    return new (Context) CXXDependentExpansionStmtPattern(
-        ESD, Init, ExpansionVarStmt, ExpansionInitializer, LParenLoc, ColonLoc,
-        RParenLoc);
+    return CXXExpansionStmtPattern::CreateDependent(
+        Context, ESD, Init, ExpansionVarStmt, ExpansionInitializer, LParenLoc,
+        ColonLoc, RParenLoc);
   }
 
   if (RequireCompleteType(ExpansionInitializer->getExprLoc(),
@@ -363,8 +363,8 @@ StmtResult Sema::BuildNonEnumeratingCXXExpansionStmtPattern(
     if (FinaliseExpansionVar(*this, ExpansionVar, Data.Initializer))
       return StmtError();
 
-    return new (Context) CXXIteratingExpansionStmtPattern(
-        ESD, Init, ExpansionVarStmt, Data.RangeDecl, Data.BeginDecl,
+    return CXXExpansionStmtPattern::CreateIterating(
+        Context, ESD, Init, ExpansionVarStmt, Data.RangeDecl, Data.BeginDecl,
         Data.EndDecl, LParenLoc, ColonLoc, RParenLoc);
   }
 
@@ -392,8 +392,7 @@ StmtResult Sema::FinishCXXExpansionStmt(Stmt *Exp, Stmt 
*Body) {
   // Even if the size isn't technically dependent, delay expansion until
   // we're no longer in a template if this is an iterating expansion statement
   // since evaluating a lambda declared in a template doesn't work too well.
-  if (CurContext->isDependentContext() &&
-      isa<CXXIteratingExpansionStmtPattern>(Expansion))
+  if (CurContext->isDependentContext() && Expansion->isIterating())
     return Expansion;
 
   // This can fail if this is an iterating expansion statement.
@@ -491,7 +490,7 @@ Sema::ComputeExpansionSize(CXXExpansionStmtPattern 
*Expansion) {
   //    for (; b != e; ++b) ++result;
   //    return result;
   // }()
-  if (auto *Iterating = dyn_cast<CXXIteratingExpansionStmtPattern>(Expansion)) 
{
+  if (Expansion->isIterating()) {
     SourceLocation Loc = Expansion->getColonLoc();
     EnterExpressionEvaluationContext ExprEvalCtx(
         *this, ExpressionEvaluationContext::ConstantEvaluated);
@@ -556,7 +555,7 @@ Sema::ComputeExpansionSize(CXXExpansionStmtPattern 
*Expansion) {
     // auto b = begin-expr;
     // auto e = end-expr;
     ForRangeBeginEndInfo Info = BuildCXXForRangeBeginEndVars(
-        getCurScope(), Iterating->getRangeVar(), Loc,
+        getCurScope(), Expansion->getRangeVar(), Loc,
         /*CoawaitLoc=*/{},
         /*LifetimeExtendTemps=*/{}, BFRK_Build, /*Constexpr=*/false);
     if (!Info.isValid())

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

Reply via email to