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

>From e99d65e03373265e571069e99761ed5de627e725 Mon Sep 17 00:00:00 2001
From: Sirraide <[email protected]>
Date: Tue, 25 Nov 2025 17:56:59 +0100
Subject: [PATCH 1/3] [Clang] [C++26] Expansion Statements (Part 2)

---
 .../clang/Basic/DiagnosticParseKinds.td       |  13 +-
 .../clang/Basic/DiagnosticSemaKinds.td        |   6 +-
 clang/include/clang/Parse/Parser.h            |  41 ++++-
 clang/include/clang/Sema/Sema.h               |  33 +++-
 clang/lib/Parse/ParseDecl.cpp                 |  37 +----
 clang/lib/Parse/ParseExpr.cpp                 |  13 +-
 clang/lib/Parse/ParseInit.cpp                 |  20 +++
 clang/lib/Parse/ParseStmt.cpp                 | 143 ++++++++++++++++--
 clang/lib/Sema/CMakeLists.txt                 |   1 +
 clang/lib/Sema/SemaDecl.cpp                   |   7 +-
 clang/lib/Sema/SemaExpand.cpp                 |  57 +++++++
 11 files changed, 310 insertions(+), 61 deletions(-)
 create mode 100644 clang/lib/Sema/SemaExpand.cpp

diff --git a/clang/include/clang/Basic/DiagnosticParseKinds.td 
b/clang/include/clang/Basic/DiagnosticParseKinds.td
index aa0ccb0c05101..55234ebab3fe4 100644
--- a/clang/include/clang/Basic/DiagnosticParseKinds.td
+++ b/clang/include/clang/Basic/DiagnosticParseKinds.td
@@ -16,6 +16,10 @@ let CategoryName = "Parse Issue" in {
 defm enum_fixed_underlying_type : CXX11Compat<
   "enumeration types with a fixed underlying type are",
   /*ext_warn=*/false>;
+
+// C++26 compatibility with C++23.
+defm expansion_statements : CXX26Compat<
+  "expansion statements are">;
 }
 
 def err_asm_qualifier_ignored : Error<
@@ -419,9 +423,10 @@ def warn_cxx98_compat_for_range : Warning<
   "range-based for loop is incompatible with C++98">,
   InGroup<CXX98Compat>, DefaultIgnore;
 def err_for_range_identifier : Error<
-  "range-based for loop requires type for loop variable">;
+  "%select{range-based for loop|expansion statement}0 requires "
+  "type for %select{loop|expansion}0 variable">;
 def err_for_range_expected_decl : Error<
-  "for range declaration must declare a variable">;
+  "%select{for range|expansion statement}0 declaration must declare a 
variable">;
 def err_argument_required_after_attribute : Error<
   "argument required after attribute">;
 def err_missing_param : Error<"expected parameter declarator">;
@@ -448,6 +453,10 @@ def err_unspecified_size_with_static : Error<
   "'static' may not be used without an array size">;
 def err_expected_parentheses_around_typename : Error<
   "expected parentheses around type name in %0 expression">;
+def err_expansion_stmt_requires_range : Error<
+  "expansion statement must be a range-based for loop">;
+def err_expansion_stmt_requires_cxx2c : Error<
+  "expansion statements are only supported in C++2c">;
 
 def err_expected_case_before_expression: Error<
   "expected 'case' keyword before expression">;
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td 
b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index 53aa86a7dabde..ca862316a2f27 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -2900,10 +2900,10 @@ def note_which_delegates_to : Note<"which delegates 
to">;
 
 // C++11 range-based for loop
 def err_for_range_decl_must_be_var : Error<
-  "for range declaration must declare a variable">;
+  "%select{for range|expansion statement}0 declaration must declare a 
variable">;
 def err_for_range_storage_class : Error<
-  "loop variable %0 may not be declared %select{'extern'|'static'|"
-  "'__private_extern__'|'auto'|'register'|'constexpr'|'thread_local'}1">;
+  "%select{loop|expansion}0 variable %1 may not be declared 
%select{'extern'|'static'|"
+  "'__private_extern__'|'auto'|'register'|'constexpr'|'thread_local'}2">;
 def err_type_defined_in_for_range : Error<
   "types may not be defined in a for range declaration">;
 def err_for_range_deduction_failure : Error<
diff --git a/clang/include/clang/Parse/Parser.h 
b/clang/include/clang/Parse/Parser.h
index 58eb1c0a7c114..ff4f7b4e1dd2d 100644
--- a/clang/include/clang/Parse/Parser.h
+++ b/clang/include/clang/Parse/Parser.h
@@ -1700,11 +1700,13 @@ class Parser : public CodeCompletionHandler {
   }
 
   /// Information on a C++0x for-range-initializer found while parsing a
-  /// declaration which turns out to be a for-range-declaration.
+  /// declaration which turns out to be a for-range-declaration. Also used
+  /// for C++26's expansion statements.
   struct ForRangeInit {
     SourceLocation ColonLoc;
     ExprResult RangeExpr;
     SmallVector<MaterializeTemporaryExpr *, 8> LifetimeExtendTemps;
+    bool ExpansionStmt = false;
     bool ParsedForRangeDecl() { return !ColonLoc.isInvalid(); }
   };
   struct ForRangeInfo : ForRangeInit {
@@ -4186,7 +4188,8 @@ class Parser : public CodeCompletionHandler {
   bool ParseExpressionList(SmallVectorImpl<Expr *> &Exprs,
                            llvm::function_ref<void()> ExpressionStarts =
                                llvm::function_ref<void()>(),
-                           bool FailImmediatelyOnInvalidExpr = false);
+                           bool FailImmediatelyOnInvalidExpr = false,
+                           bool StopAtRBraceAfterComma = false);
 
   /// ParseSimpleExpressionList - A simple comma-separated list of expressions,
   /// used for misc language extensions.
@@ -5246,6 +5249,16 @@ class Parser : public CodeCompletionHandler {
   ///
   ExprResult ParseBraceInitializer();
 
+  /// ParseExpansionInitList - Called when the initializer of an expansion
+  /// statement starts with an open brace.
+  ///
+  /// \verbatim
+  ///       expansion-init-list: [C++26 [stmt.expand]]
+  ///          '{' expression-list ','[opt] '}'
+  ///          '{' '}'
+  /// \endverbatim
+  ExprResult ParseExpansionInitList();
+
   struct DesignatorCompletionInfo {
     SmallVectorImpl<Expr *> &InitExprs;
     QualType PreferredBaseType;
@@ -7452,8 +7465,12 @@ class Parser : public CodeCompletionHandler {
   /// [C++0x]   expression
   /// [C++0x]   braced-init-list            [TODO]
   /// \endverbatim
-  StmtResult ParseForStatement(SourceLocation *TrailingElseLoc,
-                               LabelDecl *PrecedingLabel);
+  StmtResult ParseForStatement(
+      SourceLocation *TrailingElseLoc, LabelDecl *PrecedingLabel,
+      CXXExpansionStmtDecl *CXXExpansionStmtDeclaration = nullptr);
+
+  void ParseForRangeInitializerAfterColon(ForRangeInit &FRI,
+                                          ParsingDeclSpec *VarDeclSpec);
 
   /// ParseGotoStatement
   /// \verbatim
@@ -7500,6 +7517,22 @@ class Parser : public CodeCompletionHandler {
 
   StmtResult ParseBreakOrContinueStatement(bool IsContinue);
 
+  /// ParseExpansionStatement - Parse a C++26 expansion
+  /// statement ('template for').
+  ///
+  /// \verbatim
+  ///     expansion-statement:
+  ///       'template' 'for' '(' init-statement[opt]
+  ///           for-range-declaration ':' expansion-initializer ')'
+  ///           compound-statement
+  ///
+  ///     expansion-initializer:
+  ///       expression
+  ///       expansion-init-list
+  /// \endverbatim
+  StmtResult ParseExpansionStatement(SourceLocation *TrailingElseLoc,
+                                     LabelDecl *PrecedingLabel);
+
   StmtResult ParsePragmaLoopHint(StmtVector &Stmts, ParsedStmtContext StmtCtx,
                                  SourceLocation *TrailingElseLoc,
                                  ParsedAttributes &Attrs,
diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index ae500139ee6f7..786e53a2e179a 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -893,6 +893,7 @@ class Sema final : public SemaBase {
   // 33. Types (SemaType.cpp)
   // 34. FixIt Helpers (SemaFixItUtils.cpp)
   // 35. Function Effects (SemaFunctionEffects.cpp)
+  // 36. C++ Expansion Statements (SemaExpand.cpp)
 
   /// \name Semantic Analysis
   /// Implementations are in Sema.cpp
@@ -4097,7 +4098,7 @@ class Sema final : public SemaBase {
   /// complete.
   void ActOnInitializerError(Decl *Dcl);
 
-  void ActOnCXXForRangeDecl(Decl *D);
+  void ActOnCXXForRangeDecl(Decl *D, bool InExpansionStmt);
   StmtResult ActOnCXXForRangeIdentifier(Scope *S, SourceLocation IdentLoc,
                                         IdentifierInfo *Ident,
                                         ParsedAttributes &Attrs);
@@ -15613,6 +15614,36 @@ class Sema final : public SemaBase {
   void performFunctionEffectAnalysis(TranslationUnitDecl *TU);
 
   ///@}
+
+  //
+  //
+  // -------------------------------------------------------------------------
+  //
+  //
+
+  /// \name Expansion Statements
+  /// Implementations are in SemaExpand.cpp
+  ///@{
+public:
+  CXXExpansionStmtDecl *ActOnCXXExpansionStmtDecl(unsigned TemplateDepth,
+                                                  SourceLocation 
TemplateKWLoc);
+
+  CXXExpansionStmtDecl *
+  BuildCXXExpansionStmtDecl(DeclContext *Ctx, SourceLocation TemplateKWLoc,
+                            NonTypeTemplateParmDecl *NTTP);
+
+  ExprResult ActOnCXXExpansionInitList(MultiExprArg SubExprs,
+                                       SourceLocation LBraceLoc,
+                                       SourceLocation RBraceLoc);
+
+  StmtResult ActOnCXXExpansionStmtPattern(
+      CXXExpansionStmtDecl *ESD, Stmt *Init, Stmt *ExpansionVarStmt,
+      Expr *ExpansionInitializer, SourceLocation LParenLoc,
+      SourceLocation ColonLoc, SourceLocation RParenLoc,
+      ArrayRef<MaterializeTemporaryExpr *> LifetimeExtendTemps);
+
+  StmtResult FinishCXXExpansionStmt(Stmt *Expansion, Stmt *Body);
+  ///@}
 };
 
 DeductionFailureInfo
diff --git a/clang/lib/Parse/ParseDecl.cpp b/clang/lib/Parse/ParseDecl.cpp
index 8688ccf41acb5..5e1ff2be28f38 100644
--- a/clang/lib/Parse/ParseDecl.cpp
+++ b/clang/lib/Parse/ParseDecl.cpp
@@ -2304,43 +2304,18 @@ Parser::DeclGroupPtrTy 
Parser::ParseDeclGroup(ParsingDeclSpec &DS,
   // Handle the Objective-C for-in loop variable similarly, although we
   // don't need to parse the container in advance.
   if (FRI && (Tok.is(tok::colon) || isTokIdentifier_in())) {
-    bool IsForRangeLoop = false;
+    bool IsForRangeLoopOrExpansionStmt = false;
     if (TryConsumeToken(tok::colon, FRI->ColonLoc)) {
-      IsForRangeLoop = true;
-      EnterExpressionEvaluationContext ForRangeInitContext(
-          Actions, Sema::ExpressionEvaluationContext::PotentiallyEvaluated,
-          /*LambdaContextDecl=*/nullptr,
-          Sema::ExpressionEvaluationContextRecord::EK_Other,
-          getLangOpts().CPlusPlus23);
-
-      // P2718R0 - Lifetime extension in range-based for loops.
-      if (getLangOpts().CPlusPlus23) {
-        auto &LastRecord = Actions.currentEvaluationContext();
-        LastRecord.InLifetimeExtendingContext = true;
-        LastRecord.RebuildDefaultArgOrDefaultInit = true;
-      }
-
-      if (getLangOpts().OpenMP)
+      IsForRangeLoopOrExpansionStmt = true;
+      if (getLangOpts().OpenMP && !FRI->ExpansionStmt)
         Actions.OpenMP().startOpenMPCXXRangeFor();
-      if (Tok.is(tok::l_brace))
-        FRI->RangeExpr = ParseBraceInitializer();
-      else
-        FRI->RangeExpr = ParseExpression();
-
-      // Before c++23, ForRangeLifetimeExtendTemps should be empty.
-      assert(
-          getLangOpts().CPlusPlus23 ||
-          Actions.ExprEvalContexts.back().ForRangeLifetimeExtendTemps.empty());
 
-      // Move the collected materialized temporaries into ForRangeInit before
-      // ForRangeInitContext exit.
-      FRI->LifetimeExtendTemps = std::move(
-          Actions.ExprEvalContexts.back().ForRangeLifetimeExtendTemps);
+      ParseForRangeInitializerAfterColon(*FRI, &DS);
     }
 
     Decl *ThisDecl = Actions.ActOnDeclarator(getCurScope(), D);
-    if (IsForRangeLoop) {
-      Actions.ActOnCXXForRangeDecl(ThisDecl);
+    if (IsForRangeLoopOrExpansionStmt) {
+      Actions.ActOnCXXForRangeDecl(ThisDecl, FRI->ExpansionStmt);
     } else {
       // Obj-C for loop
       if (auto *VD = dyn_cast_or_null<VarDecl>(ThisDecl))
diff --git a/clang/lib/Parse/ParseExpr.cpp b/clang/lib/Parse/ParseExpr.cpp
index 3515343202de1..902afcaeed987 100644
--- a/clang/lib/Parse/ParseExpr.cpp
+++ b/clang/lib/Parse/ParseExpr.cpp
@@ -3166,7 +3166,8 @@ void Parser::injectEmbedTokens() {
 
 bool Parser::ParseExpressionList(SmallVectorImpl<Expr *> &Exprs,
                                  llvm::function_ref<void()> ExpressionStarts,
-                                 bool FailImmediatelyOnInvalidExpr) {
+                                 bool FailImmediatelyOnInvalidExpr,
+                                 bool StopAtRBraceAfterComma) {
   bool SawError = false;
   while (true) {
     if (ExpressionStarts)
@@ -3195,7 +3196,11 @@ bool Parser::ParseExpressionList(SmallVectorImpl<Expr *> 
&Exprs,
       SawError = true;
       if (FailImmediatelyOnInvalidExpr)
         break;
-      SkipUntil(tok::comma, tok::r_paren, StopAtSemi | StopBeforeMatch);
+
+      if (StopAtRBraceAfterComma)
+        SkipUntil(tok::comma, tok::r_brace, StopAtSemi | StopBeforeMatch);
+      else
+        SkipUntil(tok::comma, tok::r_paren, StopAtSemi | StopBeforeMatch);
     } else {
       Exprs.push_back(Expr.get());
     }
@@ -3205,6 +3210,10 @@ bool Parser::ParseExpressionList(SmallVectorImpl<Expr *> 
&Exprs,
     // Move to the next argument, remember where the comma was.
     Token Comma = Tok;
     ConsumeToken();
+
+    if (StopAtRBraceAfterComma && 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 0e86c4c48d5e4..11c4e983cdcd3 100644
--- a/clang/lib/Parse/ParseInit.cpp
+++ b/clang/lib/Parse/ParseInit.cpp
@@ -516,6 +516,26 @@ ExprResult Parser::ParseBraceInitializer() {
   return ExprError(); // an error occurred.
 }
 
+ExprResult Parser::ParseExpansionInitList() {
+  BalancedDelimiterTracker T(*this, tok::l_brace);
+  T.consumeOpen();
+
+  ExprVector InitExprs;
+
+  // CWG 3061: Accept a trailing comma here.
+  if (!Tok.is(tok::r_brace) &&
+      ParseExpressionList(InitExprs, /*ExpressionStarts=*/{},
+                          /*FailImmediatelyOnInvalidExpr=*/false,
+                          /*StopAtRBraceAfterComma=*/true)) {
+    T.consumeClose();
+    return ExprError();
+  }
+
+  T.consumeClose();
+  return Actions.ActOnCXXExpansionInitList(InitExprs, T.getOpenLocation(),
+                                           T.getCloseLocation());
+}
+
 bool Parser::ParseMicrosoftIfExistsBraceInitializer(ExprVector &InitExprs,
                                                     bool &InitExprsOk) {
   bool trailingComma = false;
diff --git a/clang/lib/Parse/ParseStmt.cpp b/clang/lib/Parse/ParseStmt.cpp
index 7e73d89c2a18c..39751c79c6852 100644
--- a/clang/lib/Parse/ParseStmt.cpp
+++ b/clang/lib/Parse/ParseStmt.cpp
@@ -260,6 +260,20 @@ StmtResult 
Parser::ParseStatementOrDeclarationAfterAttributes(
   }
 
   case tok::kw_template: {
+    if (NextToken().is(tok::kw_for)) {
+      // Expansion statements are not backported for now.
+      if (!getLangOpts().CPlusPlus26) {
+        Diag(Tok.getLocation(), diag::err_expansion_stmt_requires_cxx2c);
+
+        // Trying to parse this as a regular 'for' statement instead yields
+        // better error recovery.
+        ConsumeToken();
+        return ParseForStatement(TrailingElseLoc, PrecedingLabel);
+      }
+
+      return ParseExpansionStatement(TrailingElseLoc, PrecedingLabel);
+    }
+
     SourceLocation DeclEnd;
     ParseTemplateDeclarationOrSpecialization(DeclaratorContext::Block, DeclEnd,
                                              getAccessSpecifierIfPresent());
@@ -1884,8 +1898,55 @@ bool Parser::isForRangeIdentifier() {
   return false;
 }
 
-StmtResult Parser::ParseForStatement(SourceLocation *TrailingElseLoc,
-                                     LabelDecl *PrecedingLabel) {
+void Parser::ParseForRangeInitializerAfterColon(ForRangeInit &FRI,
+                                                ParsingDeclSpec *VarDeclSpec) {
+  // Use an immediate function context if this is the initializer for a
+  // constexpr variable in an expansion statement.
+  auto Ctx = Sema::ExpressionEvaluationContext::PotentiallyEvaluated;
+  if (FRI.ExpansionStmt && VarDeclSpec && VarDeclSpec->hasConstexprSpecifier())
+    Ctx = Sema::ExpressionEvaluationContext::ImmediateFunctionContext;
+
+  EnterExpressionEvaluationContext InitContext(
+      Actions, Ctx,
+      /*LambdaContextDecl=*/nullptr,
+      Sema::ExpressionEvaluationContextRecord::EK_Other,
+      getLangOpts().CPlusPlus23);
+
+  // P2718R0 - Lifetime extension in range-based for loops.
+  if (getLangOpts().CPlusPlus23) {
+    auto &LastRecord = Actions.currentEvaluationContext();
+    LastRecord.InLifetimeExtendingContext = true;
+    LastRecord.RebuildDefaultArgOrDefaultInit = true;
+  }
+
+  if (FRI.ExpansionStmt) {
+    Sema::ContextRAII CtxGuard(
+        Actions, 
Actions.CurContext->getEnclosingNonExpansionStatementContext(),
+        /*NewThis=*/false);
+
+    FRI.RangeExpr =
+        Tok.is(tok::l_brace) ? ParseExpansionInitList() : ParseExpression();
+    FRI.RangeExpr = Actions.MaybeCreateExprWithCleanups(FRI.RangeExpr);
+  } else if (Tok.is(tok::l_brace)) {
+    FRI.RangeExpr = ParseBraceInitializer();
+  } else {
+    FRI.RangeExpr = ParseExpression();
+  }
+
+  // Before c++23, ForRangeLifetimeExtendTemps should be empty.
+  assert(getLangOpts().CPlusPlus23 ||
+         Actions.ExprEvalContexts.back().ForRangeLifetimeExtendTemps.empty());
+
+  // Move the collected materialized temporaries into ForRangeInit before
+  // ForRangeInitContext exit.
+  FRI.LifetimeExtendTemps =
+      std::move(Actions.ExprEvalContexts.back().ForRangeLifetimeExtendTemps);
+}
+
+StmtResult
+Parser::ParseForStatement(SourceLocation *TrailingElseLoc,
+                          LabelDecl *PrecedingLabel,
+                          CXXExpansionStmtDecl *CXXExpansionStmtDecl) {
   assert(Tok.is(tok::kw_for) && "Not a for stmt!");
   SourceLocation ForLoc = ConsumeToken();  // eat the 'for'.
 
@@ -1920,6 +1981,8 @@ StmtResult Parser::ParseForStatement(SourceLocation 
*TrailingElseLoc,
   unsigned ScopeFlags = 0;
   if (C99orCXXorObjC)
     ScopeFlags = Scope::DeclScope | Scope::ControlScope;
+  if (CXXExpansionStmtDecl)
+    ScopeFlags |= Scope::TemplateParamScope;
 
   ParseScope ForScope(this, ScopeFlags);
 
@@ -1934,6 +1997,7 @@ StmtResult Parser::ParseForStatement(SourceLocation 
*TrailingElseLoc,
   ExprResult Collection;
   ForRangeInfo ForRangeInfo;
   FullExprArg ThirdPart(Actions);
+  ForRangeInfo.ExpansionStmt = CXXExpansionStmtDecl != nullptr;
 
   if (Tok.is(tok::code_completion)) {
     cutOffParsing();
@@ -1964,18 +2028,17 @@ StmtResult Parser::ParseForStatement(SourceLocation 
*TrailingElseLoc,
     MaybeParseCXX11Attributes(attrs);
 
     ForRangeInfo.ColonLoc = ConsumeToken();
-    if (Tok.is(tok::l_brace))
-      ForRangeInfo.RangeExpr = ParseBraceInitializer();
-    else
-      ForRangeInfo.RangeExpr = ParseExpression();
+    ParseForRangeInitializerAfterColon(ForRangeInfo, /*VarDeclSpec=*/nullptr);
 
     Diag(Loc, diag::err_for_range_identifier)
-      << ((getLangOpts().CPlusPlus11 && !getLangOpts().CPlusPlus17)
-              ? FixItHint::CreateInsertion(Loc, "auto &&")
-              : FixItHint());
-
-    ForRangeInfo.LoopVar =
-        Actions.ActOnCXXForRangeIdentifier(getCurScope(), Loc, Name, attrs);
+        << ForRangeInfo.ExpansionStmt
+        << ((getLangOpts().CPlusPlus11 && !getLangOpts().CPlusPlus17)
+                ? FixItHint::CreateInsertion(Loc, "auto &&")
+                : FixItHint());
+
+    if (!ForRangeInfo.ExpansionStmt)
+      ForRangeInfo.LoopVar =
+          Actions.ActOnCXXForRangeIdentifier(getCurScope(), Loc, Name, attrs);
   } else if (isForInitDeclaration()) {  // for (int X = 4;
     ParenBraceBracketBalancer BalancerRAIIObj(*this);
 
@@ -2067,7 +2130,8 @@ StmtResult Parser::ParseForStatement(SourceLocation 
*TrailingElseLoc,
       // User tried to write the reasonable, but ill-formed, 
for-range-statement
       //   for (expr : expr) { ... }
       Diag(Tok, diag::err_for_range_expected_decl)
-        << FirstPart.get()->getSourceRange();
+          << (CXXExpansionStmtDecl != nullptr)
+          << FirstPart.get()->getSourceRange();
       SkipUntil(tok::r_paren, StopBeforeMatch);
       SecondPart = Sema::ConditionError();
     } else {
@@ -2189,7 +2253,13 @@ StmtResult Parser::ParseForStatement(SourceLocation 
*TrailingElseLoc,
   StmtResult ForRangeStmt;
   StmtResult ForEachStmt;
 
-  if (ForRangeInfo.ParsedForRangeDecl()) {
+  if (CXXExpansionStmtDecl) {
+    ForRangeStmt = Actions.ActOnCXXExpansionStmtPattern(
+        CXXExpansionStmtDecl, FirstPart.get(), ForRangeInfo.LoopVar.get(),
+        ForRangeInfo.RangeExpr.get(), T.getOpenLocation(),
+        ForRangeInfo.ColonLoc, T.getCloseLocation(),
+        ForRangeInfo.LifetimeExtendTemps);
+  } else if (ForRangeInfo.ParsedForRangeDecl()) {
     ForRangeStmt = Actions.ActOnCXXForRangeStmt(
         getCurScope(), ForLoc, CoawaitLoc, FirstPart.get(),
         ForRangeInfo.LoopVar.get(), ForRangeInfo.ColonLoc,
@@ -2211,7 +2281,9 @@ StmtResult Parser::ParseForStatement(SourceLocation 
*TrailingElseLoc,
   // OpenACC Restricts a for-loop inside of certain construct/clause
   // combinations, so diagnose that here in OpenACC mode.
   SemaOpenACC::LoopInConstructRAII LCR{getActions().OpenACC()};
-  if (ForRangeInfo.ParsedForRangeDecl())
+  if (CXXExpansionStmtDecl)
+    ; // Nothing.
+  else if (ForRangeInfo.ParsedForRangeDecl())
     getActions().OpenACC().ActOnRangeForStmtBegin(ForLoc, ForRangeStmt.get());
   else
     getActions().OpenACC().ActOnForStmtBegin(
@@ -2265,6 +2337,15 @@ StmtResult Parser::ParseForStatement(SourceLocation 
*TrailingElseLoc,
     return Actions.ObjC().FinishObjCForCollectionStmt(ForEachStmt.get(),
                                                       Body.get());
 
+  if (CXXExpansionStmtDecl) {
+    if (!ForRangeInfo.ParsedForRangeDecl()) {
+      Diag(ForLoc, diag::err_expansion_stmt_requires_range);
+      return StmtError();
+    }
+
+    return Actions.FinishCXXExpansionStmt(ForRangeStmt.get(), Body.get());
+  }
+
   if (ForRangeInfo.ParsedForRangeDecl())
     return Actions.FinishCXXForRangeStmt(ForRangeStmt.get(), Body.get());
 
@@ -2624,6 +2705,38 @@ StmtResult Parser::ParseCXXCatchBlock(bool FnCatch) {
   return Actions.ActOnCXXCatchBlock(CatchLoc, ExceptionDecl, Block.get());
 }
 
+StmtResult Parser::ParseExpansionStatement(SourceLocation *TrailingElseLoc,
+                                           LabelDecl *PrecedingLabel) {
+  assert(Tok.is(tok::kw_template) && NextToken().is(tok::kw_for));
+  SourceLocation TemplateLoc = ConsumeToken();
+  DiagCompat(TemplateLoc, diag_compat::expansion_statements);
+
+  CXXExpansionStmtDecl *ExpansionDecl =
+      Actions.ActOnCXXExpansionStmtDecl(TemplateParameterDepth, TemplateLoc);
+
+  CXXExpansionStmtPattern *Expansion;
+  {
+    Sema::ContextRAII CtxGuard(Actions, ExpansionDecl, /*NewThis=*/false);
+    TemplateParameterDepthRAII TParamDepthGuard(TemplateParameterDepth);
+    ++TParamDepthGuard;
+
+    StmtResult SR =
+        ParseForStatement(TrailingElseLoc, PrecedingLabel, ExpansionDecl);
+    if (SR.isInvalid())
+      return SR;
+
+    Expansion = cast<CXXExpansionStmtPattern>(SR.get());
+    ExpansionDecl->setExpansionPattern(Expansion);
+  }
+
+  DeclSpec DS(AttrFactory);
+  DeclGroupPtrTy DeclGroupPtr =
+      Actions.FinalizeDeclaratorGroup(getCurScope(), DS, {ExpansionDecl});
+
+  return Actions.ActOnDeclStmt(DeclGroupPtr, Expansion->getBeginLoc(),
+                               Expansion->getEndLoc());
+}
+
 void Parser::ParseMicrosoftIfExistsStatement(StmtVector &Stmts) {
   IfExistsCondition Result;
   if (ParseMicrosoftIfExistsCondition(Result))
diff --git a/clang/lib/Sema/CMakeLists.txt b/clang/lib/Sema/CMakeLists.txt
index 0ebf56ecffe69..ef729e22c1dc8 100644
--- a/clang/lib/Sema/CMakeLists.txt
+++ b/clang/lib/Sema/CMakeLists.txt
@@ -53,6 +53,7 @@ add_clang_library(clangSema
   SemaDeclCXX.cpp
   SemaDeclObjC.cpp
   SemaExceptionSpec.cpp
+  SemaExpand.cpp
   SemaExpr.cpp
   SemaExprCXX.cpp
   SemaExprMember.cpp
diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp
index 468376039fae5..180f532561ece 100644
--- a/clang/lib/Sema/SemaDecl.cpp
+++ b/clang/lib/Sema/SemaDecl.cpp
@@ -14624,14 +14624,15 @@ void Sema::ActOnUninitializedDecl(Decl *RealDecl) {
   }
 }
 
-void Sema::ActOnCXXForRangeDecl(Decl *D) {
+void Sema::ActOnCXXForRangeDecl(Decl *D, bool InExpansionStmt) {
   // If there is no declaration, there was an error parsing it. Ignore it.
   if (!D)
     return;
 
   VarDecl *VD = dyn_cast<VarDecl>(D);
   if (!VD) {
-    Diag(D->getLocation(), diag::err_for_range_decl_must_be_var);
+    Diag(D->getLocation(), diag::err_for_range_decl_must_be_var)
+        << InExpansionStmt;
     D->setInvalidDecl();
     return;
   }
@@ -14673,7 +14674,7 @@ void Sema::ActOnCXXForRangeDecl(Decl *D) {
 
   if (Error != -1) {
     Diag(VD->getOuterLocStart(), diag::err_for_range_storage_class)
-        << VD << Error;
+        << InExpansionStmt << VD << Error;
     D->setInvalidDecl();
   }
 }
diff --git a/clang/lib/Sema/SemaExpand.cpp b/clang/lib/Sema/SemaExpand.cpp
new file mode 100644
index 0000000000000..cd89c37b50cf4
--- /dev/null
+++ b/clang/lib/Sema/SemaExpand.cpp
@@ -0,0 +1,57 @@
+//===-- SemaExpand.cpp - Semantic Analysis for Expansion 
Statements--------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM 
Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+//  This file implements semantic analysis for C++26 expansion statements,
+//  aka 'template for'.
+//
+//===----------------------------------------------------------------------===//
+
+#include "clang/AST/DeclCXX.h"
+#include "clang/AST/ExprCXX.h"
+#include "clang/AST/StmtCXX.h"
+#include "clang/Lex/Preprocessor.h"
+#include "clang/Sema/EnterExpressionEvaluationContext.h"
+#include "clang/Sema/Lookup.h"
+#include "clang/Sema/Overload.h"
+#include "clang/Sema/Sema.h"
+#include "clang/Sema/Template.h"
+
+using namespace clang;
+using namespace sema;
+
+
+CXXExpansionStmtDecl *
+Sema::ActOnCXXExpansionStmtDecl(unsigned TemplateDepth,
+                                SourceLocation TemplateKWLoc) {
+  llvm_unreachable("TODO");
+}
+
+CXXExpansionStmtDecl *
+Sema::BuildCXXExpansionStmtDecl(DeclContext *Ctx, SourceLocation TemplateKWLoc,
+                                NonTypeTemplateParmDecl *NTTP) {
+  llvm_unreachable("TODO");
+}
+
+ExprResult Sema::ActOnCXXExpansionInitList(MultiExprArg SubExprs,
+                                           SourceLocation LBraceLoc,
+                                           SourceLocation RBraceLoc) {
+  return CXXExpansionInitListExpr::Create(Context, SubExprs, LBraceLoc,
+                                          RBraceLoc);
+}
+
+StmtResult Sema::ActOnCXXExpansionStmtPattern(
+    CXXExpansionStmtDecl *ESD, Stmt *Init, Stmt *ExpansionVarStmt,
+    Expr *ExpansionInitializer, SourceLocation LParenLoc,
+    SourceLocation ColonLoc, SourceLocation RParenLoc,
+    ArrayRef<MaterializeTemporaryExpr *> LifetimeExtendTemps) {
+  llvm_unreachable("TODO");
+}
+
+StmtResult Sema::FinishCXXExpansionStmt(Stmt *Exp, Stmt *Body) {
+  llvm_unreachable("TODO");
+}

>From 83865533998dae00f72dadadf5230603856533bc Mon Sep 17 00:00:00 2001
From: Sirraide <[email protected]>
Date: Tue, 25 Nov 2025 18:26:02 +0100
Subject: [PATCH 2/3] Add Sema for CXXExpansionStmtDecl

---
 .../clang/Basic/DiagnosticCommonKinds.td      |  4 +++
 clang/lib/Sema/SemaExpand.cpp                 | 33 ++++++++++++++++---
 2 files changed, 33 insertions(+), 4 deletions(-)

diff --git a/clang/include/clang/Basic/DiagnosticCommonKinds.td 
b/clang/include/clang/Basic/DiagnosticCommonKinds.td
index 6e50e225a8cc1..0b9225980e826 100644
--- a/clang/include/clang/Basic/DiagnosticCommonKinds.td
+++ b/clang/include/clang/Basic/DiagnosticCommonKinds.td
@@ -22,6 +22,10 @@ def select_constexpr_spec_kind : TextSubstitution<
 def fatal_too_many_errors
   : Error<"too many errors emitted, stopping now">, DefaultFatal;
 
+// TODO: Remove this.
+def err_expansion_statements_todo : Error<
+  "TODO (expansion statements)">;
+
 def warn_stack_exhausted : Warning<
   "stack nearly exhausted; compilation time may suffer, and "
   "crashes due to stack overflow are likely">,
diff --git a/clang/lib/Sema/SemaExpand.cpp b/clang/lib/Sema/SemaExpand.cpp
index cd89c37b50cf4..4f3d583b474fe 100644
--- a/clang/lib/Sema/SemaExpand.cpp
+++ b/clang/lib/Sema/SemaExpand.cpp
@@ -28,13 +28,33 @@ using namespace sema;
 CXXExpansionStmtDecl *
 Sema::ActOnCXXExpansionStmtDecl(unsigned TemplateDepth,
                                 SourceLocation TemplateKWLoc) {
-  llvm_unreachable("TODO");
+  // Create a template parameter '__N'. This will be used to denote the index
+  // of the element that we're instantiating. CWG 3044 requires this type to
+  // be 'ptrdiff_t' for iterating expansion statements, so use that in all
+  // cases.
+  IdentifierInfo *ParmName = &Context.Idents.get("__N");
+  QualType ParmTy = Context.getPointerDiffType();
+  TypeSourceInfo *ParmTI =
+      Context.getTrivialTypeSourceInfo(ParmTy, TemplateKWLoc);
+
+  auto *TParam = NonTypeTemplateParmDecl::Create(
+      Context, Context.getTranslationUnitDecl(), TemplateKWLoc, TemplateKWLoc,
+      TemplateDepth, /*Position=*/0, ParmName, ParmTy, /*ParameterPack=*/false,
+      ParmTI);
+
+  return BuildCXXExpansionStmtDecl(CurContext, TemplateKWLoc, TParam);
 }
 
 CXXExpansionStmtDecl *
 Sema::BuildCXXExpansionStmtDecl(DeclContext *Ctx, SourceLocation TemplateKWLoc,
                                 NonTypeTemplateParmDecl *NTTP) {
-  llvm_unreachable("TODO");
+  auto *TParamList = TemplateParameterList::Create(
+      Context, TemplateKWLoc, TemplateKWLoc, {NTTP}, TemplateKWLoc,
+      /*RequiresClause=*/nullptr);
+  auto *Result =
+      CXXExpansionStmtDecl::Create(Context, Ctx, TemplateKWLoc, TParamList);
+  Ctx->addDecl(Result);
+  return Result;
 }
 
 ExprResult Sema::ActOnCXXExpansionInitList(MultiExprArg SubExprs,
@@ -49,9 +69,14 @@ StmtResult Sema::ActOnCXXExpansionStmtPattern(
     Expr *ExpansionInitializer, SourceLocation LParenLoc,
     SourceLocation ColonLoc, SourceLocation RParenLoc,
     ArrayRef<MaterializeTemporaryExpr *> LifetimeExtendTemps) {
-  llvm_unreachable("TODO");
+  Diag(ColonLoc, diag::err_expansion_stmt_todo);
+  return StmtError();
 }
 
 StmtResult Sema::FinishCXXExpansionStmt(Stmt *Exp, Stmt *Body) {
-  llvm_unreachable("TODO");
+  if (!Exp || !Body)
+    return StmtError();
+
+  Diag(Exp->getBeginLoc(), diag::err_expansion_stmt_todo);
+  return StmtError();
 }

>From c7cd02ef452a2d63ef09ddf2bec833060023f220 Mon Sep 17 00:00:00 2001
From: Sirraide <[email protected]>
Date: Tue, 25 Nov 2025 18:29:34 +0100
Subject: [PATCH 3/3] Add parser tests

---
 clang/lib/Sema/SemaExpand.cpp                 |  5 +-
 ...2c-expansion-statements-not-backported.cpp |  5 ++
 .../Parser/cxx2c-expansion-statements.cpp     | 63 +++++++++++++++++++
 3 files changed, 70 insertions(+), 3 deletions(-)
 create mode 100644 
clang/test/Parser/cxx2c-expansion-statements-not-backported.cpp
 create mode 100644 clang/test/Parser/cxx2c-expansion-statements.cpp

diff --git a/clang/lib/Sema/SemaExpand.cpp b/clang/lib/Sema/SemaExpand.cpp
index 4f3d583b474fe..c74ed63d3295a 100644
--- a/clang/lib/Sema/SemaExpand.cpp
+++ b/clang/lib/Sema/SemaExpand.cpp
@@ -69,7 +69,7 @@ StmtResult Sema::ActOnCXXExpansionStmtPattern(
     Expr *ExpansionInitializer, SourceLocation LParenLoc,
     SourceLocation ColonLoc, SourceLocation RParenLoc,
     ArrayRef<MaterializeTemporaryExpr *> LifetimeExtendTemps) {
-  Diag(ColonLoc, diag::err_expansion_stmt_todo);
+  Diag(ESD->getLocation(), diag::err_expansion_statements_todo);
   return StmtError();
 }
 
@@ -77,6 +77,5 @@ StmtResult Sema::FinishCXXExpansionStmt(Stmt *Exp, Stmt 
*Body) {
   if (!Exp || !Body)
     return StmtError();
 
-  Diag(Exp->getBeginLoc(), diag::err_expansion_stmt_todo);
-  return StmtError();
+  llvm_unreachable("TODO");
 }
diff --git a/clang/test/Parser/cxx2c-expansion-statements-not-backported.cpp 
b/clang/test/Parser/cxx2c-expansion-statements-not-backported.cpp
new file mode 100644
index 0000000000000..0c0a229abeedc
--- /dev/null
+++ b/clang/test/Parser/cxx2c-expansion-statements-not-backported.cpp
@@ -0,0 +1,5 @@
+// RUN: %clang_cc1 %s -std=c++23 -fsyntax-only -verify
+
+void f() {
+  template for (char x : "123") {} // expected-error {{expansion statements 
are only supported in C++2c}}
+}
diff --git a/clang/test/Parser/cxx2c-expansion-statements.cpp 
b/clang/test/Parser/cxx2c-expansion-statements.cpp
new file mode 100644
index 0000000000000..990bf4dbcc662
--- /dev/null
+++ b/clang/test/Parser/cxx2c-expansion-statements.cpp
@@ -0,0 +1,63 @@
+// RUN: %clang_cc1 %s -std=c++2c -fsyntax-only -verify
+namespace std {
+template <typename T>
+struct initializer_list {
+  const T* a;
+  const T* b;
+  initializer_list(T*, T*) {}
+};
+}
+
+void bad() {
+  template for; // expected-error {{expected '(' after 'for'}}
+  template for (); // expected-error {{expected expression}} expected-error 
{{expected ';' in 'for' statement specifier}} expected-error {{expansion 
statement must be a range-based for loop}} expected-error {{TODO (expansion 
statements)}}
+  template for (;); // expected-error {{expected ';' in 'for' statement 
specifier}} expected-error {{expansion statement must be a range-based for 
loop}} expected-error {{TODO (expansion statements)}}
+  template for (;;); // expected-error {{expansion statement must be a 
range-based for loop}} expected-error {{TODO (expansion statements)}}
+  template for (int x;;); // expected-error {{expansion statement must be a 
range-based for loop}} expected-error {{TODO (expansion statements)}}
+  template for (x : {1}); // expected-error {{expansion statement requires 
type for expansion variable}} expected-error {{TODO (expansion statements)}}
+  template for (: {1}); // expected-error {{expected expression}} 
expected-error {{expected ';' in 'for' statement specifier}} expected-error 
{{expansion statement must be a range-based for loop}} expected-error {{TODO 
(expansion statements)}}
+  template for (auto y : {1})]; // expected-error {{expected expression}} 
expected-error {{TODO (expansion statements)}}
+  template for (auto y : {1}; // expected-error {{expected ')'}} expected-note 
{{to match this '('}} expected-error {{TODO (expansion statements)}}
+  template for (extern auto y : {1, 2}); // expected-error {{expansion 
variable 'y' may not be declared 'extern'}} expected-error {{TODO (expansion 
statements)}}
+  template for (extern static auto y : {1, 2}); // expected-error {{cannot 
combine with previous 'extern' declaration specifier}} expected-error 
{{expansion variable 'y' may not be declared 'extern'}} expected-error {{TODO 
(expansion statements)}}
+  template for (static auto y : {1, 2}); // expected-error {{expansion 
variable 'y' may not be declared 'static'}} expected-error {{TODO (expansion 
statements)}}
+  template for (thread_local auto y : {1, 2}); // expected-error 
{{'thread_local' variables must have global storage}} expected-error {{TODO 
(expansion statements)}}
+  template for (static thread_local auto y : {1, 2}); // expected-error 
{{expansion variable 'y' may not be declared 'thread_local'}} expected-error 
{{TODO (expansion statements)}}
+  template for (__thread auto y : {1, 2}); // expected-error {{'__thread' 
variables must have global storage}} expected-error {{TODO (expansion 
statements)}}
+  template for (static __thread auto y : {1, 2}); // expected-error 
{{expansion variable 'y' may not be declared 'static'}} expected-error {{TODO 
(expansion statements)}}
+  template for (constinit auto y : {1, 2}); // expected-error {{local variable 
cannot be declared 'constinit'}} expected-error {{TODO (expansion statements)}}
+  template for (consteval auto y : {1, 2});  // expected-error {{consteval can 
only be used in function declarations}} expected-error {{TODO (expansion 
statements)}}
+  template for (int x; extern auto y : {1, 2}); // expected-error {{expansion 
variable 'y' may not be declared 'extern'}} expected-error {{TODO (expansion 
statements)}}
+  template for (int x; extern static auto y : {1, 2}); // expected-error 
{{cannot combine with previous 'extern' declaration specifier}} expected-error 
{{expansion variable 'y' may not be declared 'extern'}} expected-error {{TODO 
(expansion statements)}}
+  template for (int x; static auto y : {1, 2}); // expected-error {{expansion 
variable 'y' may not be declared 'static'}} expected-error {{TODO (expansion 
statements)}}
+  template for (int x; thread_local auto y : {1, 2}); // expected-error 
{{'thread_local' variables must have global storage}} expected-error {{TODO 
(expansion statements)}}
+  template for (int x; static thread_local auto y : {1, 2}); // expected-error 
{{expansion variable 'y' may not be declared 'thread_local'}} expected-error 
{{TODO (expansion statements)}}
+  template for (int x; __thread auto y : {1, 2}); // expected-error 
{{'__thread' variables must have global storage}} expected-error {{TODO 
(expansion statements)}}
+  template for (int x; static __thread auto y : {1, 2}); // expected-error 
{{expansion variable 'y' may not be declared 'static'}} expected-error {{TODO 
(expansion statements)}}
+  template for (int x; constinit auto y : {1, 2}); // expected-error {{local 
variable cannot be declared 'constinit'}} expected-error {{TODO (expansion 
statements)}}
+  template for (int x; consteval auto y : {1, 2});  // expected-error 
{{consteval can only be used in function declarations}} expected-error {{TODO 
(expansion statements)}}
+  template for (auto y : {abc, -+, }); // expected-error {{use of undeclared 
identifier 'abc'}} expected-error {{expected expression}} expected-error {{TODO 
(expansion statements)}}
+  template for (3 : "error") // expected-error {{expansion statement 
declaration must declare a variable}} \
+                                expected-error {{expansion statement must be a 
range-based for loop}} expected-error {{TODO (expansion statements)}}
+    ;
+  template while (true) {} // expected-error {{expected '<' after 'template'}}
+}
+
+void good() {
+  template for (auto y : {}); // expected-error {{TODO (expansion statements)}}
+  template for (auto y : {1, 2}); // expected-error {{TODO (expansion 
statements)}}
+  template for (int x; auto y : {1, 2}); // expected-error {{TODO (expansion 
statements)}}
+  template for (int x; int y : {1, 2}); // expected-error {{TODO (expansion 
statements)}}
+  template for (int x; constexpr auto y : {1, 2}); // expected-error {{TODO 
(expansion statements)}}
+  template for (int x; constexpr int y : {1, 2}); // expected-error {{TODO 
(expansion statements)}}
+  template for (constexpr int a : {1, 2}) { // expected-error {{TODO 
(expansion statements)}}
+    template for (constexpr int b : {1, 2}) { // expected-error {{TODO 
(expansion statements)}}
+      template for (constexpr int c : {1, 2}); // expected-error {{TODO 
(expansion statements)}}
+    }
+  }
+}
+
+void trailing_comma() {
+  template for (int x : {1, 2,}) {} // expected-error {{TODO (expansion 
statements)}}
+  template for (int x : {,}) {} // expected-error {{expected expression}} 
expected-error {{TODO (expansion statements)}}
+}

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

Reply via email to