[clang] [Clang] [C++26] Implement P1306R5 Expansion Statements (PR #165195)

via cfe-commits cfe-commits at lists.llvm.org
Mon Oct 27 14:52:45 PDT 2025


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

>From f8bd65dddb6b20c6ce491a49923633a640aa1f19 Mon Sep 17 00:00:00 2001
From: Sirraide <aeternalmail at gmail.com>
Date: Fri, 24 Oct 2025 19:11:46 +0200
Subject: [PATCH 01/33] Enumerating expansion statements mostly work

---
 clang/include/clang/AST/ASTNodeTraverser.h    |   6 +
 clang/include/clang/AST/ComputeDependence.h   |   3 +
 clang/include/clang/AST/Decl.h                |   4 +-
 clang/include/clang/AST/DeclBase.h            |  13 +
 clang/include/clang/AST/DeclTemplate.h        |  54 ++
 clang/include/clang/AST/ExprCXX.h             | 103 ++++
 clang/include/clang/AST/RecursiveASTVisitor.h |  12 +
 clang/include/clang/AST/StmtCXX.h             | 179 +++++++
 clang/include/clang/Basic/DeclNodes.td        |   1 +
 .../clang/Basic/DiagnosticParseKinds.td       |  11 +-
 .../clang/Basic/DiagnosticSemaKinds.td        |   9 +-
 clang/include/clang/Basic/StmtNodes.td        |  11 +
 clang/include/clang/Parse/Parser.h            |  42 +-
 clang/include/clang/Sema/Sema.h               |  85 +++-
 .../include/clang/Serialization/ASTBitCodes.h |  11 +
 clang/lib/AST/ComputeDependence.cpp           |   6 +
 clang/lib/AST/DeclBase.cpp                    |  14 +-
 clang/lib/AST/DeclPrinter.cpp                 |   6 +
 clang/lib/AST/DeclTemplate.cpp                |  19 +
 clang/lib/AST/Expr.cpp                        |   2 +
 clang/lib/AST/ExprCXX.cpp                     |  42 ++
 clang/lib/AST/ExprClassification.cpp          |   2 +
 clang/lib/AST/ExprConstant.cpp                |   2 +
 clang/lib/AST/ItaniumMangle.cpp               |   9 +-
 clang/lib/AST/StmtCXX.cpp                     |  96 ++++
 clang/lib/AST/StmtPrinter.cpp                 |  31 ++
 clang/lib/AST/StmtProfile.cpp                 |  24 +
 clang/lib/CodeGen/CGDecl.cpp                  |   7 +
 clang/lib/CodeGen/CGStmt.cpp                  |  33 ++
 clang/lib/CodeGen/CodeGenFunction.h           |   2 +
 clang/lib/Frontend/FrontendActions.cpp        |   2 +
 clang/lib/Parse/ParseDecl.cpp                 |  37 +-
 clang/lib/Parse/ParseInit.cpp                 |  13 +
 clang/lib/Parse/ParseStmt.cpp                 | 121 ++++-
 clang/lib/Sema/CMakeLists.txt                 |   1 +
 clang/lib/Sema/Sema.cpp                       |   4 +-
 clang/lib/Sema/SemaDecl.cpp                   |   9 +-
 clang/lib/Sema/SemaExceptionSpec.cpp          |   4 +
 clang/lib/Sema/SemaExpand.cpp                 | 468 +++++++++++++++++
 clang/lib/Sema/SemaExpr.cpp                   |   5 +-
 clang/lib/Sema/SemaLambda.cpp                 |  14 +-
 clang/lib/Sema/SemaStmt.cpp                   | 473 ++++++++++--------
 clang/lib/Sema/SemaTemplateInstantiate.cpp    |  14 +
 .../lib/Sema/SemaTemplateInstantiateDecl.cpp  |  41 ++
 clang/lib/Sema/TreeTransform.h                |  98 +++-
 clang/lib/Serialization/ASTCommon.cpp         |   1 +
 clang/lib/Serialization/ASTReaderDecl.cpp     |  11 +
 clang/lib/Serialization/ASTReaderStmt.cpp     |  63 +++
 clang/lib/Serialization/ASTWriterDecl.cpp     |   9 +
 clang/lib/Serialization/ASTWriterStmt.cpp     |  47 ++
 clang/lib/StaticAnalyzer/Core/ExprEngine.cpp  |   4 +
 .../Parser/cxx2c-expansion-statements.cpp     |  57 +++
 .../SemaCXX/cxx2c-expansion-statements.cpp    | 104 ++++
 clang/tools/libclang/CIndex.cpp               |   1 +
 clang/tools/libclang/CXCursor.cpp             |   4 +
 55 files changed, 2162 insertions(+), 282 deletions(-)
 create mode 100644 clang/lib/Sema/SemaExpand.cpp
 create mode 100644 clang/test/Parser/cxx2c-expansion-statements.cpp
 create mode 100644 clang/test/SemaCXX/cxx2c-expansion-statements.cpp

diff --git a/clang/include/clang/AST/ASTNodeTraverser.h b/clang/include/clang/AST/ASTNodeTraverser.h
index e74bb72571d64..69915800397cf 100644
--- a/clang/include/clang/AST/ASTNodeTraverser.h
+++ b/clang/include/clang/AST/ASTNodeTraverser.h
@@ -959,6 +959,12 @@ class ASTNodeTraverser
     }
   }
 
+  void VisitExpansionStmtDecl(const ExpansionStmtDecl* Node) {
+    Visit(Node->getExpansionPattern());
+    if (Traversal != TK_IgnoreUnlessSpelledInSource)
+      Visit(Node->getInstantiations());
+  }
+
   void VisitCallExpr(const CallExpr *Node) {
     for (const auto *Child :
          make_filter_range(Node->children(), [this](const Stmt *Child) {
diff --git a/clang/include/clang/AST/ComputeDependence.h b/clang/include/clang/AST/ComputeDependence.h
index c298f2620f211..792f45bea5aeb 100644
--- a/clang/include/clang/AST/ComputeDependence.h
+++ b/clang/include/clang/AST/ComputeDependence.h
@@ -94,6 +94,7 @@ class DesignatedInitExpr;
 class ParenListExpr;
 class PseudoObjectExpr;
 class AtomicExpr;
+class CXXExpansionInitListExpr;
 class ArraySectionExpr;
 class OMPArrayShapingExpr;
 class OMPIteratorExpr;
@@ -191,6 +192,8 @@ ExprDependence computeDependence(ParenListExpr *E);
 ExprDependence computeDependence(PseudoObjectExpr *E);
 ExprDependence computeDependence(AtomicExpr *E);
 
+ExprDependence computeDependence(CXXExpansionInitListExpr *E);
+
 ExprDependence computeDependence(ArraySectionExpr *E);
 ExprDependence computeDependence(OMPArrayShapingExpr *E);
 ExprDependence computeDependence(OMPIteratorExpr *E);
diff --git a/clang/include/clang/AST/Decl.h b/clang/include/clang/AST/Decl.h
index 406d79ebd6641..575bd4d160882 100644
--- a/clang/include/clang/AST/Decl.h
+++ b/clang/include/clang/AST/Decl.h
@@ -1254,7 +1254,9 @@ class VarDecl : public DeclaratorDecl, public Redeclarable<VarDecl> {
     if (getKind() != Decl::Var && getKind() != Decl::Decomposition)
       return false;
     if (const DeclContext *DC = getLexicalDeclContext())
-      return DC->getRedeclContext()->isFunctionOrMethod();
+      return DC->getEnclosingNonExpansionStatementContext()
+          ->getRedeclContext()
+          ->isFunctionOrMethod();
     return false;
   }
 
diff --git a/clang/include/clang/AST/DeclBase.h b/clang/include/clang/AST/DeclBase.h
index c6326a8ba506d..00866efa4b164 100644
--- a/clang/include/clang/AST/DeclBase.h
+++ b/clang/include/clang/AST/DeclBase.h
@@ -2195,6 +2195,10 @@ class DeclContext {
     return getDeclKind() == Decl::RequiresExprBody;
   }
 
+  bool isExpansionStmt() const {
+    return getDeclKind() == Decl::ExpansionStmt;
+  }
+
   bool isNamespace() const { return getDeclKind() == Decl::Namespace; }
 
   bool isStdNamespace() const;
@@ -2292,6 +2296,15 @@ class DeclContext {
     return const_cast<DeclContext *>(this)->getOuterLexicalRecordContext();
   }
 
+  /// Retrieve the innermost enclosing context that doesn't belong to an
+  /// expansion statement. Returns 'this' if this context is not an expansion
+  /// statement.
+  DeclContext *getEnclosingNonExpansionStatementContext();
+  const DeclContext *getEnclosingNonExpansionStatementContext() const {
+    return const_cast<DeclContext *>(this)
+        ->getEnclosingNonExpansionStatementContext();
+  }
+
   /// Test if this context is part of the enclosing namespace set of
   /// the context NS, as defined in C++0x [namespace.def]p9. If either context
   /// isn't a namespace, this is equivalent to Equals().
diff --git a/clang/include/clang/AST/DeclTemplate.h b/clang/include/clang/AST/DeclTemplate.h
index a4a1bb9c13c79..23aa3eeb0bf87 100644
--- a/clang/include/clang/AST/DeclTemplate.h
+++ b/clang/include/clang/AST/DeclTemplate.h
@@ -3343,6 +3343,60 @@ class TemplateParamObjectDecl : public ValueDecl,
   static bool classofKind(Kind K) { return K == TemplateParamObject; }
 };
 
+
+/// Represents a C++26 expansion statement declaration.
+///
+/// This is a bit of a hack, since expansion statements shouldn't really be
+/// "declarations" per se (they don't declare anything). Nevertheless, we *do*
+/// need them to be declaration *contexts*, because the DeclContext is used to
+/// compute the "template depth" of entities enclosed therein. In particular,
+/// the "template depth" is used to find instantiations of parameter variables,
+/// and a lambda enclosed within an expansion statement cannot compute its
+/// templat depth without a pointer to the enclosing expansion statement.
+///
+/// Another approach would be to extend 'CXXExpansionStmt' from 'DeclContext'
+/// without also providing a 'Decl' - but it seems as if this would be novel,
+/// and I'm not sure if existing code assumes that a 'DeclContext' is a 'Decl'.
+///
+/// TODO(P2996): This could probably be a 'TemplateDecl'.
+class ExpansionStmtDecl : public Decl, public DeclContext {
+  CXXExpansionStmt *Expansion = nullptr;
+  TemplateParameterList *TParams;
+  CXXExpansionInstantiationStmt* Instantiations = nullptr;
+
+  ExpansionStmtDecl(DeclContext *DC, SourceLocation Loc,
+                    TemplateParameterList *TParams);
+
+public:
+  friend class ASTDeclReader;
+
+  static ExpansionStmtDecl *Create(ASTContext &C, DeclContext *DC,
+                                   SourceLocation Loc,
+                                   TemplateParameterList *TParams);
+  static ExpansionStmtDecl *CreateDeserialized(ASTContext &C, GlobalDeclID ID);
+
+  CXXExpansionStmt *getExpansionPattern() { return Expansion; }
+  const CXXExpansionStmt *getExpansionPattern() const { return Expansion; }
+  void setExpansionPattern(CXXExpansionStmt *S) { Expansion = S; }
+
+  CXXExpansionInstantiationStmt *getInstantiations() { return Instantiations; }
+  const CXXExpansionInstantiationStmt *getInstantiations() const {
+    return Instantiations;
+  }
+
+  void setInstantiations(CXXExpansionInstantiationStmt *S) { Instantiations = S; }
+
+  NonTypeTemplateParmDecl *getIndexTemplateParm() const {
+    return cast<NonTypeTemplateParmDecl>(TParams->getParam(0));
+  }
+  TemplateParameterList *getTemplateParameters() const { return TParams; }
+
+  SourceRange getSourceRange() const override LLVM_READONLY;
+
+  static bool classof(const Decl *D) { return classofKind(D->getKind()); }
+  static bool classofKind(Kind K) { return K == ExpansionStmt; }
+};
+
 inline NamedDecl *getAsNamedDecl(TemplateParameter P) {
   if (auto *PD = P.dyn_cast<TemplateTypeParmDecl *>())
     return PD;
diff --git a/clang/include/clang/AST/ExprCXX.h b/clang/include/clang/AST/ExprCXX.h
index d78c7b6363b5d..c192f5bc4b4f7 100644
--- a/clang/include/clang/AST/ExprCXX.h
+++ b/clang/include/clang/AST/ExprCXX.h
@@ -5501,6 +5501,109 @@ class BuiltinBitCastExpr final
   }
 };
 
+// Represents an expansion-init-list to be expanded over by an expansion
+// statement.
+class CXXExpansionInitListExpr final
+    : public Expr,
+      llvm::TrailingObjects<CXXExpansionInitListExpr, Expr *> {
+  friend class ASTStmtReader;
+  friend TrailingObjects;
+
+  const unsigned NumExprs;
+  SourceLocation LBraceLoc;
+  SourceLocation RBraceLoc;
+
+  CXXExpansionInitListExpr(EmptyShell ES, unsigned NumExprs);
+  CXXExpansionInitListExpr(ArrayRef<Expr *> Exprs, SourceLocation LBraceLoc,
+                           SourceLocation RBraceLoc);
+
+public:
+  static CXXExpansionInitListExpr *Create(const ASTContext &C,
+                                          ArrayRef<Expr *> Exprs,
+                                          SourceLocation LBraceLoc,
+                                          SourceLocation RBraceLoc);
+
+  static CXXExpansionInitListExpr *
+  CreateEmpty(const ASTContext &C, EmptyShell Empty, unsigned NumExprs);
+
+  ArrayRef<Expr *> getExprs() const { return getTrailingObjects(NumExprs); }
+  MutableArrayRef<Expr *> getExprs() { return getTrailingObjects(NumExprs); }
+  unsigned getNumExprs() const { return NumExprs; }
+
+  SourceLocation getBeginLoc() const { return getLBraceLoc(); }
+  SourceLocation getEndLoc() const { return getRBraceLoc(); }
+
+  SourceLocation getLBraceLoc() const { return LBraceLoc; }
+  SourceLocation getRBraceLoc() const { return RBraceLoc; }
+
+  child_range children() {
+    const_child_range CCR =
+        const_cast<const CXXExpansionInitListExpr *>(this)->children();
+    return child_range(cast_away_const(CCR.begin()),
+                       cast_away_const(CCR.end()));
+  }
+
+  const_child_range children() const {
+    Stmt** Stmts = getTrailingStmts();
+    return const_child_range(Stmts, Stmts + NumExprs);
+  }
+
+  static bool classof(const Stmt *T) {
+    return T->getStmtClass() == CXXExpansionInitListExprClass;
+  }
+
+private:
+  Stmt** getTrailingStmts() const {
+    return reinterpret_cast<Stmt**>(const_cast<Expr**>(getTrailingObjects()));
+  }
+};
+
+class CXXExpansionInitListSelectExpr : public Expr {
+  friend class ASTStmtReader;
+
+  enum SubExpr { RANGE, INDEX, COUNT };
+  Expr *SubExprs[COUNT];
+
+public:
+  CXXExpansionInitListSelectExpr(EmptyShell Empty);
+  CXXExpansionInitListSelectExpr(const ASTContext &C,
+                                 CXXExpansionInitListExpr *Range, Expr *Idx);
+
+  CXXExpansionInitListExpr *getRangeExpr() {
+    return cast<CXXExpansionInitListExpr>(SubExprs[RANGE]);
+  }
+
+  const CXXExpansionInitListExpr *getRangeExpr() const {
+    return cast<CXXExpansionInitListExpr>(SubExprs[RANGE]);
+  }
+
+  void setRangeExpr(CXXExpansionInitListExpr* E) {
+    SubExprs[RANGE] = E;
+  }
+
+  Expr *getIndexExpr() { return SubExprs[INDEX]; }
+  const Expr *getIndexExpr() const { return SubExprs[INDEX]; }
+  void setIndexExpr(Expr* E) { SubExprs[INDEX] = E; }
+
+  SourceLocation getBeginLoc() const { return getRangeExpr()->getExprLoc(); }
+  SourceLocation getEndLoc() const { return getRangeExpr()->getEndLoc(); }
+
+  child_range children() {
+    return child_range(reinterpret_cast<Stmt **>(SubExprs),
+                       reinterpret_cast<Stmt **>(SubExprs + COUNT));
+  }
+
+  const_child_range children() const {
+    return const_child_range(
+            reinterpret_cast<Stmt **>(const_cast<Expr **>(SubExprs)),
+            reinterpret_cast<Stmt **>(const_cast<Expr **>(SubExprs + COUNT)));
+  }
+
+  static bool classof(const Stmt *T) {
+    return T->getStmtClass() == CXXExpansionInitListSelectExprClass;
+  }
+};
+
 } // namespace clang
 
 #endif // LLVM_CLANG_AST_EXPRCXX_H
diff --git a/clang/include/clang/AST/RecursiveASTVisitor.h b/clang/include/clang/AST/RecursiveASTVisitor.h
index 32b2b6bdb989c..98ba7edde0e80 100644
--- a/clang/include/clang/AST/RecursiveASTVisitor.h
+++ b/clang/include/clang/AST/RecursiveASTVisitor.h
@@ -1881,6 +1881,13 @@ DEF_TRAVERSE_DECL(UsingShadowDecl, {})
 
 DEF_TRAVERSE_DECL(ConstructorUsingShadowDecl, {})
 
+DEF_TRAVERSE_DECL(ExpansionStmtDecl, {
+  if (D->getInstantiations() && getDerived().shouldVisitTemplateInstantiations())
+      TRY_TO(TraverseStmt(D->getInstantiations()));
+
+  TRY_TO(TraverseStmt(D->getExpansionPattern()));
+})
+
 DEF_TRAVERSE_DECL(OMPThreadPrivateDecl, {
   for (auto *I : D->varlist()) {
     TRY_TO(TraverseStmt(I));
@@ -3117,6 +3124,11 @@ DEF_TRAVERSE_STMT(RequiresExpr, {
     TRY_TO(TraverseConceptRequirement(Req));
 })
 
+DEF_TRAVERSE_STMT(CXXEnumeratingExpansionStmt, {})
+DEF_TRAVERSE_STMT(CXXExpansionInstantiationStmt, {})
+DEF_TRAVERSE_STMT(CXXExpansionInitListExpr, {})
+DEF_TRAVERSE_STMT(CXXExpansionInitListSelectExpr, {})
+
 // These literals (all of them) do not need any action.
 DEF_TRAVERSE_STMT(IntegerLiteral, {})
 DEF_TRAVERSE_STMT(FixedPointLiteral, {})
diff --git a/clang/include/clang/AST/StmtCXX.h b/clang/include/clang/AST/StmtCXX.h
index 5d68d3ef64a20..7851d42419be2 100644
--- a/clang/include/clang/AST/StmtCXX.h
+++ b/clang/include/clang/AST/StmtCXX.h
@@ -19,6 +19,8 @@
 #include "clang/AST/Stmt.h"
 #include "llvm/Support/Compiler.h"
 
+#include <clang/AST/ExprCXX.h>
+
 namespace clang {
 
 class VarDecl;
@@ -524,6 +526,183 @@ class CoreturnStmt : public Stmt {
   }
 };
 
+/// CXXExpansionStmt - Base class for an unexpanded C++ expansion statement.
+class CXXExpansionStmt : public Stmt {
+  friend class ASTStmtReader;
+
+  enum SubStmt {
+    INIT,
+    VAR,
+    BODY,
+    COUNT
+  };
+
+  ExpansionStmtDecl* ParentDecl;
+  Stmt* SubStmts[COUNT];
+  SourceLocation ForLoc;
+  SourceLocation LParenLoc;
+  SourceLocation ColonLoc;
+  SourceLocation RParenLoc;
+
+protected:
+  CXXExpansionStmt(StmtClass SC, EmptyShell Empty);
+  CXXExpansionStmt(StmtClass SC, ExpansionStmtDecl *ESD, Stmt *Init,
+                   DeclStmt *ExpansionVar, SourceLocation ForLoc,
+                   SourceLocation LParenLoc, SourceLocation ColonLoc,
+                   SourceLocation RParenLoc);
+
+public:
+  SourceLocation getForLoc() const { return ForLoc; }
+  SourceLocation getLParenLoc() const { return LParenLoc; }
+  SourceLocation getColonLoc() const { return ColonLoc; }
+  SourceLocation getRParenLoc() const { return RParenLoc; }
+
+  SourceLocation getBeginLoc() const;
+  SourceLocation getEndLoc() const {
+    return getBody() ? getBody()->getEndLoc() : RParenLoc;
+  }
+
+  bool hasDependentSize() const;
+  size_t getNumInstantiations() const;
+
+  ExpansionStmtDecl* getDecl() { return ParentDecl; }
+  const ExpansionStmtDecl* getDecl() const { return ParentDecl; }
+
+  Stmt *getInit() { return SubStmts[INIT]; }
+  const Stmt *getInit() const { return SubStmts[INIT]; }
+  void setInit(Stmt* S) { SubStmts[INIT] = S; }
+
+  VarDecl *getExpansionVariable();
+  const VarDecl *getExpansionVariable() const {
+    return const_cast<CXXExpansionStmt *>(this)->getExpansionVariable();
+  }
+
+  DeclStmt *getExpansionVarStmt() { return cast<DeclStmt>(SubStmts[VAR]); }
+  const DeclStmt *getExpansionVarStmt() const {
+    return cast<DeclStmt>(SubStmts[VAR]);
+  }
+
+  void setExpansionVarStmt(Stmt* S) { SubStmts[VAR] = S; }
+
+  Stmt *getBody() { return SubStmts[BODY]; }
+  const Stmt *getBody() const { return SubStmts[BODY]; }
+  void setBody(Stmt* S) { SubStmts[BODY] = S; }
+
+  static bool classof(const Stmt *T) {
+    return T->getStmtClass() >= firstCXXExpansionStmtConstant &&
+           T->getStmtClass() <= lastCXXExpansionStmtConstant;
+  }
+
+  child_range children() {
+    return child_range(SubStmts, SubStmts + COUNT);
+  }
+
+  const_child_range children() const {
+    return const_child_range(SubStmts, SubStmts + COUNT);
+  }
+};
+
+/// Represents an unexpanded enumerating expansion statement.
+///
+/// The expansion initializer of this is always a CXXExpansionInitListExpr.
+class CXXEnumeratingExpansionStmt : public CXXExpansionStmt {
+  friend class ASTStmtReader;
+
+public:
+  CXXEnumeratingExpansionStmt(EmptyShell Empty);
+  CXXEnumeratingExpansionStmt(ExpansionStmtDecl *ESD, Stmt *Init,
+                              DeclStmt *ExpansionVar, SourceLocation ForLoc,
+                              SourceLocation LParenLoc, SourceLocation ColonLoc,
+                              SourceLocation RParenLoc);
+
+  static bool classof(const Stmt *T) {
+    return T->getStmtClass() == CXXEnumeratingExpansionStmtClass;
+  }
+};
+
+/// Represents the code generated for an instantiated expansion statement.
+///
+/// This holds 'shared statements' and 'instantiations'; these encode the
+/// general underlying pattern that all expansion statements desugar to:
+///
+/// \verbatim
+/// {
+///   <shared statements>
+///   {
+///     <1st instantiation>
+///   }
+///   ...
+///   {
+///     <n-th instantiation>
+///   }
+/// }
+/// \endverbatim
+class CXXExpansionInstantiationStmt final
+    : public Stmt,
+      llvm::TrailingObjects<CXXExpansionInstantiationStmt, Stmt *> {
+  friend class ASTStmtReader;
+  friend TrailingObjects;
+
+  SourceLocation Loc;
+
+  // Instantiations are stored first, then shared statements.
+  const unsigned NumInstantiations : 20;
+  const unsigned NumSharedStmts : 3;
+
+  CXXExpansionInstantiationStmt(EmptyShell Empty, unsigned NumInstantiations,
+                                unsigned NumSharedStmts);
+  CXXExpansionInstantiationStmt(SourceLocation Loc,
+                                ArrayRef<Stmt *> Instantiations,
+                                ArrayRef<Stmt *> SharedStmts);
+
+public:
+  static CXXExpansionInstantiationStmt *
+  Create(ASTContext &C, SourceLocation Loc, ArrayRef<Stmt *> Instantiations,
+         ArrayRef<Stmt *> SharedStmts);
+
+  static CXXExpansionInstantiationStmt *CreateEmpty(ASTContext &C,
+                                                    EmptyShell Empty,
+                                                    unsigned NumInstantiations,
+                                                    unsigned NumSharedStmts);
+
+  ArrayRef<Stmt*> getAllSubStmts() const {
+    return getTrailingObjects(getNumSubStmts());
+  }
+
+  MutableArrayRef<Stmt*> getAllSubStmts() {
+    return getTrailingObjects(getNumSubStmts());
+  }
+
+  unsigned getNumSubStmts() const {
+    return NumInstantiations + NumSharedStmts;
+  }
+
+  ArrayRef<Stmt*> getInstantiations() const {
+    return getTrailingObjects(NumInstantiations);
+  }
+
+  ArrayRef<Stmt*> getSharedStmts() const {
+    return getAllSubStmts().drop_front(NumInstantiations);
+  }
+
+  SourceLocation getBeginLoc() const { return Loc; }
+  SourceLocation getEndLoc() const { return Loc; }
+
+  child_range children() {
+    Stmt **S = getTrailingObjects();
+    return child_range(S, S + getNumSubStmts());
+  }
+
+  const_child_range children() const {
+    Stmt *const *S = getTrailingObjects();
+    return const_child_range(S, S + getNumSubStmts());
+  }
+
+  static bool classof(const Stmt *T) {
+    return T->getStmtClass() == CXXExpansionInstantiationStmtClass;
+  }
+};
+
 }  // end namespace clang
 
 #endif
diff --git a/clang/include/clang/Basic/DeclNodes.td b/clang/include/clang/Basic/DeclNodes.td
index 04311055bb600..405c3a5e977b0 100644
--- a/clang/include/clang/Basic/DeclNodes.td
+++ b/clang/include/clang/Basic/DeclNodes.td
@@ -101,6 +101,7 @@ def AccessSpec : DeclNode<Decl>;
 def Friend : DeclNode<Decl>;
 def FriendTemplate : DeclNode<Decl>;
 def StaticAssert : DeclNode<Decl>;
+def ExpansionStmt : DeclNode<Decl>, DeclContext;
 def Block : DeclNode<Decl, "blocks">, DeclContext;
 def OutlinedFunction : DeclNode<Decl>, DeclContext;
 def Captured : DeclNode<Decl>, DeclContext;
diff --git a/clang/include/clang/Basic/DiagnosticParseKinds.td b/clang/include/clang/Basic/DiagnosticParseKinds.td
index e5e071f43fa75..e194c22bdb614 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<
@@ -416,9 +420,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">;
@@ -445,6 +450,8 @@ 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 range-based">;
 
 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 20b499462ae94..3a24c012d2c78 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -2902,8 +2902,8 @@ def note_which_delegates_to : Note<"which delegates to">;
 def err_for_range_decl_must_be_var : Error<
   "for range 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<
@@ -3678,6 +3678,9 @@ def err_conflicting_codeseg_attribute : Error<
 def warn_duplicate_codeseg_attribute : Warning<
   "duplicate code segment specifiers">, InGroup<Section>;
 
+def err_expanded_identifier_label : Error<
+  "identifier labels are not allowed in expansion statements">;
+
 def err_attribute_patchable_function_entry_invalid_section
     : Error<"section argument to 'patchable_function_entry' attribute is not "
             "valid for this target: %0">;
@@ -5807,6 +5810,8 @@ def note_template_nsdmi_here : Note<
   "in instantiation of default member initializer %q0 requested here">;
 def note_template_type_alias_instantiation_here : Note<
   "in instantiation of template type alias %0 requested here">;
+def note_expansion_stmt_instantiation_here : Note<
+  "in instantiation of expansion statement requested here">;
 def note_template_exception_spec_instantiation_here : Note<
   "in instantiation of exception specification for %0 requested here">;
 def note_template_requirement_instantiation_here : Note<
diff --git a/clang/include/clang/Basic/StmtNodes.td b/clang/include/clang/Basic/StmtNodes.td
index bf3686bb372d5..de6e2aa8003fa 100644
--- a/clang/include/clang/Basic/StmtNodes.td
+++ b/clang/include/clang/Basic/StmtNodes.td
@@ -58,6 +58,11 @@ def CXXForRangeStmt : StmtNode<Stmt>;
 def CoroutineBodyStmt : StmtNode<Stmt>;
 def CoreturnStmt : StmtNode<Stmt>;
 
+// C++ expansion statements (P1306)
+def CXXExpansionStmt : StmtNode<Stmt, 1>;
+def CXXEnumeratingExpansionStmt : StmtNode<CXXExpansionStmt>;
+def CXXExpansionInstantiationStmt : StmtNode<Stmt>; // *Not* derived from CXXExpansionStmt!
+
 // Expressions
 def Expr : StmtNode<ValueStmt, 1>;
 def PredefinedExpr : StmtNode<Expr>;
@@ -177,6 +182,12 @@ def CoyieldExpr : StmtNode<CoroutineSuspendExpr>;
 def ConceptSpecializationExpr : StmtNode<Expr>;
 def RequiresExpr : StmtNode<Expr>;
 
+// C++26 Expansion statement support expressions
+def CXXExpansionInitListExpr : StmtNode<Expr>;
+def CXXExpansionInitListSelectExpr : StmtNode<Expr>;
+//def CXXDestructurableExpansionSelectExpr : StmtNode<Expr>;
+//def CXXIterableExpansionSelectExpr : StmtNode<Expr>;
+
 // Obj-C Expressions.
 def ObjCStringLiteral : StmtNode<Expr>;
 def ObjCBoxedExpr : StmtNode<Expr>;
diff --git a/clang/include/clang/Parse/Parser.h b/clang/include/clang/Parse/Parser.h
index 0d2316f73fb62..a006ff24ea7ae 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 {
@@ -5250,6 +5252,15 @@ 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;
@@ -7436,9 +7447,8 @@ class Parser : public CodeCompletionHandler {
   ///       for-statement: [C99 6.8.5.3]
   ///         'for' '(' expr[opt] ';' expr[opt] ';' expr[opt] ')' statement
   ///         'for' '(' declaration expr[opt] ';' expr[opt] ')' statement
-  /// [C++]   'for' '(' for-init-statement condition[opt] ';' expression[opt] ')'
-  /// [C++]       statement
-  /// [C++0x] 'for'
+  /// [C++]   'for' '(' for-init-statement condition[opt] ';' expression[opt]
+  /// ')' [C++]       statement [C++0x] 'for'
   ///             'co_await'[opt]    [Coroutines]
   ///             '(' for-range-declaration ':' for-range-initializer ')'
   ///             statement
@@ -7457,7 +7467,11 @@ class Parser : public CodeCompletionHandler {
   /// [C++0x]   braced-init-list            [TODO]
   /// \endverbatim
   StmtResult ParseForStatement(SourceLocation *TrailingElseLoc,
-                               LabelDecl *PrecedingLabel);
+                               LabelDecl *PrecedingLabel,
+                               ExpansionStmtDecl *ExpansionStmtDeclaration = nullptr);
+
+  void ParseForRangeInitializerAfterColon(ForRangeInit &FRI,
+                                          ParsingDeclSpec *VarDeclSpec);
 
   /// ParseGotoStatement
   /// \verbatim
@@ -7504,6 +7518,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,
@@ -7677,7 +7707,7 @@ class Parser : public CodeCompletionHandler {
   /// [GNU] asm-clobbers:
   ///         asm-string-literal
   ///         asm-clobbers ',' asm-string-literal
-  /// \endverbatim 
+  /// \endverbatim
   ///
   StmtResult ParseAsmStatement(bool &msAsm);
 
diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index 189798f71dbad..0afe30f64e9c8 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
@@ -4102,7 +4103,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);
@@ -11054,6 +11055,32 @@ class Sema final : public SemaBase {
       BuildForRangeKind Kind,
       ArrayRef<MaterializeTemporaryExpr *> LifetimeExtendTemps = {});
 
+  /// Holds the 'begin' and 'end' variables of a range-based for loop or
+  /// expansion statement; begin-expr and end-expr are also provided; the
+  /// latter are used in some diagnostics.
+  struct ForRangeBeginEndInfo {
+    VarDecl *BeginVar{};
+    VarDecl *EndVar{};
+    Expr *BeginExpr{};
+    Expr *EndExpr{};
+    bool isValid() const { return BeginVar != nullptr && EndVar != nullptr; }
+  };
+
+  /// Determine begin-expr and end-expr and build variable declarations for
+  /// them as per [stmt.ranged].
+  ForRangeBeginEndInfo BuildCXXForRangeBeginEndVars(
+      Scope *S, VarDecl *RangeVar, SourceLocation ColonLoc,
+      SourceLocation CoawaitLoc,
+      ArrayRef<MaterializeTemporaryExpr *> LifetimeExtendTemps,
+      BuildForRangeKind Kind, bool ForExpansionStmt,
+      StmtResult *RebuildResult = nullptr,
+      llvm::function_ref<StmtResult()> RebuildWithDereference = {});
+
+  /// Build the range variable of a range-based for loop or iterating
+  /// expansion statement and return its DeclStmt.
+  StmtResult BuildCXXForRangeRangeVar(Scope *S, Expr *Range,
+                                      bool ForExpansionStmt);
+
   /// FinishCXXForRangeStmt - Attach the body to a C++0x for-range statement.
   /// This is a separate step from ActOnCXXForRangeStmt because analysis of the
   /// body cannot be performed until after the type of the range variable is
@@ -11195,6 +11222,9 @@ class Sema final : public SemaBase {
                                            SourceLocation Loc,
                                            unsigned NumParams);
 
+  void ApplyForRangeOrExpansionStatementLifetimeExtension(
+      VarDecl *RangeVar, ArrayRef<MaterializeTemporaryExpr *> Temporaries);
+
 private:
   /// Check whether the given statement can have musttail applied to it,
   /// issuing a diagnostic and returning false if not.
@@ -13151,6 +13181,9 @@ class Sema final : public SemaBase {
 
       /// We are performing partial ordering for template template parameters.
       PartialOrderingTTP,
+
+      /// We are instantiating an expansion statement.
+      ExpansionStmtInstantiation,
     } Kind;
 
     /// Was the enclosing context a non-instantiation SFINAE context?
@@ -13362,6 +13395,12 @@ class Sema final : public SemaBase {
                           sema::TemplateDeductionInfo &DeductionInfo,
                           SourceRange InstantiationRange = SourceRange());
 
+    /// \brief Note that we are substituting the body of an expansion statement.
+    InstantiatingTemplate(Sema &SemaRef, SourceLocation PointOfInstantiation,
+                          CXXExpansionStmt *ExpansionStmt,
+                          ArrayRef<TemplateArgument> TArgs,
+                          SourceRange InstantiationRange);
+
     /// \brief Note that we are checking the satisfaction of the constraint
     /// expression inside of a nested requirement.
     InstantiatingTemplate(Sema &SemaRef, SourceLocation PointOfInstantiation,
@@ -15611,6 +15650,50 @@ class Sema final : public SemaBase {
   void performFunctionEffectAnalysis(TranslationUnitDecl *TU);
 
   ///@}
+
+  //
+  //
+  // -------------------------------------------------------------------------
+  //
+  //
+
+  /// \name Expansion Statements
+  /// Implementations are in SemaExpand.cpp
+  ///@{
+public:
+  ExpansionStmtDecl *ActOnExpansionStmtDecl(unsigned TemplateDepth,
+                                            SourceLocation TemplateKWLoc);
+
+  ExpansionStmtDecl *BuildExpansionStmtDecl(DeclContext *Ctx,
+                                            SourceLocation TemplateKWLoc,
+                                            NonTypeTemplateParmDecl *NTTP);
+
+  ExprResult ActOnCXXExpansionInitList(MultiExprArg SubExprs,
+                                       SourceLocation LBraceLoc,
+                                       SourceLocation RBraceLoc);
+
+  StmtResult ActOnCXXExpansionStmt(
+      ExpansionStmtDecl *ESD, Stmt *Init, Stmt *ExpansionVarStmt,
+      Expr *ExpansionInitializer, SourceLocation ForLoc,
+      SourceLocation LParenLoc, SourceLocation ColonLoc,
+      SourceLocation RParenLoc, BuildForRangeKind Kind,
+      ArrayRef<MaterializeTemporaryExpr *> LifetimeExtendTemps);
+
+  StmtResult FinishCXXExpansionStmt(Stmt *Expansion, Stmt *Body);
+
+  ExprResult BuildCXXExpansionInitializer(ExpansionStmtDecl *ESD,
+                                          Expr *ExpansionInitializer);
+
+  StmtResult BuildCXXEnumeratingExpansionStmt(
+      Decl *ESD, Stmt *Init, Stmt *ExpansionVar, SourceLocation ForLoc,
+      SourceLocation LParenLoc, SourceLocation ColonLoc,
+      SourceLocation RParenLoc);
+
+  ExprResult
+  BuildCXXExpansionInitListSelectExpr(CXXExpansionInitListExpr *Range,
+                                      Expr *Idx);
+
+  ///@}
 };
 
 DeductionFailureInfo
diff --git a/clang/include/clang/Serialization/ASTBitCodes.h b/clang/include/clang/Serialization/ASTBitCodes.h
index 5d09d5536e5ab..24d34fd02b557 100644
--- a/clang/include/clang/Serialization/ASTBitCodes.h
+++ b/clang/include/clang/Serialization/ASTBitCodes.h
@@ -1460,6 +1460,9 @@ enum DeclCode {
   /// \brief A StaticAssertDecl record.
   DECL_STATIC_ASSERT,
 
+  /// A C++ expansion statement.
+  DECL_EXPANSION_STMT,
+
   /// A record containing CXXBaseSpecifiers.
   DECL_CXX_BASE_SPECIFIERS,
 
@@ -1833,6 +1836,12 @@ enum StmtCode {
 
   STMT_CXX_FOR_RANGE,
 
+  /// A CXXEnumeratedExpansionStmt.
+  STMT_CXX_ENUMERATING_EXPANSION,
+
+  /// A CXXExpansionInstantiationStmt.
+  STMT_CXX_EXPANSION_INSTANTIATION,
+
   /// A CXXOperatorCallExpr record.
   EXPR_CXX_OPERATOR_CALL,
 
@@ -1924,6 +1933,8 @@ enum StmtCode {
   EXPR_CXX_FOLD,                          // CXXFoldExpr
   EXPR_CONCEPT_SPECIALIZATION,            // ConceptSpecializationExpr
   EXPR_REQUIRES,                          // RequiresExpr
+  EXPR_CXX_EXPANSION_INIT_LIST,           // CXXExpansionInitListExpr
+  EXPR_CXX_EXPANSION_INIT_LIST_SELECT,    // CXXExpansionInitListSelectExpr
 
   // CUDA
   EXPR_CUDA_KERNEL_CALL, // CUDAKernelCallExpr
diff --git a/clang/lib/AST/ComputeDependence.cpp b/clang/lib/AST/ComputeDependence.cpp
index e0cf0deb12bd2..c220e10a6e439 100644
--- a/clang/lib/AST/ComputeDependence.cpp
+++ b/clang/lib/AST/ComputeDependence.cpp
@@ -959,3 +959,9 @@ ExprDependence clang::computeDependence(OpenACCAsteriskSizeExpr *E) {
   // way.
   return ExprDependence::None;
 }
+
+ExprDependence clang::computeDependence(CXXExpansionInitListExpr* ILE) {
+  auto D = ExprDependence::None;
+  for (Expr* E : ILE->getExprs()) D |= E->getDependence();
+  return D;
+}
diff --git a/clang/lib/AST/DeclBase.cpp b/clang/lib/AST/DeclBase.cpp
index 30c6d3ed91f1e..07e0e2437b7be 100644
--- a/clang/lib/AST/DeclBase.cpp
+++ b/clang/lib/AST/DeclBase.cpp
@@ -325,6 +325,9 @@ unsigned Decl::getTemplateDepth() const {
   if (auto *TPL = getDescribedTemplateParams())
     return TPL->getDepth() + 1;
 
+  if (auto *ESD = dyn_cast<ExpansionStmtDecl>(this))
+    return ESD->getIndexTemplateParm()->getDepth() + 1;
+
   // If this is a dependent lambda, there might be an enclosing variable
   // template. In this case, the next step is not the parent DeclContext (or
   // even a DeclContext at all).
@@ -1018,6 +1021,7 @@ unsigned Decl::getIdentifierNamespaceForKind(Kind DeclKind) {
     case ImplicitConceptSpecialization:
     case OpenACCDeclare:
     case OpenACCRoutine:
+    case ExpansionStmt:
       // Never looked up by name.
       return 0;
   }
@@ -1382,7 +1386,7 @@ bool DeclContext::isDependentContext() const {
   if (isFileContext())
     return false;
 
-  if (isa<ClassTemplatePartialSpecializationDecl>(this))
+  if (isa<ClassTemplatePartialSpecializationDecl, ExpansionStmtDecl>(this))
     return true;
 
   if (const auto *Record = dyn_cast<CXXRecordDecl>(this)) {
@@ -1491,6 +1495,7 @@ DeclContext *DeclContext::getPrimaryContext() {
   case Decl::OMPDeclareReduction:
   case Decl::OMPDeclareMapper:
   case Decl::RequiresExprBody:
+  case Decl::ExpansionStmt:
     // There is only one DeclContext for these entities.
     return this;
 
@@ -2079,6 +2084,13 @@ RecordDecl *DeclContext::getOuterLexicalRecordContext() {
   return OutermostRD;
 }
 
+DeclContext *DeclContext::getEnclosingNonExpansionStatementContext() {
+  DeclContext *DC = this;
+  while (isa<ExpansionStmtDecl>(DC))
+    DC = DC->getParent();
+  return DC;
+}
+
 bool DeclContext::InEnclosingNamespaceSetOf(const DeclContext *O) const {
   // For non-file contexts, this is equivalent to Equals.
   if (!isFileContext())
diff --git a/clang/lib/AST/DeclPrinter.cpp b/clang/lib/AST/DeclPrinter.cpp
index 47ae613b643b6..45da7ef5a6cc5 100644
--- a/clang/lib/AST/DeclPrinter.cpp
+++ b/clang/lib/AST/DeclPrinter.cpp
@@ -113,6 +113,7 @@ namespace {
     void VisitNonTypeTemplateParmDecl(const NonTypeTemplateParmDecl *NTTP);
     void VisitTemplateTemplateParmDecl(const TemplateTemplateParmDecl *);
     void VisitHLSLBufferDecl(HLSLBufferDecl *D);
+    void VisitExpansionStmtDecl(const ExpansionStmtDecl* D);
 
     void VisitOpenACCDeclareDecl(OpenACCDeclareDecl *D);
     void VisitOpenACCRoutineDecl(OpenACCRoutineDecl *D);
@@ -1329,6 +1330,11 @@ void DeclPrinter::VisitClassTemplatePartialSpecializationDecl(
   VisitCXXRecordDecl(D);
 }
 
+void DeclPrinter::VisitExpansionStmtDecl(const ExpansionStmtDecl* D) {
+  D->getExpansionPattern()->printPretty(Out, nullptr, Policy, Indentation, "\n",
+                                        &Context);
+}
+
 //----------------------------------------------------------------------------
 // Objective-C declarations
 //----------------------------------------------------------------------------
diff --git a/clang/lib/AST/DeclTemplate.cpp b/clang/lib/AST/DeclTemplate.cpp
index 2f7ae6d6cac63..5ee9b357e7b86 100644
--- a/clang/lib/AST/DeclTemplate.cpp
+++ b/clang/lib/AST/DeclTemplate.cpp
@@ -1663,6 +1663,7 @@ clang::getReplacedTemplateParameter(Decl *D, unsigned Index) {
   case Decl::Kind::TemplateTemplateParm:
   case Decl::Kind::TypeAliasTemplate:
   case Decl::Kind::VarTemplate:
+  case Decl::Kind::ExpansionStmt:
     return {cast<TemplateDecl>(D)->getTemplateParameters()->getParam(Index),
             {}};
   case Decl::Kind::ClassTemplateSpecialization: {
@@ -1788,3 +1789,21 @@ const Decl &clang::adjustDeclToTemplate(const Decl &D) {
   // FIXME: Adjust alias templates?
   return D;
 }
+
+ExpansionStmtDecl::ExpansionStmtDecl(DeclContext *DC, SourceLocation Loc,
+                                     TemplateParameterList *TParams)
+    : Decl(ExpansionStmt, DC, Loc), DeclContext(ExpansionStmt), TParams(TParams) {}
+
+
+ExpansionStmtDecl *ExpansionStmtDecl::Create(ASTContext &C, DeclContext *DC,
+                                 SourceLocation Loc,
+                                 TemplateParameterList *TParams) {
+  return new (C, DC) ExpansionStmtDecl(DC, Loc, TParams);
+}
+ExpansionStmtDecl *ExpansionStmtDecl::CreateDeserialized(ASTContext &C, GlobalDeclID ID) {
+  return new (C, ID) ExpansionStmtDecl(nullptr, SourceLocation(), nullptr);
+}
+
+SourceRange ExpansionStmtDecl::getSourceRange() const {
+  return Expansion ? Expansion->getSourceRange() : SourceRange();
+}
diff --git a/clang/lib/AST/Expr.cpp b/clang/lib/AST/Expr.cpp
index 340bb4b2ed6a3..dea07cbde39cd 100644
--- a/clang/lib/AST/Expr.cpp
+++ b/clang/lib/AST/Expr.cpp
@@ -3688,6 +3688,8 @@ bool Expr::HasSideEffects(const ASTContext &Ctx,
   case FunctionParmPackExprClass:
   case RecoveryExprClass:
   case CXXFoldExprClass:
+  case CXXExpansionInitListSelectExprClass:
+  case CXXExpansionInitListExprClass:
     // Make a conservative assumption for dependent nodes.
     return IncludePossibleEffects;
 
diff --git a/clang/lib/AST/ExprCXX.cpp b/clang/lib/AST/ExprCXX.cpp
index c7f0ff040194d..0d3e6b2aa10a0 100644
--- a/clang/lib/AST/ExprCXX.cpp
+++ b/clang/lib/AST/ExprCXX.cpp
@@ -2020,3 +2020,45 @@ CXXFoldExpr::CXXFoldExpr(QualType T, UnresolvedLookupExpr *Callee,
   SubExprs[SubExpr::RHS] = RHS;
   setDependence(computeDependence(this));
 }
+
+CXXExpansionInitListExpr::CXXExpansionInitListExpr(EmptyShell ES,
+                                                   unsigned NumExprs)
+    : Expr(CXXExpansionInitListExprClass, ES), NumExprs(NumExprs) {}
+
+CXXExpansionInitListExpr::CXXExpansionInitListExpr(ArrayRef<Expr *> Exprs,
+                                                   SourceLocation LBraceLoc,
+                                                   SourceLocation RBraceLoc)
+    : Expr(CXXExpansionInitListExprClass, QualType(), VK_PRValue, OK_Ordinary),
+      NumExprs(static_cast<unsigned>(Exprs.size())), LBraceLoc(LBraceLoc),
+      RBraceLoc(RBraceLoc) {
+  llvm::uninitialized_copy(Exprs, getTrailingObjects());
+  setDependence(computeDependence(this));
+}
+
+CXXExpansionInitListExpr *
+CXXExpansionInitListExpr::Create(const ASTContext &C, ArrayRef<Expr *> Exprs,
+                                 SourceLocation LBraceLoc,
+                                 SourceLocation RBraceLoc) {
+  void* Mem = C.Allocate(totalSizeToAlloc<Expr *>(Exprs.size()));
+  return new (Mem) CXXExpansionInitListExpr(Exprs, LBraceLoc, RBraceLoc);
+}
+
+CXXExpansionInitListExpr *
+CXXExpansionInitListExpr::CreateEmpty(const ASTContext &C, EmptyShell Empty,
+                                      unsigned NumExprs) {
+  void *Mem = C.Allocate(totalSizeToAlloc<Expr *>(NumExprs));
+  return new (Mem) CXXExpansionInitListExpr(Empty, NumExprs);
+}
+
+CXXExpansionInitListSelectExpr::CXXExpansionInitListSelectExpr(EmptyShell Empty)
+    : Expr(CXXExpansionInitListSelectExprClass, Empty) {
+}
+
+CXXExpansionInitListSelectExpr::CXXExpansionInitListSelectExpr(
+    const ASTContext &C, CXXExpansionInitListExpr *Range, Expr *Idx)
+    : Expr(CXXExpansionInitListSelectExprClass, C.DependentTy, VK_PRValue,
+           OK_Ordinary) {
+  setDependence(ExprDependence::TypeValueInstantiation);
+  SubExprs[RANGE] = Range;
+  SubExprs[INDEX] = Idx;
+}
diff --git a/clang/lib/AST/ExprClassification.cpp b/clang/lib/AST/ExprClassification.cpp
index aeacd0dc765ef..f676b2d95d26a 100644
--- a/clang/lib/AST/ExprClassification.cpp
+++ b/clang/lib/AST/ExprClassification.cpp
@@ -216,6 +216,8 @@ static Cl::Kinds ClassifyInternal(ASTContext &Ctx, const Expr *E) {
   case Expr::SourceLocExprClass:
   case Expr::ConceptSpecializationExprClass:
   case Expr::RequiresExprClass:
+  case Expr::CXXExpansionInitListExprClass:
+  case Expr::CXXExpansionInitListSelectExprClass:
     return Cl::CL_PRValue;
 
   case Expr::EmbedExprClass:
diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp
index 00aaaab957591..a5d12a0d26fd5 100644
--- a/clang/lib/AST/ExprConstant.cpp
+++ b/clang/lib/AST/ExprConstant.cpp
@@ -19026,6 +19026,8 @@ static ICEDiag CheckICE(const Expr* E, const ASTContext &Ctx) {
   case Expr::SYCLUniqueStableNameExprClass:
   case Expr::CXXParenListInitExprClass:
   case Expr::HLSLOutArgExprClass:
+  case Expr::CXXExpansionInitListExprClass:
+  case Expr::CXXExpansionInitListSelectExprClass:
     return ICEDiag(IK_NotICE, E->getBeginLoc());
 
   case Expr::InitListExprClass: {
diff --git a/clang/lib/AST/ItaniumMangle.cpp b/clang/lib/AST/ItaniumMangle.cpp
index 5572e0a7ae59c..2fe8e0400e141 100644
--- a/clang/lib/AST/ItaniumMangle.cpp
+++ b/clang/lib/AST/ItaniumMangle.cpp
@@ -42,7 +42,7 @@ using namespace clang;
 namespace {
 
 static bool isLocalContainerContext(const DeclContext *DC) {
-  return isa<FunctionDecl>(DC) || isa<ObjCMethodDecl>(DC) || isa<BlockDecl>(DC);
+  return isa<FunctionDecl, ObjCMethodDecl, BlockDecl, ExpansionStmtDecl>(DC);
 }
 
 static const FunctionDecl *getStructor(const FunctionDecl *fn) {
@@ -1851,6 +1851,8 @@ static GlobalDecl getParentOfLocalEntity(const DeclContext *DC) {
     GD = GlobalDecl(CD, Ctor_Complete);
   else if (auto *DD = dyn_cast<CXXDestructorDecl>(DC))
     GD = GlobalDecl(DD, Dtor_Complete);
+  else if (DC->isExpansionStmt())
+    GD = getParentOfLocalEntity(DC->getEnclosingNonExpansionStatementContext());
   else
     GD = GlobalDecl(cast<FunctionDecl>(DC));
   return GD;
@@ -2191,6 +2193,9 @@ void CXXNameMangler::manglePrefix(const DeclContext *DC, bool NoFunction) {
   if (NoFunction && isLocalContainerContext(DC))
     return;
 
+  if (DC->isExpansionStmt())
+    return;
+
   const NamedDecl *ND = cast<NamedDecl>(DC);
   if (mangleSubstitution(ND))
     return;
@@ -4940,6 +4945,8 @@ void CXXNameMangler::mangleExpression(const Expr *E, unsigned Arity,
   case Expr::CXXInheritedCtorInitExprClass:
   case Expr::CXXParenListInitExprClass:
   case Expr::PackIndexingExprClass:
+  case Expr::CXXExpansionInitListSelectExprClass:
+  case Expr::CXXExpansionInitListExprClass:
     llvm_unreachable("unexpected statement kind");
 
   case Expr::ConstantExprClass:
diff --git a/clang/lib/AST/StmtCXX.cpp b/clang/lib/AST/StmtCXX.cpp
index 6a69fe75136f3..312074e69a6e9 100644
--- a/clang/lib/AST/StmtCXX.cpp
+++ b/clang/lib/AST/StmtCXX.cpp
@@ -125,3 +125,99 @@ CoroutineBodyStmt::CoroutineBodyStmt(CoroutineBodyStmt::CtorArgs const &Args)
       Args.ReturnStmtOnAllocFailure;
   llvm::copy(Args.ParamMoves, const_cast<Stmt **>(getParamMoves().data()));
 }
+
+CXXExpansionStmt::CXXExpansionStmt(StmtClass SC, EmptyShell Empty)
+    : Stmt(SC, Empty) {}
+
+CXXExpansionStmt::CXXExpansionStmt(StmtClass SC, ExpansionStmtDecl *ESD,
+                                   Stmt *Init, DeclStmt *ExpansionVar,
+                                   SourceLocation ForLoc,
+                                   SourceLocation LParenLoc,
+                                   SourceLocation ColonLoc,
+                                   SourceLocation RParenLoc)
+    : Stmt(SC), ParentDecl(ESD), ForLoc(ForLoc), LParenLoc(LParenLoc),
+      ColonLoc(ColonLoc), RParenLoc(RParenLoc) {
+  SubStmts[INIT] = Init;
+  SubStmts[VAR] = ExpansionVar;
+  SubStmts[BODY] = nullptr;
+}
+
+CXXEnumeratingExpansionStmt::CXXEnumeratingExpansionStmt(EmptyShell Empty)
+    : CXXExpansionStmt(CXXEnumeratingExpansionStmtClass, Empty) {}
+
+CXXEnumeratingExpansionStmt::CXXEnumeratingExpansionStmt(
+    ExpansionStmtDecl *ESD, Stmt *Init, DeclStmt *ExpansionVar,
+    SourceLocation ForLoc, SourceLocation LParenLoc, SourceLocation ColonLoc,
+    SourceLocation RParenLoc)
+    : CXXExpansionStmt(CXXEnumeratingExpansionStmtClass, ESD, Init,
+                       ExpansionVar, ForLoc, LParenLoc, ColonLoc, RParenLoc) {}
+
+SourceLocation CXXExpansionStmt::getBeginLoc() const {
+  return ParentDecl->getLocation();
+}
+
+// FIXME: Copy-pasted from CXXForRangeStmt. Can we convert this into a helper
+// function and put it somewhere else maybe?
+VarDecl *CXXExpansionStmt::getExpansionVariable() {
+  Decl *LV = cast<DeclStmt>(getExpansionVarStmt())->getSingleDecl();
+  assert(LV && "No expansion variable in CXXExpansionStmt");
+  return cast<VarDecl>(LV);
+}
+
+bool CXXExpansionStmt::hasDependentSize() const {
+  if (isa<CXXEnumeratingExpansionStmt>(this))
+    return getExpansionVariable()->getInit()->containsUnexpandedParameterPack();
+
+  llvm_unreachable("Invalid expansion statement class");
+}
+
+size_t CXXExpansionStmt::getNumInstantiations() const {
+  if (isa<CXXEnumeratingExpansionStmt>(this))
+    return cast<CXXExpansionInitListSelectExpr>(
+               getExpansionVariable()->getInit())
+        ->getRangeExpr()
+        ->getExprs()
+        .size();
+
+  llvm_unreachable("Invalid expansion statement class");
+}
+
+CXXExpansionInstantiationStmt::CXXExpansionInstantiationStmt(
+    EmptyShell Empty, unsigned NumInstantiations, unsigned NumSharedStmts)
+    : Stmt(CXXExpansionInstantiationStmtClass, Empty),
+      NumInstantiations(NumInstantiations), NumSharedStmts(NumSharedStmts) {
+  assert(NumSharedStmts <= 4 && "might have to allocate more bits for this");
+}
+
+CXXExpansionInstantiationStmt::CXXExpansionInstantiationStmt(
+    SourceLocation Loc, ArrayRef<Stmt *> Instantiations,
+    ArrayRef<Stmt *> SharedStmts)
+    : Stmt(CXXExpansionInstantiationStmtClass), Loc(Loc),
+      NumInstantiations(unsigned(Instantiations.size())),
+      NumSharedStmts(unsigned(SharedStmts.size())) {
+  assert(NumSharedStmts <= 4 && "might have to allocate more bits for this");
+  llvm::uninitialized_copy(Instantiations, getTrailingObjects());
+  llvm::uninitialized_copy(SharedStmts, getTrailingObjects() + NumInstantiations);
+}
+
+CXXExpansionInstantiationStmt *
+CXXExpansionInstantiationStmt::Create(ASTContext &C, SourceLocation Loc,
+                                      ArrayRef<Stmt *> Instantiations,
+                                      ArrayRef<Stmt *> SharedStmts) {
+  void *Mem = C.Allocate(
+      totalSizeToAlloc<Stmt *>(Instantiations.size() + SharedStmts.size()),
+      alignof(CXXExpansionInstantiationStmt));
+  return new (Mem)
+      CXXExpansionInstantiationStmt(Loc, Instantiations, SharedStmts);
+}
+
+CXXExpansionInstantiationStmt *
+CXXExpansionInstantiationStmt::CreateEmpty(ASTContext &C, EmptyShell Empty,
+                                           unsigned NumInstantiations,
+                                           unsigned NumSharedStmts) {
+  void *Mem =
+      C.Allocate(totalSizeToAlloc<Stmt *>(NumInstantiations + NumSharedStmts),
+                 alignof(CXXExpansionInstantiationStmt));
+  return new (Mem)
+      CXXExpansionInstantiationStmt(Empty, NumInstantiations, NumSharedStmts);
+}
diff --git a/clang/lib/AST/StmtPrinter.cpp b/clang/lib/AST/StmtPrinter.cpp
index ff8ca01ec5477..87eedd6a3eeed 100644
--- a/clang/lib/AST/StmtPrinter.cpp
+++ b/clang/lib/AST/StmtPrinter.cpp
@@ -447,6 +447,37 @@ void StmtPrinter::VisitCXXForRangeStmt(CXXForRangeStmt *Node) {
   PrintControlledStmt(Node->getBody());
 }
 
+void StmtPrinter::VisitCXXEnumeratingExpansionStmt(
+    CXXEnumeratingExpansionStmt *Node) {
+  Indent() << "template for (";
+  if (Node->getInit())
+    PrintInitStmt(Node->getInit(), 14);
+  PrintingPolicy SubPolicy(Policy);
+  SubPolicy.SuppressInitializers = true;
+  Node->getExpansionVariable()->print(OS, SubPolicy, IndentLevel);
+  OS << ":";
+  PrintExpr(Node->getExpansionVariable()->getInit());
+  OS << ")";
+  PrintControlledStmt(Node->getBody());
+}
+
+void StmtPrinter::VisitCXXExpansionInstantiationStmt(
+    CXXExpansionInstantiationStmt *) {
+  llvm_unreachable("should never be printed");
+}
+
+void StmtPrinter::VisitCXXExpansionInitListExpr(
+    CXXExpansionInitListExpr *Node) {
+  OS << "{ ";
+  llvm::interleaveComma(Node->getExprs(), OS, [&](Expr* E) { PrintExpr(E); });
+  OS << " }";
+}
+
+void StmtPrinter::VisitCXXExpansionInitListSelectExpr(
+    CXXExpansionInitListSelectExpr *Node) {
+  PrintExpr(Node->getRangeExpr());
+}
+
 void StmtPrinter::VisitMSDependentExistsStmt(MSDependentExistsStmt *Node) {
   Indent();
   if (Node->isIfExists())
diff --git a/clang/lib/AST/StmtProfile.cpp b/clang/lib/AST/StmtProfile.cpp
index 05b64ccda0d01..31c4cf397d9b9 100644
--- a/clang/lib/AST/StmtProfile.cpp
+++ b/clang/lib/AST/StmtProfile.cpp
@@ -364,6 +364,20 @@ void StmtProfiler::VisitCXXForRangeStmt(const CXXForRangeStmt *S) {
   VisitStmt(S);
 }
 
+void StmtProfiler::VisitCXXExpansionStmt(const CXXExpansionStmt *S) {
+  VisitStmt(S);
+}
+
+void StmtProfiler::VisitCXXEnumeratingExpansionStmt(
+    const CXXEnumeratingExpansionStmt *S) {
+  VisitCXXExpansionStmt(S);
+}
+
+void StmtProfiler::VisitCXXExpansionInstantiationStmt(
+    const CXXExpansionInstantiationStmt *S) {
+  VisitStmt(S);
+}
+
 void StmtProfiler::VisitMSDependentExistsStmt(const MSDependentExistsStmt *S) {
   VisitStmt(S);
   ID.AddBoolean(S->isIfExists());
@@ -2392,6 +2406,16 @@ void StmtProfiler::VisitSourceLocExpr(const SourceLocExpr *E) {
 
 void StmtProfiler::VisitEmbedExpr(const EmbedExpr *E) { VisitExpr(E); }
 
+void StmtProfiler::VisitCXXExpansionInitListExpr(
+    const CXXExpansionInitListExpr *E) {
+  VisitExpr(E);
+}
+
+void StmtProfiler::VisitCXXExpansionInitListSelectExpr(
+    const CXXExpansionInitListSelectExpr *E) {
+  VisitExpr(E);
+}
+
 void StmtProfiler::VisitRecoveryExpr(const RecoveryExpr *E) { VisitExpr(E); }
 
 void StmtProfiler::VisitObjCStringLiteral(const ObjCStringLiteral *S) {
diff --git a/clang/lib/CodeGen/CGDecl.cpp b/clang/lib/CodeGen/CGDecl.cpp
index 8b1cd83af2396..2e4bfac36d97b 100644
--- a/clang/lib/CodeGen/CGDecl.cpp
+++ b/clang/lib/CodeGen/CGDecl.cpp
@@ -143,6 +143,13 @@ void CodeGenFunction::EmitDecl(const Decl &D, bool EvaluateConditionDecl) {
     // None of these decls require codegen support.
     return;
 
+  case Decl::ExpansionStmt: {
+    const auto* ESD = cast<ExpansionStmtDecl>(&D);
+    assert(ESD->getInstantiations() && "expansion statement not expanded?");
+    EmitStmt(ESD->getInstantiations());
+    return;
+  }
+
   case Decl::NamespaceAlias:
     if (CGDebugInfo *DI = getDebugInfo())
         DI->EmitNamespaceAlias(cast<NamespaceAliasDecl>(D));
diff --git a/clang/lib/CodeGen/CGStmt.cpp b/clang/lib/CodeGen/CGStmt.cpp
index fdc1a11f6c55c..e1d020bf2aaab 100644
--- a/clang/lib/CodeGen/CGStmt.cpp
+++ b/clang/lib/CodeGen/CGStmt.cpp
@@ -204,6 +204,11 @@ void CodeGenFunction::EmitStmt(const Stmt *S, ArrayRef<const Attr *> Attrs) {
   case Stmt::CXXForRangeStmtClass:
     EmitCXXForRangeStmt(cast<CXXForRangeStmt>(*S), Attrs);
     break;
+  case Stmt::CXXEnumeratingExpansionStmtClass:
+    llvm_unreachable("unexpanded expansion statements should not be emitted");
+  case Stmt::CXXExpansionInstantiationStmtClass:
+    EmitCXXExpansionInstantiationStmt(cast<CXXExpansionInstantiationStmt>(*S));
+    break;
   case Stmt::SEHTryStmtClass:
     EmitSEHTryStmt(cast<SEHTryStmt>(*S));
     break;
@@ -1559,6 +1564,34 @@ CodeGenFunction::EmitCXXForRangeStmt(const CXXForRangeStmt &S,
   }
 }
 
+void CodeGenFunction::EmitCXXExpansionInstantiationStmt(
+    const CXXExpansionInstantiationStmt &S) {
+  LexicalScope Scope(*this, S.getSourceRange());
+
+  for (const Stmt* DS : S.getSharedStmts())
+    EmitStmt(DS);
+
+  if (S.getInstantiations().empty() || !HaveInsertPoint())
+    return;
+
+  JumpDest ExpandExit = getJumpDestInCurrentScope("expand.end");
+  JumpDest ContinueDest;
+  for (auto [N, Inst] : enumerate(S.getInstantiations())) {
+    if (!HaveInsertPoint())
+      return;
+
+    if (N == S.getInstantiations().size() - 1)
+      ContinueDest = ExpandExit;
+     else
+      ContinueDest = getJumpDestInCurrentScope("expand.next");
+
+    BreakContinueStack.push_back(BreakContinue(S, ExpandExit, ContinueDest));
+    EmitStmt(Inst);
+    BreakContinueStack.pop_back();
+    EmitBlock(ContinueDest.getBlock(), true);
+  }
+}
+
 void CodeGenFunction::EmitReturnOfRValue(RValue RV, QualType Ty) {
   if (RV.isScalar()) {
     Builder.CreateStore(RV.getScalarVal(), ReturnValue);
diff --git a/clang/lib/CodeGen/CodeGenFunction.h b/clang/lib/CodeGen/CodeGenFunction.h
index 1f0be2d8756de..70fd03d7f6b6e 100644
--- a/clang/lib/CodeGen/CodeGenFunction.h
+++ b/clang/lib/CodeGen/CodeGenFunction.h
@@ -3685,6 +3685,8 @@ class CodeGenFunction : public CodeGenTypeCache {
   void EmitCXXForRangeStmt(const CXXForRangeStmt &S,
                            ArrayRef<const Attr *> Attrs = {});
 
+  void EmitCXXExpansionInstantiationStmt(const CXXExpansionInstantiationStmt& S);
+
   /// Controls insertion of cancellation exit blocks in worksharing constructs.
   class OMPCancelStackRAII {
     CodeGenFunction &CGF;
diff --git a/clang/lib/Frontend/FrontendActions.cpp b/clang/lib/Frontend/FrontendActions.cpp
index d7d56b8166350..2c3950c5b0b52 100644
--- a/clang/lib/Frontend/FrontendActions.cpp
+++ b/clang/lib/Frontend/FrontendActions.cpp
@@ -476,6 +476,8 @@ class DefaultTemplateInstCallback : public TemplateInstantiationCallback {
       return "TypeAliasTemplateInstantiation";
     case CodeSynthesisContext::PartialOrderingTTP:
       return "PartialOrderingTTP";
+    case CodeSynthesisContext::ExpansionStmtInstantiation:
+      return "ExpansionStmtInstantiation";
     }
     return "";
   }
diff --git a/clang/lib/Parse/ParseDecl.cpp b/clang/lib/Parse/ParseDecl.cpp
index e4b158e4a6248..e700e96ef8e53 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/ParseInit.cpp b/clang/lib/Parse/ParseInit.cpp
index a3be3744a9327..7f010493a477b 100644
--- a/clang/lib/Parse/ParseInit.cpp
+++ b/clang/lib/Parse/ParseInit.cpp
@@ -516,6 +516,19 @@ ExprResult Parser::ParseBraceInitializer() {
   return ExprError(); // an error occurred.
 }
 
+ExprResult Parser::ParseExpansionInitList() {
+  BalancedDelimiterTracker T(*this, tok::l_brace);
+  T.consumeOpen();
+
+  ExprVector InitExprs;
+  if (!Tok.is(tok::r_brace) && ParseExpressionList(InitExprs))
+    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 92038985f9163..0f73d13dc9fe6 100644
--- a/clang/lib/Parse/ParseStmt.cpp
+++ b/clang/lib/Parse/ParseStmt.cpp
@@ -260,6 +260,9 @@ StmtResult Parser::ParseStatementOrDeclarationAfterAttributes(
   }
 
   case tok::kw_template: {
+    if (NextToken().is(tok::kw_for))
+      return ParseExpansionStatement(TrailingElseLoc, PrecedingLabel);
+
     SourceLocation DeclEnd;
     ParseTemplateDeclarationOrSpecialization(DeclaratorContext::Block, DeclEnd,
                                              getAccessSpecifierIfPresent());
@@ -1890,8 +1893,54 @@ bool Parser::isForRangeIdentifier() {
   return false;
 }
 
+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())
+    // TODO: Shouldn't this be 'ConstantEvaluated'?
+    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) {
+                                     LabelDecl *PrecedingLabel,
+                                     ExpansionStmtDecl *ExpansionStmtDeclaration) {
   assert(Tok.is(tok::kw_for) && "Not a for stmt!");
   SourceLocation ForLoc = ConsumeToken();  // eat the 'for'.
 
@@ -1926,6 +1975,8 @@ StmtResult Parser::ParseForStatement(SourceLocation *TrailingElseLoc,
   unsigned ScopeFlags = 0;
   if (C99orCXXorObjC)
     ScopeFlags = Scope::DeclScope | Scope::ControlScope;
+  if (ExpansionStmtDeclaration)
+    ScopeFlags |= Scope::TemplateParamScope;
 
   ParseScope ForScope(this, ScopeFlags);
 
@@ -1940,6 +1991,7 @@ StmtResult Parser::ParseForStatement(SourceLocation *TrailingElseLoc,
   ExprResult Collection;
   ForRangeInfo ForRangeInfo;
   FullExprArg ThirdPart(Actions);
+  ForRangeInfo.ExpansionStmt = ExpansionStmtDeclaration != nullptr;
 
   if (Tok.is(tok::code_completion)) {
     cutOffParsing();
@@ -1970,18 +2022,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)
+      << ForRangeInfo.ExpansionStmt
       << ((getLangOpts().CPlusPlus11 && !getLangOpts().CPlusPlus17)
               ? FixItHint::CreateInsertion(Loc, "auto &&")
               : FixItHint());
 
-    ForRangeInfo.LoopVar =
-        Actions.ActOnCXXForRangeIdentifier(getCurScope(), Loc, Name, attrs);
+    if (!ForRangeInfo.ExpansionStmt)
+      ForRangeInfo.LoopVar =
+          Actions.ActOnCXXForRangeIdentifier(getCurScope(), Loc, Name, attrs);
   } else if (isForInitDeclaration()) {  // for (int X = 4;
     ParenBraceBracketBalancer BalancerRAIIObj(*this);
 
@@ -2073,7 +2124,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();
+          << (ExpansionStmtDeclaration != nullptr)
+          << FirstPart.get()->getSourceRange();
       SkipUntil(tok::r_paren, StopBeforeMatch);
       SecondPart = Sema::ConditionError();
     } else {
@@ -2195,7 +2247,13 @@ StmtResult Parser::ParseForStatement(SourceLocation *TrailingElseLoc,
   StmtResult ForRangeStmt;
   StmtResult ForEachStmt;
 
-  if (ForRangeInfo.ParsedForRangeDecl()) {
+  if (ExpansionStmtDeclaration) {
+    ForRangeStmt = Actions.ActOnCXXExpansionStmt(
+        ExpansionStmtDeclaration, FirstPart.get(), ForRangeInfo.LoopVar.get(),
+        ForRangeInfo.RangeExpr.get(), ForLoc, T.getOpenLocation(),
+        ForRangeInfo.ColonLoc, T.getCloseLocation(), Sema::BFRK_Build,
+        ForRangeInfo.LifetimeExtendTemps);
+  } else if (ForRangeInfo.ParsedForRangeDecl()) {
     ForRangeStmt = Actions.ActOnCXXForRangeStmt(
         getCurScope(), ForLoc, CoawaitLoc, FirstPart.get(),
         ForRangeInfo.LoopVar.get(), ForRangeInfo.ColonLoc,
@@ -2217,7 +2275,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 (ExpansionStmtDeclaration)
+    ; // TODO: Figure out what to do here, if anything.
+  else if (ForRangeInfo.ParsedForRangeDecl())
     getActions().OpenACC().ActOnRangeForStmtBegin(ForLoc, ForRangeStmt.get());
   else
     getActions().OpenACC().ActOnForStmtBegin(
@@ -2271,6 +2331,15 @@ StmtResult Parser::ParseForStatement(SourceLocation *TrailingElseLoc,
     return Actions.ObjC().FinishObjCForCollectionStmt(ForEachStmt.get(),
                                                       Body.get());
 
+  if (ExpansionStmtDeclaration) {
+    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());
 
@@ -2630,6 +2699,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);
+
+  ExpansionStmtDecl *ExpansionDecl =
+      Actions.ActOnExpansionStmtDecl(TemplateParameterDepth, TemplateLoc);
+
+  CXXExpansionStmt *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<CXXExpansionStmt>(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/Sema.cpp b/clang/lib/Sema/Sema.cpp
index 23bf7f217a01a..9a75bfe52bd1b 100644
--- a/clang/lib/Sema/Sema.cpp
+++ b/clang/lib/Sema/Sema.cpp
@@ -1631,8 +1631,8 @@ DeclContext *Sema::getFunctionLevelDeclContext(bool AllowLambda) const {
   DeclContext *DC = CurContext;
 
   while (true) {
-    if (isa<BlockDecl>(DC) || isa<EnumDecl>(DC) || isa<CapturedDecl>(DC) ||
-        isa<RequiresExprBodyDecl>(DC)) {
+    if (isa<BlockDecl, EnumDecl, CapturedDecl, RequiresExprBodyDecl,
+            ExpansionStmtDecl>(DC)) {
       DC = DC->getParent();
     } else if (!AllowLambda && isa<CXXMethodDecl>(DC) &&
                cast<CXXMethodDecl>(DC)->getOverloadedOperator() == OO_Call &&
diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp
index fc3aabf5741ca..873ddc1c3950d 100644
--- a/clang/lib/Sema/SemaDecl.cpp
+++ b/clang/lib/Sema/SemaDecl.cpp
@@ -7430,7 +7430,7 @@ static bool shouldConsiderLinkage(const VarDecl *VD) {
   if (DC->getDeclKind() == Decl::HLSLBuffer)
     return false;
 
-  if (isa<RequiresExprBodyDecl>(DC))
+  if (isa<RequiresExprBodyDecl, ExpansionStmtDecl>(DC))
     return false;
   llvm_unreachable("Unexpected context");
 }
@@ -14591,7 +14591,7 @@ 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;
@@ -14603,7 +14603,8 @@ void Sema::ActOnCXXForRangeDecl(Decl *D) {
     return;
   }
 
-  VD->setCXXForRangeDecl(true);
+  if (!InExpansionStmt)
+    VD->setCXXForRangeDecl(true);
 
   // for-range-declaration cannot be given a storage class specifier.
   int Error = -1;
@@ -14640,7 +14641,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/SemaExceptionSpec.cpp b/clang/lib/Sema/SemaExceptionSpec.cpp
index a0483c3027199..ccf9c1b575948 100644
--- a/clang/lib/Sema/SemaExceptionSpec.cpp
+++ b/clang/lib/Sema/SemaExceptionSpec.cpp
@@ -1288,6 +1288,8 @@ CanThrowResult Sema::canThrow(const Stmt *S) {
   case Expr::ConvertVectorExprClass:
   case Expr::VAArgExprClass:
   case Expr::CXXParenListInitExprClass:
+  case Expr::CXXExpansionInitListSelectExprClass:
+  case Expr::CXXExpansionInitListExprClass:
     return canSubStmtsThrow(*this, S);
 
   case Expr::CompoundLiteralExprClass:
@@ -1538,6 +1540,8 @@ CanThrowResult Sema::canThrow(const Stmt *S) {
   case Stmt::SEHTryStmtClass:
   case Stmt::SwitchStmtClass:
   case Stmt::WhileStmtClass:
+  case Stmt::CXXEnumeratingExpansionStmtClass:
+  case Stmt::CXXExpansionInstantiationStmtClass:
     return canSubStmtsThrow(*this, S);
 
   case Stmt::DeclStmtClass: {
diff --git a/clang/lib/Sema/SemaExpand.cpp b/clang/lib/Sema/SemaExpand.cpp
new file mode 100644
index 0000000000000..70e6af1886aa7
--- /dev/null
+++ b/clang/lib/Sema/SemaExpand.cpp
@@ -0,0 +1,468 @@
+//===-- 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/DynamicRecursiveASTVisitor.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;
+
+static unsigned ExtractParmVarDeclDepth(Expr *E) {
+  if (auto *DRE = dyn_cast<DeclRefExpr>(E)) {
+    if (auto *PVD = cast<NonTypeTemplateParmDecl>(DRE->getDecl()))
+      return PVD->getDepth();
+  } else if (auto *SNTTPE = cast<SubstNonTypeTemplateParmExpr>(E)) {
+    if (auto *PVD = cast<NonTypeTemplateParmDecl>(SNTTPE->getAssociatedDecl()))
+      return PVD->getDepth();
+  }
+  return 0;
+}
+
+/*
+// Returns 'true' if the 'Range' is an iterable expression, and 'false'
+// otherwise. If 'true', then 'Result' contains the resulting
+// 'CXXIterableExpansionSelectExpr' (or error).
+static bool TryMakeCXXIterableExpansionSelectExpr(
+    Sema &S, Expr *Range, Expr *Index, VarDecl *ExpansionVar,
+    ArrayRef<MaterializeTemporaryExpr *> LifetimeExtendTemps,
+    ExprResult &SelectResult) {
+  auto Ctx = Sema::ExpressionEvaluationContext::PotentiallyEvaluated;
+  if (ExpansionVar->isConstexpr())
+    // TODO: Shouldn’t this be 'ConstantEvaluated'?
+    Ctx = Sema::ExpressionEvaluationContext::ImmediateFunctionContext;
+  EnterExpressionEvaluationContext ExprEvalCtx(S, Ctx);
+
+  // C++26 [stmt.expand]p3: An expression is expansion-iterable if it does not
+  // have array type [...]
+  if (Range->getType()->isArrayType())
+    return false;
+
+  SourceLocation RangeLoc = Range->getExprLoc();
+  DeclarationNameInfo BeginName(&S.PP.getIdentifierTable().get("begin"),
+                                RangeLoc);
+  LookupResult BeginLR(S, BeginName, Sema::LookupMemberName);
+  if (auto *RD = Range->getType()->getAsCXXRecordDecl())
+    S.LookupQualifiedName(BeginLR, RD);
+
+  VarDecl *RangeVar;
+  Expr *VarRef;
+  {
+    assert(isa<ExpansionStmtDecl>(S.CurContext));
+    DeclContext *DC = S.CurContext->getEnclosingNonExpansionStatementContext();
+    IdentifierInfo *II = &S.PP.getIdentifierTable().get("__range");
+    QualType QT = Range->getType().withConst();
+    TypeSourceInfo *TSI = S.Context.getTrivialTypeSourceInfo(QT);
+    RangeVar = VarDecl::Create(S.Context, DC, Range->getBeginLoc(),
+                               Range->getBeginLoc(), II, QT, TSI, SC_Auto);
+
+    if (ExpansionVar->isConstexpr())
+      RangeVar->setConstexpr(true);
+    else if (!LifetimeExtendTemps.empty()) {
+      // TODO: The original patch was performing lifetime extension here, but
+      // CWG 3043 seems to have removed that clause. Is that actually what we
+      // want here?
+      // S.ApplyForRangeOrExpansionStatementLifetimeExtension(
+      //     RangeVar, LifetimeExtendTemps);
+    }
+
+    S.AddInitializerToDecl(RangeVar, Range, /*DirectInit=#1#false);
+    if (RangeVar->isInvalidDecl())
+      return false;
+
+    DeclarationNameInfo Name(II, Range->getBeginLoc());
+    VarRef = S.BuildDeclRefExpr(RangeVar, Range->getType(), VK_LValue, Name,
+                                /*CXXScopeSpec=#1#nullptr, RangeVar);
+  }
+
+  ExprResult BeginResult;
+  {
+    OverloadCandidateSet CandidateSet(RangeLoc,
+                                      OverloadCandidateSet::CSK_Normal);
+    Sema::ForRangeStatus Status =
+        S.BuildForRangeBeginEndCall(RangeLoc, RangeLoc, BeginName, BeginLR,
+                                    &CandidateSet, VarRef, &BeginResult);
+    if (Status != Sema::FRS_Success)
+      return false;
+
+    assert(!BeginResult.isInvalid());
+  }
+  SelectResult = ExprError();
+
+  // At this point, we know that this is supposed to be an iterable expansion
+  // statement, so any failure here is a hard error.
+  ExprResult BeginPlusIndex = S.ActOnBinOp(S.getCurScope(), RangeLoc, tok::plus,
+                                           BeginResult.get(), Index);
+  if (BeginPlusIndex.isInvalid()) {
+    SelectResult = ExprError();
+    return true;
+  }
+
+  ExprResult Deref = S.ActOnUnaryOp(S.getCurScope(), RangeLoc, tok::star,
+                                    BeginPlusIndex.get());
+  if (Deref.isInvalid()) {
+    SelectResult = ExprError();
+    return true;
+  }
+
+  SelectResult = S.BuildCXXIterableExpansionSelectExpr(RangeVar, Impl.get());
+  return true;
+}*/
+
+/// Determine whether this should be an iterable expansion statement, and, if
+/// so, synthesise the various AST nodes that are required for one.
+///
+/// \return ExprEmpty() if this is not an iterable expansion statement.
+/// \return ExprError() if there was a hard error.
+/// \return A CXXIterableExpansionSelectExpr otherwise.
+static ExprResult TryBuildIterableExpansionSelectExpr(Sema &S, Scope *Scope,
+                                                      Expr *Range, Expr *Index,
+                                                      VarDecl *ExpansionVar,
+                                                      SourceLocation ColonLoc) {
+  llvm_unreachable("TODO");
+  /*// C++26 [stmt.expand]p3: An expression is expansion-iterable if it does not
+  // have array type [...]
+  if (Range->getType()->isArrayType())
+    return ExprEmpty();
+
+  // Build the 'range', 'begin', and 'end' variables.
+  DeclStmt* RangeVar{};
+  auto BuildBeginEnd = [&](Sema::BuildForRangeKind Kind) ->
+  Sema::ForRangeBeginEndInfo { StmtResult Var =
+        S.BuildCXXForRangeRangeVar(Scope, Range, /*ForExpansionStmt=#1#true);
+    if (!Var.isUsable())
+      return {};
+
+    RangeVar = cast<DeclStmt>(Var.get());
+    return S.BuildCXXForRangeBeginEndVars(
+        Scope, cast<VarDecl>(RangeVar->getSingleDecl()), ColonLoc,
+        /*CoawaitLoc=#1#{},
+        /*LifetimeExtendTemps=#1#{}, Kind, /*ForExpansionStmt=#1#true);
+  };
+
+  // The construction of begin-expr and end-expr proceeds as for range-based for
+  // loops, except that the 'begin' and 'end' variables are 'static constexpr'.
+  //
+  // FIXME: Instead of doing this jank, do the lookup for begin/end manually
+  // (or factor it out from the for-range code), and only then build the begin/end
+  // expression.
+  {
+    Sema::SFINAETrap Trap(S);
+    if (!BuildBeginEnd(Sema::BFRK_Check).isValid())
+      return ExprEmpty();
+  }
+
+  // Ok, we have confirmed that this is possible; rebuild it without the trap.
+  Sema::ForRangeBeginEndInfo Info =BuildBeginEnd(Sema::BFRK_Build);
+  if (!Info.isValid())
+    return ExprError();
+
+  // 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;
+  // }()
+  //
+  // 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'.
+  auto CreateBeginDRE = [&] {
+    return S.BuildDeclRefExpr(Info.BeginVar,
+                              Info.BeginVar->getType().getNonReferenceType(),
+                              VK_LValue, ColonLoc);
+  };
+
+  DeclRefExpr *Begin = CreateBeginDRE();
+  DeclRefExpr *End = S.BuildDeclRefExpr(
+      Info.EndVar, Info.EndVar->getType().getNonReferenceType(), VK_LValue,
+      ColonLoc);
+
+  ExprResult N = S.ActOnBinOp(Scope, ColonLoc, tok::minus, Begin, End);
+  if (N.isInvalid())
+    return ExprError();
+
+  // Build '*(begin + i)'.
+  Begin = CreateBeginDRE();
+  ExprResult BeginPlusI = S.ActOnBinOp(Scope, ColonLoc, tok::plus, Begin,
+  Index); if (BeginPlusI.isInvalid()) return ExprError();
+
+  ExprResult Deref = S.ActOnUnaryOp(Scope, ColonLoc, tok::star,
+  BeginPlusI.get()); if (Deref.isInvalid()) return ExprError();
+
+  Deref = S.MaybeCreateExprWithCleanups(Deref.get());*/
+}
+
+ExpansionStmtDecl *Sema::ActOnExpansionStmtDecl(unsigned TemplateDepth,
+                                                SourceLocation TemplateKWLoc) {
+  // Create a template parameter '__N'. This will be used to denote the index
+  // of the element that we're instantiating. The wording around iterable
+  // expansion statements (which are the only kind of expansion statements that
+  // actually use this parameter in an expression) implies that its type should
+  // be 'ptrdiff_t', 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 BuildExpansionStmtDecl(CurContext, TemplateKWLoc, TParam);
+}
+
+ExpansionStmtDecl *Sema::BuildExpansionStmtDecl(DeclContext *Ctx,
+                                                SourceLocation TemplateKWLoc,
+                                                NonTypeTemplateParmDecl *NTTP) {
+  auto *TParamList = TemplateParameterList::Create(
+      Context, TemplateKWLoc, TemplateKWLoc, {NTTP}, TemplateKWLoc,
+      /*RequiresClause=*/nullptr);
+  auto *Result =
+      ExpansionStmtDecl::Create(Context, Ctx, TemplateKWLoc, TParamList);
+  Ctx->addDecl(Result);
+  return Result;
+}
+
+ExprResult Sema::ActOnCXXExpansionInitList(MultiExprArg SubExprs,
+                                           SourceLocation LBraceLoc,
+                                           SourceLocation RBraceLoc) {
+  return CXXExpansionInitListExpr::Create(Context, SubExprs, LBraceLoc,
+                                          RBraceLoc);
+}
+
+StmtResult Sema::ActOnCXXExpansionStmt(
+    ExpansionStmtDecl *ESD, Stmt *Init, Stmt *ExpansionVarStmt,
+    Expr *ExpansionInitializer, SourceLocation ForLoc, SourceLocation LParenLoc,
+    SourceLocation ColonLoc, SourceLocation RParenLoc, BuildForRangeKind Kind,
+    ArrayRef<MaterializeTemporaryExpr *> LifetimeExtendTemps) {
+  // TODO: Do we actually need a BuildForRangeKind here at all?
+  if (!ExpansionInitializer || !ExpansionVarStmt || Kind == BFRK_Check)
+    return StmtError();
+
+  auto *DS = cast<DeclStmt>(ExpansionVarStmt);
+  if (!DS->isSingleDecl()) {
+    Diag(DS->getBeginLoc(), diag::err_type_defined_in_for_range);
+    return StmtError();
+  }
+
+  VarDecl *ExpansionVar = cast<VarDecl>(DS->getSingleDecl());
+  if (!ExpansionVar || ExpansionVar->isInvalidDecl())
+    return StmtError();
+
+  ExprResult ER = BuildCXXExpansionInitializer(ESD, ExpansionInitializer);
+  if (ER.isInvalid()) {
+    ActOnInitializerError(ExpansionVar);
+    return StmtError();
+  }
+
+  Expr *Initializer = ER.get();
+  AddInitializerToDecl(ExpansionVar, Initializer, /*DirectInit=*/false);
+  if (ExpansionVar->isInvalidDecl())
+    return StmtError();
+
+  if (isa<CXXExpansionInitListExpr>(ExpansionInitializer))
+    return BuildCXXEnumeratingExpansionStmt(ESD, Init, DS, ForLoc, LParenLoc,
+                                            ColonLoc, RParenLoc);
+
+
+  llvm_unreachable("TODO");
+}
+
+StmtResult Sema::BuildCXXEnumeratingExpansionStmt(Decl *ESD, Stmt *Init,
+                                                  Stmt *ExpansionVar,
+                                                  SourceLocation ForLoc,
+                                                  SourceLocation LParenLoc,
+                                                  SourceLocation ColonLoc,
+                                                  SourceLocation RParenLoc) {
+  return new (Context) CXXEnumeratingExpansionStmt(
+      cast<ExpansionStmtDecl>(ESD), Init, cast<DeclStmt>(ExpansionVar), ForLoc,
+      LParenLoc, ColonLoc, RParenLoc);
+}
+
+/*
+StmtResult Sema::BuildCXXExpansionStmt(
+      ExpansionStmtDecl *ESD, Stmt *Init, Stmt *ExpansionVarStmt,
+      Expr *ExpansionInitializer, SourceLocation ForLoc,
+      SourceLocation LParenLoc, SourceLocation ColonLoc,
+      SourceLocation RParenLoc,
+      ArrayRef<MaterializeTemporaryExpr *> LifetimeExtendTemps) {
+  auto *ExpansionVar = cast<DeclStmt>(ExpansionVarStmt);
+  Expr *Initializer = cast<VarDecl>(ExpansionVar->getSingleDecl())->getInit();
+  assert(Initializer);
+
+  if (auto *WithCleanups = dyn_cast<ExprWithCleanups>(Initializer))
+    Initializer = WithCleanups->getSubExpr();
+
+  if (Initializer->isTypeDependent())
+    llvm_unreachable("TODO");
+
+  if (isa<CXXDependentExpansionInitListSelectExpr>(Initializer))
+    return CXXExpansionStmt::Create(Context, Init, ExpansionVar,
+                                    ESD->getLocation(), ForLoc, LParenLoc,
+                                    ColonLoc, RParenLoc);
+
+  llvm_unreachable("TODO");
+  /*else if (isa<CXXDestructurableExpansionSelectExpr>(Initializer)) {
+    return BuildCXXDestructurableExpansionStmt(TemplateKWLoc, ForLoc, LParenLoc,
+                                               Init, ExpansionVarStmt, ColonLoc,
+                                               RParenLoc, Index);
+  } else if (auto *IESE = dyn_cast<CXXIterableExpansionSelectExpr>(Initializer))
+  { ExprResult Size = makeIterableExpansionSizeExpr(*this, IESE->getRangeVar());
+    if (Size.isInvalid()) {
+      Diag(IESE->getExprLoc(), diag::err_compute_expansion_size_index) << 0;
+      return StmtError();
+    }
+    return BuildCXXIterableExpansionStmt(TemplateKWLoc, ForLoc, LParenLoc, Init,
+                                         ExpansionVarStmt, ColonLoc, RParenLoc,
+                                         Index, Size.get());
+  }
+  llvm_unreachable("unknown expansion select expression");#1#
+}*/
+
+StmtResult Sema::FinishCXXExpansionStmt(Stmt* Exp, Stmt *Body) {
+  if (!Exp || !Body)
+    return StmtError();
+
+  auto *Expansion = cast<CXXExpansionStmt>(Exp);
+  assert(!Expansion->getDecl()->getInstantiations() &&
+         "should not rebuild expansion statement after instantiation");
+
+  // Diagnose identifier labels.
+  // TODO: Do this somewhere, somehow, but not every time we instantiate this.
+  /*struct DiagnoseLabels : DynamicRecursiveASTVisitor {
+    Sema &SemaRef;
+    DiagnoseLabels(Sema &S) : SemaRef(S) {}
+    bool VisitLabelStmt(LabelStmt *S) override {
+      SemaRef.Diag(S->getIdentLoc(), diag::err_expanded_identifier_label);
+      return false;
+    }
+  } Visitor(*this);
+  if (!Visitor.TraverseStmt(Body))
+    return StmtError();*/
+
+  Expansion->setBody(Body);
+  if (Expansion->hasDependentSize())
+    return Expansion;
+
+  // Collect shared statements.
+  SmallVector<Stmt*, 1> Shared;
+  if (Expansion->getInit())
+    Shared.push_back(Expansion->getInit());
+
+  // Return an empty statement if the range is empty.
+  size_t NumInstantiations = Expansion->getNumInstantiations();
+  if (NumInstantiations == 0) {
+    Expansion->getDecl()->setInstantiations(
+        CXXExpansionInstantiationStmt::Create(Context, Expansion->getBeginLoc(),
+                                              /*Instantiations=*/{}, Shared));
+    return Expansion;
+  }
+
+  // Create a compound statement binding loop and body.
+  Stmt *VarAndBody[] = {Expansion->getExpansionVarStmt(), Body};
+  Stmt *CombinedBody =
+      CompoundStmt::Create(Context, VarAndBody, FPOptionsOverride(),
+                           Expansion->getBeginLoc(), Expansion->getEndLoc());
+
+  // Expand the body for each instantiation.
+  SmallVector<Stmt *, 4> Instantiations;
+  ExpansionStmtDecl *ESD = Expansion->getDecl();
+  for (size_t I = 0; I < NumInstantiations; ++I) {
+    ContextRAII CtxGuard(*this,
+                         CurContext->getEnclosingNonExpansionStatementContext(),
+                         /*NewThis=*/false);
+
+    TemplateArgument Arg{Context, llvm::APSInt::get(I),
+                         Context.getPointerDiffType()};
+    MultiLevelTemplateArgumentList MTArgList(ESD, Arg, true);
+    MTArgList.addOuterRetainedLevels(
+        Expansion->getDecl()->getIndexTemplateParm()->getDepth());
+
+    LocalInstantiationScope LIScope(*this, /*CombineWithOuterScope=*/true);
+    InstantiatingTemplate Inst(*this, Body->getBeginLoc(), Expansion, Arg,
+                               Body->getSourceRange());
+
+    StmtResult Instantiation = SubstStmt(CombinedBody, MTArgList);
+
+    if (Instantiation.isInvalid())
+      return StmtError();
+    Instantiations.push_back(Instantiation.get());
+  }
+
+  auto *InstantiationsStmt = CXXExpansionInstantiationStmt::Create(
+      Context, Expansion->getBeginLoc(), Instantiations, Shared);
+
+  Expansion->getDecl()->setInstantiations(InstantiationsStmt);
+  return Expansion;
+}
+
+ExprResult Sema::BuildCXXExpansionInitializer(ExpansionStmtDecl *ESD,
+                                              Expr *ExpansionInitializer) {
+  if (ExpansionInitializer->containsErrors())
+    return ExprError();
+
+  // This should only happen when we first parse the statement.
+  //
+  // Note that lifetime extension only applies to destructurable expansion
+  // statements, so we just ignore 'LifetimeExtendedTemps' entirely for other
+  // types of expansion statements (this is CWG 3043).
+  if (auto *ILE = dyn_cast<CXXExpansionInitListExpr>(ExpansionInitializer)) {
+    // Build a 'DeclRefExpr' designating the template parameter '__N'.
+    DeclRefExpr *Index =
+        BuildDeclRefExpr(ESD->getIndexTemplateParm(), Context.getSizeType(),
+                         VK_PRValue, ESD->getBeginLoc());
+
+    return BuildCXXExpansionInitListSelectExpr(ILE, Index);
+  }
+
+  if (ExpansionInitializer->isTypeDependent())
+    return ExpansionInitializer;
+
+  ExpansionInitializer->dumpColor();
+  llvm_unreachable("TODO: handle this expansion initialiser");
+  /*ExprResult IterableExprResult = TryBuildIterableExpansionSelectExpr(
+      *this, Range, Index, ExpansionVar, LifetimeExtendTemps,
+      IterableExprResult);
+  if (!IterableExprResult.isUnset())
+    return IterableExprResult;
+
+  return BuildDestructurableExpansionSelectExpr(
+      *this, Range, Index, ExpansionVar, LifetimeExtendTemps);*/
+}
+
+ExprResult
+Sema::BuildCXXExpansionInitListSelectExpr(CXXExpansionInitListExpr *Range,
+                                          Expr *Idx) {
+  if (Range->containsUnexpandedParameterPack() || Idx->isValueDependent())
+    return new (Context) CXXExpansionInitListSelectExpr(Context, Range, Idx);
+
+  // The index is a DRE to a template parameter; we should never
+  // fail to evaluate it.
+  Expr::EvalResult ER;
+  if (!Idx->EvaluateAsInt(ER, Context))
+    llvm_unreachable("Failed to evaluate expansion init list index");
+
+  size_t I = ER.Val.getInt().getZExtValue();
+  return Range->getExprs()[I];
+}
diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp
index a50c27610dc96..b37f1b8253f4c 100644
--- a/clang/lib/Sema/SemaExpr.cpp
+++ b/clang/lib/Sema/SemaExpr.cpp
@@ -19302,11 +19302,12 @@ bool Sema::tryCaptureVariable(
     QualType &DeclRefType, const unsigned *const FunctionScopeIndexToStopAt) {
   // An init-capture is notionally from the context surrounding its
   // declaration, but its parent DC is the lambda class.
-  DeclContext *VarDC = Var->getDeclContext();
+  DeclContext *VarDC =
+      Var->getDeclContext()->getEnclosingNonExpansionStatementContext();
   DeclContext *DC = CurContext;
 
   // Skip past RequiresExprBodys because they don't constitute function scopes.
-  while (DC->isRequiresExprBody())
+  while (DC->isRequiresExprBody() || DC->isExpansionStmt())
     DC = DC->getParent();
 
   // tryCaptureVariable is called every time a DeclRef is formed,
diff --git a/clang/lib/Sema/SemaLambda.cpp b/clang/lib/Sema/SemaLambda.cpp
index fbc2e7eb30676..d2effd4ebc16b 100644
--- a/clang/lib/Sema/SemaLambda.cpp
+++ b/clang/lib/Sema/SemaLambda.cpp
@@ -101,7 +101,7 @@ static inline UnsignedOrNone getStackIndexOfNearestEnclosingCaptureReadyLambda(
     // arrived here) - so we don't yet have a lambda that can capture the
     // variable.
     if (IsCapturingVariable &&
-        VarToCapture->getDeclContext()->Equals(EnclosingDC))
+        VarToCapture->getDeclContext()->getEnclosingNonExpansionStatementContext()->Equals(EnclosingDC))
       return NoLambdaIsCaptureReady;
 
     // For an enclosing lambda to be capture ready for an entity, all
@@ -126,7 +126,7 @@ static inline UnsignedOrNone getStackIndexOfNearestEnclosingCaptureReadyLambda(
       if (IsCapturingThis && !LSI->isCXXThisCaptured())
         return NoLambdaIsCaptureReady;
     }
-    EnclosingDC = getLambdaAwareParentOfDeclContext(EnclosingDC);
+    EnclosingDC = getLambdaAwareParentOfDeclContext(EnclosingDC)->getEnclosingNonExpansionStatementContext();
 
     assert(CurScopeIndex);
     --CurScopeIndex;
@@ -248,7 +248,7 @@ CXXRecordDecl *
 Sema::createLambdaClosureType(SourceRange IntroducerRange, TypeSourceInfo *Info,
                               unsigned LambdaDependencyKind,
                               LambdaCaptureDefault CaptureDefault) {
-  DeclContext *DC = CurContext;
+  DeclContext *DC = CurContext->getEnclosingNonExpansionStatementContext();
 
   bool IsGenericLambda =
       Info && getGenericLambdaTemplateParameterList(getCurLambda(), *this);
@@ -1376,7 +1376,9 @@ void Sema::ActOnLambdaClosureQualifiers(LambdaIntroducer &Intro,
   // odr-use 'this' (in particular, in a default initializer for a non-static
   // data member).
   if (Intro.Default != LCD_None &&
-      !LSI->Lambda->getParent()->isFunctionOrMethod() &&
+      !LSI->Lambda->getParent()
+           ->getEnclosingNonExpansionStatementContext()
+           ->isFunctionOrMethod() &&
       (getCurrentThisType().isNull() ||
        CheckCXXThisCapture(SourceLocation(), /*Explicit=*/true,
                            /*BuildAndDiagnose=*/false)))
@@ -2520,8 +2522,8 @@ Sema::LambdaScopeForCallOperatorInstantiationRAII::
     InstantiationAndPatterns.emplace_back(FDPattern, FD);
 
     FDPattern =
-        dyn_cast<FunctionDecl>(getLambdaAwareParentOfDeclContext(FDPattern));
-    FD = dyn_cast<FunctionDecl>(getLambdaAwareParentOfDeclContext(FD));
+        dyn_cast<FunctionDecl>(getLambdaAwareParentOfDeclContext(FDPattern)->getEnclosingNonExpansionStatementContext());
+    FD = dyn_cast<FunctionDecl>(getLambdaAwareParentOfDeclContext(FD)->getEnclosingNonExpansionStatementContext());
   }
 
   // Add instantiated parameters and local vars to scopes, starting from the
diff --git a/clang/lib/Sema/SemaStmt.cpp b/clang/lib/Sema/SemaStmt.cpp
index f39896336053e..93b73e5e61b81 100644
--- a/clang/lib/Sema/SemaStmt.cpp
+++ b/clang/lib/Sema/SemaStmt.cpp
@@ -2409,8 +2409,8 @@ 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) {
+VarDecl *BuildForRangeVarDecl(Sema &SemaRef, SourceLocation Loc, QualType Type,
+                              StringRef Name, bool ForExpansionStmt) {
   DeclContext *DC = SemaRef.CurContext;
   IdentifierInfo *II = &SemaRef.PP.getIdentifierTable().get(Name);
   TypeSourceInfo *TInfo = SemaRef.Context.getTrivialTypeSourceInfo(Type, Loc);
@@ -2418,6 +2418,10 @@ VarDecl *BuildForRangeVarDecl(Sema &SemaRef, SourceLocation Loc,
                                   TInfo, SC_None);
   Decl->setImplicit();
   Decl->setCXXForRangeImplicitVar(true);
+  if (ForExpansionStmt) {
+    Decl->setConstexpr(true);
+    Decl->setStorageClass(SC_Static);
+  }
   return Decl;
 }
 
@@ -2428,6 +2432,25 @@ static bool ObjCEnumerationCollection(Expr *Collection) {
           && Collection->getType()->getAs<ObjCObjectPointerType>() != nullptr;
 }
 
+StmtResult Sema::BuildCXXForRangeRangeVar(Scope *S, Expr *Range,
+                                          bool ForExpansionStmt) {
+  // Divide by 2, since the variables are in the inner scope (loop body).
+  const auto DepthStr = std::to_string(S->getDepth() / 2);
+  SourceLocation RangeLoc = Range->getBeginLoc();
+  VarDecl *RangeVar =
+      BuildForRangeVarDecl(*this, RangeLoc, Context.getAutoRRefDeductType(),
+                           std::string("__range") + DepthStr, ForExpansionStmt);
+  if (FinishForRangeVarDecl(*this, RangeVar, Range, RangeLoc,
+                            diag::err_for_range_deduction_failure))
+
+    return StmtError();
+
+  // Claim the type doesn't contain auto: we've already done the checking.
+  DeclGroupPtrTy RangeGroup =
+      BuildDeclaratorGroup(MutableArrayRef<Decl *>((Decl **)&RangeVar, 1));
+  return ActOnDeclStmt(RangeGroup, RangeLoc, RangeLoc);
+}
+
 StmtResult Sema::ActOnCXXForRangeStmt(
     Scope *S, SourceLocation ForLoc, SourceLocation CoawaitLoc, Stmt *InitStmt,
     Stmt *First, SourceLocation ColonLoc, Expr *Range, SourceLocation RParenLoc,
@@ -2472,22 +2495,8 @@ StmtResult Sema::ActOnCXXForRangeStmt(
   }
 
   // Build  auto && __range = range-init
-  // Divide by 2, since the variables are in the inner scope (loop body).
-  const auto DepthStr = std::to_string(S->getDepth() / 2);
-  SourceLocation RangeLoc = Range->getBeginLoc();
-  VarDecl *RangeVar = BuildForRangeVarDecl(*this, RangeLoc,
-                                           Context.getAutoRRefDeductType(),
-                                           std::string("__range") + DepthStr);
-  if (FinishForRangeVarDecl(*this, RangeVar, Range, RangeLoc,
-                            diag::err_for_range_deduction_failure)) {
-    ActOnInitializerError(LoopVar);
-    return StmtError();
-  }
-
-  // Claim the type doesn't contain auto: we've already done the checking.
-  DeclGroupPtrTy RangeGroup =
-      BuildDeclaratorGroup(MutableArrayRef<Decl *>((Decl **)&RangeVar, 1));
-  StmtResult RangeDecl = ActOnDeclStmt(RangeGroup, RangeLoc, RangeLoc);
+  auto RangeDecl =
+      BuildCXXForRangeRangeVar(S, Range, /*ForExpansionStmt=*/false);
   if (RangeDecl.isInvalid()) {
     ActOnInitializerError(LoopVar);
     return StmtError();
@@ -2686,6 +2695,216 @@ static StmtResult RebuildForRangeWithDereference(Sema &SemaRef, Scope *S,
       AdjustedRange.get(), RParenLoc, Sema::BFRK_Rebuild);
 }
 
+void Sema::ApplyForRangeOrExpansionStatementLifetimeExtension(
+    VarDecl *RangeVar, ArrayRef<MaterializeTemporaryExpr *> Temporaries) {
+    if (Temporaries.empty())
+      return;
+
+    InitializedEntity Entity = InitializedEntity::InitializeVariable(RangeVar);
+    for (auto *MTE : Temporaries)
+      MTE->setExtendingDecl(RangeVar, Entity.allocateManglingNumber());
+}
+
+Sema::ForRangeBeginEndInfo Sema::BuildCXXForRangeBeginEndVars(
+    Scope *S, VarDecl *RangeVar, SourceLocation ColonLoc,
+    SourceLocation CoawaitLoc,
+    ArrayRef<MaterializeTemporaryExpr *> LifetimeExtendTemps,
+    BuildForRangeKind Kind, bool ForExpansionStmt,
+    StmtResult *RebuildResult,
+    llvm::function_ref<StmtResult()> RebuildWithDereference) {
+  QualType RangeVarType = RangeVar->getType();
+  SourceLocation RangeLoc = RangeVar->getLocation();
+  const QualType RangeVarNonRefType = RangeVarType.getNonReferenceType();
+
+  ExprResult BeginRangeRef =
+      BuildDeclRefExpr(RangeVar, RangeVarNonRefType, VK_LValue, ColonLoc);
+  if (BeginRangeRef.isInvalid())
+    return {};
+
+  ExprResult EndRangeRef =
+      BuildDeclRefExpr(RangeVar, RangeVarNonRefType, VK_LValue, ColonLoc);
+  if (EndRangeRef.isInvalid())
+    return {};
+
+  QualType AutoType = Context.getAutoDeductType();
+  Expr *Range = RangeVar->getInit();
+  if (!Range)
+    return {};
+  QualType RangeType = Range->getType();
+
+  if (RequireCompleteType(RangeLoc, RangeType,
+                          diag::err_for_range_incomplete_type))
+    return {};
+
+  // P2718R0 - Lifetime extension in range-based for loops.
+  //
+  // CWG 3043 – Do not apply lifetime extension to iterating
+  // expansion statements.
+  if (getLangOpts().CPlusPlus23 && !ForExpansionStmt)
+    ApplyForRangeOrExpansionStatementLifetimeExtension(RangeVar,
+                                                       LifetimeExtendTemps);
+
+  // Build auto __begin = begin-expr, __end = end-expr.
+  // Divide by 2, since the variables are in the inner scope (loop body).
+  const auto DepthStr = std::to_string(S->getDepth() / 2);
+  VarDecl *BeginVar =
+      BuildForRangeVarDecl(*this, ColonLoc, AutoType,
+                           std::string("__begin") + DepthStr, ForExpansionStmt);
+  VarDecl *EndVar =
+      BuildForRangeVarDecl(*this, ColonLoc, AutoType,
+                           std::string("__end") + DepthStr, ForExpansionStmt);
+
+  // Build begin-expr and end-expr and attach to __begin and __end variables.
+  ExprResult BeginExpr, EndExpr;
+  if (const ArrayType *UnqAT = RangeType->getAsArrayTypeUnsafe()) {
+    // - if _RangeT is an array type, begin-expr and end-expr are __range and
+    //   __range + __bound, respectively, where __bound is the array bound. If
+    //   _RangeT is an array of unknown size or an array of incomplete type,
+    //   the program is ill-formed;
+
+    // begin-expr is __range.
+    BeginExpr = BeginRangeRef;
+    if (!CoawaitLoc.isInvalid()) {
+      BeginExpr = ActOnCoawaitExpr(S, ColonLoc, BeginExpr.get());
+      if (BeginExpr.isInvalid())
+        return {};
+    }
+    if (FinishForRangeVarDecl(*this, BeginVar, BeginRangeRef.get(), ColonLoc,
+                              diag::err_for_range_iter_deduction_failure)) {
+      NoteForRangeBeginEndFunction(*this, BeginExpr.get(), BEF_begin);
+      return {};
+    }
+
+    // Find the array bound.
+    ExprResult BoundExpr;
+    if (const ConstantArrayType *CAT = dyn_cast<ConstantArrayType>(UnqAT))
+      BoundExpr = IntegerLiteral::Create(
+          Context, CAT->getSize(), Context.getPointerDiffType(), RangeLoc);
+    else if (const VariableArrayType *VAT =
+                 dyn_cast<VariableArrayType>(UnqAT)) {
+      // For a variably modified type we can't just use the expression within
+      // the array bounds, since we don't want that to be re-evaluated here.
+      // Rather, we need to determine what it was when the array was first
+      // created - so we resort to using sizeof(vla)/sizeof(element).
+      // For e.g.
+      //  void f(int b) {
+      //    int vla[b];
+      //    b = -1;   <-- This should not affect the num of iterations below
+      //    for (int &c : vla) { .. }
+      //  }
+
+      // FIXME: This results in codegen generating IR that recalculates the
+      // run-time number of elements (as opposed to just using the IR Value
+      // that corresponds to the run-time value of each bound that was
+      // generated when the array was created.) If this proves too embarrassing
+      // even for unoptimized IR, consider passing a magic-value/cookie to
+      // codegen that then knows to simply use that initial llvm::Value (that
+      // corresponds to the bound at time of array creation) within
+      // getelementptr.  But be prepared to pay the price of increasing a
+      // customized form of coupling between the two components - which  could
+      // be hard to maintain as the codebase evolves.
+
+      ExprResult SizeOfVLAExprR = ActOnUnaryExprOrTypeTraitExpr(
+          EndVar->getLocation(), UETT_SizeOf,
+          /*IsType=*/true,
+          CreateParsedType(VAT->desugar(), Context.getTrivialTypeSourceInfo(
+                                               VAT->desugar(), RangeLoc))
+              .getAsOpaquePtr(),
+          EndVar->getSourceRange());
+      if (SizeOfVLAExprR.isInvalid())
+        return {};
+
+      ExprResult SizeOfEachElementExprR = ActOnUnaryExprOrTypeTraitExpr(
+          EndVar->getLocation(), UETT_SizeOf,
+          /*IsType=*/true,
+          CreateParsedType(VAT->desugar(), Context.getTrivialTypeSourceInfo(
+                                               VAT->getElementType(), RangeLoc))
+              .getAsOpaquePtr(),
+          EndVar->getSourceRange());
+      if (SizeOfEachElementExprR.isInvalid())
+        return {};
+
+      BoundExpr =
+          ActOnBinOp(S, EndVar->getLocation(), tok::slash, SizeOfVLAExprR.get(),
+                     SizeOfEachElementExprR.get());
+      if (BoundExpr.isInvalid())
+        return {};
+
+    } else {
+      // Can't be a DependentSizedArrayType or an IncompleteArrayType since
+      // UnqAT is not incomplete and Range is not type-dependent.
+      llvm_unreachable("Unexpected array type in for-range");
+    }
+
+    // end-expr is __range + __bound.
+    EndExpr =
+        ActOnBinOp(S, ColonLoc, tok::plus, EndRangeRef.get(), BoundExpr.get());
+    if (EndExpr.isInvalid())
+      return {};
+    if (FinishForRangeVarDecl(*this, EndVar, EndExpr.get(), ColonLoc,
+                              diag::err_for_range_iter_deduction_failure)) {
+      NoteForRangeBeginEndFunction(*this, EndExpr.get(), BEF_end);
+      return {};
+    }
+  } else {
+    OverloadCandidateSet CandidateSet(RangeLoc,
+                                      OverloadCandidateSet::CSK_Normal);
+    BeginEndFunction BEFFailure;
+    ForRangeStatus RangeStatus =
+        BuildNonArrayForRange(*this, BeginRangeRef.get(), EndRangeRef.get(),
+                              RangeType, BeginVar, EndVar, ColonLoc, CoawaitLoc,
+                              &CandidateSet, &BeginExpr, &EndExpr, &BEFFailure);
+
+    if (Kind == BFRK_Build && RangeStatus == FRS_NoViableFunction &&
+        BEFFailure == BEF_begin) {
+      // If the range is being built from an array parameter, emit a
+      // a diagnostic that it is being treated as a pointer.
+      if (DeclRefExpr *DRE = dyn_cast<DeclRefExpr>(Range)) {
+        if (ParmVarDecl *PVD = dyn_cast<ParmVarDecl>(DRE->getDecl())) {
+          QualType ArrayTy = PVD->getOriginalType();
+          QualType PointerTy = PVD->getType();
+          if (PointerTy->isPointerType() && ArrayTy->isArrayType()) {
+            Diag(Range->getBeginLoc(), diag::err_range_on_array_parameter)
+                << RangeLoc << PVD << ArrayTy << PointerTy;
+            Diag(PVD->getLocation(), diag::note_declared_at);
+            return {};
+          }
+        }
+      }
+
+      // If building the range failed, try dereferencing the range expression
+      // unless a diagnostic was issued or the end function is problematic.
+      if (RebuildWithDereference) {
+        assert(RebuildResult);
+        StmtResult SR = RebuildWithDereference();
+        if (SR.isInvalid() || SR.isUsable()) {
+          *RebuildResult = SR;
+          return {};
+        }
+      }
+    }
+
+    // Otherwise, emit diagnostics if we haven't already.
+    if (RangeStatus == FRS_NoViableFunction) {
+      Expr *Range = BEFFailure ? EndRangeRef.get() : BeginRangeRef.get();
+      CandidateSet.NoteCandidates(
+          PartialDiagnosticAt(Range->getBeginLoc(),
+                              PDiag(diag::err_for_range_invalid)
+                                  << RangeLoc << Range->getType()
+                                  << BEFFailure),
+          *this, OCD_AllCandidates, Range);
+    }
+    // Return an error if no fix was discovered.
+    if (RangeStatus != FRS_Success)
+      return {};
+  }
+
+  assert(!BeginExpr.isInvalid() && !EndExpr.isInvalid() &&
+         "invalid range expression in for loop");
+
+  return {BeginVar, EndVar, BeginExpr.get(), EndExpr.get()};
+}
+
 StmtResult Sema::BuildCXXForRangeStmt(
     SourceLocation ForLoc, SourceLocation CoawaitLoc, Stmt *InitStmt,
     SourceLocation ColonLoc, Stmt *RangeDecl, Stmt *Begin, Stmt *End,
@@ -2729,204 +2948,34 @@ StmtResult Sema::BuildCXXForRangeStmt(
       LoopVar->setType(SubstAutoTypeDependent(LoopVar->getType()));
     }
   } else if (!BeginDeclStmt.get()) {
-    SourceLocation RangeLoc = RangeVar->getLocation();
-
-    const QualType RangeVarNonRefType = RangeVarType.getNonReferenceType();
-
-    ExprResult BeginRangeRef = BuildDeclRefExpr(RangeVar, RangeVarNonRefType,
-                                                VK_LValue, ColonLoc);
-    if (BeginRangeRef.isInvalid())
-      return StmtError();
+    StmtResult RebuildResult;
+    auto RebuildWithDereference = [&] {
+      return RebuildForRangeWithDereference(
+          *this, S, ForLoc, CoawaitLoc, InitStmt, LoopVarDecl, ColonLoc,
+          RangeVar->getInit(), RangeVar->getLocation(), RParenLoc);
+    };
 
-    ExprResult EndRangeRef = BuildDeclRefExpr(RangeVar, RangeVarNonRefType,
-                                              VK_LValue, ColonLoc);
-    if (EndRangeRef.isInvalid())
-      return StmtError();
+    auto BeginRangeRefTy = RangeVar->getType().getNonReferenceType();
+    auto [BeginVar, EndVar, BeginExpr, EndExpr] = BuildCXXForRangeBeginEndVars(
+        S, RangeVar, ColonLoc, CoawaitLoc, LifetimeExtendTemps, Kind,
+        /*ForExpansionStmt=*/false, &RebuildResult, RebuildWithDereference);
 
-    QualType AutoType = Context.getAutoDeductType();
-    Expr *Range = RangeVar->getInit();
-    if (!Range)
+    if (!RebuildResult.isUnset())
+      return RebuildResult;
+    if (!BeginVar || !EndVar)
       return StmtError();
-    QualType RangeType = Range->getType();
-
-    if (RequireCompleteType(RangeLoc, RangeType,
-                            diag::err_for_range_incomplete_type))
-      return StmtError();
-
-    // P2718R0 - Lifetime extension in range-based for loops.
-    if (getLangOpts().CPlusPlus23 && !LifetimeExtendTemps.empty()) {
-      InitializedEntity Entity =
-          InitializedEntity::InitializeVariable(RangeVar);
-      for (auto *MTE : LifetimeExtendTemps)
-        MTE->setExtendingDecl(RangeVar, Entity.allocateManglingNumber());
-    }
-
-    // Build auto __begin = begin-expr, __end = end-expr.
-    // Divide by 2, since the variables are in the inner scope (loop body).
-    const auto DepthStr = std::to_string(S->getDepth() / 2);
-    VarDecl *BeginVar = BuildForRangeVarDecl(*this, ColonLoc, AutoType,
-                                             std::string("__begin") + DepthStr);
-    VarDecl *EndVar = BuildForRangeVarDecl(*this, ColonLoc, AutoType,
-                                           std::string("__end") + DepthStr);
-
-    // Build begin-expr and end-expr and attach to __begin and __end variables.
-    ExprResult BeginExpr, EndExpr;
-    if (const ArrayType *UnqAT = RangeType->getAsArrayTypeUnsafe()) {
-      // - if _RangeT is an array type, begin-expr and end-expr are __range and
-      //   __range + __bound, respectively, where __bound is the array bound. If
-      //   _RangeT is an array of unknown size or an array of incomplete type,
-      //   the program is ill-formed;
-
-      // begin-expr is __range.
-      BeginExpr = BeginRangeRef;
-      if (!CoawaitLoc.isInvalid()) {
-        BeginExpr = ActOnCoawaitExpr(S, ColonLoc, BeginExpr.get());
-        if (BeginExpr.isInvalid())
-          return StmtError();
-      }
-      if (FinishForRangeVarDecl(*this, BeginVar, BeginRangeRef.get(), ColonLoc,
-                                diag::err_for_range_iter_deduction_failure)) {
-        NoteForRangeBeginEndFunction(*this, BeginExpr.get(), BEF_begin);
-        return StmtError();
-      }
-
-      // Find the array bound.
-      ExprResult BoundExpr;
-      if (const ConstantArrayType *CAT = dyn_cast<ConstantArrayType>(UnqAT))
-        BoundExpr = IntegerLiteral::Create(
-            Context, CAT->getSize(), Context.getPointerDiffType(), RangeLoc);
-      else if (const VariableArrayType *VAT =
-               dyn_cast<VariableArrayType>(UnqAT)) {
-        // For a variably modified type we can't just use the expression within
-        // the array bounds, since we don't want that to be re-evaluated here.
-        // Rather, we need to determine what it was when the array was first
-        // created - so we resort to using sizeof(vla)/sizeof(element).
-        // For e.g.
-        //  void f(int b) {
-        //    int vla[b];
-        //    b = -1;   <-- This should not affect the num of iterations below
-        //    for (int &c : vla) { .. }
-        //  }
-
-        // FIXME: This results in codegen generating IR that recalculates the
-        // run-time number of elements (as opposed to just using the IR Value
-        // that corresponds to the run-time value of each bound that was
-        // generated when the array was created.) If this proves too embarrassing
-        // even for unoptimized IR, consider passing a magic-value/cookie to
-        // codegen that then knows to simply use that initial llvm::Value (that
-        // corresponds to the bound at time of array creation) within
-        // getelementptr.  But be prepared to pay the price of increasing a
-        // customized form of coupling between the two components - which  could
-        // be hard to maintain as the codebase evolves.
-
-        ExprResult SizeOfVLAExprR = ActOnUnaryExprOrTypeTraitExpr(
-            EndVar->getLocation(), UETT_SizeOf,
-            /*IsType=*/true,
-            CreateParsedType(VAT->desugar(), Context.getTrivialTypeSourceInfo(
-                                                 VAT->desugar(), RangeLoc))
-                .getAsOpaquePtr(),
-            EndVar->getSourceRange());
-        if (SizeOfVLAExprR.isInvalid())
-          return StmtError();
-
-        ExprResult SizeOfEachElementExprR = ActOnUnaryExprOrTypeTraitExpr(
-            EndVar->getLocation(), UETT_SizeOf,
-            /*IsType=*/true,
-            CreateParsedType(VAT->desugar(),
-                             Context.getTrivialTypeSourceInfo(
-                                 VAT->getElementType(), RangeLoc))
-                .getAsOpaquePtr(),
-            EndVar->getSourceRange());
-        if (SizeOfEachElementExprR.isInvalid())
-          return StmtError();
-
-        BoundExpr =
-            ActOnBinOp(S, EndVar->getLocation(), tok::slash,
-                       SizeOfVLAExprR.get(), SizeOfEachElementExprR.get());
-        if (BoundExpr.isInvalid())
-          return StmtError();
-
-      } else {
-        // Can't be a DependentSizedArrayType or an IncompleteArrayType since
-        // UnqAT is not incomplete and Range is not type-dependent.
-        llvm_unreachable("Unexpected array type in for-range");
-      }
-
-      // end-expr is __range + __bound.
-      EndExpr = ActOnBinOp(S, ColonLoc, tok::plus, EndRangeRef.get(),
-                           BoundExpr.get());
-      if (EndExpr.isInvalid())
-        return StmtError();
-      if (FinishForRangeVarDecl(*this, EndVar, EndExpr.get(), ColonLoc,
-                                diag::err_for_range_iter_deduction_failure)) {
-        NoteForRangeBeginEndFunction(*this, EndExpr.get(), BEF_end);
-        return StmtError();
-      }
-    } else {
-      OverloadCandidateSet CandidateSet(RangeLoc,
-                                        OverloadCandidateSet::CSK_Normal);
-      BeginEndFunction BEFFailure;
-      ForRangeStatus RangeStatus = BuildNonArrayForRange(
-          *this, BeginRangeRef.get(), EndRangeRef.get(), RangeType, BeginVar,
-          EndVar, ColonLoc, CoawaitLoc, &CandidateSet, &BeginExpr, &EndExpr,
-          &BEFFailure);
-
-      if (Kind == BFRK_Build && RangeStatus == FRS_NoViableFunction &&
-          BEFFailure == BEF_begin) {
-        // If the range is being built from an array parameter, emit a
-        // a diagnostic that it is being treated as a pointer.
-        if (DeclRefExpr *DRE = dyn_cast<DeclRefExpr>(Range)) {
-          if (ParmVarDecl *PVD = dyn_cast<ParmVarDecl>(DRE->getDecl())) {
-            QualType ArrayTy = PVD->getOriginalType();
-            QualType PointerTy = PVD->getType();
-            if (PointerTy->isPointerType() && ArrayTy->isArrayType()) {
-              Diag(Range->getBeginLoc(), diag::err_range_on_array_parameter)
-                  << RangeLoc << PVD << ArrayTy << PointerTy;
-              Diag(PVD->getLocation(), diag::note_declared_at);
-              return StmtError();
-            }
-          }
-        }
-
-        // If building the range failed, try dereferencing the range expression
-        // unless a diagnostic was issued or the end function is problematic.
-        StmtResult SR = RebuildForRangeWithDereference(*this, S, ForLoc,
-                                                       CoawaitLoc, InitStmt,
-                                                       LoopVarDecl, ColonLoc,
-                                                       Range, RangeLoc,
-                                                       RParenLoc);
-        if (SR.isInvalid() || SR.isUsable())
-          return SR;
-      }
-
-      // Otherwise, emit diagnostics if we haven't already.
-      if (RangeStatus == FRS_NoViableFunction) {
-        Expr *Range = BEFFailure ? EndRangeRef.get() : BeginRangeRef.get();
-        CandidateSet.NoteCandidates(
-            PartialDiagnosticAt(Range->getBeginLoc(),
-                                PDiag(diag::err_for_range_invalid)
-                                    << RangeLoc << Range->getType()
-                                    << BEFFailure),
-            *this, OCD_AllCandidates, Range);
-      }
-      // Return an error if no fix was discovered.
-      if (RangeStatus != FRS_Success)
-        return StmtError();
-    }
-
-    assert(!BeginExpr.isInvalid() && !EndExpr.isInvalid() &&
-           "invalid range expression in for loop");
 
     // C++11 [dcl.spec.auto]p7: BeginType and EndType must be the same.
     // C++1z removes this restriction.
+    SourceLocation RangeLoc = RangeVar->getLocation();
     QualType BeginType = BeginVar->getType(), EndType = EndVar->getType();
     if (!Context.hasSameType(BeginType, EndType)) {
       Diag(RangeLoc, getLangOpts().CPlusPlus17
                          ? diag::warn_for_range_begin_end_types_differ
                          : diag::ext_for_range_begin_end_types_differ)
           << BeginType << EndType;
-      NoteForRangeBeginEndFunction(*this, BeginExpr.get(), BEF_begin);
-      NoteForRangeBeginEndFunction(*this, EndExpr.get(), BEF_end);
+      NoteForRangeBeginEndFunction(*this, BeginExpr, BEF_begin);
+      NoteForRangeBeginEndFunction(*this, EndExpr, BEF_end);
     }
 
     BeginDeclStmt =
@@ -2955,10 +3004,10 @@ StmtResult Sema::BuildCXXForRangeStmt(
           ActOnFinishFullExpr(NotEqExpr.get(), /*DiscardedValue*/ false);
     if (NotEqExpr.isInvalid()) {
       Diag(RangeLoc, diag::note_for_range_invalid_iterator)
-        << RangeLoc << 0 << BeginRangeRef.get()->getType();
-      NoteForRangeBeginEndFunction(*this, BeginExpr.get(), BEF_begin);
+        << RangeLoc << 0 << BeginRangeRefTy;
+      NoteForRangeBeginEndFunction(*this, BeginExpr, BEF_begin);
       if (!Context.hasSameType(BeginType, EndType))
-        NoteForRangeBeginEndFunction(*this, EndExpr.get(), BEF_end);
+        NoteForRangeBeginEndFunction(*this, EndExpr, BEF_end);
       return StmtError();
     }
 
@@ -2978,8 +3027,8 @@ StmtResult Sema::BuildCXXForRangeStmt(
       IncrExpr = ActOnFinishFullExpr(IncrExpr.get(), /*DiscardedValue*/ false);
     if (IncrExpr.isInvalid()) {
       Diag(RangeLoc, diag::note_for_range_invalid_iterator)
-        << RangeLoc << 2 << BeginRangeRef.get()->getType() ;
-      NoteForRangeBeginEndFunction(*this, BeginExpr.get(), BEF_begin);
+        << RangeLoc << 2 << BeginRangeRefTy ;
+      NoteForRangeBeginEndFunction(*this, BeginExpr, BEF_begin);
       return StmtError();
     }
 
@@ -2992,8 +3041,8 @@ StmtResult Sema::BuildCXXForRangeStmt(
     ExprResult DerefExpr = ActOnUnaryOp(S, ColonLoc, tok::star, BeginRef.get());
     if (DerefExpr.isInvalid()) {
       Diag(RangeLoc, diag::note_for_range_invalid_iterator)
-        << RangeLoc << 1 << BeginRangeRef.get()->getType();
-      NoteForRangeBeginEndFunction(*this, BeginExpr.get(), BEF_begin);
+        << RangeLoc << 1 << BeginRangeRefTy;
+      NoteForRangeBeginEndFunction(*this, BeginExpr, BEF_begin);
       return StmtError();
     }
 
@@ -3003,7 +3052,7 @@ StmtResult Sema::BuildCXXForRangeStmt(
       AddInitializerToDecl(LoopVar, DerefExpr.get(), /*DirectInit=*/false);
       if (LoopVar->isInvalidDecl() ||
           (LoopVar->getInit() && LoopVar->getInit()->containsErrors()))
-        NoteForRangeBeginEndFunction(*this, BeginExpr.get(), BEF_begin);
+        NoteForRangeBeginEndFunction(*this, BeginExpr, BEF_begin);
     }
   }
 
diff --git a/clang/lib/Sema/SemaTemplateInstantiate.cpp b/clang/lib/Sema/SemaTemplateInstantiate.cpp
index 7f858050db13e..a7cb4a4e525e9 100644
--- a/clang/lib/Sema/SemaTemplateInstantiate.cpp
+++ b/clang/lib/Sema/SemaTemplateInstantiate.cpp
@@ -573,6 +573,7 @@ bool Sema::CodeSynthesisContext::isInstantiationRecord() const {
   case PriorTemplateArgumentSubstitution:
   case ConstraintsCheck:
   case NestedRequirementConstraintsCheck:
+  case ExpansionStmtInstantiation:
     return true;
 
   case RequirementInstantiation:
@@ -770,6 +771,15 @@ Sema::InstantiatingTemplate::InstantiatingTemplate(
           PointOfInstantiation, InstantiationRange, /*Entity=*/nullptr,
           /*Template=*/nullptr, /*TemplateArgs=*/{}, &DeductionInfo) {}
 
+Sema::InstantiatingTemplate::InstantiatingTemplate(
+    Sema &SemaRef, SourceLocation PointOfInstantiation,
+    CXXExpansionStmt *ExpansionStmt, ArrayRef<TemplateArgument> TArgs,
+    SourceRange InstantiationRange)
+    : InstantiatingTemplate(
+          SemaRef, CodeSynthesisContext::ExpansionStmtInstantiation,
+          PointOfInstantiation, InstantiationRange, /*Entity=*/nullptr,
+          /*Template=*/nullptr, /*TemplateArgs=*/TArgs) {}
+
 Sema::InstantiatingTemplate::InstantiatingTemplate(
     Sema &SemaRef, SourceLocation PointOfInstantiation,
     concepts::NestedRequirement *Req, ConstraintsCheck,
@@ -1278,6 +1288,9 @@ void Sema::PrintInstantiationStack(InstantiationContextDiagFuncRef DiagFunc) {
                                << /*isTemplateTemplateParam=*/true
                                << Active->InstantiationRange);
       break;
+    case CodeSynthesisContext::ExpansionStmtInstantiation:
+      Diags.Report(Active->PointOfInstantiation,
+                   diag::note_expansion_stmt_instantiation_here);
     }
   }
 }
@@ -1306,6 +1319,7 @@ std::optional<TemplateDeductionInfo *> Sema::isSFINAEContext() const {
     case CodeSynthesisContext::ParameterMappingSubstitution:
     case CodeSynthesisContext::ConstraintNormalization:
     case CodeSynthesisContext::NestedRequirementConstraintsCheck:
+    case CodeSynthesisContext::ExpansionStmtInstantiation:
       // This is a template instantiation, so there is no SFINAE.
       return std::nullopt;
     case CodeSynthesisContext::LambdaExpressionSubstitution:
diff --git a/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp b/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp
index 28925cca8f956..166bdf68e55e7 100644
--- a/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp
+++ b/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp
@@ -2070,6 +2070,39 @@ Decl *TemplateDeclInstantiator::VisitStaticAssertDecl(StaticAssertDecl *D) {
       InstantiatedMessageExpr.get(), D->getRParenLoc(), D->isFailed());
 }
 
+Decl *TemplateDeclInstantiator::VisitExpansionStmtDecl(ExpansionStmtDecl *OldESD) {
+  Decl *Index = VisitNonTypeTemplateParmDecl(OldESD->getIndexTemplateParm());
+  ExpansionStmtDecl *NewESD = SemaRef.BuildExpansionStmtDecl(
+      Owner, OldESD->getBeginLoc(), cast<NonTypeTemplateParmDecl>(Index));
+  SemaRef.CurrentInstantiationScope->InstantiatedLocal(OldESD, NewESD);
+
+  // Enter the scope of this instantiation. We don't use
+  // PushDeclContext because we don't have a scope.
+  Sema::ContextRAII Context(SemaRef, NewESD, /*NewThis=*/false);
+
+  // If this was already expanded, only instantiate the expansion and
+  // don't touch the unexpanded expansion statement.
+  if (CXXExpansionInstantiationStmt *OldInst = OldESD->getInstantiations()) {
+    StmtResult NewInst = SemaRef.SubstStmt(OldInst, TemplateArgs);
+    if (NewInst.isInvalid())
+      return nullptr;
+
+    NewESD->setInstantiations(NewInst.getAs<CXXExpansionInstantiationStmt>());
+    NewESD->setExpansionPattern(OldESD->getExpansionPattern());
+    return NewESD;
+  }
+
+  StmtResult Expansion =
+      SemaRef.SubstStmt(OldESD->getExpansionPattern(), TemplateArgs);
+  if (Expansion.isInvalid())
+    return nullptr;
+
+  // The code that handles CXXExpansionStmt takes care of calling
+  // setInstantiation() on the ESD if there was an expansion.
+  NewESD->setExpansionPattern(cast<CXXExpansionStmt>(Expansion.get()));
+  return NewESD;
+}
+
 Decl *TemplateDeclInstantiator::VisitEnumDecl(EnumDecl *D) {
   EnumDecl *PrevDecl = nullptr;
   if (EnumDecl *PatternPrev = getPreviousDeclForInstantiation(D)) {
@@ -7154,6 +7187,14 @@ NamedDecl *Sema::FindInstantiatedDecl(SourceLocation Loc, NamedDecl *D,
     }
 
     D = Result;
+  } else if (CurrentInstantiationScope && ParentDC->isExpansionStmt()) {
+    // If this is the expansion variable of an expansion statement, then it will
+    // have been instantiated as part of expanding the statement; this doesn't
+    // involve instantiating the parent decl context (only the body and
+    // expansion variable are instantiated; not the entire expansion statement).
+    assert(isa<VarDecl>(D));
+    return cast<NamedDecl>(
+        cast<Decl*>(*CurrentInstantiationScope->findInstantiationOf(D)));
   }
 
   return D;
diff --git a/clang/lib/Sema/TreeTransform.h b/clang/lib/Sema/TreeTransform.h
index 0c8c1d18d317e..0bdb96836eb51 100644
--- a/clang/lib/Sema/TreeTransform.h
+++ b/clang/lib/Sema/TreeTransform.h
@@ -9284,6 +9284,102 @@ TreeTransform<Derived>::TransformCXXForRangeStmt(CXXForRangeStmt *S) {
   return FinishCXXForRangeStmt(NewStmt.get(), Body.get());
 }
 
+template <typename Derived>
+StmtResult TreeTransform<Derived>::TransformCXXEnumeratingExpansionStmt(
+    CXXEnumeratingExpansionStmt *S) {
+  Decl* ESD = getDerived().TransformDecl(S->getDecl()->getLocation(), S->getDecl());
+  if (!ESD || ESD->isInvalidDecl())
+    return StmtError();
+
+  Stmt *Init = S->getInit();
+  if (Init) {
+    StmtResult SR = getDerived().TransformStmt(Init);
+    if (SR.isInvalid())
+      return StmtError();
+    Init = SR.get();
+  }
+
+  StmtResult ExpansionVar = getDerived().TransformStmt(S->getExpansionVarStmt());
+  if (ExpansionVar.isInvalid())
+    return StmtError();
+
+  auto *Expansion = new (SemaRef.Context) CXXEnumeratingExpansionStmt(
+      cast<ExpansionStmtDecl>(ESD), Init, ExpansionVar.getAs<DeclStmt>(),
+      S->getForLoc(), S->getLParenLoc(), S->getColonLoc(), S->getRParenLoc());
+
+  StmtResult Body = getDerived().TransformStmt(S->getBody());
+  if (Body.isInvalid())
+    return StmtError();
+
+  // Finish expanding the statement.
+  return SemaRef.FinishCXXExpansionStmt(Expansion, Body.get());
+}
+
+template <typename Derived>
+ExprResult TreeTransform<Derived>::TransformCXXExpansionInitListExpr(
+    CXXExpansionInitListExpr *E) {
+  bool ArgChanged = false;
+  SmallVector<Expr *> SubExprs;
+  if (getDerived().TransformExprs(E->getExprs().data(),
+                                  E->getExprs().size(), false, SubExprs,
+                                  &ArgChanged))
+    return ExprError();
+
+  if (!getDerived().AlwaysRebuild() && !ArgChanged)
+    return E;
+
+  return CXXExpansionInitListExpr::Create(SemaRef.Context, SubExprs,
+                                          E->getLBraceLoc(), E->getRBraceLoc());
+}
+
+template <typename Derived>
+StmtResult TreeTransform<Derived>::TransformCXXExpansionInstantiationStmt(
+    CXXExpansionInstantiationStmt *S) {
+  bool SubStmtChanged = false;
+  auto TransformStmts = [&](SmallVectorImpl<Stmt *> &NewStmts,
+                            ArrayRef<Stmt *> OldStmts) {
+    for (Stmt *OldDS : OldStmts) {
+      StmtResult NewDS = getDerived().TransformStmt(OldDS);
+      if (NewDS.isInvalid())
+        return true;
+
+      SubStmtChanged |= NewDS.get() != OldDS;
+      NewStmts.push_back(NewDS.get());
+    }
+
+    return false;
+  };
+
+  SmallVector<Stmt*> SharedStmts;
+  SmallVector<Stmt*> Instantiations;
+  if (TransformStmts(SharedStmts, S->getSharedStmts()) ||
+      TransformStmts(Instantiations, S->getInstantiations()))
+    return StmtError();
+
+  if (!getDerived().AlwaysRebuild() && !SubStmtChanged)
+    return S;
+
+  return CXXExpansionInstantiationStmt::Create(
+      SemaRef.Context, S->getBeginLoc(), Instantiations, SharedStmts);
+}
+
+template <typename Derived>
+ExprResult TreeTransform<Derived>::TransformCXXExpansionInitListSelectExpr(
+    CXXExpansionInitListSelectExpr *E) {
+  ExprResult Range = getDerived().TransformExpr(E->getRangeExpr());
+  ExprResult Idx = getDerived().TransformExpr(E->getIndexExpr());
+  if (Range.isInvalid() || Idx.isInvalid())
+    return ExprError();
+
+  if (!getDerived().AlwaysRebuild() && Range.get() == E->getRangeExpr() &&
+      Idx.get() == E->getIndexExpr())
+    return E;
+
+  return SemaRef.BuildCXXExpansionInitListSelectExpr(
+          Range.getAs<CXXExpansionInitListExpr>(), Idx.get());
+}
+
+
 template<typename Derived>
 StmtResult
 TreeTransform<Derived>::TransformMSDependentExistsStmt(
@@ -15596,7 +15692,7 @@ TreeTransform<Derived>::TransformLambdaExpr(LambdaExpr *E) {
   // will be deemed as dependent even if there are no dependent template
   // arguments.
   // (A ClassTemplateSpecializationDecl is always a dependent context.)
-  while (DC->isRequiresExprBody())
+  while (DC->isRequiresExprBody() || isa<ExpansionStmtDecl>(DC))
     DC = DC->getParent();
   if ((getSema().isUnevaluatedContext() ||
        getSema().isConstantEvaluatedContext()) &&
diff --git a/clang/lib/Serialization/ASTCommon.cpp b/clang/lib/Serialization/ASTCommon.cpp
index 69db02f2efc40..a5e0baf80038f 100644
--- a/clang/lib/Serialization/ASTCommon.cpp
+++ b/clang/lib/Serialization/ASTCommon.cpp
@@ -459,6 +459,7 @@ bool serialization::isRedeclarableDeclKind(unsigned Kind) {
   case Decl::HLSLRootSignature:
   case Decl::OpenACCDeclare:
   case Decl::OpenACCRoutine:
+  case Decl::ExpansionStmt:
     return false;
 
   // These indirectly derive from Redeclarable<T> but are not actually
diff --git a/clang/lib/Serialization/ASTReaderDecl.cpp b/clang/lib/Serialization/ASTReaderDecl.cpp
index 5456e73956659..d530a09ae36ea 100644
--- a/clang/lib/Serialization/ASTReaderDecl.cpp
+++ b/clang/lib/Serialization/ASTReaderDecl.cpp
@@ -405,6 +405,7 @@ class ASTDeclReader : public DeclVisitor<ASTDeclReader, void> {
   void VisitFriendDecl(FriendDecl *D);
   void VisitFriendTemplateDecl(FriendTemplateDecl *D);
   void VisitStaticAssertDecl(StaticAssertDecl *D);
+  void VisitExpansionStmtDecl(ExpansionStmtDecl *D);
   void VisitBlockDecl(BlockDecl *BD);
   void VisitOutlinedFunctionDecl(OutlinedFunctionDecl *D);
   void VisitCapturedDecl(CapturedDecl *CD);
@@ -2769,6 +2770,13 @@ void ASTDeclReader::VisitStaticAssertDecl(StaticAssertDecl *D) {
   D->RParenLoc = readSourceLocation();
 }
 
+void ASTDeclReader::VisitExpansionStmtDecl(ExpansionStmtDecl *D) {
+  VisitDecl(D);
+  D->Expansion = cast<CXXExpansionStmt>(Record.readStmt());
+  D->Instantiations = cast<CXXExpansionInstantiationStmt>(Record.readStmt());
+  D->TParams = Record.readTemplateParameterList();
+}
+
 void ASTDeclReader::VisitEmptyDecl(EmptyDecl *D) {
   VisitDecl(D);
 }
@@ -4083,6 +4091,9 @@ Decl *ASTReader::ReadDeclRecord(GlobalDeclID ID) {
   case DECL_STATIC_ASSERT:
     D = StaticAssertDecl::CreateDeserialized(Context, ID);
     break;
+  case DECL_EXPANSION_STMT:
+    D = ExpansionStmtDecl::CreateDeserialized(Context, ID);
+    break;
   case DECL_OBJC_METHOD:
     D = ObjCMethodDecl::CreateDeserialized(Context, ID);
     break;
diff --git a/clang/lib/Serialization/ASTReaderStmt.cpp b/clang/lib/Serialization/ASTReaderStmt.cpp
index eef97a8588f0b..1cdfb4377f367 100644
--- a/clang/lib/Serialization/ASTReaderStmt.cpp
+++ b/clang/lib/Serialization/ASTReaderStmt.cpp
@@ -1730,6 +1730,49 @@ void ASTStmtReader::VisitCXXForRangeStmt(CXXForRangeStmt *S) {
   S->setBody(Record.readSubStmt());
 }
 
+void ASTStmtReader::VisitCXXExpansionStmt(CXXExpansionStmt *S) {
+  VisitStmt(S);
+  S->ForLoc = readSourceLocation();
+  S->LParenLoc = readSourceLocation();
+  S->ColonLoc = readSourceLocation();
+  S->RParenLoc = readSourceLocation();
+  S->ParentDecl = cast<ExpansionStmtDecl>(Record.readDeclRef());
+  S->setInit(Record.readSubStmt());
+  S->setExpansionVarStmt(Record.readSubStmt());
+  S->setBody(Record.readSubStmt());
+}
+
+void ASTStmtReader::VisitCXXExpansionInstantiationStmt(
+    CXXExpansionInstantiationStmt *S) {
+  VisitStmt(S);
+  Record.skipInts(2);
+  S->Loc = readSourceLocation();
+  for (unsigned I = 0; I < S->getNumSubStmts(); ++I)
+    S->getAllSubStmts()[I] = Record.readSubStmt();
+}
+
+void ASTStmtReader::VisitCXXEnumeratingExpansionStmt(
+    CXXEnumeratingExpansionStmt *S) {
+  VisitCXXExpansionStmt(S);
+}
+
+void ASTStmtReader::VisitCXXExpansionInitListExpr(CXXExpansionInitListExpr *E) {
+  VisitExpr(E);
+  assert(Record.peekInt() == E->getNumExprs() && "NumExprFields is wrong ?");
+  Record.skipInts(1);
+  E->LBraceLoc = readSourceLocation();
+  E->RBraceLoc = readSourceLocation();
+  for (unsigned I = 0; I < E->getNumExprs(); ++I)
+    E->getExprs()[I] = Record.readSubExpr();
+}
+
+void ASTStmtReader::VisitCXXExpansionInitListSelectExpr(
+    CXXExpansionInitListSelectExpr *E) {
+  VisitExpr(E);
+  E->setRangeExpr(cast<CXXExpansionInitListExpr>(Record.readSubExpr()));
+  E->setIndexExpr(Record.readSubExpr());
+}
+
 void ASTStmtReader::VisitMSDependentExistsStmt(MSDependentExistsStmt *S) {
   VisitStmt(S);
   S->KeywordLoc = readSourceLocation();
@@ -3566,6 +3609,16 @@ Stmt *ASTReader::ReadStmtFromStream(ModuleFile &F) {
              /*numHandlers=*/Record[ASTStmtReader::NumStmtFields]);
       break;
 
+    case STMT_CXX_ENUMERATING_EXPANSION:
+      S = new (Context) CXXEnumeratingExpansionStmt(Empty);
+      break;
+
+    case STMT_CXX_EXPANSION_INSTANTIATION:
+      S = CXXExpansionInstantiationStmt::CreateEmpty(
+          Context, Empty, Record[ASTStmtReader::NumStmtFields],
+          Record[ASTStmtReader::NumStmtFields + 1]);
+      break;
+
     case STMT_CXX_FOR_RANGE:
       S = new (Context) CXXForRangeStmt(Empty);
       break;
@@ -4443,6 +4496,16 @@ Stmt *ASTReader::ReadStmtFromStream(ModuleFile &F) {
       S = new (Context) ConceptSpecializationExpr(Empty);
       break;
     }
+
+    case EXPR_CXX_EXPANSION_INIT_LIST:
+      S = CXXExpansionInitListExpr::CreateEmpty(
+          Context, Empty, Record[ASTStmtReader::NumExprFields]);
+      break;
+
+    case EXPR_CXX_EXPANSION_INIT_LIST_SELECT:
+      S = new (Context) CXXExpansionInitListSelectExpr(Empty);
+      break;
+
     case STMT_OPENACC_COMPUTE_CONSTRUCT: {
       unsigned NumClauses = Record[ASTStmtReader::NumStmtFields];
       S = OpenACCComputeConstruct::CreateEmpty(Context, NumClauses);
diff --git a/clang/lib/Serialization/ASTWriterDecl.cpp b/clang/lib/Serialization/ASTWriterDecl.cpp
index a8c487005f6ec..733d5ec73cc67 100644
--- a/clang/lib/Serialization/ASTWriterDecl.cpp
+++ b/clang/lib/Serialization/ASTWriterDecl.cpp
@@ -144,6 +144,7 @@ namespace clang {
     void VisitFriendDecl(FriendDecl *D);
     void VisitFriendTemplateDecl(FriendTemplateDecl *D);
     void VisitStaticAssertDecl(StaticAssertDecl *D);
+    void VisitExpansionStmtDecl(ExpansionStmtDecl *D);
     void VisitBlockDecl(BlockDecl *D);
     void VisitOutlinedFunctionDecl(OutlinedFunctionDecl *D);
     void VisitCapturedDecl(CapturedDecl *D);
@@ -2188,6 +2189,14 @@ void ASTDeclWriter::VisitStaticAssertDecl(StaticAssertDecl *D) {
   Code = serialization::DECL_STATIC_ASSERT;
 }
 
+void ASTDeclWriter::VisitExpansionStmtDecl(ExpansionStmtDecl *D) {
+  VisitDecl(D);
+  Record.AddStmt(D->getExpansionPattern());
+  Record.AddStmt(D->getInstantiations());
+  Record.AddTemplateParameterList(D->getTemplateParameters());
+  Code = serialization::DECL_EXPANSION_STMT;
+}
+
 /// Emit the DeclContext part of a declaration context decl.
 void ASTDeclWriter::VisitDeclContext(DeclContext *DC) {
   static_assert(DeclContext::NumDeclContextBits == 13,
diff --git a/clang/lib/Serialization/ASTWriterStmt.cpp b/clang/lib/Serialization/ASTWriterStmt.cpp
index acf345392aa1a..779596e10a992 100644
--- a/clang/lib/Serialization/ASTWriterStmt.cpp
+++ b/clang/lib/Serialization/ASTWriterStmt.cpp
@@ -1704,6 +1704,53 @@ void ASTStmtWriter::VisitCXXForRangeStmt(CXXForRangeStmt *S) {
   Code = serialization::STMT_CXX_FOR_RANGE;
 }
 
+void ASTStmtWriter::VisitCXXExpansionStmt(CXXExpansionStmt *S) {
+  VisitStmt(S);
+  Record.AddSourceLocation(S->getForLoc());
+  Record.AddSourceLocation(S->getLParenLoc());
+  Record.AddSourceLocation(S->getColonLoc());
+  Record.AddSourceLocation(S->getRParenLoc());
+  Record.AddDeclRef(S->getDecl());
+  Record.AddStmt(S->getInit());
+  Record.AddStmt(S->getExpansionVarStmt());
+  Record.AddStmt(S->getBody());
+}
+
+void ASTStmtWriter::VisitCXXExpansionInstantiationStmt(
+    CXXExpansionInstantiationStmt *S) {
+  VisitStmt(S);
+  Record.push_back(S->getInstantiations().size());
+  Record.push_back(S->getSharedStmts().size());
+  Record.AddSourceLocation(S->getBeginLoc());
+  for (Stmt *St : S->getAllSubStmts())
+    Record.AddStmt(St);
+  Code = serialization::STMT_CXX_EXPANSION_INSTANTIATION;
+}
+
+void ASTStmtWriter::VisitCXXEnumeratingExpansionStmt(
+    CXXEnumeratingExpansionStmt *S) {
+  VisitCXXExpansionStmt(S);
+  Code = serialization::STMT_CXX_ENUMERATING_EXPANSION;
+}
+
+void ASTStmtWriter::VisitCXXExpansionInitListExpr(CXXExpansionInitListExpr *E) {
+  VisitExpr(E);
+  Record.push_back(E->getNumExprs());
+  Record.AddSourceLocation(E->getLBraceLoc());
+  Record.AddSourceLocation(E->getRBraceLoc());
+  for (Expr* SubExpr : E->getExprs())
+    Record.AddStmt(SubExpr);
+  Code = serialization::EXPR_CXX_EXPANSION_INIT_LIST;
+}
+
+void ASTStmtWriter::VisitCXXExpansionInitListSelectExpr(
+    CXXExpansionInitListSelectExpr *E) {
+  VisitExpr(E);
+  Record.AddStmt(E->getRangeExpr());
+  Record.AddStmt(E->getIndexExpr());
+  Code = serialization::EXPR_CXX_EXPANSION_INIT_LIST_SELECT;
+}
+
 void ASTStmtWriter::VisitMSDependentExistsStmt(MSDependentExistsStmt *S) {
   VisitStmt(S);
   Record.AddSourceLocation(S->getKeywordLoc());
diff --git a/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp b/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp
index 4e472b7fc38b0..668c62a066283 100644
--- a/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp
+++ b/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp
@@ -1747,6 +1747,10 @@ void ExprEngine::Visit(const Stmt *S, ExplodedNode *Pred,
     case Stmt::SEHExceptStmtClass:
     case Stmt::SEHLeaveStmtClass:
     case Stmt::SEHFinallyStmtClass:
+    case Stmt::CXXEnumeratingExpansionStmtClass:
+    case Stmt::CXXExpansionInstantiationStmtClass:
+    case Stmt::CXXExpansionInitListExprClass:
+    case Stmt::CXXExpansionInitListSelectExprClass:
     case Stmt::OMPCanonicalLoopClass:
     case Stmt::OMPParallelDirectiveClass:
     case Stmt::OMPSimdDirectiveClass:
diff --git a/clang/test/Parser/cxx2c-expansion-statements.cpp b/clang/test/Parser/cxx2c-expansion-statements.cpp
new file mode 100644
index 0000000000000..ae1808b88c3a7
--- /dev/null
+++ b/clang/test/Parser/cxx2c-expansion-statements.cpp
@@ -0,0 +1,57 @@
+// 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 range-based}}
+  template for (;); // expected-error {{expected ';' in 'for' statement specifier}} expected-error {{expansion statement must be range-based}}
+  template for (;;); // expected-error {{expansion statement must be range-based}}
+  template for (int x;;); // expected-error {{expansion statement must be range-based}}
+  template for (x : {1}); // expected-error {{expansion statement requires type for expansion variable}}
+  template for (: {1}); // expected-error {{expected expression}} expected-error {{expected ';' in 'for' statement specifier}} expected-error {{expansion statement must be range-based}}
+  template for (auto y : {1})]; // expected-error {{expected expression}}
+  template for (auto y : {1}; // expected-error {{expected ')'}} expected-note {{to match this '('}}
+
+  template for (extern auto y : {1, 2}); // expected-error {{expansion variable 'y' may not be declared 'extern'}}
+  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'}}
+  template for (static auto y : {1, 2}); // expected-error {{expansion variable 'y' may not be declared 'static'}}
+  template for (thread_local auto y : {1, 2}); // expected-error {{'thread_local' variables must have global storage}}
+  template for (static thread_local auto y : {1, 2}); // expected-error {{expansion variable 'y' may not be declared 'thread_local'}}
+  template for (__thread auto y : {1, 2}); // expected-error {{'__thread' variables must have global storage}}
+  template for (static __thread auto y : {1, 2}); // expected-error {{expansion variable 'y' may not be declared 'static'}}
+  template for (constinit auto y : {1, 2}); // expected-error {{local variable cannot be declared 'constinit'}}
+  template for (consteval auto y : {1, 2});  // expected-error {{consteval can only be used in function declarations}}
+  template for (int x; extern auto y : {1, 2}); // expected-error {{expansion variable 'y' may not be declared 'extern'}}
+  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'}}
+  template for (int x; static auto y : {1, 2}); // expected-error {{expansion variable 'y' may not be declared 'static'}}
+  template for (int x; thread_local auto y : {1, 2}); // expected-error {{'thread_local' variables must have global storage}}
+  template for (int x; static thread_local auto y : {1, 2}); // expected-error {{expansion variable 'y' may not be declared 'thread_local'}}
+  template for (int x; __thread auto y : {1, 2}); // expected-error {{'__thread' variables must have global storage}}
+  template for (int x; static __thread auto y : {1, 2}); // expected-error {{expansion variable 'y' may not be declared 'static'}}
+  template for (int x; constinit auto y : {1, 2}); // expected-error {{local variable cannot be declared 'constinit'}}
+  template for (int x; consteval auto y : {1, 2});  // expected-error {{consteval can only be used in function declarations}}
+  template for (auto y : {abc, -+, }); // expected-error {{use of undeclared identifier 'abc'}} expected-error 2 {{expected expression}}
+  template while (true) {} // expected-error {{expected '<' after 'template'}}
+  template for (auto y : {{1}, {2}, {3, {4}}, {{{5}}}});
+}
+
+void good() {
+  template for (auto y : {});
+  template for (auto y : {1, 2});
+  template for (int x; auto y : {1, 2});
+  template for (int x; int y : {1, 2});
+  template for (int x; constexpr auto y : {1, 2});
+  template for (int x; constexpr int y : {1, 2});
+  template for (constexpr int a : {1, 2}) {
+    template for (constexpr int b : {1, 2}) {
+      template for (constexpr int c : {1, 2});
+    }
+  }
+}
diff --git a/clang/test/SemaCXX/cxx2c-expansion-statements.cpp b/clang/test/SemaCXX/cxx2c-expansion-statements.cpp
new file mode 100644
index 0000000000000..4991fdb7abd72
--- /dev/null
+++ b/clang/test/SemaCXX/cxx2c-expansion-statements.cpp
@@ -0,0 +1,104 @@
+// 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* a, T* b): a{a}, b{b} {}
+};
+}
+
+struct S {
+  int x;
+  constexpr S(int x) : x{x} {}
+};
+
+void g(int);
+template <int n> constexpr int tg() { return n; }
+
+void f1() {
+  template for (auto x : {}) static_assert(false, "discarded");
+  template for (constexpr auto x : {}) static_assert(false, "discarded");
+  template for (auto x : {1}) g(x);
+  template for (auto x : {1, 2, 3}) g(x);
+  template for (constexpr auto x : {1}) g(x);
+  template for (constexpr auto x : {1, 2, 3}) g(x);
+  template for (constexpr auto x : {1}) tg<x>();
+  template for (constexpr auto x : {1, 2, 3})
+    static_assert(tg<x>());
+
+  template for (int x : {1, 2, 3}) g(x);
+  template for (S x : {1, 2, 3}) g(x.x);
+  template for (constexpr S x : {1, 2, 3}) tg<x.x>();
+
+  template for (int x : {"1", S(1), {1, 2}}) { // expected-error {{cannot initialize a variable of type 'int' with an lvalue of type 'const char[2]'}} \
+                                                  expected-error {{no viable conversion from 'S' to 'int'}} \
+                                                  expected-error {{excess elements in scalar initializer}} \
+                                                  expected-note 3 {{in instantiation of expansion statement requested here}}
+    g(x);
+  }
+
+  template for (constexpr auto x : {1, 2, 3, 4}) { // expected-note 3 {{in instantiation of expansion statement requested here}}
+    static_assert(tg<x>() == 4); // expected-error 3 {{static assertion failed due to requirement 'tg<x>() == 4'}} \
+                                    expected-note {{expression evaluates to '1 == 4'}} \
+                                    expected-note {{expression evaluates to '2 == 4'}} \
+                                    expected-note {{expression evaluates to '3 == 4'}}
+  }
+
+
+  template for (constexpr auto x : {1, 2}) { // expected-note 2 {{in instantiation of expansion statement requested here}}
+    static_assert(false, "not discarded"); // expected-error 2 {{static assertion failed: not discarded}}
+  }
+}
+
+template <typename T>
+void t1() {
+  template for (T x : {}) g(x);
+  template for (constexpr T x : {}) g(x);
+  template for (auto x : {}) g(x);
+  template for (constexpr auto x : {}) g(x);
+  template for (T x : {1, 2}) g(x);
+  template for (T x : {T(1), T(2)}) g(x);
+  template for (auto x : {T(1), T(2)}) g(x);
+  template for (constexpr T x : {T(1), T(2)}) static_assert(tg<x>());
+  template for (constexpr auto x : {T(1), T(2)}) static_assert(tg<x>());
+}
+
+template <typename U>
+struct s1 {
+  template <typename T>
+  void tf() {
+      template for (T x : {}) g(x);
+      template for (constexpr T x : {}) g(x);
+      template for (U x : {}) g(x);
+      template for (constexpr U x : {}) g(x);
+      template for (auto x : {}) g(x);
+      template for (constexpr auto x : {}) g(x);
+      template for (T x : {1, 2}) g(x);
+      template for (U x : {1, 2}) g(x);
+      template for (U x : {T(1), T(2)}) g(x);
+      template for (T x : {U(1), U(2)}) g(x);
+      template for (auto x : {T(1), T(2)}) g(x);
+      template for (auto x : {U(1), T(2)}) g(x);
+      template for (constexpr U x : {T(1), T(2)}) static_assert(tg<x>());
+      template for (constexpr T x : {U(1), U(2)}) static_assert(tg<x>());
+      template for (constexpr auto x : {T(1), U(2)}) static_assert(tg<x>());
+    }
+};
+
+template <typename T>
+void t2() {
+  template for (T x : {}) g(x);
+}
+
+void f2() {
+  t1<int>();
+  t1<long>();
+  s1<long>().tf<long>();
+  s1<int>().tf<int>();
+  s1<int>().tf<long>();
+  s1<long>().tf<int>();
+  t2<S>();
+  t2<S[1231]>();
+  t2<S***>();
+}
diff --git a/clang/tools/libclang/CIndex.cpp b/clang/tools/libclang/CIndex.cpp
index fc27fd29da933..ac7dbd908fea7 100644
--- a/clang/tools/libclang/CIndex.cpp
+++ b/clang/tools/libclang/CIndex.cpp
@@ -7247,6 +7247,7 @@ CXCursor clang_getCursorDefinition(CXCursor C) {
   case Decl::UnresolvedUsingIfExists:
   case Decl::OpenACCDeclare:
   case Decl::OpenACCRoutine:
+  case Decl::ExpansionStmt:
     return C;
 
   // Declaration kinds that don't make any sense here, but are
diff --git a/clang/tools/libclang/CXCursor.cpp b/clang/tools/libclang/CXCursor.cpp
index 0a43d73063c1f..999bd1c0d78be 100644
--- a/clang/tools/libclang/CXCursor.cpp
+++ b/clang/tools/libclang/CXCursor.cpp
@@ -290,6 +290,8 @@ CXCursor cxcursor::MakeCXCursor(const Stmt *S, const Decl *Parent,
 
   case Stmt::CoroutineBodyStmtClass:
   case Stmt::CoreturnStmtClass:
+  case Stmt::CXXEnumeratingExpansionStmtClass:
+  case Stmt::CXXExpansionInstantiationStmtClass:
     K = CXCursor_UnexposedStmt;
     break;
 
@@ -338,6 +340,8 @@ CXCursor cxcursor::MakeCXCursor(const Stmt *S, const Decl *Parent,
   case Stmt::EmbedExprClass:
   case Stmt::HLSLOutArgExprClass:
   case Stmt::OpenACCAsteriskSizeExprClass:
+  case Stmt::CXXExpansionInitListExprClass:
+  case Stmt::CXXExpansionInitListSelectExprClass:
     K = CXCursor_UnexposedExpr;
     break;
 

>From b864b4ca9d65737ebfc3cda1808180614437f816 Mon Sep 17 00:00:00 2001
From: Sirraide <aeternalmail at gmail.com>
Date: Fri, 24 Oct 2025 21:46:08 +0200
Subject: [PATCH 02/33] Fix some instantiation issues and add codegen tests

---
 clang/lib/CodeGen/CGStmt.cpp                  |   1 +
 clang/lib/Sema/SemaExpand.cpp                 |   2 +
 .../lib/Sema/SemaTemplateInstantiateDecl.cpp  |  23 +-
 .../CodeGenCXX/cxx2c-expansion-statements.cpp | 863 ++++++++++++++++++
 4 files changed, 877 insertions(+), 12 deletions(-)
 create mode 100644 clang/test/CodeGenCXX/cxx2c-expansion-statements.cpp

diff --git a/clang/lib/CodeGen/CGStmt.cpp b/clang/lib/CodeGen/CGStmt.cpp
index e1d020bf2aaab..887e8871fb3c9 100644
--- a/clang/lib/CodeGen/CGStmt.cpp
+++ b/clang/lib/CodeGen/CGStmt.cpp
@@ -1585,6 +1585,7 @@ void CodeGenFunction::EmitCXXExpansionInstantiationStmt(
      else
       ContinueDest = getJumpDestInCurrentScope("expand.next");
 
+    LexicalScope ExpansionScope(*this, S.getSourceRange());
     BreakContinueStack.push_back(BreakContinue(S, ExpandExit, ContinueDest));
     EmitStmt(Inst);
     BreakContinueStack.pop_back();
diff --git a/clang/lib/Sema/SemaExpand.cpp b/clang/lib/Sema/SemaExpand.cpp
index 70e6af1886aa7..5442752aacf10 100644
--- a/clang/lib/Sema/SemaExpand.cpp
+++ b/clang/lib/Sema/SemaExpand.cpp
@@ -389,6 +389,8 @@ StmtResult Sema::FinishCXXExpansionStmt(Stmt* Exp, Stmt *Body) {
   SmallVector<Stmt *, 4> Instantiations;
   ExpansionStmtDecl *ESD = Expansion->getDecl();
   for (size_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->getEnclosingNonExpansionStatementContext(),
                          /*NewThis=*/false);
diff --git a/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp b/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp
index 166bdf68e55e7..d24c404585900 100644
--- a/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp
+++ b/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp
@@ -2076,10 +2076,6 @@ Decl *TemplateDeclInstantiator::VisitExpansionStmtDecl(ExpansionStmtDecl *OldESD
       Owner, OldESD->getBeginLoc(), cast<NonTypeTemplateParmDecl>(Index));
   SemaRef.CurrentInstantiationScope->InstantiatedLocal(OldESD, NewESD);
 
-  // Enter the scope of this instantiation. We don't use
-  // PushDeclContext because we don't have a scope.
-  Sema::ContextRAII Context(SemaRef, NewESD, /*NewThis=*/false);
-
   // If this was already expanded, only instantiate the expansion and
   // don't touch the unexpanded expansion statement.
   if (CXXExpansionInstantiationStmt *OldInst = OldESD->getInstantiations()) {
@@ -2092,6 +2088,11 @@ Decl *TemplateDeclInstantiator::VisitExpansionStmtDecl(ExpansionStmtDecl *OldESD
     return NewESD;
   }
 
+  // Enter the scope of this expansion statement; don't do this if we've
+  // already expanded it, as in that case we no longer want to treat its
+  // content as dependent.
+  Sema::ContextRAII Context(SemaRef, NewESD, /*NewThis=*/false);
+
   StmtResult Expansion =
       SemaRef.SubstStmt(OldESD->getExpansionPattern(), TemplateArgs);
   if (Expansion.isInvalid())
@@ -7094,6 +7095,12 @@ NamedDecl *Sema::FindInstantiatedDecl(SourceLocation Loc, NamedDecl *D,
     // anonymous unions in class templates).
   }
 
+  if (CurrentInstantiationScope) {
+    if (auto Found = CurrentInstantiationScope->getInstantiationOfIfExists(D))
+      if (auto *FD = dyn_cast<NamedDecl>(cast<Decl*>(*Found)))
+        return FD;
+  }
+
   if (!ParentDependsOnArgs)
     return D;
 
@@ -7187,14 +7194,6 @@ NamedDecl *Sema::FindInstantiatedDecl(SourceLocation Loc, NamedDecl *D,
     }
 
     D = Result;
-  } else if (CurrentInstantiationScope && ParentDC->isExpansionStmt()) {
-    // If this is the expansion variable of an expansion statement, then it will
-    // have been instantiated as part of expanding the statement; this doesn't
-    // involve instantiating the parent decl context (only the body and
-    // expansion variable are instantiated; not the entire expansion statement).
-    assert(isa<VarDecl>(D));
-    return cast<NamedDecl>(
-        cast<Decl*>(*CurrentInstantiationScope->findInstantiationOf(D)));
   }
 
   return D;
diff --git a/clang/test/CodeGenCXX/cxx2c-expansion-statements.cpp b/clang/test/CodeGenCXX/cxx2c-expansion-statements.cpp
new file mode 100644
index 0000000000000..3de2ee177f12e
--- /dev/null
+++ b/clang/test/CodeGenCXX/cxx2c-expansion-statements.cpp
@@ -0,0 +1,863 @@
+// RUN: %clang_cc1 -std=c++2c -triple x86_64-unknown-linux-gnu -emit-llvm -o - %s | FileCheck %s
+
+struct S {
+  int x;
+  constexpr S(int x) : x{x} {}
+};
+
+void g(int);
+void g(long);
+void g(const char*);
+void g(S);
+
+template <int n> constexpr int tg() { return n; }
+
+void h(int, int);
+
+void f1() {
+  template for (auto x : {1, 2, 3}) g(x);
+}
+
+void f2() {
+  template for (auto x : {1, "123", S(45)}) g(x);
+}
+
+void f3() {
+  template for (auto x : {}) g(x);
+}
+
+void f4() {
+  template for (auto x : {1, 2})
+    template for (auto y : {3, 4})
+      h(x, y);
+}
+
+void f5() {
+  template for (auto x : {}) static_assert(false, "discarded");
+  template for (constexpr auto x : {}) static_assert(false, "discarded");
+  template for (auto x : {1}) g(x);
+  template for (auto x : {2, 3, 4}) g(x);
+  template for (constexpr auto x : {5}) g(x);
+  template for (constexpr auto x : {6, 7, 8}) g(x);
+  template for (constexpr auto x : {9}) tg<x>();
+  template for (constexpr auto x : {10, 11, 12})
+    static_assert(tg<x>());
+
+  template for (int x : {13, 14, 15}) g(x);
+  template for (S x : {16, 17, 18}) g(x.x);
+  template for (constexpr S x : {19, 20, 21}) tg<x.x>();
+}
+
+template <typename T>
+void t1() {
+  template for (T x : {}) g(x);
+  template for (constexpr T x : {}) g(x);
+  template for (auto x : {}) g(x);
+  template for (constexpr auto x : {}) g(x);
+  template for (T x : {1, 2}) g(x);
+  template for (T x : {T(3), T(4)}) g(x);
+  template for (auto x : {T(5), T(6)}) g(x);
+  template for (constexpr T x : {T(7), T(8)}) static_assert(tg<x>());
+  template for (constexpr auto x : {T(9), T(10)}) static_assert(tg<x>());
+}
+
+template <typename U>
+struct s1 {
+  template <typename T>
+  void tf() {
+    template for (T x : {}) g(x);
+    template for (constexpr T x : {}) g(x);
+    template for (U x : {}) g(x);
+    template for (constexpr U x : {}) g(x);
+    template for (auto x : {}) g(x);
+    template for (constexpr auto x : {}) g(x);
+    template for (T x : {1, 2}) g(x);
+    template for (U x : {3, 4}) g(x);
+    template for (U x : {T(5), T(6)}) g(x);
+    template for (T x : {U(7), U(8)}) g(x);
+    template for (auto x : {T(9), T(10)}) g(x);
+    template for (auto x : {U(11), T(12)}) g(x);
+    template for (constexpr U x : {T(13), T(14)}) static_assert(tg<x>());
+    template for (constexpr T x : {U(15), U(16)}) static_assert(tg<x>());
+    template for (constexpr auto x : {T(17), U(18)}) static_assert(tg<x>());
+  }
+};
+
+template <typename T>
+void t2() {
+  template for (T x : {}) g(x);
+}
+
+void f6() {
+  t1<int>();
+  t1<long>();
+  s1<long>().tf<long>();
+  s1<int>().tf<int>();
+  s1<int>().tf<long>();
+  s1<long>().tf<int>();
+  t2<S>();
+  t2<S[1231]>();
+  t2<S***>();
+}
+
+// CHECK-LABEL: define {{.*}} void @_Z2f1v()
+// CHECK: entry:
+// CHECK-NEXT:   %x = alloca i32, align 4
+// CHECK-NEXT:   %x1 = alloca i32, align 4
+// CHECK-NEXT:   %x3 = alloca i32, align 4
+// CHECK-NEXT:   store i32 1, ptr %x, align 4
+// CHECK-NEXT:   %0 = load i32, ptr %x, align 4
+// CHECK-NEXT:   call void @_Z1gi(i32 {{.*}} %0)
+// CHECK-NEXT:   br label %expand.next
+// CHECK: expand.next:
+// CHECK-NEXT:   store i32 2, ptr %x1, align 4
+// CHECK-NEXT:   %1 = load i32, ptr %x1, align 4
+// CHECK-NEXT:   call void @_Z1gi(i32 {{.*}} %1)
+// CHECK-NEXT:   br label %expand.next2
+// CHECK: expand.next2:
+// CHECK-NEXT:   store i32 3, ptr %x3, align 4
+// CHECK-NEXT:   %2 = load i32, ptr %x3, align 4
+// CHECK-NEXT:   call void @_Z1gi(i32 {{.*}} %2)
+// CHECK-NEXT:   br label %expand.end
+// CHECK: expand.end:
+// CHECK-NEXT:   ret void
+
+
+// CHECK-LABEL: define {{.*}} void @_Z2f2v()
+// CHECK: entry:
+// CHECK-NEXT:   %x = alloca i32, align 4
+// CHECK-NEXT:   %x1 = alloca ptr, align 8
+// CHECK-NEXT:   %x3 = alloca %struct.S, align 4
+// CHECK-NEXT:   %agg.tmp = alloca %struct.S, align 4
+// CHECK-NEXT:   store i32 1, ptr %x, align 4
+// CHECK-NEXT:   %0 = load i32, ptr %x, align 4
+// CHECK-NEXT:   call void @_Z1gi(i32 {{.*}} %0)
+// CHECK-NEXT:   br label %expand.next
+// CHECK: expand.next:
+// CHECK-NEXT:   store ptr @.str, ptr %x1, align 8
+// CHECK-NEXT:   %1 = load ptr, ptr %x1, align 8
+// CHECK-NEXT:   call void @_Z1gPKc(ptr {{.*}} %1)
+// CHECK-NEXT:   br label %expand.next2
+// CHECK: expand.next2:
+// CHECK-NEXT:   call void @_ZN1SC1Ei(ptr {{.*}} %x3, i32 {{.*}} 45)
+// CHECK-NEXT:   call void @llvm.memcpy.p0.p0.i64(ptr align 4 %agg.tmp, ptr align 4 %x3, i64 4, i1 false)
+// CHECK-NEXT:   %coerce.dive = getelementptr inbounds nuw %struct.S, ptr %agg.tmp, i32 0, i32 0
+// CHECK-NEXT:   %2 = load i32, ptr %coerce.dive, align 4
+// CHECK-NEXT:   call void @_Z1g1S(i32 %2)
+// CHECK-NEXT:   br label %expand.end
+// CHECK: expand.end:
+// CHECK-NEXT:   ret void
+
+
+// CHECK-LABEL: define {{.*}} void @_Z2f3v()
+// CHECK: entry:
+// CHECK-NEXT:   ret void
+
+
+// CHECK-LABEL: define {{.*}} void @_Z2f4v()
+// CHECK: entry:
+// CHECK-NEXT:   %x = alloca i32, align 4
+// CHECK-NEXT:   %y = alloca i32, align 4
+// CHECK-NEXT:   %y1 = alloca i32, align 4
+// CHECK-NEXT:   %x3 = alloca i32, align 4
+// CHECK-NEXT:   %y4 = alloca i32, align 4
+// CHECK-NEXT:   %y6 = alloca i32, align 4
+// CHECK-NEXT:   store i32 1, ptr %x, align 4
+// CHECK-NEXT:   store i32 3, ptr %y, align 4
+// CHECK-NEXT:   %0 = load i32, ptr %x, align 4
+// CHECK-NEXT:   %1 = load i32, ptr %y, align 4
+// CHECK-NEXT:   call void @_Z1hii(i32 {{.*}} %0, i32 {{.*}} %1)
+// CHECK-NEXT:   br label %expand.next
+// CHECK: expand.next:
+// CHECK-NEXT:   store i32 4, ptr %y1, align 4
+// CHECK-NEXT:   %2 = load i32, ptr %x, align 4
+// CHECK-NEXT:   %3 = load i32, ptr %y1, align 4
+// CHECK-NEXT:   call void @_Z1hii(i32 {{.*}} %2, i32 {{.*}} %3)
+// CHECK-NEXT:   br label %expand.end
+// CHECK: expand.end:
+// CHECK-NEXT:   br label %expand.next2
+// CHECK: expand.next2:
+// CHECK-NEXT:   store i32 2, ptr %x3, align 4
+// CHECK-NEXT:   store i32 3, ptr %y4, align 4
+// CHECK-NEXT:   %4 = load i32, ptr %x3, align 4
+// CHECK-NEXT:   %5 = load i32, ptr %y4, align 4
+// CHECK-NEXT:   call void @_Z1hii(i32 {{.*}} %4, i32 {{.*}} %5)
+// CHECK-NEXT:   br label %expand.next5
+// CHECK: expand.next5:
+// CHECK-NEXT:   store i32 4, ptr %y6, align 4
+// CHECK-NEXT:   %6 = load i32, ptr %x3, align 4
+// CHECK-NEXT:   %7 = load i32, ptr %y6, align 4
+// CHECK-NEXT:   call void @_Z1hii(i32 {{.*}} %6, i32 {{.*}} %7)
+// CHECK-NEXT:   br label %expand.end7
+// CHECK: expand.end7:
+// CHECK-NEXT:   br label %expand.end8
+// CHECK: expand.end8:
+// CHECK-NEXT:   ret void
+
+
+// CHECK-LABEL: define {{.*}} void @_Z2f5v()
+// CHECK: entry:
+// CHECK-NEXT:   %x = alloca i32, align 4
+// CHECK-NEXT:   %x1 = alloca i32, align 4
+// CHECK-NEXT:   %x2 = alloca i32, align 4
+// CHECK-NEXT:   %x4 = alloca i32, align 4
+// CHECK-NEXT:   %x6 = alloca i32, align 4
+// CHECK-NEXT:   %x8 = alloca i32, align 4
+// CHECK-NEXT:   %x10 = alloca i32, align 4
+// CHECK-NEXT:   %x12 = alloca i32, align 4
+// CHECK-NEXT:   %x14 = alloca i32, align 4
+// CHECK-NEXT:   %x16 = alloca i32, align 4
+// CHECK-NEXT:   %x18 = alloca i32, align 4
+// CHECK-NEXT:   %x20 = alloca i32, align 4
+// CHECK-NEXT:   %x22 = alloca i32, align 4
+// CHECK-NEXT:   %x24 = alloca i32, align 4
+// CHECK-NEXT:   %x26 = alloca i32, align 4
+// CHECK-NEXT:   %x28 = alloca %struct.S, align 4
+// CHECK-NEXT:   %x31 = alloca %struct.S, align 4
+// CHECK-NEXT:   %x34 = alloca %struct.S, align 4
+// CHECK-NEXT:   %x37 = alloca %struct.S, align 4
+// CHECK-NEXT:   %x40 = alloca %struct.S, align 4
+// CHECK-NEXT:   %x43 = alloca %struct.S, align 4
+// CHECK-NEXT:   store i32 1, ptr %x, align 4
+// CHECK-NEXT:   %0 = load i32, ptr %x, align 4
+// CHECK-NEXT:   call void @_Z1gi(i32 {{.*}} %0)
+// CHECK-NEXT:   br label %expand.end
+// CHECK: expand.end:
+// CHECK-NEXT:   store i32 2, ptr %x1, align 4
+// CHECK-NEXT:   %1 = load i32, ptr %x1, align 4
+// CHECK-NEXT:   call void @_Z1gi(i32 {{.*}} %1)
+// CHECK-NEXT:   br label %expand.next
+// CHECK: expand.next:
+// CHECK-NEXT:   store i32 3, ptr %x2, align 4
+// CHECK-NEXT:   %2 = load i32, ptr %x2, align 4
+// CHECK-NEXT:   call void @_Z1gi(i32 {{.*}} %2)
+// CHECK-NEXT:   br label %expand.next3
+// CHECK: expand.next3:
+// CHECK-NEXT:   store i32 4, ptr %x4, align 4
+// CHECK-NEXT:   %3 = load i32, ptr %x4, align 4
+// CHECK-NEXT:   call void @_Z1gi(i32 {{.*}} %3)
+// CHECK-NEXT:   br label %expand.end5
+// CHECK: expand.end5:
+// CHECK-NEXT:   store i32 5, ptr %x6, align 4
+// CHECK-NEXT:   call void @_Z1gi(i32 {{.*}} 5)
+// CHECK-NEXT:   br label %expand.end7
+// CHECK: expand.end7:
+// CHECK-NEXT:   store i32 6, ptr %x8, align 4
+// CHECK-NEXT:   call void @_Z1gi(i32 {{.*}} 6)
+// CHECK-NEXT:   br label %expand.next9
+// CHECK: expand.next9:
+// CHECK-NEXT:   store i32 7, ptr %x10, align 4
+// CHECK-NEXT:   call void @_Z1gi(i32 {{.*}} 7)
+// CHECK-NEXT:   br label %expand.next11
+// CHECK: expand.next11:
+// CHECK-NEXT:   store i32 8, ptr %x12, align 4
+// CHECK-NEXT:   call void @_Z1gi(i32 {{.*}} 8)
+// CHECK-NEXT:   br label %expand.end13
+// CHECK: expand.end13:
+// CHECK-NEXT:   store i32 9, ptr %x14, align 4
+// CHECK-NEXT:   %call = call {{.*}} i32 @_Z2tgILi9EEiv()
+// CHECK-NEXT:   br label %expand.end15
+// CHECK: expand.end15:
+// CHECK-NEXT:   store i32 10, ptr %x16, align 4
+// CHECK-NEXT:   br label %expand.next17
+// CHECK: expand.next17:
+// CHECK-NEXT:   store i32 11, ptr %x18, align 4
+// CHECK-NEXT:   br label %expand.next19
+// CHECK: expand.next19:
+// CHECK-NEXT:   store i32 12, ptr %x20, align 4
+// CHECK-NEXT:   br label %expand.end21
+// CHECK: expand.end21:
+// CHECK-NEXT:   store i32 13, ptr %x22, align 4
+// CHECK-NEXT:   %4 = load i32, ptr %x22, align 4
+// CHECK-NEXT:   call void @_Z1gi(i32 {{.*}} %4)
+// CHECK-NEXT:   br label %expand.next23
+// CHECK: expand.next23:
+// CHECK-NEXT:   store i32 14, ptr %x24, align 4
+// CHECK-NEXT:   %5 = load i32, ptr %x24, align 4
+// CHECK-NEXT:   call void @_Z1gi(i32 {{.*}} %5)
+// CHECK-NEXT:   br label %expand.next25
+// CHECK: expand.next25:
+// CHECK-NEXT:   store i32 15, ptr %x26, align 4
+// CHECK-NEXT:   %6 = load i32, ptr %x26, align 4
+// CHECK-NEXT:   call void @_Z1gi(i32 {{.*}} %6)
+// CHECK-NEXT:   br label %expand.end27
+// CHECK: expand.end27:
+// CHECK-NEXT:   call void @_ZN1SC1Ei(ptr {{.*}} %x28, i32 {{.*}} 16)
+// CHECK-NEXT:   %x29 = getelementptr inbounds nuw %struct.S, ptr %x28, i32 0, i32 0
+// CHECK-NEXT:   %7 = load i32, ptr %x29, align 4
+// CHECK-NEXT:   call void @_Z1gi(i32 {{.*}} %7)
+// CHECK-NEXT:   br label %expand.next30
+// CHECK: expand.next30:
+// CHECK-NEXT:   call void @_ZN1SC1Ei(ptr {{.*}} %x31, i32 {{.*}} 17)
+// CHECK-NEXT:   %x32 = getelementptr inbounds nuw %struct.S, ptr %x31, i32 0, i32 0
+// CHECK-NEXT:   %8 = load i32, ptr %x32, align 4
+// CHECK-NEXT:   call void @_Z1gi(i32 {{.*}} %8)
+// CHECK-NEXT:   br label %expand.next33
+// CHECK: expand.next33:
+// CHECK-NEXT:   call void @_ZN1SC1Ei(ptr {{.*}} %x34, i32 {{.*}} 18)
+// CHECK-NEXT:   %x35 = getelementptr inbounds nuw %struct.S, ptr %x34, i32 0, i32 0
+// CHECK-NEXT:   %9 = load i32, ptr %x35, align 4
+// CHECK-NEXT:   call void @_Z1gi(i32 {{.*}} %9)
+// CHECK-NEXT:   br label %expand.end36
+// CHECK: expand.end36:
+// CHECK-NEXT:   call void @llvm.memcpy.p0.p0.i64(ptr align 4 %x37, ptr align 4 @__const._Z2f5v.x, i64 4, i1 false)
+// CHECK-NEXT:   %call38 = call {{.*}} i32 @_Z2tgILi19EEiv()
+// CHECK-NEXT:   br label %expand.next39
+// CHECK: expand.next39:
+// CHECK-NEXT:   call void @llvm.memcpy.p0.p0.i64(ptr align 4 %x40, ptr align 4 @__const._Z2f5v.x.1, i64 4, i1 false)
+// CHECK-NEXT:   %call41 = call {{.*}} i32 @_Z2tgILi20EEiv()
+// CHECK-NEXT:   br label %expand.next42
+// CHECK: expand.next42:
+// CHECK-NEXT:   call void @llvm.memcpy.p0.p0.i64(ptr align 4 %x43, ptr align 4 @__const._Z2f5v.x.2, i64 4, i1 false)
+// CHECK-NEXT:   %call44 = call {{.*}} i32 @_Z2tgILi21EEiv()
+// CHECK-NEXT:   br label %expand.end45
+// CHECK: expand.end45:
+// CHECK-NEXT:   ret void
+
+
+// CHECK-LABEL: define {{.*}} void @_Z2f6v()
+// CHECK: entry:
+// CHECK-NEXT:   %ref.tmp = alloca %struct.s1, align 1
+// CHECK-NEXT:   %ref.tmp1 = alloca %struct.s1.0, align 1
+// CHECK-NEXT:   %ref.tmp2 = alloca %struct.s1.0, align 1
+// CHECK-NEXT:   %ref.tmp3 = alloca %struct.s1, align 1
+// CHECK-NEXT:   call void @_Z2t1IiEvv()
+// CHECK-NEXT:   call void @_Z2t1IlEvv()
+// CHECK-NEXT:   call void @_ZN2s1IlE2tfIlEEvv(ptr {{.*}} %ref.tmp)
+// CHECK-NEXT:   call void @_ZN2s1IiE2tfIiEEvv(ptr {{.*}} %ref.tmp1)
+// CHECK-NEXT:   call void @_ZN2s1IiE2tfIlEEvv(ptr {{.*}} %ref.tmp2)
+// CHECK-NEXT:   call void @_ZN2s1IlE2tfIiEEvv(ptr {{.*}} %ref.tmp3)
+// CHECK-NEXT:   call void @_Z2t2I1SEvv()
+// CHECK-NEXT:   call void @_Z2t2IA1231_1SEvv()
+// CHECK-NEXT:   call void @_Z2t2IPPP1SEvv()
+// CHECK-NEXT:   ret void
+
+
+// CHECK-LABEL: define {{.*}} void @_Z2t1IiEvv()
+// CHECK: entry:
+// CHECK-NEXT:   %x = alloca i32, align 4
+// CHECK-NEXT:   %x1 = alloca i32, align 4
+// CHECK-NEXT:   %x2 = alloca i32, align 4
+// CHECK-NEXT:   %x4 = alloca i32, align 4
+// CHECK-NEXT:   %x6 = alloca i32, align 4
+// CHECK-NEXT:   %x8 = alloca i32, align 4
+// CHECK-NEXT:   %x10 = alloca i32, align 4
+// CHECK-NEXT:   %x12 = alloca i32, align 4
+// CHECK-NEXT:   %x14 = alloca i32, align 4
+// CHECK-NEXT:   %x16 = alloca i32, align 4
+// CHECK-NEXT:   store i32 1, ptr %x, align 4
+// CHECK-NEXT:   %0 = load i32, ptr %x, align 4
+// CHECK-NEXT:   call void @_Z1gi(i32 {{.*}} %0)
+// CHECK-NEXT:   br label %expand.next
+// CHECK: expand.next:
+// CHECK-NEXT:   store i32 2, ptr %x1, align 4
+// CHECK-NEXT:   %1 = load i32, ptr %x1, align 4
+// CHECK-NEXT:   call void @_Z1gi(i32 {{.*}} %1)
+// CHECK-NEXT:   br label %expand.end
+// CHECK: expand.end:
+// CHECK-NEXT:   store i32 3, ptr %x2, align 4
+// CHECK-NEXT:   %2 = load i32, ptr %x2, align 4
+// CHECK-NEXT:   call void @_Z1gi(i32 {{.*}} %2)
+// CHECK-NEXT:   br label %expand.next3
+// CHECK: expand.next3:
+// CHECK-NEXT:   store i32 4, ptr %x4, align 4
+// CHECK-NEXT:   %3 = load i32, ptr %x4, align 4
+// CHECK-NEXT:   call void @_Z1gi(i32 {{.*}} %3)
+// CHECK-NEXT:   br label %expand.end5
+// CHECK: expand.end5:
+// CHECK-NEXT:   store i32 5, ptr %x6, align 4
+// CHECK-NEXT:   %4 = load i32, ptr %x6, align 4
+// CHECK-NEXT:   call void @_Z1gi(i32 {{.*}} %4)
+// CHECK-NEXT:   br label %expand.next7
+// CHECK: expand.next7:
+// CHECK-NEXT:   store i32 6, ptr %x8, align 4
+// CHECK-NEXT:   %5 = load i32, ptr %x8, align 4
+// CHECK-NEXT:   call void @_Z1gi(i32 {{.*}} %5)
+// CHECK-NEXT:   br label %expand.end9
+// CHECK: expand.end9:
+// CHECK-NEXT:   store i32 7, ptr %x10, align 4
+// CHECK-NEXT:   br label %expand.next11
+// CHECK: expand.next11:
+// CHECK-NEXT:   store i32 8, ptr %x12, align 4
+// CHECK-NEXT:   br label %expand.end13
+// CHECK: expand.end13:
+// CHECK-NEXT:   store i32 9, ptr %x14, align 4
+// CHECK-NEXT:   br label %expand.next15
+// CHECK: expand.next15:
+// CHECK-NEXT:   store i32 10, ptr %x16, align 4
+// CHECK-NEXT:   br label %expand.end17
+// CHECK: expand.end17:
+// CHECK-NEXT:   ret void
+
+
+// CHECK-LABEL: define {{.*}} void @_Z2t1IlEvv()
+// CHECK: entry:
+// CHECK-NEXT:   %x = alloca i64, align 8
+// CHECK-NEXT:   %x1 = alloca i64, align 8
+// CHECK-NEXT:   %x2 = alloca i64, align 8
+// CHECK-NEXT:   %x4 = alloca i64, align 8
+// CHECK-NEXT:   %x6 = alloca i64, align 8
+// CHECK-NEXT:   %x8 = alloca i64, align 8
+// CHECK-NEXT:   %x10 = alloca i64, align 8
+// CHECK-NEXT:   %x12 = alloca i64, align 8
+// CHECK-NEXT:   %x14 = alloca i64, align 8
+// CHECK-NEXT:   %x16 = alloca i64, align 8
+// CHECK-NEXT:   store i64 1, ptr %x, align 8
+// CHECK-NEXT:   %0 = load i64, ptr %x, align 8
+// CHECK-NEXT:   call void @_Z1gl(i64 {{.*}} %0)
+// CHECK-NEXT:   br label %expand.next
+// CHECK: expand.next:
+// CHECK-NEXT:   store i64 2, ptr %x1, align 8
+// CHECK-NEXT:   %1 = load i64, ptr %x1, align 8
+// CHECK-NEXT:   call void @_Z1gl(i64 {{.*}} %1)
+// CHECK-NEXT:   br label %expand.end
+// CHECK: expand.end:
+// CHECK-NEXT:   store i64 3, ptr %x2, align 8
+// CHECK-NEXT:   %2 = load i64, ptr %x2, align 8
+// CHECK-NEXT:   call void @_Z1gl(i64 {{.*}} %2)
+// CHECK-NEXT:   br label %expand.next3
+// CHECK: expand.next3:
+// CHECK-NEXT:   store i64 4, ptr %x4, align 8
+// CHECK-NEXT:   %3 = load i64, ptr %x4, align 8
+// CHECK-NEXT:   call void @_Z1gl(i64 {{.*}} %3)
+// CHECK-NEXT:   br label %expand.end5
+// CHECK: expand.end5:
+// CHECK-NEXT:   store i64 5, ptr %x6, align 8
+// CHECK-NEXT:   %4 = load i64, ptr %x6, align 8
+// CHECK-NEXT:   call void @_Z1gl(i64 {{.*}} %4)
+// CHECK-NEXT:   br label %expand.next7
+// CHECK: expand.next7:
+// CHECK-NEXT:   store i64 6, ptr %x8, align 8
+// CHECK-NEXT:   %5 = load i64, ptr %x8, align 8
+// CHECK-NEXT:   call void @_Z1gl(i64 {{.*}} %5)
+// CHECK-NEXT:   br label %expand.end9
+// CHECK: expand.end9:
+// CHECK-NEXT:   store i64 7, ptr %x10, align 8
+// CHECK-NEXT:   br label %expand.next11
+// CHECK: expand.next11:
+// CHECK-NEXT:   store i64 8, ptr %x12, align 8
+// CHECK-NEXT:   br label %expand.end13
+// CHECK: expand.end13:
+// CHECK-NEXT:   store i64 9, ptr %x14, align 8
+// CHECK-NEXT:   br label %expand.next15
+// CHECK: expand.next15:
+// CHECK-NEXT:   store i64 10, ptr %x16, align 8
+// CHECK-NEXT:   br label %expand.end17
+// CHECK: expand.end17:
+// CHECK-NEXT:   ret void
+
+
+// CHECK-LABEL: define {{.*}} void @_ZN2s1IlE2tfIlEEvv(ptr {{.*}} %this)
+// CHECK: entry:
+// CHECK-NEXT:   %this.addr = alloca ptr, align 8
+// CHECK-NEXT:   %x = alloca i64, align 8
+// CHECK-NEXT:   %x2 = alloca i64, align 8
+// CHECK-NEXT:   %x3 = alloca i64, align 8
+// CHECK-NEXT:   %x5 = alloca i64, align 8
+// CHECK-NEXT:   %x7 = alloca i64, align 8
+// CHECK-NEXT:   %x9 = alloca i64, align 8
+// CHECK-NEXT:   %x11 = alloca i64, align 8
+// CHECK-NEXT:   %x13 = alloca i64, align 8
+// CHECK-NEXT:   %x15 = alloca i64, align 8
+// CHECK-NEXT:   %x17 = alloca i64, align 8
+// CHECK-NEXT:   %x19 = alloca i64, align 8
+// CHECK-NEXT:   %x21 = alloca i64, align 8
+// CHECK-NEXT:   %x23 = alloca i64, align 8
+// CHECK-NEXT:   %x25 = alloca i64, align 8
+// CHECK-NEXT:   %x27 = alloca i64, align 8
+// CHECK-NEXT:   %x29 = alloca i64, align 8
+// CHECK-NEXT:   %x31 = alloca i64, align 8
+// CHECK-NEXT:   %x33 = alloca i64, align 8
+// CHECK-NEXT:   store ptr %this, ptr %this.addr, align 8
+// CHECK-NEXT:   %this1 = load ptr, ptr %this.addr, align 8
+// CHECK-NEXT:   store i64 1, ptr %x, align 8
+// CHECK-NEXT:   %0 = load i64, ptr %x, align 8
+// CHECK-NEXT:   call void @_Z1gl(i64 {{.*}} %0)
+// CHECK-NEXT:   br label %expand.next
+// CHECK: expand.next:
+// CHECK-NEXT:   store i64 2, ptr %x2, align 8
+// CHECK-NEXT:   %1 = load i64, ptr %x2, align 8
+// CHECK-NEXT:   call void @_Z1gl(i64 {{.*}} %1)
+// CHECK-NEXT:   br label %expand.end
+// CHECK: expand.end:
+// CHECK-NEXT:   store i64 3, ptr %x3, align 8
+// CHECK-NEXT:   %2 = load i64, ptr %x3, align 8
+// CHECK-NEXT:   call void @_Z1gl(i64 {{.*}} %2)
+// CHECK-NEXT:   br label %expand.next4
+// CHECK: expand.next4:
+// CHECK-NEXT:   store i64 4, ptr %x5, align 8
+// CHECK-NEXT:   %3 = load i64, ptr %x5, align 8
+// CHECK-NEXT:   call void @_Z1gl(i64 {{.*}} %3)
+// CHECK-NEXT:   br label %expand.end6
+// CHECK: expand.end6:
+// CHECK-NEXT:   store i64 5, ptr %x7, align 8
+// CHECK-NEXT:   %4 = load i64, ptr %x7, align 8
+// CHECK-NEXT:   call void @_Z1gl(i64 {{.*}} %4)
+// CHECK-NEXT:   br label %expand.next8
+// CHECK: expand.next8:
+// CHECK-NEXT:   store i64 6, ptr %x9, align 8
+// CHECK-NEXT:   %5 = load i64, ptr %x9, align 8
+// CHECK-NEXT:   call void @_Z1gl(i64 {{.*}} %5)
+// CHECK-NEXT:   br label %expand.end10
+// CHECK: expand.end10:
+// CHECK-NEXT:   store i64 7, ptr %x11, align 8
+// CHECK-NEXT:   %6 = load i64, ptr %x11, align 8
+// CHECK-NEXT:   call void @_Z1gl(i64 {{.*}} %6)
+// CHECK-NEXT:   br label %expand.next12
+// CHECK: expand.next12:
+// CHECK-NEXT:   store i64 8, ptr %x13, align 8
+// CHECK-NEXT:   %7 = load i64, ptr %x13, align 8
+// CHECK-NEXT:   call void @_Z1gl(i64 {{.*}} %7)
+// CHECK-NEXT:   br label %expand.end14
+// CHECK: expand.end14:
+// CHECK-NEXT:   store i64 9, ptr %x15, align 8
+// CHECK-NEXT:   %8 = load i64, ptr %x15, align 8
+// CHECK-NEXT:   call void @_Z1gl(i64 {{.*}} %8)
+// CHECK-NEXT:   br label %expand.next16
+// CHECK: expand.next16:
+// CHECK-NEXT:   store i64 10, ptr %x17, align 8
+// CHECK-NEXT:   %9 = load i64, ptr %x17, align 8
+// CHECK-NEXT:   call void @_Z1gl(i64 {{.*}} %9)
+// CHECK-NEXT:   br label %expand.end18
+// CHECK: expand.end18:
+// CHECK-NEXT:   store i64 11, ptr %x19, align 8
+// CHECK-NEXT:   %10 = load i64, ptr %x19, align 8
+// CHECK-NEXT:   call void @_Z1gl(i64 {{.*}} %10)
+// CHECK-NEXT:   br label %expand.next20
+// CHECK: expand.next20:
+// CHECK-NEXT:   store i64 12, ptr %x21, align 8
+// CHECK-NEXT:   %11 = load i64, ptr %x21, align 8
+// CHECK-NEXT:   call void @_Z1gl(i64 {{.*}} %11)
+// CHECK-NEXT:   br label %expand.end22
+// CHECK: expand.end22:
+// CHECK-NEXT:   store i64 13, ptr %x23, align 8
+// CHECK-NEXT:   br label %expand.next24
+// CHECK: expand.next24:
+// CHECK-NEXT:   store i64 14, ptr %x25, align 8
+// CHECK-NEXT:   br label %expand.end26
+// CHECK: expand.end26:
+// CHECK-NEXT:   store i64 15, ptr %x27, align 8
+// CHECK-NEXT:   br label %expand.next28
+// CHECK: expand.next28:
+// CHECK-NEXT:   store i64 16, ptr %x29, align 8
+// CHECK-NEXT:   br label %expand.end30
+// CHECK: expand.end30:
+// CHECK-NEXT:   store i64 17, ptr %x31, align 8
+// CHECK-NEXT:   br label %expand.next32
+// CHECK: expand.next32:
+// CHECK-NEXT:   store i64 18, ptr %x33, align 8
+// CHECK-NEXT:   br label %expand.end34
+// CHECK: expand.end34:
+// CHECK-NEXT:   ret void
+
+
+// CHECK-LABEL: define {{.*}} void @_ZN2s1IiE2tfIiEEvv(ptr {{.*}} %this)
+// CHECK: entry:
+// CHECK-NEXT:   %this.addr = alloca ptr, align 8
+// CHECK-NEXT:   %x = alloca i32, align 4
+// CHECK-NEXT:   %x2 = alloca i32, align 4
+// CHECK-NEXT:   %x3 = alloca i32, align 4
+// CHECK-NEXT:   %x5 = alloca i32, align 4
+// CHECK-NEXT:   %x7 = alloca i32, align 4
+// CHECK-NEXT:   %x9 = alloca i32, align 4
+// CHECK-NEXT:   %x11 = alloca i32, align 4
+// CHECK-NEXT:   %x13 = alloca i32, align 4
+// CHECK-NEXT:   %x15 = alloca i32, align 4
+// CHECK-NEXT:   %x17 = alloca i32, align 4
+// CHECK-NEXT:   %x19 = alloca i32, align 4
+// CHECK-NEXT:   %x21 = alloca i32, align 4
+// CHECK-NEXT:   %x23 = alloca i32, align 4
+// CHECK-NEXT:   %x25 = alloca i32, align 4
+// CHECK-NEXT:   %x27 = alloca i32, align 4
+// CHECK-NEXT:   %x29 = alloca i32, align 4
+// CHECK-NEXT:   %x31 = alloca i32, align 4
+// CHECK-NEXT:   %x33 = alloca i32, align 4
+// CHECK-NEXT:   store ptr %this, ptr %this.addr, align 8
+// CHECK-NEXT:   %this1 = load ptr, ptr %this.addr, align 8
+// CHECK-NEXT:   store i32 1, ptr %x, align 4
+// CHECK-NEXT:   %0 = load i32, ptr %x, align 4
+// CHECK-NEXT:   call void @_Z1gi(i32 {{.*}} %0)
+// CHECK-NEXT:   br label %expand.next
+// CHECK: expand.next:
+// CHECK-NEXT:   store i32 2, ptr %x2, align 4
+// CHECK-NEXT:   %1 = load i32, ptr %x2, align 4
+// CHECK-NEXT:   call void @_Z1gi(i32 {{.*}} %1)
+// CHECK-NEXT:   br label %expand.end
+// CHECK: expand.end:
+// CHECK-NEXT:   store i32 3, ptr %x3, align 4
+// CHECK-NEXT:   %2 = load i32, ptr %x3, align 4
+// CHECK-NEXT:   call void @_Z1gi(i32 {{.*}} %2)
+// CHECK-NEXT:   br label %expand.next4
+// CHECK: expand.next4:
+// CHECK-NEXT:   store i32 4, ptr %x5, align 4
+// CHECK-NEXT:   %3 = load i32, ptr %x5, align 4
+// CHECK-NEXT:   call void @_Z1gi(i32 {{.*}} %3)
+// CHECK-NEXT:   br label %expand.end6
+// CHECK: expand.end6:
+// CHECK-NEXT:   store i32 5, ptr %x7, align 4
+// CHECK-NEXT:   %4 = load i32, ptr %x7, align 4
+// CHECK-NEXT:   call void @_Z1gi(i32 {{.*}} %4)
+// CHECK-NEXT:   br label %expand.next8
+// CHECK: expand.next8:
+// CHECK-NEXT:   store i32 6, ptr %x9, align 4
+// CHECK-NEXT:   %5 = load i32, ptr %x9, align 4
+// CHECK-NEXT:   call void @_Z1gi(i32 {{.*}} %5)
+// CHECK-NEXT:   br label %expand.end10
+// CHECK: expand.end10:
+// CHECK-NEXT:   store i32 7, ptr %x11, align 4
+// CHECK-NEXT:   %6 = load i32, ptr %x11, align 4
+// CHECK-NEXT:   call void @_Z1gi(i32 {{.*}} %6)
+// CHECK-NEXT:   br label %expand.next12
+// CHECK: expand.next12:
+// CHECK-NEXT:   store i32 8, ptr %x13, align 4
+// CHECK-NEXT:   %7 = load i32, ptr %x13, align 4
+// CHECK-NEXT:   call void @_Z1gi(i32 {{.*}} %7)
+// CHECK-NEXT:   br label %expand.end14
+// CHECK: expand.end14:
+// CHECK-NEXT:   store i32 9, ptr %x15, align 4
+// CHECK-NEXT:   %8 = load i32, ptr %x15, align 4
+// CHECK-NEXT:   call void @_Z1gi(i32 {{.*}} %8)
+// CHECK-NEXT:   br label %expand.next16
+// CHECK: expand.next16:
+// CHECK-NEXT:   store i32 10, ptr %x17, align 4
+// CHECK-NEXT:   %9 = load i32, ptr %x17, align 4
+// CHECK-NEXT:   call void @_Z1gi(i32 {{.*}} %9)
+// CHECK-NEXT:   br label %expand.end18
+// CHECK: expand.end18:
+// CHECK-NEXT:   store i32 11, ptr %x19, align 4
+// CHECK-NEXT:   %10 = load i32, ptr %x19, align 4
+// CHECK-NEXT:   call void @_Z1gi(i32 {{.*}} %10)
+// CHECK-NEXT:   br label %expand.next20
+// CHECK: expand.next20:
+// CHECK-NEXT:   store i32 12, ptr %x21, align 4
+// CHECK-NEXT:   %11 = load i32, ptr %x21, align 4
+// CHECK-NEXT:   call void @_Z1gi(i32 {{.*}} %11)
+// CHECK-NEXT:   br label %expand.end22
+// CHECK: expand.end22:
+// CHECK-NEXT:   store i32 13, ptr %x23, align 4
+// CHECK-NEXT:   br label %expand.next24
+// CHECK: expand.next24:
+// CHECK-NEXT:   store i32 14, ptr %x25, align 4
+// CHECK-NEXT:   br label %expand.end26
+// CHECK: expand.end26:
+// CHECK-NEXT:   store i32 15, ptr %x27, align 4
+// CHECK-NEXT:   br label %expand.next28
+// CHECK: expand.next28:
+// CHECK-NEXT:   store i32 16, ptr %x29, align 4
+// CHECK-NEXT:   br label %expand.end30
+// CHECK: expand.end30:
+// CHECK-NEXT:   store i32 17, ptr %x31, align 4
+// CHECK-NEXT:   br label %expand.next32
+// CHECK: expand.next32:
+// CHECK-NEXT:   store i32 18, ptr %x33, align 4
+// CHECK-NEXT:   br label %expand.end34
+// CHECK: expand.end34:
+// CHECK-NEXT:   ret void
+
+
+// CHECK-LABEL: define {{.*}} void @_ZN2s1IiE2tfIlEEvv(ptr {{.*}} %this)
+// CHECK: entry:
+// CHECK-NEXT:   %this.addr = alloca ptr, align 8
+// CHECK-NEXT:   %x = alloca i64, align 8
+// CHECK-NEXT:   %x2 = alloca i64, align 8
+// CHECK-NEXT:   %x3 = alloca i32, align 4
+// CHECK-NEXT:   %x5 = alloca i32, align 4
+// CHECK-NEXT:   %x7 = alloca i32, align 4
+// CHECK-NEXT:   %x9 = alloca i32, align 4
+// CHECK-NEXT:   %x11 = alloca i64, align 8
+// CHECK-NEXT:   %x13 = alloca i64, align 8
+// CHECK-NEXT:   %x15 = alloca i64, align 8
+// CHECK-NEXT:   %x17 = alloca i64, align 8
+// CHECK-NEXT:   %x19 = alloca i32, align 4
+// CHECK-NEXT:   %x21 = alloca i64, align 8
+// CHECK-NEXT:   %x23 = alloca i32, align 4
+// CHECK-NEXT:   %x25 = alloca i32, align 4
+// CHECK-NEXT:   %x27 = alloca i64, align 8
+// CHECK-NEXT:   %x29 = alloca i64, align 8
+// CHECK-NEXT:   %x31 = alloca i64, align 8
+// CHECK-NEXT:   %x33 = alloca i32, align 4
+// CHECK-NEXT:   store ptr %this, ptr %this.addr, align 8
+// CHECK-NEXT:   %this1 = load ptr, ptr %this.addr, align 8
+// CHECK-NEXT:   store i64 1, ptr %x, align 8
+// CHECK-NEXT:   %0 = load i64, ptr %x, align 8
+// CHECK-NEXT:   call void @_Z1gl(i64 {{.*}} %0)
+// CHECK-NEXT:   br label %expand.next
+// CHECK: expand.next:
+// CHECK-NEXT:   store i64 2, ptr %x2, align 8
+// CHECK-NEXT:   %1 = load i64, ptr %x2, align 8
+// CHECK-NEXT:   call void @_Z1gl(i64 {{.*}} %1)
+// CHECK-NEXT:   br label %expand.end
+// CHECK: expand.end:
+// CHECK-NEXT:   store i32 3, ptr %x3, align 4
+// CHECK-NEXT:   %2 = load i32, ptr %x3, align 4
+// CHECK-NEXT:   call void @_Z1gi(i32 {{.*}} %2)
+// CHECK-NEXT:   br label %expand.next4
+// CHECK: expand.next4:
+// CHECK-NEXT:   store i32 4, ptr %x5, align 4
+// CHECK-NEXT:   %3 = load i32, ptr %x5, align 4
+// CHECK-NEXT:   call void @_Z1gi(i32 {{.*}} %3)
+// CHECK-NEXT:   br label %expand.end6
+// CHECK: expand.end6:
+// CHECK-NEXT:   store i32 5, ptr %x7, align 4
+// CHECK-NEXT:   %4 = load i32, ptr %x7, align 4
+// CHECK-NEXT:   call void @_Z1gi(i32 {{.*}} %4)
+// CHECK-NEXT:   br label %expand.next8
+// CHECK: expand.next8:
+// CHECK-NEXT:   store i32 6, ptr %x9, align 4
+// CHECK-NEXT:   %5 = load i32, ptr %x9, align 4
+// CHECK-NEXT:   call void @_Z1gi(i32 {{.*}} %5)
+// CHECK-NEXT:   br label %expand.end10
+// CHECK: expand.end10:
+// CHECK-NEXT:   store i64 7, ptr %x11, align 8
+// CHECK-NEXT:   %6 = load i64, ptr %x11, align 8
+// CHECK-NEXT:   call void @_Z1gl(i64 {{.*}} %6)
+// CHECK-NEXT:   br label %expand.next12
+// CHECK: expand.next12:
+// CHECK-NEXT:   store i64 8, ptr %x13, align 8
+// CHECK-NEXT:   %7 = load i64, ptr %x13, align 8
+// CHECK-NEXT:   call void @_Z1gl(i64 {{.*}} %7)
+// CHECK-NEXT:   br label %expand.end14
+// CHECK: expand.end14:
+// CHECK-NEXT:   store i64 9, ptr %x15, align 8
+// CHECK-NEXT:   %8 = load i64, ptr %x15, align 8
+// CHECK-NEXT:   call void @_Z1gl(i64 {{.*}} %8)
+// CHECK-NEXT:   br label %expand.next16
+// CHECK: expand.next16:
+// CHECK-NEXT:   store i64 10, ptr %x17, align 8
+// CHECK-NEXT:   %9 = load i64, ptr %x17, align 8
+// CHECK-NEXT:   call void @_Z1gl(i64 {{.*}} %9)
+// CHECK-NEXT:   br label %expand.end18
+// CHECK: expand.end18:
+// CHECK-NEXT:   store i32 11, ptr %x19, align 4
+// CHECK-NEXT:   %10 = load i32, ptr %x19, align 4
+// CHECK-NEXT:   call void @_Z1gi(i32 {{.*}} %10)
+// CHECK-NEXT:   br label %expand.next20
+// CHECK: expand.next20:
+// CHECK-NEXT:   store i64 12, ptr %x21, align 8
+// CHECK-NEXT:   %11 = load i64, ptr %x21, align 8
+// CHECK-NEXT:   call void @_Z1gl(i64 {{.*}} %11)
+// CHECK-NEXT:   br label %expand.end22
+// CHECK: expand.end22:
+// CHECK-NEXT:   store i32 13, ptr %x23, align 4
+// CHECK-NEXT:   br label %expand.next24
+// CHECK: expand.next24:
+// CHECK-NEXT:   store i32 14, ptr %x25, align 4
+// CHECK-NEXT:   br label %expand.end26
+// CHECK: expand.end26:
+// CHECK-NEXT:   store i64 15, ptr %x27, align 8
+// CHECK-NEXT:   br label %expand.next28
+// CHECK: expand.next28:
+// CHECK-NEXT:   store i64 16, ptr %x29, align 8
+// CHECK-NEXT:   br label %expand.end30
+// CHECK: expand.end30:
+// CHECK-NEXT:   store i64 17, ptr %x31, align 8
+// CHECK-NEXT:   br label %expand.next32
+// CHECK: expand.next32:
+// CHECK-NEXT:   store i32 18, ptr %x33, align 4
+// CHECK-NEXT:   br label %expand.end34
+// CHECK: expand.end34:
+// CHECK-NEXT:   ret void
+
+
+// CHECK-LABEL: define {{.*}} void @_ZN2s1IlE2tfIiEEvv(ptr {{.*}} %this)
+// CHECK: entry:
+// CHECK-NEXT:   %this.addr = alloca ptr, align 8
+// CHECK-NEXT:   %x = alloca i32, align 4
+// CHECK-NEXT:   %x2 = alloca i32, align 4
+// CHECK-NEXT:   %x3 = alloca i64, align 8
+// CHECK-NEXT:   %x5 = alloca i64, align 8
+// CHECK-NEXT:   %x7 = alloca i64, align 8
+// CHECK-NEXT:   %x9 = alloca i64, align 8
+// CHECK-NEXT:   %x11 = alloca i32, align 4
+// CHECK-NEXT:   %x13 = alloca i32, align 4
+// CHECK-NEXT:   %x15 = alloca i32, align 4
+// CHECK-NEXT:   %x17 = alloca i32, align 4
+// CHECK-NEXT:   %x19 = alloca i64, align 8
+// CHECK-NEXT:   %x21 = alloca i32, align 4
+// CHECK-NEXT:   %x23 = alloca i64, align 8
+// CHECK-NEXT:   %x25 = alloca i64, align 8
+// CHECK-NEXT:   %x27 = alloca i32, align 4
+// CHECK-NEXT:   %x29 = alloca i32, align 4
+// CHECK-NEXT:   %x31 = alloca i32, align 4
+// CHECK-NEXT:   %x33 = alloca i64, align 8
+// CHECK-NEXT:   store ptr %this, ptr %this.addr, align 8
+// CHECK-NEXT:   %this1 = load ptr, ptr %this.addr, align 8
+// CHECK-NEXT:   store i32 1, ptr %x, align 4
+// CHECK-NEXT:   %0 = load i32, ptr %x, align 4
+// CHECK-NEXT:   call void @_Z1gi(i32 {{.*}} %0)
+// CHECK-NEXT:   br label %expand.next
+// CHECK: expand.next:
+// CHECK-NEXT:   store i32 2, ptr %x2, align 4
+// CHECK-NEXT:   %1 = load i32, ptr %x2, align 4
+// CHECK-NEXT:   call void @_Z1gi(i32 {{.*}} %1)
+// CHECK-NEXT:   br label %expand.end
+// CHECK: expand.end:
+// CHECK-NEXT:   store i64 3, ptr %x3, align 8
+// CHECK-NEXT:   %2 = load i64, ptr %x3, align 8
+// CHECK-NEXT:   call void @_Z1gl(i64 {{.*}} %2)
+// CHECK-NEXT:   br label %expand.next4
+// CHECK: expand.next4:
+// CHECK-NEXT:   store i64 4, ptr %x5, align 8
+// CHECK-NEXT:   %3 = load i64, ptr %x5, align 8
+// CHECK-NEXT:   call void @_Z1gl(i64 {{.*}} %3)
+// CHECK-NEXT:   br label %expand.end6
+// CHECK: expand.end6:
+// CHECK-NEXT:   store i64 5, ptr %x7, align 8
+// CHECK-NEXT:   %4 = load i64, ptr %x7, align 8
+// CHECK-NEXT:   call void @_Z1gl(i64 {{.*}} %4)
+// CHECK-NEXT:   br label %expand.next8
+// CHECK: expand.next8:
+// CHECK-NEXT:   store i64 6, ptr %x9, align 8
+// CHECK-NEXT:   %5 = load i64, ptr %x9, align 8
+// CHECK-NEXT:   call void @_Z1gl(i64 {{.*}} %5)
+// CHECK-NEXT:   br label %expand.end10
+// CHECK: expand.end10:
+// CHECK-NEXT:   store i32 7, ptr %x11, align 4
+// CHECK-NEXT:   %6 = load i32, ptr %x11, align 4
+// CHECK-NEXT:   call void @_Z1gi(i32 {{.*}} %6)
+// CHECK-NEXT:   br label %expand.next12
+// CHECK: expand.next12:
+// CHECK-NEXT:   store i32 8, ptr %x13, align 4
+// CHECK-NEXT:   %7 = load i32, ptr %x13, align 4
+// CHECK-NEXT:   call void @_Z1gi(i32 {{.*}} %7)
+// CHECK-NEXT:   br label %expand.end14
+// CHECK: expand.end14:
+// CHECK-NEXT:   store i32 9, ptr %x15, align 4
+// CHECK-NEXT:   %8 = load i32, ptr %x15, align 4
+// CHECK-NEXT:   call void @_Z1gi(i32 {{.*}} %8)
+// CHECK-NEXT:   br label %expand.next16
+// CHECK: expand.next16:
+// CHECK-NEXT:   store i32 10, ptr %x17, align 4
+// CHECK-NEXT:   %9 = load i32, ptr %x17, align 4
+// CHECK-NEXT:   call void @_Z1gi(i32 {{.*}} %9)
+// CHECK-NEXT:   br label %expand.end18
+// CHECK: expand.end18:
+// CHECK-NEXT:   store i64 11, ptr %x19, align 8
+// CHECK-NEXT:   %10 = load i64, ptr %x19, align 8
+// CHECK-NEXT:   call void @_Z1gl(i64 {{.*}} %10)
+// CHECK-NEXT:   br label %expand.next20
+// CHECK: expand.next20:
+// CHECK-NEXT:   store i32 12, ptr %x21, align 4
+// CHECK-NEXT:   %11 = load i32, ptr %x21, align 4
+// CHECK-NEXT:   call void @_Z1gi(i32 {{.*}} %11)
+// CHECK-NEXT:   br label %expand.end22
+// CHECK: expand.end22:
+// CHECK-NEXT:   store i64 13, ptr %x23, align 8
+// CHECK-NEXT:   br label %expand.next24
+// CHECK: expand.next24:
+// CHECK-NEXT:   store i64 14, ptr %x25, align 8
+// CHECK-NEXT:   br label %expand.end26
+// CHECK: expand.end26:
+// CHECK-NEXT:   store i32 15, ptr %x27, align 4
+// CHECK-NEXT:   br label %expand.next28
+// CHECK: expand.next28:
+// CHECK-NEXT:   store i32 16, ptr %x29, align 4
+// CHECK-NEXT:   br label %expand.end30
+// CHECK: expand.end30:
+// CHECK-NEXT:   store i32 17, ptr %x31, align 4
+// CHECK-NEXT:   br label %expand.next32
+// CHECK: expand.next32:
+// CHECK-NEXT:   store i64 18, ptr %x33, align 8
+// CHECK-NEXT:   br label %expand.end34
+// CHECK: expand.end34:
+// CHECK-NEXT:   ret void

>From cb6621141fe53418ebb0233b6c5a505b614ce2a0 Mon Sep 17 00:00:00 2001
From: Sirraide <aeternalmail at gmail.com>
Date: Fri, 24 Oct 2025 23:21:01 +0200
Subject: [PATCH 03/33] Support pack expansion in enumerating expansion
 statements

---
 clang/include/clang/AST/ExprCXX.h             |   2 +
 clang/include/clang/AST/TextNodeDumper.h      |   1 +
 clang/lib/AST/ExprCXX.cpp                     |   6 +
 clang/lib/AST/StmtCXX.cpp                     |  10 +-
 clang/lib/AST/TextNodeDumper.cpp              |   5 +
 clang/lib/Sema/SemaExpand.cpp                 |   2 +-
 clang/lib/Sema/SemaTemplateInstantiate.cpp    |  14 +-
 clang/lib/Sema/SemaTemplateVariadic.cpp       |   8 +-
 .../CodeGenCXX/cxx2c-expansion-statements.cpp | 585 ++++++++++++++++++
 9 files changed, 626 insertions(+), 7 deletions(-)

diff --git a/clang/include/clang/AST/ExprCXX.h b/clang/include/clang/AST/ExprCXX.h
index c192f5bc4b4f7..6532d0b8f7f36 100644
--- a/clang/include/clang/AST/ExprCXX.h
+++ b/clang/include/clang/AST/ExprCXX.h
@@ -5530,6 +5530,8 @@ class CXXExpansionInitListExpr final
   MutableArrayRef<Expr *> getExprs() { return getTrailingObjects(NumExprs); }
   unsigned getNumExprs() const { return NumExprs; }
 
+  bool containsPackExpansion() const;
+
   SourceLocation getBeginLoc() const { return getLBraceLoc(); }
   SourceLocation getEndLoc() const { return getRBraceLoc(); }
 
diff --git a/clang/include/clang/AST/TextNodeDumper.h b/clang/include/clang/AST/TextNodeDumper.h
index 88ecd526e3d7e..3da9c5076fb1f 100644
--- a/clang/include/clang/AST/TextNodeDumper.h
+++ b/clang/include/clang/AST/TextNodeDumper.h
@@ -310,6 +310,7 @@ class TextNodeDumper
   void VisitSizeOfPackExpr(const SizeOfPackExpr *Node);
   void
   VisitCXXDependentScopeMemberExpr(const CXXDependentScopeMemberExpr *Node);
+  void VisitCXXExpansionInitListExpr(const CXXExpansionInitListExpr *Node);
   void VisitObjCAtCatchStmt(const ObjCAtCatchStmt *Node);
   void VisitObjCEncodeExpr(const ObjCEncodeExpr *Node);
   void VisitObjCMessageExpr(const ObjCMessageExpr *Node);
diff --git a/clang/lib/AST/ExprCXX.cpp b/clang/lib/AST/ExprCXX.cpp
index 0d3e6b2aa10a0..4215c964032a3 100644
--- a/clang/lib/AST/ExprCXX.cpp
+++ b/clang/lib/AST/ExprCXX.cpp
@@ -2050,6 +2050,12 @@ CXXExpansionInitListExpr::CreateEmpty(const ASTContext &C, EmptyShell Empty,
   return new (Mem) CXXExpansionInitListExpr(Empty, NumExprs);
 }
 
+bool CXXExpansionInitListExpr::containsPackExpansion() const {
+  return llvm::any_of(getExprs(), [](const Expr* E) {
+    return isa<PackExpansionExpr>(E);
+  });
+}
+
 CXXExpansionInitListSelectExpr::CXXExpansionInitListSelectExpr(EmptyShell Empty)
     : Expr(CXXExpansionInitListSelectExprClass, Empty) {
 }
diff --git a/clang/lib/AST/StmtCXX.cpp b/clang/lib/AST/StmtCXX.cpp
index 312074e69a6e9..09797bba97a4d 100644
--- a/clang/lib/AST/StmtCXX.cpp
+++ b/clang/lib/AST/StmtCXX.cpp
@@ -166,18 +166,24 @@ VarDecl *CXXExpansionStmt::getExpansionVariable() {
 
 bool CXXExpansionStmt::hasDependentSize() const {
   if (isa<CXXEnumeratingExpansionStmt>(this))
-    return getExpansionVariable()->getInit()->containsUnexpandedParameterPack();
+    return cast<CXXExpansionInitListSelectExpr>(
+               getExpansionVariable()->getInit())
+        ->getRangeExpr()
+        ->containsPackExpansion();
 
   llvm_unreachable("Invalid expansion statement class");
 }
 
 size_t CXXExpansionStmt::getNumInstantiations() const {
-  if (isa<CXXEnumeratingExpansionStmt>(this))
+  assert(!hasDependentSize());
+
+  if (isa<CXXEnumeratingExpansionStmt>(this)) {
     return cast<CXXExpansionInitListSelectExpr>(
                getExpansionVariable()->getInit())
         ->getRangeExpr()
         ->getExprs()
         .size();
+  }
 
   llvm_unreachable("Invalid expansion statement class");
 }
diff --git a/clang/lib/AST/TextNodeDumper.cpp b/clang/lib/AST/TextNodeDumper.cpp
index 41aebdb8d2f1b..614c21ac7f5e0 100644
--- a/clang/lib/AST/TextNodeDumper.cpp
+++ b/clang/lib/AST/TextNodeDumper.cpp
@@ -1826,6 +1826,11 @@ void TextNodeDumper::VisitCXXDependentScopeMemberExpr(
   OS << " " << (Node->isArrow() ? "->" : ".") << Node->getMember();
 }
 
+void TextNodeDumper::VisitCXXExpansionInitListExpr(const CXXExpansionInitListExpr *Node) {
+  if (Node->containsPackExpansion())
+    OS << " contains_pack";
+}
+
 void TextNodeDumper::VisitObjCMessageExpr(const ObjCMessageExpr *Node) {
   OS << " selector=";
   Node->getSelector().print(OS);
diff --git a/clang/lib/Sema/SemaExpand.cpp b/clang/lib/Sema/SemaExpand.cpp
index 5442752aacf10..89e0c1fc7f82a 100644
--- a/clang/lib/Sema/SemaExpand.cpp
+++ b/clang/lib/Sema/SemaExpand.cpp
@@ -456,7 +456,7 @@ ExprResult Sema::BuildCXXExpansionInitializer(ExpansionStmtDecl *ESD,
 ExprResult
 Sema::BuildCXXExpansionInitListSelectExpr(CXXExpansionInitListExpr *Range,
                                           Expr *Idx) {
-  if (Range->containsUnexpandedParameterPack() || Idx->isValueDependent())
+  if (Range->containsPackExpansion() || Idx->isValueDependent())
     return new (Context) CXXExpansionInitListSelectExpr(Context, Range, Idx);
 
   // The index is a DRE to a template parameter; we should never
diff --git a/clang/lib/Sema/SemaTemplateInstantiate.cpp b/clang/lib/Sema/SemaTemplateInstantiate.cpp
index a7cb4a4e525e9..5105697f42a89 100644
--- a/clang/lib/Sema/SemaTemplateInstantiate.cpp
+++ b/clang/lib/Sema/SemaTemplateInstantiate.cpp
@@ -2013,6 +2013,12 @@ Decl *TemplateInstantiator::TransformDecl(SourceLocation Loc, Decl *D) {
       maybeInstantiateFunctionParameterToScope(PVD))
     return nullptr;
 
+  if (isa<ExpansionStmtDecl>(D)) {
+    assert(SemaRef.CurrentInstantiationScope);
+    return cast<Decl *>(
+        *SemaRef.CurrentInstantiationScope->findInstantiationOf(D));
+  }
+
   return SemaRef.FindInstantiatedDecl(Loc, cast<NamedDecl>(D), TemplateArgs);
 }
 
@@ -2408,8 +2414,12 @@ TemplateInstantiator::TransformFunctionParmPackRefExpr(DeclRefExpr *E,
                                                        ValueDecl *PD) {
   typedef LocalInstantiationScope::DeclArgumentPack DeclArgumentPack;
   llvm::PointerUnion<Decl *, DeclArgumentPack *> *Found
-    = getSema().CurrentInstantiationScope->findInstantiationOf(PD);
-  assert(Found && "no instantiation for parameter pack");
+    = getSema().CurrentInstantiationScope->getInstantiationOfIfExists(PD);
+
+  // This can happen when instantiating an expansion statement that contains
+  // a pack (e.g. `template for (auto x : {{ts...}})`).
+  if (!Found)
+    return E;
 
   Decl *TransformedDecl;
   if (DeclArgumentPack *Pack = dyn_cast<DeclArgumentPack *>(*Found)) {
diff --git a/clang/lib/Sema/SemaTemplateVariadic.cpp b/clang/lib/Sema/SemaTemplateVariadic.cpp
index 0f72d6a13ae06..3eecb67066a9d 100644
--- a/clang/lib/Sema/SemaTemplateVariadic.cpp
+++ b/clang/lib/Sema/SemaTemplateVariadic.cpp
@@ -909,10 +909,14 @@ bool Sema::CheckParameterPacksForExpansion(
     unsigned NewPackSize, PendingPackExpansionSize = 0;
     if (IsVarDeclPack) {
       // Figure out whether we're instantiating to an argument pack or not.
+      //
+      // The instantiation may not exist; this can happen when instantiating an
+      // expansion statement that contains a pack (e.g.
+      // `template for (auto x : {{ts...}})`).
       llvm::PointerUnion<Decl *, DeclArgumentPack *> *Instantiation =
-          CurrentInstantiationScope->findInstantiationOf(
+          CurrentInstantiationScope->getInstantiationOfIfExists(
               cast<NamedDecl *>(ParmPack.first));
-      if (isa<DeclArgumentPack *>(*Instantiation)) {
+      if (Instantiation && isa<DeclArgumentPack *>(*Instantiation)) {
         // We could expand this function parameter pack.
         NewPackSize = cast<DeclArgumentPack *>(*Instantiation)->size();
       } else {
diff --git a/clang/test/CodeGenCXX/cxx2c-expansion-statements.cpp b/clang/test/CodeGenCXX/cxx2c-expansion-statements.cpp
index 3de2ee177f12e..a30f453263bbf 100644
--- a/clang/test/CodeGenCXX/cxx2c-expansion-statements.cpp
+++ b/clang/test/CodeGenCXX/cxx2c-expansion-statements.cpp
@@ -100,6 +100,79 @@ void f6() {
   t2<S***>();
 }
 
+struct X {
+  int a, b, c;
+};
+
+template <typename ...Ts>
+void t3(Ts... ts) {
+  template for (auto x : {ts...}) g(x);
+  template for (auto x : {1, ts..., 2, ts..., 3}) g(x);
+  template for (auto x : {4, ts..., ts..., 5}) g(x);
+  template for (X x : {{ts...}, {ts...}, {6, 7, 8}}) g(x.a);
+  template for (X x : {X{ts...}}) g(x.a);
+}
+
+template <int ...is>
+void t4() {
+  template for (constexpr auto x : {is...}) {
+    g(x);
+    tg<x>();
+  }
+
+  template for (constexpr auto x : {1, is..., 2, is..., 3}) {
+    g(x);
+    tg<x>();
+  }
+
+  template for (constexpr auto x : {4, is..., is..., 5}) {
+    g(x);
+    tg<x>();
+  }
+
+  template for (constexpr X x : {{is...}, {is...}, {6, 7, 8}}) {
+    g(x.a);
+    tg<x.a>();
+  }
+
+  template for (constexpr X x : {X{is...}}) {
+    g(x.a);
+    tg<x.a>();
+  }
+}
+
+template <int ...is>
+struct s2 {
+  template <int ...js>
+  void tf() {
+    template for (auto x : {is..., js...}) g(x);
+    template for (X x : {{is...}, {js...}}) g(x.a);
+    template for (constexpr auto x : {is..., js...}) tg<x>();
+    template for (constexpr X x : {{is...}, {js...}}) tg<x.a>();
+  }
+};
+
+void f7() {
+  t3(42, 43, 44);
+  t4<42, 43, 44>();
+  s2<1, 2, 3>().tf<4, 5, 6>();
+}
+
+template <int ...is>
+void t5() {
+  ([] {
+    template for (constexpr auto x : {is}) {
+      g(x);
+      tg<x>();
+    }
+  }(), ...);
+}
+
+void f8() {
+  t5<1, 2, 3>();
+}
+
+
 // CHECK-LABEL: define {{.*}} void @_Z2f1v()
 // CHECK: entry:
 // CHECK-NEXT:   %x = alloca i32, align 4
@@ -861,3 +934,515 @@ void f6() {
 // CHECK-NEXT:   br label %expand.end34
 // CHECK: expand.end34:
 // CHECK-NEXT:   ret void
+
+
+// CHECK-LABEL: define {{.*}} void @_Z2f7v()
+// CHECK: entry:
+// CHECK-NEXT:   %ref.tmp = alloca %struct.s2, align 1
+// CHECK-NEXT:   call void @_Z2t3IJiiiEEvDpT_(i32 {{.*}} 42, i32 {{.*}} 43, i32 {{.*}} 44)
+// CHECK-NEXT:   call void @_Z2t4IJLi42ELi43ELi44EEEvv()
+// CHECK-NEXT:   call void @_ZN2s2IJLi1ELi2ELi3EEE2tfIJLi4ELi5ELi6EEEEvv(ptr {{.*}} %ref.tmp)
+// CHECK-NEXT:   ret void
+
+// CHECK-LABEL: define {{.*}} void @_Z2t3IJiiiEEvDpT_(i32 {{.*}} %ts, i32 {{.*}} %ts1, i32 {{.*}} %ts3)
+// CHECK: entry:
+// CHECK-NEXT:   %ts.addr = alloca i32, align 4
+// CHECK-NEXT:   %ts.addr2 = alloca i32, align 4
+// CHECK-NEXT:   %ts.addr4 = alloca i32, align 4
+// CHECK-NEXT:   %x = alloca i32, align 4
+// CHECK-NEXT:   %x5 = alloca i32, align 4
+// CHECK-NEXT:   %x7 = alloca i32, align 4
+// CHECK-NEXT:   %x8 = alloca i32, align 4
+// CHECK-NEXT:   %x10 = alloca i32, align 4
+// CHECK-NEXT:   %x12 = alloca i32, align 4
+// CHECK-NEXT:   %x14 = alloca i32, align 4
+// CHECK-NEXT:   %x16 = alloca i32, align 4
+// CHECK-NEXT:   %x18 = alloca i32, align 4
+// CHECK-NEXT:   %x20 = alloca i32, align 4
+// CHECK-NEXT:   %x22 = alloca i32, align 4
+// CHECK-NEXT:   %x24 = alloca i32, align 4
+// CHECK-NEXT:   %x26 = alloca i32, align 4
+// CHECK-NEXT:   %x28 = alloca i32, align 4
+// CHECK-NEXT:   %x30 = alloca i32, align 4
+// CHECK-NEXT:   %x32 = alloca i32, align 4
+// CHECK-NEXT:   %x34 = alloca i32, align 4
+// CHECK-NEXT:   %x36 = alloca i32, align 4
+// CHECK-NEXT:   %x38 = alloca i32, align 4
+// CHECK-NEXT:   %x40 = alloca i32, align 4
+// CHECK-NEXT:   %x42 = alloca %struct.X, align 4
+// CHECK-NEXT:   %x45 = alloca %struct.X, align 4
+// CHECK-NEXT:   %x51 = alloca %struct.X, align 4
+// CHECK-NEXT:   %x54 = alloca %struct.X, align 4
+// CHECK-NEXT:   store i32 %ts, ptr %ts.addr, align 4
+// CHECK-NEXT:   store i32 %ts1, ptr %ts.addr2, align 4
+// CHECK-NEXT:   store i32 %ts3, ptr %ts.addr4, align 4
+// CHECK-NEXT:   %0 = load i32, ptr %ts.addr, align 4
+// CHECK-NEXT:   store i32 %0, ptr %x, align 4
+// CHECK-NEXT:   %1 = load i32, ptr %x, align 4
+// CHECK-NEXT:   call void @_Z1gi(i32 {{.*}} %1)
+// CHECK-NEXT:   br label %expand.next
+// CHECK: expand.next:
+// CHECK-NEXT:   %2 = load i32, ptr %ts.addr2, align 4
+// CHECK-NEXT:   store i32 %2, ptr %x5, align 4
+// CHECK-NEXT:   %3 = load i32, ptr %x5, align 4
+// CHECK-NEXT:   call void @_Z1gi(i32 {{.*}} %3)
+// CHECK-NEXT:   br label %expand.next6
+// CHECK: expand.next6:
+// CHECK-NEXT:   %4 = load i32, ptr %ts.addr4, align 4
+// CHECK-NEXT:   store i32 %4, ptr %x7, align 4
+// CHECK-NEXT:   %5 = load i32, ptr %x7, align 4
+// CHECK-NEXT:   call void @_Z1gi(i32 {{.*}} %5)
+// CHECK-NEXT:   br label %expand.end
+// CHECK: expand.end:
+// CHECK-NEXT:   store i32 1, ptr %x8, align 4
+// CHECK-NEXT:   %6 = load i32, ptr %x8, align 4
+// CHECK-NEXT:   call void @_Z1gi(i32 {{.*}} %6)
+// CHECK-NEXT:   br label %expand.next9
+// CHECK: expand.next9:
+// CHECK-NEXT:   %7 = load i32, ptr %ts.addr, align 4
+// CHECK-NEXT:   store i32 %7, ptr %x10, align 4
+// CHECK-NEXT:   %8 = load i32, ptr %x10, align 4
+// CHECK-NEXT:   call void @_Z1gi(i32 {{.*}} %8)
+// CHECK-NEXT:   br label %expand.next11
+// CHECK: expand.next11:
+// CHECK-NEXT:   %9 = load i32, ptr %ts.addr2, align 4
+// CHECK-NEXT:   store i32 %9, ptr %x12, align 4
+// CHECK-NEXT:   %10 = load i32, ptr %x12, align 4
+// CHECK-NEXT:   call void @_Z1gi(i32 {{.*}} %10)
+// CHECK-NEXT:   br label %expand.next13
+// CHECK: expand.next13:
+// CHECK-NEXT:   %11 = load i32, ptr %ts.addr4, align 4
+// CHECK-NEXT:   store i32 %11, ptr %x14, align 4
+// CHECK-NEXT:   %12 = load i32, ptr %x14, align 4
+// CHECK-NEXT:   call void @_Z1gi(i32 {{.*}} %12)
+// CHECK-NEXT:   br label %expand.next15
+// CHECK: expand.next15:
+// CHECK-NEXT:   store i32 2, ptr %x16, align 4
+// CHECK-NEXT:   %13 = load i32, ptr %x16, align 4
+// CHECK-NEXT:   call void @_Z1gi(i32 {{.*}} %13)
+// CHECK-NEXT:   br label %expand.next17
+// CHECK: expand.next17:
+// CHECK-NEXT:   %14 = load i32, ptr %ts.addr, align 4
+// CHECK-NEXT:   store i32 %14, ptr %x18, align 4
+// CHECK-NEXT:   %15 = load i32, ptr %x18, align 4
+// CHECK-NEXT:   call void @_Z1gi(i32 {{.*}} %15)
+// CHECK-NEXT:   br label %expand.next19
+// CHECK: expand.next19:
+// CHECK-NEXT:   %16 = load i32, ptr %ts.addr2, align 4
+// CHECK-NEXT:   store i32 %16, ptr %x20, align 4
+// CHECK-NEXT:   %17 = load i32, ptr %x20, align 4
+// CHECK-NEXT:   call void @_Z1gi(i32 {{.*}} %17)
+// CHECK-NEXT:   br label %expand.next21
+// CHECK: expand.next21:
+// CHECK-NEXT:   %18 = load i32, ptr %ts.addr4, align 4
+// CHECK-NEXT:   store i32 %18, ptr %x22, align 4
+// CHECK-NEXT:   %19 = load i32, ptr %x22, align 4
+// CHECK-NEXT:   call void @_Z1gi(i32 {{.*}} %19)
+// CHECK-NEXT:   br label %expand.next23
+// CHECK: expand.next23:
+// CHECK-NEXT:   store i32 3, ptr %x24, align 4
+// CHECK-NEXT:   %20 = load i32, ptr %x24, align 4
+// CHECK-NEXT:   call void @_Z1gi(i32 {{.*}} %20)
+// CHECK-NEXT:   br label %expand.end25
+// CHECK: expand.end25:
+// CHECK-NEXT:   store i32 4, ptr %x26, align 4
+// CHECK-NEXT:   %21 = load i32, ptr %x26, align 4
+// CHECK-NEXT:   call void @_Z1gi(i32 {{.*}} %21)
+// CHECK-NEXT:   br label %expand.next27
+// CHECK: expand.next27:
+// CHECK-NEXT:   %22 = load i32, ptr %ts.addr, align 4
+// CHECK-NEXT:   store i32 %22, ptr %x28, align 4
+// CHECK-NEXT:   %23 = load i32, ptr %x28, align 4
+// CHECK-NEXT:   call void @_Z1gi(i32 {{.*}} %23)
+// CHECK-NEXT:   br label %expand.next29
+// CHECK: expand.next29:
+// CHECK-NEXT:   %24 = load i32, ptr %ts.addr2, align 4
+// CHECK-NEXT:   store i32 %24, ptr %x30, align 4
+// CHECK-NEXT:   %25 = load i32, ptr %x30, align 4
+// CHECK-NEXT:   call void @_Z1gi(i32 {{.*}} %25)
+// CHECK-NEXT:   br label %expand.next31
+// CHECK: expand.next31:
+// CHECK-NEXT:   %26 = load i32, ptr %ts.addr4, align 4
+// CHECK-NEXT:   store i32 %26, ptr %x32, align 4
+// CHECK-NEXT:   %27 = load i32, ptr %x32, align 4
+// CHECK-NEXT:   call void @_Z1gi(i32 {{.*}} %27)
+// CHECK-NEXT:   br label %expand.next33
+// CHECK: expand.next33:
+// CHECK-NEXT:   %28 = load i32, ptr %ts.addr, align 4
+// CHECK-NEXT:   store i32 %28, ptr %x34, align 4
+// CHECK-NEXT:   %29 = load i32, ptr %x34, align 4
+// CHECK-NEXT:   call void @_Z1gi(i32 {{.*}} %29)
+// CHECK-NEXT:   br label %expand.next35
+// CHECK: expand.next35:
+// CHECK-NEXT:   %30 = load i32, ptr %ts.addr2, align 4
+// CHECK-NEXT:   store i32 %30, ptr %x36, align 4
+// CHECK-NEXT:   %31 = load i32, ptr %x36, align 4
+// CHECK-NEXT:   call void @_Z1gi(i32 {{.*}} %31)
+// CHECK-NEXT:   br label %expand.next37
+// CHECK: expand.next37:
+// CHECK-NEXT:   %32 = load i32, ptr %ts.addr4, align 4
+// CHECK-NEXT:   store i32 %32, ptr %x38, align 4
+// CHECK-NEXT:   %33 = load i32, ptr %x38, align 4
+// CHECK-NEXT:   call void @_Z1gi(i32 {{.*}} %33)
+// CHECK-NEXT:   br label %expand.next39
+// CHECK: expand.next39:
+// CHECK-NEXT:   store i32 5, ptr %x40, align 4
+// CHECK-NEXT:   %34 = load i32, ptr %x40, align 4
+// CHECK-NEXT:   call void @_Z1gi(i32 {{.*}} %34)
+// CHECK-NEXT:   br label %expand.end41
+// CHECK: expand.end41:
+// CHECK-NEXT:   %a = getelementptr inbounds nuw %struct.X, ptr %x42, i32 0, i32 0
+// CHECK-NEXT:   %35 = load i32, ptr %ts.addr, align 4
+// CHECK-NEXT:   store i32 %35, ptr %a, align 4
+// CHECK-NEXT:   %b = getelementptr inbounds nuw %struct.X, ptr %x42, i32 0, i32 1
+// CHECK-NEXT:   %36 = load i32, ptr %ts.addr2, align 4
+// CHECK-NEXT:   store i32 %36, ptr %b, align 4
+// CHECK-NEXT:   %c = getelementptr inbounds nuw %struct.X, ptr %x42, i32 0, i32 2
+// CHECK-NEXT:   %37 = load i32, ptr %ts.addr4, align 4
+// CHECK-NEXT:   store i32 %37, ptr %c, align 4
+// CHECK-NEXT:   %a43 = getelementptr inbounds nuw %struct.X, ptr %x42, i32 0, i32 0
+// CHECK-NEXT:   %38 = load i32, ptr %a43, align 4
+// CHECK-NEXT:   call void @_Z1gi(i32 {{.*}} %38)
+// CHECK-NEXT:   br label %expand.next44
+// CHECK: expand.next44:
+// CHECK-NEXT:   %a46 = getelementptr inbounds nuw %struct.X, ptr %x45, i32 0, i32 0
+// CHECK-NEXT:   %39 = load i32, ptr %ts.addr, align 4
+// CHECK-NEXT:   store i32 %39, ptr %a46, align 4
+// CHECK-NEXT:   %b47 = getelementptr inbounds nuw %struct.X, ptr %x45, i32 0, i32 1
+// CHECK-NEXT:   %40 = load i32, ptr %ts.addr2, align 4
+// CHECK-NEXT:   store i32 %40, ptr %b47, align 4
+// CHECK-NEXT:   %c48 = getelementptr inbounds nuw %struct.X, ptr %x45, i32 0, i32 2
+// CHECK-NEXT:   %41 = load i32, ptr %ts.addr4, align 4
+// CHECK-NEXT:   store i32 %41, ptr %c48, align 4
+// CHECK-NEXT:   %a49 = getelementptr inbounds nuw %struct.X, ptr %x45, i32 0, i32 0
+// CHECK-NEXT:   %42 = load i32, ptr %a49, align 4
+// CHECK-NEXT:   call void @_Z1gi(i32 {{.*}} %42)
+// CHECK-NEXT:   br label %expand.next50
+// CHECK: expand.next50:
+// CHECK-NEXT:   call void @llvm.memcpy.p0.p0.i64(ptr align 4 %x51, ptr align 4 @__const._Z2t3IJiiiEEvDpT_.x, i64 12, i1 false)
+// CHECK-NEXT:   %a52 = getelementptr inbounds nuw %struct.X, ptr %x51, i32 0, i32 0
+// CHECK-NEXT:   %43 = load i32, ptr %a52, align 4
+// CHECK-NEXT:   call void @_Z1gi(i32 {{.*}} %43)
+// CHECK-NEXT:   br label %expand.end53
+// CHECK: expand.end53:
+// CHECK-NEXT:   %a55 = getelementptr inbounds nuw %struct.X, ptr %x54, i32 0, i32 0
+// CHECK-NEXT:   %44 = load i32, ptr %ts.addr, align 4
+// CHECK-NEXT:   store i32 %44, ptr %a55, align 4
+// CHECK-NEXT:   %b56 = getelementptr inbounds nuw %struct.X, ptr %x54, i32 0, i32 1
+// CHECK-NEXT:   %45 = load i32, ptr %ts.addr2, align 4
+// CHECK-NEXT:   store i32 %45, ptr %b56, align 4
+// CHECK-NEXT:   %c57 = getelementptr inbounds nuw %struct.X, ptr %x54, i32 0, i32 2
+// CHECK-NEXT:   %46 = load i32, ptr %ts.addr4, align 4
+// CHECK-NEXT:   store i32 %46, ptr %c57, align 4
+// CHECK-NEXT:   %a58 = getelementptr inbounds nuw %struct.X, ptr %x54, i32 0, i32 0
+// CHECK-NEXT:   %47 = load i32, ptr %a58, align 4
+// CHECK-NEXT:   call void @_Z1gi(i32 {{.*}} %47)
+// CHECK-NEXT:   br label %expand.end59
+// CHECK: expand.end59:
+// CHECK-NEXT:   ret void
+
+
+// CHECK-LABEL: define {{.*}} void @_Z2t4IJLi42ELi43ELi44EEEvv()
+// CHECK: entry:
+// CHECK-NEXT:   %x = alloca i32, align 4
+// CHECK-NEXT:   %x1 = alloca i32, align 4
+// CHECK-NEXT:   %x4 = alloca i32, align 4
+// CHECK-NEXT:   %x6 = alloca i32, align 4
+// CHECK-NEXT:   %x9 = alloca i32, align 4
+// CHECK-NEXT:   %x12 = alloca i32, align 4
+// CHECK-NEXT:   %x15 = alloca i32, align 4
+// CHECK-NEXT:   %x18 = alloca i32, align 4
+// CHECK-NEXT:   %x21 = alloca i32, align 4
+// CHECK-NEXT:   %x24 = alloca i32, align 4
+// CHECK-NEXT:   %x27 = alloca i32, align 4
+// CHECK-NEXT:   %x30 = alloca i32, align 4
+// CHECK-NEXT:   %x33 = alloca i32, align 4
+// CHECK-NEXT:   %x36 = alloca i32, align 4
+// CHECK-NEXT:   %x39 = alloca i32, align 4
+// CHECK-NEXT:   %x42 = alloca i32, align 4
+// CHECK-NEXT:   %x45 = alloca i32, align 4
+// CHECK-NEXT:   %x48 = alloca i32, align 4
+// CHECK-NEXT:   %x51 = alloca i32, align 4
+// CHECK-NEXT:   %x54 = alloca i32, align 4
+// CHECK-NEXT:   %x57 = alloca %struct.X, align 4
+// CHECK-NEXT:   %x60 = alloca %struct.X, align 4
+// CHECK-NEXT:   %x63 = alloca %struct.X, align 4
+// CHECK-NEXT:   %x66 = alloca %struct.X, align 4
+// CHECK-NEXT:   store i32 42, ptr %x, align 4
+// CHECK-NEXT:   call void @_Z1gi(i32 {{.*}} 42)
+// CHECK-NEXT:   %call = call {{.*}} i32 @_Z2tgILi42EEiv()
+// CHECK-NEXT:   br label %expand.next
+// CHECK: expand.next:
+// CHECK-NEXT:   store i32 43, ptr %x1, align 4
+// CHECK-NEXT:   call void @_Z1gi(i32 {{.*}} 43)
+// CHECK-NEXT:   %call2 = call {{.*}} i32 @_Z2tgILi43EEiv()
+// CHECK-NEXT:   br label %expand.next3
+// CHECK: expand.next3:
+// CHECK-NEXT:   store i32 44, ptr %x4, align 4
+// CHECK-NEXT:   call void @_Z1gi(i32 {{.*}} 44)
+// CHECK-NEXT:   %call5 = call {{.*}} i32 @_Z2tgILi44EEiv()
+// CHECK-NEXT:   br label %expand.end
+// CHECK: expand.end:
+// CHECK-NEXT:   store i32 1, ptr %x6, align 4
+// CHECK-NEXT:   call void @_Z1gi(i32 {{.*}} 1)
+// CHECK-NEXT:   %call7 = call {{.*}} i32 @_Z2tgILi1EEiv()
+// CHECK-NEXT:   br label %expand.next8
+// CHECK: expand.next8:
+// CHECK-NEXT:   store i32 42, ptr %x9, align 4
+// CHECK-NEXT:   call void @_Z1gi(i32 {{.*}} 42)
+// CHECK-NEXT:   %call10 = call {{.*}} i32 @_Z2tgILi42EEiv()
+// CHECK-NEXT:   br label %expand.next11
+// CHECK: expand.next11:
+// CHECK-NEXT:   store i32 43, ptr %x12, align 4
+// CHECK-NEXT:   call void @_Z1gi(i32 {{.*}} 43)
+// CHECK-NEXT:   %call13 = call {{.*}} i32 @_Z2tgILi43EEiv()
+// CHECK-NEXT:   br label %expand.next14
+// CHECK: expand.next14:
+// CHECK-NEXT:   store i32 44, ptr %x15, align 4
+// CHECK-NEXT:   call void @_Z1gi(i32 {{.*}} 44)
+// CHECK-NEXT:   %call16 = call {{.*}} i32 @_Z2tgILi44EEiv()
+// CHECK-NEXT:   br label %expand.next17
+// CHECK: expand.next17:
+// CHECK-NEXT:   store i32 2, ptr %x18, align 4
+// CHECK-NEXT:   call void @_Z1gi(i32 {{.*}} 2)
+// CHECK-NEXT:   %call19 = call {{.*}} i32 @_Z2tgILi2EEiv()
+// CHECK-NEXT:   br label %expand.next20
+// CHECK: expand.next20:
+// CHECK-NEXT:   store i32 42, ptr %x21, align 4
+// CHECK-NEXT:   call void @_Z1gi(i32 {{.*}} 42)
+// CHECK-NEXT:   %call22 = call {{.*}} i32 @_Z2tgILi42EEiv()
+// CHECK-NEXT:   br label %expand.next23
+// CHECK: expand.next23:
+// CHECK-NEXT:   store i32 43, ptr %x24, align 4
+// CHECK-NEXT:   call void @_Z1gi(i32 {{.*}} 43)
+// CHECK-NEXT:   %call25 = call {{.*}} i32 @_Z2tgILi43EEiv()
+// CHECK-NEXT:   br label %expand.next26
+// CHECK: expand.next26:
+// CHECK-NEXT:   store i32 44, ptr %x27, align 4
+// CHECK-NEXT:   call void @_Z1gi(i32 {{.*}} 44)
+// CHECK-NEXT:   %call28 = call {{.*}} i32 @_Z2tgILi44EEiv()
+// CHECK-NEXT:   br label %expand.next29
+// CHECK: expand.next29:
+// CHECK-NEXT:   store i32 3, ptr %x30, align 4
+// CHECK-NEXT:   call void @_Z1gi(i32 {{.*}} 3)
+// CHECK-NEXT:   %call31 = call {{.*}} i32 @_Z2tgILi3EEiv()
+// CHECK-NEXT:   br label %expand.end32
+// CHECK: expand.end32:
+// CHECK-NEXT:   store i32 4, ptr %x33, align 4
+// CHECK-NEXT:   call void @_Z1gi(i32 {{.*}} 4)
+// CHECK-NEXT:   %call34 = call {{.*}} i32 @_Z2tgILi4EEiv()
+// CHECK-NEXT:   br label %expand.next35
+// CHECK: expand.next35:
+// CHECK-NEXT:   store i32 42, ptr %x36, align 4
+// CHECK-NEXT:   call void @_Z1gi(i32 {{.*}} 42)
+// CHECK-NEXT:   %call37 = call {{.*}} i32 @_Z2tgILi42EEiv()
+// CHECK-NEXT:   br label %expand.next38
+// CHECK: expand.next38:
+// CHECK-NEXT:   store i32 43, ptr %x39, align 4
+// CHECK-NEXT:   call void @_Z1gi(i32 {{.*}} 43)
+// CHECK-NEXT:   %call40 = call {{.*}} i32 @_Z2tgILi43EEiv()
+// CHECK-NEXT:   br label %expand.next41
+// CHECK: expand.next41:
+// CHECK-NEXT:   store i32 44, ptr %x42, align 4
+// CHECK-NEXT:   call void @_Z1gi(i32 {{.*}} 44)
+// CHECK-NEXT:   %call43 = call {{.*}} i32 @_Z2tgILi44EEiv()
+// CHECK-NEXT:   br label %expand.next44
+// CHECK: expand.next44:
+// CHECK-NEXT:   store i32 42, ptr %x45, align 4
+// CHECK-NEXT:   call void @_Z1gi(i32 {{.*}} 42)
+// CHECK-NEXT:   %call46 = call {{.*}} i32 @_Z2tgILi42EEiv()
+// CHECK-NEXT:   br label %expand.next47
+// CHECK: expand.next47:
+// CHECK-NEXT:   store i32 43, ptr %x48, align 4
+// CHECK-NEXT:   call void @_Z1gi(i32 {{.*}} 43)
+// CHECK-NEXT:   %call49 = call {{.*}} i32 @_Z2tgILi43EEiv()
+// CHECK-NEXT:   br label %expand.next50
+// CHECK: expand.next50:
+// CHECK-NEXT:   store i32 44, ptr %x51, align 4
+// CHECK-NEXT:   call void @_Z1gi(i32 {{.*}} 44)
+// CHECK-NEXT:   %call52 = call {{.*}} i32 @_Z2tgILi44EEiv()
+// CHECK-NEXT:   br label %expand.next53
+// CHECK: expand.next53:
+// CHECK-NEXT:   store i32 5, ptr %x54, align 4
+// CHECK-NEXT:   call void @_Z1gi(i32 {{.*}} 5)
+// CHECK-NEXT:   %call55 = call {{.*}} i32 @_Z2tgILi5EEiv()
+// CHECK-NEXT:   br label %expand.end56
+// CHECK: expand.end56:
+// CHECK-NEXT:   call void @llvm.memcpy.p0.p0.i64(ptr align 4 %x57, ptr align 4 @__const._Z2t4IJLi42ELi43ELi44EEEvv.x, i64 12, i1 false)
+// CHECK-NEXT:   call void @_Z1gi(i32 {{.*}} 42)
+// CHECK-NEXT:   %call58 = call {{.*}} i32 @_Z2tgILi42EEiv()
+// CHECK-NEXT:   br label %expand.next59
+// CHECK: expand.next59:
+// CHECK-NEXT:   call void @llvm.memcpy.p0.p0.i64(ptr align 4 %x60, ptr align 4 @__const._Z2t4IJLi42ELi43ELi44EEEvv.x.3, i64 12, i1 false)
+// CHECK-NEXT:   call void @_Z1gi(i32 {{.*}} 42)
+// CHECK-NEXT:   %call61 = call {{.*}} i32 @_Z2tgILi42EEiv()
+// CHECK-NEXT:   br label %expand.next62
+// CHECK: expand.next62:
+// CHECK-NEXT:   call void @llvm.memcpy.p0.p0.i64(ptr align 4 %x63, ptr align 4 @__const._Z2t4IJLi42ELi43ELi44EEEvv.x.4, i64 12, i1 false)
+// CHECK-NEXT:   call void @_Z1gi(i32 {{.*}} 6)
+// CHECK-NEXT:   %call64 = call {{.*}} i32 @_Z2tgILi6EEiv()
+// CHECK-NEXT:   br label %expand.end65
+// CHECK: expand.end65:
+// CHECK-NEXT:   call void @llvm.memcpy.p0.p0.i64(ptr align 4 %x66, ptr align 4 @__const._Z2t4IJLi42ELi43ELi44EEEvv.x.5, i64 12, i1 false)
+// CHECK-NEXT:   call void @_Z1gi(i32 {{.*}} 42)
+// CHECK-NEXT:   %call67 = call {{.*}} i32 @_Z2tgILi42EEiv()
+// CHECK-NEXT:   br label %expand.end68
+// CHECK: expand.end68:
+// CHECK-NEXT:   ret void
+
+
+// CHECK-LABEL: define {{.*}} void @_ZN2s2IJLi1ELi2ELi3EEE2tfIJLi4ELi5ELi6EEEEvv(ptr {{.*}} %this)
+// CHECK: entry:
+// CHECK-NEXT:   %this.addr = alloca ptr, align 8
+// CHECK-NEXT:   %x = alloca i32, align 4
+// CHECK-NEXT:   %x2 = alloca i32, align 4
+// CHECK-NEXT:   %x4 = alloca i32, align 4
+// CHECK-NEXT:   %x6 = alloca i32, align 4
+// CHECK-NEXT:   %x8 = alloca i32, align 4
+// CHECK-NEXT:   %x10 = alloca i32, align 4
+// CHECK-NEXT:   %x11 = alloca %struct.X, align 4
+// CHECK-NEXT:   %x13 = alloca %struct.X, align 4
+// CHECK-NEXT:   %x16 = alloca i32, align 4
+// CHECK-NEXT:   %x18 = alloca i32, align 4
+// CHECK-NEXT:   %x21 = alloca i32, align 4
+// CHECK-NEXT:   %x24 = alloca i32, align 4
+// CHECK-NEXT:   %x27 = alloca i32, align 4
+// CHECK-NEXT:   %x30 = alloca i32, align 4
+// CHECK-NEXT:   %x33 = alloca %struct.X, align 4
+// CHECK-NEXT:   %x36 = alloca %struct.X, align 4
+// CHECK-NEXT:   store ptr %this, ptr %this.addr, align 8
+// CHECK-NEXT:   %this1 = load ptr, ptr %this.addr, align 8
+// CHECK-NEXT:   store i32 1, ptr %x, align 4
+// CHECK-NEXT:   %0 = load i32, ptr %x, align 4
+// CHECK-NEXT:   call void @_Z1gi(i32 {{.*}} %0)
+// CHECK-NEXT:   br label %expand.next
+// CHECK: expand.next:
+// CHECK-NEXT:   store i32 2, ptr %x2, align 4
+// CHECK-NEXT:   %1 = load i32, ptr %x2, align 4
+// CHECK-NEXT:   call void @_Z1gi(i32 {{.*}} %1)
+// CHECK-NEXT:   br label %expand.next3
+// CHECK: expand.next3:
+// CHECK-NEXT:   store i32 3, ptr %x4, align 4
+// CHECK-NEXT:   %2 = load i32, ptr %x4, align 4
+// CHECK-NEXT:   call void @_Z1gi(i32 {{.*}} %2)
+// CHECK-NEXT:   br label %expand.next5
+// CHECK: expand.next5:
+// CHECK-NEXT:   store i32 4, ptr %x6, align 4
+// CHECK-NEXT:   %3 = load i32, ptr %x6, align 4
+// CHECK-NEXT:   call void @_Z1gi(i32 {{.*}} %3)
+// CHECK-NEXT:   br label %expand.next7
+// CHECK: expand.next7:
+// CHECK-NEXT:   store i32 5, ptr %x8, align 4
+// CHECK-NEXT:   %4 = load i32, ptr %x8, align 4
+// CHECK-NEXT:   call void @_Z1gi(i32 {{.*}} %4)
+// CHECK-NEXT:   br label %expand.next9
+// CHECK: expand.next9:
+// CHECK-NEXT:   store i32 6, ptr %x10, align 4
+// CHECK-NEXT:   %5 = load i32, ptr %x10, align 4
+// CHECK-NEXT:   call void @_Z1gi(i32 {{.*}} %5)
+// CHECK-NEXT:   br label %expand.end
+// CHECK: expand.end:
+// CHECK-NEXT:   call void @llvm.memcpy.p0.p0.i64(ptr align 4 %x11, ptr align 4 @__const._ZN2s2IJLi1ELi2ELi3EEE2tfIJLi4ELi5ELi6EEEEvv.x, i64 12, i1 false)
+// CHECK-NEXT:   %a = getelementptr inbounds nuw %struct.X, ptr %x11, i32 0, i32 0
+// CHECK-NEXT:   %6 = load i32, ptr %a, align 4
+// CHECK-NEXT:   call void @_Z1gi(i32 {{.*}} %6)
+// CHECK-NEXT:   br label %expand.next12
+// CHECK: expand.next12:
+// CHECK-NEXT:   call void @llvm.memcpy.p0.p0.i64(ptr align 4 %x13, ptr align 4 @__const._ZN2s2IJLi1ELi2ELi3EEE2tfIJLi4ELi5ELi6EEEEvv.x.6, i64 12, i1 false)
+// CHECK-NEXT:   %a14 = getelementptr inbounds nuw %struct.X, ptr %x13, i32 0, i32 0
+// CHECK-NEXT:   %7 = load i32, ptr %a14, align 4
+// CHECK-NEXT:   call void @_Z1gi(i32 {{.*}} %7)
+// CHECK-NEXT:   br label %expand.end15
+// CHECK: expand.end15:
+// CHECK-NEXT:   store i32 1, ptr %x16, align 4
+// CHECK-NEXT:   %call = call {{.*}} i32 @_Z2tgILi1EEiv()
+// CHECK-NEXT:   br label %expand.next17
+// CHECK: expand.next17:
+// CHECK-NEXT:   store i32 2, ptr %x18, align 4
+// CHECK-NEXT:   %call19 = call {{.*}} i32 @_Z2tgILi2EEiv()
+// CHECK-NEXT:   br label %expand.next20
+// CHECK: expand.next20:
+// CHECK-NEXT:   store i32 3, ptr %x21, align 4
+// CHECK-NEXT:   %call22 = call {{.*}} i32 @_Z2tgILi3EEiv()
+// CHECK-NEXT:   br label %expand.next23
+// CHECK: expand.next23:
+// CHECK-NEXT:   store i32 4, ptr %x24, align 4
+// CHECK-NEXT:   %call25 = call {{.*}} i32 @_Z2tgILi4EEiv()
+// CHECK-NEXT:   br label %expand.next26
+// CHECK: expand.next26:
+// CHECK-NEXT:   store i32 5, ptr %x27, align 4
+// CHECK-NEXT:   %call28 = call {{.*}} i32 @_Z2tgILi5EEiv()
+// CHECK-NEXT:   br label %expand.next29
+// CHECK: expand.next29:
+// CHECK-NEXT:   store i32 6, ptr %x30, align 4
+// CHECK-NEXT:   %call31 = call {{.*}} i32 @_Z2tgILi6EEiv()
+// CHECK-NEXT:   br label %expand.end32
+// CHECK: expand.end32:
+// CHECK-NEXT:   call void @llvm.memcpy.p0.p0.i64(ptr align 4 %x33, ptr align 4 @__const._ZN2s2IJLi1ELi2ELi3EEE2tfIJLi4ELi5ELi6EEEEvv.x.7, i64 12, i1 false)
+// CHECK-NEXT:   %call34 = call {{.*}} i32 @_Z2tgILi1EEiv()
+// CHECK-NEXT:   br label %expand.next35
+// CHECK: expand.next35:
+// CHECK-NEXT:   call void @llvm.memcpy.p0.p0.i64(ptr align 4 %x36, ptr align 4 @__const._ZN2s2IJLi1ELi2ELi3EEE2tfIJLi4ELi5ELi6EEEEvv.x.8, i64 12, i1 false)
+// CHECK-NEXT:   %call37 = call {{.*}} i32 @_Z2tgILi4EEiv()
+// CHECK-NEXT:   br label %expand.end38
+// CHECK: expand.end38:
+// CHECK-NEXT:   ret void
+
+
+// CHECK-LABEL: define {{.*}} void @_Z2f8v()
+// CHECK: entry:
+// CHECK-NEXT:   call void @_Z2t5IJLi1ELi2ELi3EEEvv()
+// CHECK-NEXT:   ret void
+
+
+// CHECK-LABEL: define {{.*}} void @_Z2t5IJLi1ELi2ELi3EEEvv()
+// CHECK: entry:
+// CHECK-NEXT:   %ref.tmp = alloca %class.anon, align 1
+// CHECK-NEXT:   %ref.tmp1 = alloca %class.anon.1, align 1
+// CHECK-NEXT:   %ref.tmp2 = alloca %class.anon.3, align 1
+// CHECK-NEXT:   call void @_ZZ2t5IJLi1ELi2ELi3EEEvvENKUlvE1_clEv(ptr {{.*}} %ref.tmp)
+// CHECK-NEXT:   call void @_ZZ2t5IJLi1ELi2ELi3EEEvvENKUlvE0_clEv(ptr {{.*}} %ref.tmp1)
+// CHECK-NEXT:   call void @_ZZ2t5IJLi1ELi2ELi3EEEvvENKUlvE_clEv(ptr {{.*}} %ref.tmp2)
+// CHECK-NEXT:   ret void
+
+
+// CHECK-LABEL: define {{.*}} void @_ZZ2t5IJLi1ELi2ELi3EEEvvENKUlvE1_clEv(ptr {{.*}} %this)
+// CHECK: entry:
+// CHECK-NEXT:   %this.addr = alloca ptr, align 8
+// CHECK-NEXT:   %x = alloca i32, align 4
+// CHECK-NEXT:   store ptr %this, ptr %this.addr, align 8
+// CHECK-NEXT:   %this1 = load ptr, ptr %this.addr, align 8
+// CHECK-NEXT:   store i32 1, ptr %x, align 4
+// CHECK-NEXT:   call void @_Z1gi(i32 {{.*}} 1)
+// CHECK-NEXT:   %call = call {{.*}} i32 @_Z2tgILi1EEiv()
+// CHECK-NEXT:   br label %expand.end
+// CHECK: expand.end:
+// CHECK-NEXT:   ret void
+
+
+// CHECK-LABEL: define {{.*}} void @_ZZ2t5IJLi1ELi2ELi3EEEvvENKUlvE0_clEv(ptr {{.*}} %this)
+// CHECK: entry:
+// CHECK-NEXT:   %this.addr = alloca ptr, align 8
+// CHECK-NEXT:   %x = alloca i32, align 4
+// CHECK-NEXT:   store ptr %this, ptr %this.addr, align 8
+// CHECK-NEXT:   %this1 = load ptr, ptr %this.addr, align 8
+// CHECK-NEXT:   store i32 2, ptr %x, align 4
+// CHECK-NEXT:   call void @_Z1gi(i32 {{.*}} 2)
+// CHECK-NEXT:   %call = call {{.*}} i32 @_Z2tgILi2EEiv()
+// CHECK-NEXT:   br label %expand.end
+// CHECK: expand.end:
+// CHECK-NEXT:   ret void
+
+
+// CHECK-LABEL: define {{.*}} void @_ZZ2t5IJLi1ELi2ELi3EEEvvENKUlvE_clEv(ptr {{.*}} %this)
+// CHECK: entry:
+// CHECK-NEXT:   %this.addr = alloca ptr, align 8
+// CHECK-NEXT:   %x = alloca i32, align 4
+// CHECK-NEXT:   store ptr %this, ptr %this.addr, align 8
+// CHECK-NEXT:   %this1 = load ptr, ptr %this.addr, align 8
+// CHECK-NEXT:   store i32 3, ptr %x, align 4
+// CHECK-NEXT:   call void @_Z1gi(i32 {{.*}} 3)
+// CHECK-NEXT:   %call = call {{.*}} i32 @_Z2tgILi3EEiv()
+// CHECK-NEXT:   br label %expand.end
+// CHECK: expand.end:
+// CHECK-NEXT:   ret void

>From 34960d63cceb49dd1660c8d19ce520d2274c072d Mon Sep 17 00:00:00 2001
From: Sirraide <aeternalmail at gmail.com>
Date: Sat, 25 Oct 2025 20:29:19 +0200
Subject: [PATCH 04/33] Iterating expansion statements

---
 clang/include/clang/AST/RecursiveASTVisitor.h |   1 +
 clang/include/clang/AST/StmtCXX.h             | 103 +++-
 .../clang/Basic/DiagnosticSemaKinds.td        |   8 +
 clang/include/clang/Basic/StmtNodes.td        |   2 +-
 clang/include/clang/Sema/Sema.h               |   1 +
 .../include/clang/Serialization/ASTBitCodes.h |   3 +
 clang/lib/AST/DeclTemplate.cpp                |   5 +-
 clang/lib/AST/ExprConstant.cpp                |  35 ++
 clang/lib/AST/StmtCXX.cpp                     |  29 +-
 clang/lib/AST/StmtPrinter.cpp                 |  14 +-
 clang/lib/AST/StmtProfile.cpp                 |   5 +
 clang/lib/CodeGen/CGStmt.cpp                  |   1 +
 clang/lib/Sema/SemaDeclCXX.cpp                |   3 +
 clang/lib/Sema/SemaExceptionSpec.cpp          |   1 +
 clang/lib/Sema/SemaExpand.cpp                 | 484 ++++++++++--------
 clang/lib/Sema/SemaStmt.cpp                   |   5 +
 clang/lib/Sema/TreeTransform.h                |  72 ++-
 clang/lib/Serialization/ASTReaderStmt.cpp     |  12 +
 clang/lib/Serialization/ASTWriterStmt.cpp     |   9 +
 clang/lib/StaticAnalyzer/Core/ExprEngine.cpp  |   1 +
 ...xx2c-enumerating-expansion-statements.cpp} |   0
 .../cxx2c-iterating-expansion-stmt.cpp        | 435 ++++++++++++++++
 .../SemaCXX/cxx2c-expansion-statements.cpp    | 253 +++++++++
 clang/tools/libclang/CXCursor.cpp             |   1 +
 24 files changed, 1234 insertions(+), 249 deletions(-)
 rename clang/test/CodeGenCXX/{cxx2c-expansion-statements.cpp => cxx2c-enumerating-expansion-statements.cpp} (100%)
 create mode 100644 clang/test/CodeGenCXX/cxx2c-iterating-expansion-stmt.cpp

diff --git a/clang/include/clang/AST/RecursiveASTVisitor.h b/clang/include/clang/AST/RecursiveASTVisitor.h
index 98ba7edde0e80..a729773581f72 100644
--- a/clang/include/clang/AST/RecursiveASTVisitor.h
+++ b/clang/include/clang/AST/RecursiveASTVisitor.h
@@ -3125,6 +3125,7 @@ DEF_TRAVERSE_STMT(RequiresExpr, {
 })
 
 DEF_TRAVERSE_STMT(CXXEnumeratingExpansionStmt, {})
+DEF_TRAVERSE_STMT(CXXIteratingExpansionStmt, {})
 DEF_TRAVERSE_STMT(CXXExpansionInstantiationStmt, {})
 DEF_TRAVERSE_STMT(CXXExpansionInitListExpr, {})
 DEF_TRAVERSE_STMT(CXXExpansionInitListSelectExpr, {})
diff --git a/clang/include/clang/AST/StmtCXX.h b/clang/include/clang/AST/StmtCXX.h
index 7851d42419be2..856b2912b0e1e 100644
--- a/clang/include/clang/AST/StmtCXX.h
+++ b/clang/include/clang/AST/StmtCXX.h
@@ -530,6 +530,13 @@ class CoreturnStmt : public Stmt {
 class CXXExpansionStmt : public Stmt {
   friend class ASTStmtReader;
 
+  ExpansionStmtDecl* ParentDecl;
+  SourceLocation ForLoc;
+  SourceLocation LParenLoc;
+  SourceLocation ColonLoc;
+  SourceLocation RParenLoc;
+
+protected:
   enum SubStmt {
     INIT,
     VAR,
@@ -537,14 +544,9 @@ class CXXExpansionStmt : public Stmt {
     COUNT
   };
 
-  ExpansionStmtDecl* ParentDecl;
+  // This must be the last member of this class.
   Stmt* SubStmts[COUNT];
-  SourceLocation ForLoc;
-  SourceLocation LParenLoc;
-  SourceLocation ColonLoc;
-  SourceLocation RParenLoc;
 
-protected:
   CXXExpansionStmt(StmtClass SC, EmptyShell Empty);
   CXXExpansionStmt(StmtClass SC, ExpansionStmtDecl *ESD, Stmt *Init,
                    DeclStmt *ExpansionVar, SourceLocation ForLoc,
@@ -563,7 +565,6 @@ class CXXExpansionStmt : public Stmt {
   }
 
   bool hasDependentSize() const;
-  size_t getNumInstantiations() const;
 
   ExpansionStmtDecl* getDecl() { return ParentDecl; }
   const ExpansionStmtDecl* getDecl() const { return ParentDecl; }
@@ -620,6 +621,94 @@ class CXXEnumeratingExpansionStmt : public CXXExpansionStmt {
   }
 };
 
+/// Represents an unexpanded iterating expansion statement.
+///
+/// The expression used to compute the size of the expansion is not stored in
+/// this as it is only created at the moment of expansion.
+class CXXIteratingExpansionStmt : public CXXExpansionStmt {
+  friend class ASTStmtReader;
+
+  enum SubStmt {
+    RANGE,
+    BEGIN,
+    END,
+    COUNT
+  };
+
+  // This must be the first member of this class.
+  DeclStmt* SubStmts[COUNT];
+
+public:
+  CXXIteratingExpansionStmt(EmptyShell Empty);
+  CXXIteratingExpansionStmt(ExpansionStmtDecl *ESD, Stmt *Init,
+                            DeclStmt *ExpansionVar, DeclStmt *Range,
+                            DeclStmt *Begin, DeclStmt *End,
+                            SourceLocation ForLoc, SourceLocation LParenLoc,
+                            SourceLocation ColonLoc, SourceLocation RParenLoc);
+
+  const DeclStmt* getRangeVarStmt() const { return SubStmts[RANGE]; }
+  DeclStmt* getRangeVarStmt() { return SubStmts[RANGE]; }
+  void setRangeVarStmt(DeclStmt* S) { SubStmts[RANGE] = S; }
+
+  const VarDecl* getRangeVar() const {
+    return cast<VarDecl>(getRangeVarStmt()->getSingleDecl());
+  }
+
+  VarDecl* getRangeVar() {
+    return cast<VarDecl>(getRangeVarStmt()->getSingleDecl());
+  }
+
+  const DeclStmt* getBeginVarStmt() const { return SubStmts[BEGIN]; }
+  DeclStmt* getBeginVarStmt() { return SubStmts[BEGIN]; }
+  void setBeginVarStmt(DeclStmt* S) { SubStmts[BEGIN] = S; }
+
+  const VarDecl* getBeginVar() const {
+    return cast<VarDecl>(getBeginVarStmt()->getSingleDecl());
+  }
+
+  VarDecl* getBeginVar() {
+    return cast<VarDecl>(getBeginVarStmt()->getSingleDecl());
+  }
+
+  const DeclStmt* getEndVarStmt() const { return SubStmts[END]; }
+  DeclStmt* getEndVarStmt() { return SubStmts[END]; }
+  void setEndVarStmt(DeclStmt* S) { SubStmts[END] = S; }
+
+  const VarDecl* getEndVar() const {
+    return cast<VarDecl>(getEndVarStmt()->getSingleDecl());
+  }
+
+  VarDecl* getEndVar() {
+    return cast<VarDecl>(getEndVarStmt()->getSingleDecl());
+  }
+
+  child_range children() {
+    const_child_range CCR =
+        const_cast<const CXXIteratingExpansionStmt *>(this)->children();
+    return child_range(cast_away_const(CCR.begin()),
+                       cast_away_const(CCR.end()));
+  }
+
+  const_child_range children() const {
+    // Build a contiguous range consisting of the end of the base
+    // CXXExpansionStmt’s SubStmts and ours.
+    //
+    // This is rather terrible, but allocating all this state in the derived
+    // classes of CXXExpansionStmt instead or moving it into trailing data
+    // would be quite a bit more complicated.
+    //
+    // FIXME: There ought to be a better way of doing this.
+    Stmt *const *FirstParentSubStmt = CXXExpansionStmt::SubStmts;
+    unsigned Count = static_cast<unsigned>(CXXExpansionStmt::COUNT) +
+                     static_cast<unsigned>(CXXIteratingExpansionStmt::COUNT);
+    return const_child_range(FirstParentSubStmt, FirstParentSubStmt + Count);
+  }
+
+  static bool classof(const Stmt *T) {
+    return T->getStmtClass() == CXXIteratingExpansionStmtClass;
+  }
+};
+
 /// Represents the code generated for an instantiated expansion statement.
 ///
 /// This holds 'shared statements' and 'instantiations'; these encode the
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index 3a24c012d2c78..fb4e98726b8d1 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -165,6 +165,14 @@ 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)">;
+def err_expansion_too_big : Error<
+  "expansion size %0 exceeds maximum configured size %1">;
+def note_use_fexpansion_limit : Note<
+  "use -fexpansion-limit=N to adjust this limit">;
 
 // Semantic analysis of constant literals.
 def ext_predef_outside_function : Warning<
diff --git a/clang/include/clang/Basic/StmtNodes.td b/clang/include/clang/Basic/StmtNodes.td
index de6e2aa8003fa..ecb2a6bdc5b39 100644
--- a/clang/include/clang/Basic/StmtNodes.td
+++ b/clang/include/clang/Basic/StmtNodes.td
@@ -61,6 +61,7 @@ def CoreturnStmt : StmtNode<Stmt>;
 // C++ expansion statements (P1306)
 def CXXExpansionStmt : StmtNode<Stmt, 1>;
 def CXXEnumeratingExpansionStmt : StmtNode<CXXExpansionStmt>;
+def CXXIteratingExpansionStmt : StmtNode<CXXExpansionStmt>;
 def CXXExpansionInstantiationStmt : StmtNode<Stmt>; // *Not* derived from CXXExpansionStmt!
 
 // Expressions
@@ -186,7 +187,6 @@ def RequiresExpr : StmtNode<Expr>;
 def CXXExpansionInitListExpr : StmtNode<Expr>;
 def CXXExpansionInitListSelectExpr : StmtNode<Expr>;
 //def CXXDestructurableExpansionSelectExpr : StmtNode<Expr>;
-//def CXXIterableExpansionSelectExpr : StmtNode<Expr>;
 
 // Obj-C Expressions.
 def ObjCStringLiteral : StmtNode<Expr>;
diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index 0afe30f64e9c8..1da1ddb189510 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -15693,6 +15693,7 @@ class Sema final : public SemaBase {
   BuildCXXExpansionInitListSelectExpr(CXXExpansionInitListExpr *Range,
                                       Expr *Idx);
 
+  std::optional<uint64_t> ComputeExpansionSize(CXXExpansionStmt *Expansion);
   ///@}
 };
 
diff --git a/clang/include/clang/Serialization/ASTBitCodes.h b/clang/include/clang/Serialization/ASTBitCodes.h
index 24d34fd02b557..f3410f593c4af 100644
--- a/clang/include/clang/Serialization/ASTBitCodes.h
+++ b/clang/include/clang/Serialization/ASTBitCodes.h
@@ -1839,6 +1839,9 @@ enum StmtCode {
   /// A CXXEnumeratedExpansionStmt.
   STMT_CXX_ENUMERATING_EXPANSION,
 
+  /// A CXXIteratingExpansionStmt.
+  STMT_CXX_ITERATING_EXPANSION,
+
   /// A CXXExpansionInstantiationStmt.
   STMT_CXX_EXPANSION_INSTANTIATION,
 
diff --git a/clang/lib/AST/DeclTemplate.cpp b/clang/lib/AST/DeclTemplate.cpp
index 5ee9b357e7b86..eea5eec0d6dbf 100644
--- a/clang/lib/AST/DeclTemplate.cpp
+++ b/clang/lib/AST/DeclTemplate.cpp
@@ -1663,7 +1663,6 @@ clang::getReplacedTemplateParameter(Decl *D, unsigned Index) {
   case Decl::Kind::TemplateTemplateParm:
   case Decl::Kind::TypeAliasTemplate:
   case Decl::Kind::VarTemplate:
-  case Decl::Kind::ExpansionStmt:
     return {cast<TemplateDecl>(D)->getTemplateParameters()->getParam(Index),
             {}};
   case Decl::Kind::ClassTemplateSpecialization: {
@@ -1718,6 +1717,10 @@ clang::getReplacedTemplateParameter(Decl *D, unsigned Index) {
     return getReplacedTemplateParameter(
         cast<FunctionDecl>(D)->getTemplateSpecializationInfo()->getTemplate(),
         Index);
+  case Decl::Kind::ExpansionStmt:
+    return {
+        cast<ExpansionStmtDecl>(D)->getTemplateParameters()->getParam(Index),
+        {}};
   default:
     llvm_unreachable("Unhandled templated declaration kind");
   }
diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp
index a5d12a0d26fd5..2bb49860fcf73 100644
--- a/clang/lib/AST/ExprConstant.cpp
+++ b/clang/lib/AST/ExprConstant.cpp
@@ -5664,6 +5664,12 @@ static EvalStmtResult EvaluateStmt(StmtResult &Result, EvalInfo &Info,
       const VarDecl *VD = dyn_cast_or_null<VarDecl>(D);
       if (VD && !CheckLocalVariableDeclaration(Info, VD))
         return ESR_Failed;
+
+      if (const auto *ESD = dyn_cast<ExpansionStmtDecl>(D)) {
+        assert(ESD->getInstantiations() && "not expanded?");
+        return EvaluateStmt(Result, Info, ESD->getInstantiations(), Case);
+      }
+
       // Each declaration initialization is its own full-expression.
       FullExpressionRAII Scope(Info);
       if (!EvaluateDecl(Info, D, /*EvaluateConditionDecl=*/true) &&
@@ -5936,6 +5942,35 @@ static EvalStmtResult EvaluateStmt(StmtResult &Result, EvalInfo &Info,
     return Scope.destroy() ? ESR_Succeeded : ESR_Failed;
   }
 
+  case Stmt::CXXExpansionInstantiationStmtClass: {
+    BlockScopeRAII Scope(Info);
+    const auto *Expansion = cast<CXXExpansionInstantiationStmt>(S);
+    for (const Stmt* Shared : Expansion->getSharedStmts()) {
+      EvalStmtResult ESR = EvaluateStmt(Result, Info, Shared);
+      if (ESR != ESR_Succeeded) {
+        if (ESR != ESR_Failed && !Scope.destroy())
+          return ESR_Failed;
+        return ESR;
+      }
+    }
+
+    // No need to push an extra scope for these since they're already
+    // CompoundStmts.
+    for (const Stmt* Instantiation : Expansion->getInstantiations()) {
+      EvalStmtResult ESR = EvaluateStmt(Result, Info, Instantiation);
+      if (ESR == ESR_Failed ||
+          ShouldPropagateBreakContinue(Info, Expansion, &Scope, ESR))
+        return ESR;
+      if (ESR != ESR_Continue) {
+        // Succeeded here actually means we encountered a 'break'.
+        assert(ESR == ESR_Succeeded);
+        break;
+      }
+    }
+
+    return Scope.destroy() ? ESR_Succeeded : ESR_Failed;
+  }
+
   case Stmt::SwitchStmtClass:
     return EvaluateSwitch(Result, Info, cast<SwitchStmt>(S));
 
diff --git a/clang/lib/AST/StmtCXX.cpp b/clang/lib/AST/StmtCXX.cpp
index 09797bba97a4d..515b06bb0e917 100644
--- a/clang/lib/AST/StmtCXX.cpp
+++ b/clang/lib/AST/StmtCXX.cpp
@@ -171,21 +171,28 @@ bool CXXExpansionStmt::hasDependentSize() const {
         ->getRangeExpr()
         ->containsPackExpansion();
 
+  if (auto *Iterating = dyn_cast<CXXIteratingExpansionStmt>(this)) {
+    const Expr* Begin = Iterating->getBeginVar()->getInit();
+    const Expr* End = Iterating->getBeginVar()->getInit();
+    return Begin->isTypeDependent() || Begin->isValueDependent() ||
+           End->isTypeDependent() || End->isValueDependent();
+  }
+
   llvm_unreachable("Invalid expansion statement class");
 }
 
-size_t CXXExpansionStmt::getNumInstantiations() const {
-  assert(!hasDependentSize());
-
-  if (isa<CXXEnumeratingExpansionStmt>(this)) {
-    return cast<CXXExpansionInitListSelectExpr>(
-               getExpansionVariable()->getInit())
-        ->getRangeExpr()
-        ->getExprs()
-        .size();
-  }
+CXXIteratingExpansionStmt::CXXIteratingExpansionStmt(EmptyShell Empty)
+  : CXXExpansionStmt(CXXIteratingExpansionStmtClass, Empty) {}
 
-  llvm_unreachable("Invalid expansion statement class");
+CXXIteratingExpansionStmt::CXXIteratingExpansionStmt(
+    ExpansionStmtDecl *ESD, Stmt *Init, DeclStmt *ExpansionVar, DeclStmt *Range,
+    DeclStmt *Begin, DeclStmt *End, SourceLocation ForLoc,
+    SourceLocation LParenLoc, SourceLocation ColonLoc, SourceLocation RParenLoc)
+    : CXXExpansionStmt(CXXIteratingExpansionStmtClass, ESD, Init, ExpansionVar,
+                       ForLoc, LParenLoc, ColonLoc, RParenLoc) {
+  SubStmts[BEGIN] = Begin;
+  SubStmts[END] = End;
+  SubStmts[RANGE] = Range;
 }
 
 CXXExpansionInstantiationStmt::CXXExpansionInstantiationStmt(
diff --git a/clang/lib/AST/StmtPrinter.cpp b/clang/lib/AST/StmtPrinter.cpp
index 87eedd6a3eeed..6de6894f2c5e3 100644
--- a/clang/lib/AST/StmtPrinter.cpp
+++ b/clang/lib/AST/StmtPrinter.cpp
@@ -160,6 +160,7 @@ namespace {
     }
 
     void VisitCXXNamedCastExpr(CXXNamedCastExpr *Node);
+    void VisitCXXExpansionStmt(CXXExpansionStmt* Node);
 
 #define ABSTRACT_STMT(CLASS)
 #define STMT(CLASS, PARENT) \
@@ -447,8 +448,7 @@ void StmtPrinter::VisitCXXForRangeStmt(CXXForRangeStmt *Node) {
   PrintControlledStmt(Node->getBody());
 }
 
-void StmtPrinter::VisitCXXEnumeratingExpansionStmt(
-    CXXEnumeratingExpansionStmt *Node) {
+void StmtPrinter::VisitCXXExpansionStmt(CXXExpansionStmt *Node) {
   Indent() << "template for (";
   if (Node->getInit())
     PrintInitStmt(Node->getInit(), 14);
@@ -461,6 +461,16 @@ void StmtPrinter::VisitCXXEnumeratingExpansionStmt(
   PrintControlledStmt(Node->getBody());
 }
 
+void StmtPrinter::VisitCXXEnumeratingExpansionStmt(
+    CXXEnumeratingExpansionStmt *Node) {
+  VisitCXXExpansionStmt(Node);
+}
+
+void StmtPrinter::VisitCXXIteratingExpansionStmt(
+    CXXIteratingExpansionStmt *Node) {
+  VisitCXXExpansionStmt(Node);
+}
+
 void StmtPrinter::VisitCXXExpansionInstantiationStmt(
     CXXExpansionInstantiationStmt *) {
   llvm_unreachable("should never be printed");
diff --git a/clang/lib/AST/StmtProfile.cpp b/clang/lib/AST/StmtProfile.cpp
index 31c4cf397d9b9..a834a6eb61b97 100644
--- a/clang/lib/AST/StmtProfile.cpp
+++ b/clang/lib/AST/StmtProfile.cpp
@@ -373,6 +373,11 @@ void StmtProfiler::VisitCXXEnumeratingExpansionStmt(
   VisitCXXExpansionStmt(S);
 }
 
+void StmtProfiler::VisitCXXIteratingExpansionStmt(
+    const CXXIteratingExpansionStmt *S) {
+  VisitCXXExpansionStmt(S);
+}
+
 void StmtProfiler::VisitCXXExpansionInstantiationStmt(
     const CXXExpansionInstantiationStmt *S) {
   VisitStmt(S);
diff --git a/clang/lib/CodeGen/CGStmt.cpp b/clang/lib/CodeGen/CGStmt.cpp
index 887e8871fb3c9..f1b18d7014c86 100644
--- a/clang/lib/CodeGen/CGStmt.cpp
+++ b/clang/lib/CodeGen/CGStmt.cpp
@@ -205,6 +205,7 @@ void CodeGenFunction::EmitStmt(const Stmt *S, ArrayRef<const Attr *> Attrs) {
     EmitCXXForRangeStmt(cast<CXXForRangeStmt>(*S), Attrs);
     break;
   case Stmt::CXXEnumeratingExpansionStmtClass:
+  case Stmt::CXXIteratingExpansionStmtClass:
     llvm_unreachable("unexpanded expansion statements should not be emitted");
   case Stmt::CXXExpansionInstantiationStmtClass:
     EmitCXXExpansionInstantiationStmt(cast<CXXExpansionInstantiationStmt>(*S));
diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp
index d41ab126c426f..817b7fa988993 100644
--- a/clang/lib/Sema/SemaDeclCXX.cpp
+++ b/clang/lib/Sema/SemaDeclCXX.cpp
@@ -2027,6 +2027,9 @@ static bool CheckConstexprDeclStmt(Sema &SemaRef, const FunctionDecl *Dcl,
       //   - using-enum-declaration
       continue;
 
+    case Decl::ExpansionStmt:
+      continue;
+
     case Decl::Typedef:
     case Decl::TypeAlias: {
       //   - typedef declarations and alias-declarations that do not define
diff --git a/clang/lib/Sema/SemaExceptionSpec.cpp b/clang/lib/Sema/SemaExceptionSpec.cpp
index ccf9c1b575948..69c3dbc4eeb31 100644
--- a/clang/lib/Sema/SemaExceptionSpec.cpp
+++ b/clang/lib/Sema/SemaExceptionSpec.cpp
@@ -1541,6 +1541,7 @@ CanThrowResult Sema::canThrow(const Stmt *S) {
   case Stmt::SwitchStmtClass:
   case Stmt::WhileStmtClass:
   case Stmt::CXXEnumeratingExpansionStmtClass:
+  case Stmt::CXXIteratingExpansionStmtClass:
   case Stmt::CXXExpansionInstantiationStmtClass:
     return canSubStmtsThrow(*this, S);
 
diff --git a/clang/lib/Sema/SemaExpand.cpp b/clang/lib/Sema/SemaExpand.cpp
index 89e0c1fc7f82a..dfb52b39d5ec7 100644
--- a/clang/lib/Sema/SemaExpand.cpp
+++ b/clang/lib/Sema/SemaExpand.cpp
@@ -6,7 +6,7 @@
 //
 //===----------------------------------------------------------------------===//
 //
-//  This file implements semantic analysis for C++ 26 expansion statements,
+//  This file implements semantic analysis for C++26 expansion statements,
 //  aka 'template for'.
 //
 //===----------------------------------------------------------------------===//
@@ -16,200 +16,171 @@
 #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/EnterExpressionEvaluationContext.h>
 #include <clang/Sema/Template.h>
 
 using namespace clang;
 using namespace sema;
 
-static unsigned ExtractParmVarDeclDepth(Expr *E) {
-  if (auto *DRE = dyn_cast<DeclRefExpr>(E)) {
-    if (auto *PVD = cast<NonTypeTemplateParmDecl>(DRE->getDecl()))
-      return PVD->getDepth();
-  } else if (auto *SNTTPE = cast<SubstNonTypeTemplateParmExpr>(E)) {
-    if (auto *PVD = cast<NonTypeTemplateParmDecl>(SNTTPE->getAssociatedDecl()))
-      return PVD->getDepth();
-  }
-  return 0;
-}
+namespace {
+struct IterableExpansionStmtData {
+  enum class State {
+    NotIterable,
+    Error,
+    Ok,
+  };
 
-/*
-// Returns 'true' if the 'Range' is an iterable expression, and 'false'
-// otherwise. If 'true', then 'Result' contains the resulting
-// 'CXXIterableExpansionSelectExpr' (or error).
-static bool TryMakeCXXIterableExpansionSelectExpr(
-    Sema &S, Expr *Range, Expr *Index, VarDecl *ExpansionVar,
-    ArrayRef<MaterializeTemporaryExpr *> LifetimeExtendTemps,
-    ExprResult &SelectResult) {
-  auto Ctx = Sema::ExpressionEvaluationContext::PotentiallyEvaluated;
-  if (ExpansionVar->isConstexpr())
-    // TODO: Shouldn’t this be 'ConstantEvaluated'?
-    Ctx = Sema::ExpressionEvaluationContext::ImmediateFunctionContext;
-  EnterExpressionEvaluationContext ExprEvalCtx(S, Ctx);
+  DeclStmt *RangeDecl{};
+  DeclStmt *BeginDecl{};
+  DeclStmt *EndDecl{};
+  Expr *Initializer{};
+  State TheState = State::NotIterable;
+
+  bool isIterable() const { return TheState == State::Ok; }
+  bool hasError() { return TheState == State::Error; }
+};
+} // namespace
+
+  /*
+// 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;
+// }()
+//
+// 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'.
+auto CreateBeginDRE = [&] {
+  return S.BuildDeclRefExpr(Info.BeginVar,
+                            Info.BeginVar->getType().getNonReferenceType(),
+                            VK_LValue, ColonLoc);
+};
+
+DeclRefExpr *Begin = CreateBeginDRE();
+DeclRefExpr *End = S.BuildDeclRefExpr(
+    Info.EndVar, Info.EndVar->getType().getNonReferenceType(), VK_LValue,
+    ColonLoc);
+
+ExprResult N = S.ActOnBinOp(Scope, ColonLoc, tok::minus, Begin, End);
+if (N.isInvalid())
+  return ExprError();
+*/
+
+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 [...]
-  if (Range->getType()->isArrayType())
-    return false;
+  QualType Ty = ExpansionInitializer->getType().getNonReferenceType();
+  if (Ty->isArrayType())
+    return Data;
 
-  SourceLocation RangeLoc = Range->getExprLoc();
+  // 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"),
-                                RangeLoc);
-  LookupResult BeginLR(S, BeginName, Sema::LookupMemberName);
-  if (auto *RD = Range->getType()->getAsCXXRecordDecl())
-    S.LookupQualifiedName(BeginLR, RD);
-
-  VarDecl *RangeVar;
-  Expr *VarRef;
-  {
-    assert(isa<ExpansionStmtDecl>(S.CurContext));
-    DeclContext *DC = S.CurContext->getEnclosingNonExpansionStatementContext();
-    IdentifierInfo *II = &S.PP.getIdentifierTable().get("__range");
-    QualType QT = Range->getType().withConst();
-    TypeSourceInfo *TSI = S.Context.getTrivialTypeSourceInfo(QT);
-    RangeVar = VarDecl::Create(S.Context, DC, Range->getBeginLoc(),
-                               Range->getBeginLoc(), II, QT, TSI, SC_Auto);
-
-    if (ExpansionVar->isConstexpr())
-      RangeVar->setConstexpr(true);
-    else if (!LifetimeExtendTemps.empty()) {
-      // TODO: The original patch was performing lifetime extension here, but
-      // CWG 3043 seems to have removed that clause. Is that actually what we
-      // want here?
-      // S.ApplyForRangeOrExpansionStatementLifetimeExtension(
-      //     RangeVar, LifetimeExtendTemps);
-    }
-
-    S.AddInitializerToDecl(RangeVar, Range, /*DirectInit=#1#false);
-    if (RangeVar->isInvalidDecl())
-      return false;
-
-    DeclarationNameInfo Name(II, Range->getBeginLoc());
-    VarRef = S.BuildDeclRefExpr(RangeVar, Range->getType(), VK_LValue, Name,
-                                /*CXXScopeSpec=#1#nullptr, RangeVar);
+                                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, BeginName, Sema::LookupMemberName);
+    FoundBeginEnd = S.LookupQualifiedName(BeginLR, Record) &&
+                    S.LookupQualifiedName(EndLR, Record);
   }
 
-  ExprResult BeginResult;
-  {
-    OverloadCandidateSet CandidateSet(RangeLoc,
+  // Try ADL.
+  if (!FoundBeginEnd) {
+    OverloadCandidateSet Candidates(ColonLoc,
                                       OverloadCandidateSet::CSK_Normal);
-    Sema::ForRangeStatus Status =
-        S.BuildForRangeBeginEndCall(RangeLoc, RangeLoc, BeginName, BeginLR,
-                                    &CandidateSet, VarRef, &BeginResult);
-    if (Status != Sema::FRS_Success)
-      return false;
-
-    assert(!BeginResult.isInvalid());
-  }
-  SelectResult = ExprError();
-
-  // At this point, we know that this is supposed to be an iterable expansion
-  // statement, so any failure here is a hard error.
-  ExprResult BeginPlusIndex = S.ActOnBinOp(S.getCurScope(), RangeLoc, tok::plus,
-                                           BeginResult.get(), Index);
-  if (BeginPlusIndex.isInvalid()) {
-    SelectResult = ExprError();
-    return true;
-  }
 
-  ExprResult Deref = S.ActOnUnaryOp(S.getCurScope(), RangeLoc, tok::star,
-                                    BeginPlusIndex.get());
-  if (Deref.isInvalid()) {
-    SelectResult = ExprError();
-    return true;
-  }
+    S.AddArgumentDependentLookupCandidates(BeginName.getName(), ColonLoc,
+                                           ExpansionInitializer, nullptr,
+                                           Candidates);
 
-  SelectResult = S.BuildCXXIterableExpansionSelectExpr(RangeVar, Impl.get());
-  return true;
-}*/
+    if (Candidates.empty())
+      return Data;
 
-/// Determine whether this should be an iterable expansion statement, and, if
-/// so, synthesise the various AST nodes that are required for one.
-///
-/// \return ExprEmpty() if this is not an iterable expansion statement.
-/// \return ExprError() if there was a hard error.
-/// \return A CXXIterableExpansionSelectExpr otherwise.
-static ExprResult TryBuildIterableExpansionSelectExpr(Sema &S, Scope *Scope,
-                                                      Expr *Range, Expr *Index,
-                                                      VarDecl *ExpansionVar,
-                                                      SourceLocation ColonLoc) {
-  llvm_unreachable("TODO");
-  /*// C++26 [stmt.expand]p3: An expression is expansion-iterable if it does not
-  // have array type [...]
-  if (Range->getType()->isArrayType())
-    return ExprEmpty();
-
-  // Build the 'range', 'begin', and 'end' variables.
-  DeclStmt* RangeVar{};
-  auto BuildBeginEnd = [&](Sema::BuildForRangeKind Kind) ->
-  Sema::ForRangeBeginEndInfo { StmtResult Var =
-        S.BuildCXXForRangeRangeVar(Scope, Range, /*ForExpansionStmt=#1#true);
-    if (!Var.isUsable())
-      return {};
-
-    RangeVar = cast<DeclStmt>(Var.get());
-    return S.BuildCXXForRangeBeginEndVars(
-        Scope, cast<VarDecl>(RangeVar->getSingleDecl()), ColonLoc,
-        /*CoawaitLoc=#1#{},
-        /*LifetimeExtendTemps=#1#{}, Kind, /*ForExpansionStmt=#1#true);
-  };
+    Candidates.clear(OverloadCandidateSet::CSK_Normal);
+    S.AddArgumentDependentLookupCandidates(EndName.getName(), ColonLoc,
+                                           ExpansionInitializer, nullptr,
+                                           Candidates);
 
-  // The construction of begin-expr and end-expr proceeds as for range-based for
-  // loops, except that the 'begin' and 'end' variables are 'static constexpr'.
-  //
-  // FIXME: Instead of doing this jank, do the lookup for begin/end manually
-  // (or factor it out from the for-range code), and only then build the begin/end
-  // expression.
-  {
-    Sema::SFINAETrap Trap(S);
-    if (!BuildBeginEnd(Sema::BFRK_Check).isValid())
-      return ExprEmpty();
+    if (Candidates.empty())
+      return Data;
   }
 
-  // Ok, we have confirmed that this is possible; rebuild it without the trap.
-  Sema::ForRangeBeginEndInfo Info =BuildBeginEnd(Sema::BFRK_Build);
-  if (!Info.isValid())
-    return ExprError();
+  auto Ctx = Sema::ExpressionEvaluationContext::PotentiallyEvaluated;
+  if (VarIsConstexpr)
+    Ctx = Sema::ExpressionEvaluationContext::ImmediateFunctionContext;
+  EnterExpressionEvaluationContext ExprEvalCtx(S, Ctx);
 
-  // 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;
-  // }()
+  // 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.
   //
-  // 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'.
-  auto CreateBeginDRE = [&] {
-    return S.BuildDeclRefExpr(Info.BeginVar,
-                              Info.BeginVar->getType().getNonReferenceType(),
-                              VK_LValue, ColonLoc);
-  };
+  // Any failure at this point is a hard error.
+  Data.TheState = IterableExpansionStmtData::State::Error;
+  Scope *Scope = S.getCurScope();
+  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);
 
-  DeclRefExpr *Begin = CreateBeginDRE();
-  DeclRefExpr *End = S.BuildDeclRefExpr(
-      Info.EndVar, Info.EndVar->getType().getNonReferenceType(), VK_LValue,
-      ColonLoc);
+  if (!Info.isValid())
+    return Data;
 
-  ExprResult N = S.ActOnBinOp(Scope, ColonLoc, tok::minus, Begin, End);
-  if (N.isInvalid())
-    return ExprError();
+  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)'.
-  Begin = CreateBeginDRE();
-  ExprResult BeginPlusI = S.ActOnBinOp(Scope, ColonLoc, tok::plus, Begin,
-  Index); if (BeginPlusI.isInvalid()) return ExprError();
-
-  ExprResult Deref = S.ActOnUnaryOp(Scope, ColonLoc, tok::star,
-  BeginPlusI.get()); if (Deref.isInvalid()) return ExprError();
+  DeclRefExpr *Begin = S.BuildDeclRefExpr(
+      Info.BeginVar, Info.BeginVar->getType().getNonReferenceType(), VK_LValue,
+      ColonLoc);
 
-  Deref = S.MaybeCreateExprWithCleanups(Deref.get());*/
+  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;
 }
 
 ExpansionStmtDecl *Sema::ActOnExpansionStmtDecl(unsigned TemplateDepth,
@@ -260,6 +231,7 @@ StmtResult Sema::ActOnCXXExpansionStmt(
   if (!ExpansionInitializer || !ExpansionVarStmt || Kind == BFRK_Check)
     return StmtError();
 
+  assert(CurContext->isExpansionStmt());
   auto *DS = cast<DeclStmt>(ExpansionVarStmt);
   if (!DS->isSingleDecl()) {
     Diag(DS->getBeginLoc(), diag::err_type_defined_in_for_range);
@@ -267,26 +239,61 @@ StmtResult Sema::ActOnCXXExpansionStmt(
   }
 
   VarDecl *ExpansionVar = cast<VarDecl>(DS->getSingleDecl());
-  if (!ExpansionVar || ExpansionVar->isInvalidDecl())
+  if (!ExpansionVar || ExpansionVar->isInvalidDecl() ||
+      ExpansionInitializer->containsErrors())
     return StmtError();
 
-  ExprResult ER = BuildCXXExpansionInitializer(ESD, ExpansionInitializer);
-  if (ER.isInvalid()) {
+  auto FinaliseExpansionVar = [&](ExprResult Initializer) {
+    if (Initializer.isInvalid()) {
+      ActOnInitializerError(ExpansionVar);
+      return true;
+    }
+
+    AddInitializerToDecl(ExpansionVar, Initializer.get(), /*DirectInit=*/false);
+    return ExpansionVar->isInvalidDecl();
+  };
+
+  // Build a 'DeclRefExpr' designating the template parameter '__N'.
+  DeclRefExpr *Index = BuildDeclRefExpr(ESD->getIndexTemplateParm(),
+                                        Context.getPointerDiffType(),
+                                        VK_PRValue, ESD->getBeginLoc());
+
+  // This is an enumerating expansion statement.
+  if (auto *ILE = dyn_cast<CXXExpansionInitListExpr>(ExpansionInitializer)) {
+
+    ExprResult Initializer = BuildCXXExpansionInitListSelectExpr(ILE, Index);
+    if (FinaliseExpansionVar(Initializer))
+      return StmtError();
+
+    // Note that lifetime extension only applies to destructurable expansion
+    // statements, so we just ignore 'LifetimeExtendedTemps' entirely for other
+    // types of expansion statements (this is CWG 3043).
+    return BuildCXXEnumeratingExpansionStmt(ESD, Init, DS, ForLoc, LParenLoc,
+                                            ColonLoc, RParenLoc);
+  }
+
+  if (ExpansionInitializer->isTypeDependent())
+    llvm_unreachable("TODO: Dependent expansion initializer");
+
+  // Otherwise, if it can be an iterating expansion statement, it is one.
+  IterableExpansionStmtData Data = TryBuildIterableExpansionStmtInitializer(
+      *this, ExpansionInitializer, Index, ColonLoc, ExpansionVar->isConstexpr());
+  if (Data.hasError()) {
     ActOnInitializerError(ExpansionVar);
     return StmtError();
   }
 
-  Expr *Initializer = ER.get();
-  AddInitializerToDecl(ExpansionVar, Initializer, /*DirectInit=*/false);
-  if (ExpansionVar->isInvalidDecl())
-    return StmtError();
+  if (Data.isIterable()) {
+    if (FinaliseExpansionVar(Data.Initializer))
+      return StmtError();
 
-  if (isa<CXXExpansionInitListExpr>(ExpansionInitializer))
-    return BuildCXXEnumeratingExpansionStmt(ESD, Init, DS, ForLoc, LParenLoc,
-                                            ColonLoc, RParenLoc);
+    return new (Context) CXXIteratingExpansionStmt(
+        ESD, Init, DS, Data.RangeDecl, Data.BeginDecl, Data.EndDecl, ForLoc,
+        LParenLoc, ColonLoc, RParenLoc);
+  }
 
 
-  llvm_unreachable("TODO");
+  llvm_unreachable("TODO: Destructuring expansion statement");
 }
 
 StmtResult Sema::BuildCXXEnumeratingExpansionStmt(Decl *ESD, Stmt *Init,
@@ -365,14 +372,36 @@ StmtResult Sema::FinishCXXExpansionStmt(Stmt* Exp, Stmt *Body) {
   if (Expansion->hasDependentSize())
     return Expansion;
 
+  // This can fail if this is an iterating expansion statement.
+  std::optional<uint64_t> NumInstantiations = ComputeExpansionSize(Expansion);
+  if (!NumInstantiations)
+    return StmtError();
+
+  // TODO: Actually make this configurable. It is set to 32 for now so our
+  // tests don't take for ever to run; we should pick a larger default value
+  // once we add an option for this and then pass '-fexpansion-limit=32' to
+  // the tests.
+  static constexpr uint64_t MaxExpansionSize = 32;
+  if (MaxExpansionSize != 0 && *NumInstantiations > MaxExpansionSize) {
+    Diag(Expansion->getColonLoc(), diag::err_expansion_too_big)
+        << *NumInstantiations << MaxExpansionSize;
+    Diag(Expansion->getColonLoc(), diag::note_use_fexpansion_limit);
+    return StmtError();
+  }
+
   // Collect shared statements.
   SmallVector<Stmt*, 1> Shared;
   if (Expansion->getInit())
     Shared.push_back(Expansion->getInit());
 
+  if (auto *Iter = dyn_cast<CXXIteratingExpansionStmt>(Expansion)) {
+    Shared.push_back(Iter->getRangeVarStmt());
+    Shared.push_back(Iter->getBeginVarStmt());
+    Shared.push_back(Iter->getEndVarStmt());
+  }
+
   // Return an empty statement if the range is empty.
-  size_t NumInstantiations = Expansion->getNumInstantiations();
-  if (NumInstantiations == 0) {
+  if (*NumInstantiations == 0) {
     Expansion->getDecl()->setInstantiations(
         CXXExpansionInstantiationStmt::Create(Context, Expansion->getBeginLoc(),
                                               /*Instantiations=*/{}, Shared));
@@ -388,7 +417,7 @@ StmtResult Sema::FinishCXXExpansionStmt(Stmt* Exp, Stmt *Body) {
   // Expand the body for each instantiation.
   SmallVector<Stmt *, 4> Instantiations;
   ExpansionStmtDecl *ESD = Expansion->getDecl();
-  for (size_t I = 0; I < NumInstantiations; ++I) {
+  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,
@@ -419,40 +448,6 @@ StmtResult Sema::FinishCXXExpansionStmt(Stmt* Exp, Stmt *Body) {
   return Expansion;
 }
 
-ExprResult Sema::BuildCXXExpansionInitializer(ExpansionStmtDecl *ESD,
-                                              Expr *ExpansionInitializer) {
-  if (ExpansionInitializer->containsErrors())
-    return ExprError();
-
-  // This should only happen when we first parse the statement.
-  //
-  // Note that lifetime extension only applies to destructurable expansion
-  // statements, so we just ignore 'LifetimeExtendedTemps' entirely for other
-  // types of expansion statements (this is CWG 3043).
-  if (auto *ILE = dyn_cast<CXXExpansionInitListExpr>(ExpansionInitializer)) {
-    // Build a 'DeclRefExpr' designating the template parameter '__N'.
-    DeclRefExpr *Index =
-        BuildDeclRefExpr(ESD->getIndexTemplateParm(), Context.getSizeType(),
-                         VK_PRValue, ESD->getBeginLoc());
-
-    return BuildCXXExpansionInitListSelectExpr(ILE, Index);
-  }
-
-  if (ExpansionInitializer->isTypeDependent())
-    return ExpansionInitializer;
-
-  ExpansionInitializer->dumpColor();
-  llvm_unreachable("TODO: handle this expansion initialiser");
-  /*ExprResult IterableExprResult = TryBuildIterableExpansionSelectExpr(
-      *this, Range, Index, ExpansionVar, LifetimeExtendTemps,
-      IterableExprResult);
-  if (!IterableExprResult.isUnset())
-    return IterableExprResult;
-
-  return BuildDestructurableExpansionSelectExpr(
-      *this, Range, Index, ExpansionVar, LifetimeExtendTemps);*/
-}
-
 ExprResult
 Sema::BuildCXXExpansionInitListSelectExpr(CXXExpansionInitListExpr *Range,
                                           Expr *Idx) {
@@ -465,6 +460,69 @@ Sema::BuildCXXExpansionInitListSelectExpr(CXXExpansionInitListExpr *Range,
   if (!Idx->EvaluateAsInt(ER, Context))
     llvm_unreachable("Failed to evaluate expansion init list index");
 
-  size_t I = ER.Val.getInt().getZExtValue();
+  uint64_t I = ER.Val.getInt().getZExtValue();
   return Range->getExprs()[I];
 }
+
+std::optional<uint64_t>  Sema::ComputeExpansionSize(CXXExpansionStmt *Expansion) {
+  assert(!Expansion->hasDependentSize());
+
+  if (isa<CXXEnumeratingExpansionStmt>(Expansion)) {
+    uint64_t Size = cast<CXXExpansionInitListSelectExpr>(
+               Expansion->getExpansionVariable()->getInit())
+        ->getRangeExpr()
+        ->getExprs()
+        .size();
+
+    return Size;
+  }
+
+  // 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;
+  // }()
+  if (auto *Iterating = dyn_cast<CXXIteratingExpansionStmt>(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("Invalid expansion statement class");
+}
diff --git a/clang/lib/Sema/SemaStmt.cpp b/clang/lib/Sema/SemaStmt.cpp
index 93b73e5e61b81..56ac191f17d3a 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);
diff --git a/clang/lib/Sema/TreeTransform.h b/clang/lib/Sema/TreeTransform.h
index 0bdb96836eb51..ee09910a8d370 100644
--- a/clang/lib/Sema/TreeTransform.h
+++ b/clang/lib/Sema/TreeTransform.h
@@ -857,6 +857,35 @@ class TreeTransform {
 
   StmtResult TransformOMPInformationalDirective(OMPExecutableDirective *S);
 
+  struct TransformCXXExpansionStmtResult {
+    ExpansionStmtDecl* NewESD{};
+    Stmt* NewInit{};
+    DeclStmt* NewExpansionVarDecl{};
+    bool isValid() const { return NewESD != nullptr; }
+  };
+
+  TransformCXXExpansionStmtResult
+  TransformCXXExpansionStmtCommonParts(CXXExpansionStmt *S) {
+    Decl* ESD = getDerived().TransformDecl(S->getDecl()->getLocation(), S->getDecl());
+    if (!ESD || ESD->isInvalidDecl())
+      return {};
+
+    Stmt *Init = S->getInit();
+    if (Init) {
+      StmtResult SR = getDerived().TransformStmt(Init);
+      if (SR.isInvalid())
+        return {};
+      Init = SR.get();
+    }
+
+    StmtResult ExpansionVar = getDerived().TransformStmt(S->getExpansionVarStmt());
+    if (ExpansionVar.isInvalid())
+      return {};
+
+    return {cast<ExpansionStmtDecl>(ESD), Init, ExpansionVar.getAs<DeclStmt>()};
+  }
+
+
 // FIXME: We use LLVM_ATTRIBUTE_NOINLINE because inlining causes a ridiculous
 // amount of stack usage with clang.
 #define STMT(Node, Parent)                        \
@@ -9287,31 +9316,46 @@ TreeTransform<Derived>::TransformCXXForRangeStmt(CXXForRangeStmt *S) {
 template <typename Derived>
 StmtResult TreeTransform<Derived>::TransformCXXEnumeratingExpansionStmt(
     CXXEnumeratingExpansionStmt *S) {
-  Decl* ESD = getDerived().TransformDecl(S->getDecl()->getLocation(), S->getDecl());
-  if (!ESD || ESD->isInvalidDecl())
+  TransformCXXExpansionStmtResult Common = TransformCXXExpansionStmtCommonParts(S);
+  if (!Common.isValid())
     return StmtError();
 
-  Stmt *Init = S->getInit();
-  if (Init) {
-    StmtResult SR = getDerived().TransformStmt(Init);
-    if (SR.isInvalid())
-      return StmtError();
-    Init = SR.get();
-  }
+  auto *Expansion = new (SemaRef.Context) CXXEnumeratingExpansionStmt(
+      Common.NewESD, Common.NewInit, Common.NewExpansionVarDecl, S->getForLoc(),
+      S->getLParenLoc(), S->getColonLoc(), S->getRParenLoc());
 
-  StmtResult ExpansionVar = getDerived().TransformStmt(S->getExpansionVarStmt());
-  if (ExpansionVar.isInvalid())
+  StmtResult Body = getDerived().TransformStmt(S->getBody());
+  if (Body.isInvalid())
     return StmtError();
 
-  auto *Expansion = new (SemaRef.Context) CXXEnumeratingExpansionStmt(
-      cast<ExpansionStmtDecl>(ESD), Init, ExpansionVar.getAs<DeclStmt>(),
+  return SemaRef.FinishCXXExpansionStmt(Expansion, Body.get());
+}
+
+template <typename Derived>
+StmtResult TreeTransform<Derived>::TransformCXXIteratingExpansionStmt(
+    CXXIteratingExpansionStmt *S) {
+  TransformCXXExpansionStmtResult Common = TransformCXXExpansionStmtCommonParts(S);
+  if (!Common.isValid())
+    return StmtError();
+
+  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();
+
+  auto *Expansion = new (SemaRef.Context) CXXIteratingExpansionStmt(
+      Common.NewESD, Common.NewInit, Common.NewExpansionVarDecl,
+      Range.getAs<DeclStmt>(), Begin.getAs<DeclStmt>(), End.getAs<DeclStmt>(),
       S->getForLoc(), S->getLParenLoc(), S->getColonLoc(), S->getRParenLoc());
 
   StmtResult Body = getDerived().TransformStmt(S->getBody());
   if (Body.isInvalid())
     return StmtError();
 
-  // Finish expanding the statement.
   return SemaRef.FinishCXXExpansionStmt(Expansion, Body.get());
 }
 
diff --git a/clang/lib/Serialization/ASTReaderStmt.cpp b/clang/lib/Serialization/ASTReaderStmt.cpp
index 1cdfb4377f367..c66b19e5bf752 100644
--- a/clang/lib/Serialization/ASTReaderStmt.cpp
+++ b/clang/lib/Serialization/ASTReaderStmt.cpp
@@ -1756,6 +1756,14 @@ void ASTStmtReader::VisitCXXEnumeratingExpansionStmt(
   VisitCXXExpansionStmt(S);
 }
 
+void ASTStmtReader::VisitCXXIteratingExpansionStmt(
+    CXXIteratingExpansionStmt *S) {
+  VisitCXXExpansionStmt(S);
+  S->setRangeVarStmt(cast<DeclStmt>(Record.readSubStmt()));
+  S->setBeginVarStmt(cast<DeclStmt>(Record.readSubStmt()));
+  S->setEndVarStmt(cast<DeclStmt>(Record.readSubStmt()));
+}
+
 void ASTStmtReader::VisitCXXExpansionInitListExpr(CXXExpansionInitListExpr *E) {
   VisitExpr(E);
   assert(Record.peekInt() == E->getNumExprs() && "NumExprFields is wrong ?");
@@ -3613,6 +3621,10 @@ Stmt *ASTReader::ReadStmtFromStream(ModuleFile &F) {
       S = new (Context) CXXEnumeratingExpansionStmt(Empty);
       break;
 
+    case STMT_CXX_ITERATING_EXPANSION:
+      S = new (Context) CXXIteratingExpansionStmt(Empty);
+      break;
+
     case STMT_CXX_EXPANSION_INSTANTIATION:
       S = CXXExpansionInstantiationStmt::CreateEmpty(
           Context, Empty, Record[ASTStmtReader::NumStmtFields],
diff --git a/clang/lib/Serialization/ASTWriterStmt.cpp b/clang/lib/Serialization/ASTWriterStmt.cpp
index 779596e10a992..bc04c23cb11f4 100644
--- a/clang/lib/Serialization/ASTWriterStmt.cpp
+++ b/clang/lib/Serialization/ASTWriterStmt.cpp
@@ -1733,6 +1733,15 @@ void ASTStmtWriter::VisitCXXEnumeratingExpansionStmt(
   Code = serialization::STMT_CXX_ENUMERATING_EXPANSION;
 }
 
+void ASTStmtWriter::VisitCXXIteratingExpansionStmt(
+    CXXIteratingExpansionStmt *S) {
+  VisitCXXExpansionStmt(S);
+  Record.AddStmt(S->getRangeVarStmt());
+  Record.AddStmt(S->getBeginVarStmt());
+  Record.AddStmt(S->getEndVarStmt());
+  Code = serialization::STMT_CXX_ITERATING_EXPANSION;
+}
+
 void ASTStmtWriter::VisitCXXExpansionInitListExpr(CXXExpansionInitListExpr *E) {
   VisitExpr(E);
   Record.push_back(E->getNumExprs());
diff --git a/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp b/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp
index 668c62a066283..54b3ba11e698d 100644
--- a/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp
+++ b/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp
@@ -1748,6 +1748,7 @@ void ExprEngine::Visit(const Stmt *S, ExplodedNode *Pred,
     case Stmt::SEHLeaveStmtClass:
     case Stmt::SEHFinallyStmtClass:
     case Stmt::CXXEnumeratingExpansionStmtClass:
+    case Stmt::CXXIteratingExpansionStmtClass:
     case Stmt::CXXExpansionInstantiationStmtClass:
     case Stmt::CXXExpansionInitListExprClass:
     case Stmt::CXXExpansionInitListSelectExprClass:
diff --git a/clang/test/CodeGenCXX/cxx2c-expansion-statements.cpp b/clang/test/CodeGenCXX/cxx2c-enumerating-expansion-statements.cpp
similarity index 100%
rename from clang/test/CodeGenCXX/cxx2c-expansion-statements.cpp
rename to clang/test/CodeGenCXX/cxx2c-enumerating-expansion-statements.cpp
diff --git a/clang/test/CodeGenCXX/cxx2c-iterating-expansion-stmt.cpp b/clang/test/CodeGenCXX/cxx2c-iterating-expansion-stmt.cpp
new file mode 100644
index 0000000000000..0175216ded0b7
--- /dev/null
+++ b/clang/test/CodeGenCXX/cxx2c-iterating-expansion-stmt.cpp
@@ -0,0 +1,435 @@
+// RUN: %clang_cc1 -std=c++2c -triple x86_64-unknown-linux-gnu -emit-llvm -o - %s | FileCheck %s
+
+template <typename T, __SIZE_TYPE__ size>
+struct Array {
+  T data[size]{};
+  constexpr const T* begin() const { return data; }
+  constexpr const T* end() const { return data + size; }
+};
+
+int f1() {
+  static constexpr Array<int, 3> integers{1, 2, 3};
+  int sum = 0;
+  template for (auto x : integers) sum += x;
+  return sum;
+}
+
+int f2() {
+  static constexpr Array<int, 3> integers{1, 2, 3};
+  int sum = 0;
+  template for (constexpr auto x : integers) sum += x;
+  return sum;
+}
+
+int f3() {
+  static constexpr Array<int, 0> integers{};
+  int sum = 0;
+  template for (constexpr auto x : integers) {
+    static_assert(false, "not expanded");
+    sum += x;
+  }
+  return sum;
+}
+
+int f4() {
+  static constexpr Array<int, 2> a{1, 2};
+  static constexpr Array<int, 2> b{3, 4};
+  int sum = 0;
+
+  template for (auto x : a)
+    template for (auto y : b)
+      sum += x + y;
+
+  template for (constexpr auto x : a)
+    template for (constexpr auto y : b)
+      sum += x + y;
+
+  return sum;
+}
+
+struct Private {
+  static constexpr Array<int, 3> integers{1, 2, 3};
+  friend constexpr int friend_func();
+
+private:
+  constexpr const int* begin() const { return integers.begin(); }
+  constexpr const int* end() const { return integers.end(); }
+
+public:
+  static int member_func();
+};
+
+int Private::member_func() {
+  int sum = 0;
+  static constexpr Private p1;
+  template for (auto x : p1) sum += x;
+  return sum;
+}
+
+struct CustomIterator {
+  struct iterator {
+    int n;
+
+    constexpr iterator operator+(int m) const {
+      return {n + m};
+    }
+
+    constexpr int operator*() const {
+      return n;
+    }
+
+    // FIXME: Should be '!=' once we support that properly.
+    friend constexpr __PTRDIFF_TYPE__ operator-(iterator a, iterator b) {
+      return a.n - b.n;
+    }
+  };
+
+   constexpr iterator begin() const { return iterator(1); }
+   constexpr iterator end() const { return iterator(5); }
+};
+
+int custom_iterator() {
+  static constexpr CustomIterator c;
+  int sum = 0;
+  template for (auto x : c) sum += x;
+  template for (constexpr auto x : c) sum += x;
+  return sum;
+}
+
+// CHECK: @_ZZ2f1vE8integers = internal constant %struct.Array { [3 x i32] [i32 1, i32 2, i32 3] }, align 4
+// CHECK: @_ZZ2f1vE8__range1 = internal constant ptr @_ZZ2f1vE8integers, align 8
+// CHECK: @_ZZ2f1vE8__begin1 = internal constant ptr @_ZZ2f1vE8integers, align 8
+// CHECK: @_ZZ2f1vE6__end1 = internal constant ptr getelementptr (i8, ptr @_ZZ2f1vE8integers, i64 12), align 8
+// CHECK: @_ZZ2f2vE8integers = internal constant %struct.Array { [3 x i32] [i32 1, i32 2, i32 3] }, align 4
+// CHECK: @_ZZ2f2vE8__range1 = internal constant ptr @_ZZ2f2vE8integers, align 8
+// CHECK: @_ZZ2f2vE8__begin1 = internal constant ptr @_ZZ2f2vE8integers, align 8
+// CHECK: @_ZZ2f2vE6__end1 = internal constant ptr getelementptr (i8, ptr @_ZZ2f2vE8integers, i64 12), align 8
+// CHECK: @_ZZ2f3vE8integers = internal constant %struct.Array.0 zeroinitializer, align 4
+// CHECK: @_ZZ2f3vE8__range1 = internal constant ptr @_ZZ2f3vE8integers, align 8
+// CHECK: @_ZZ2f3vE8__begin1 = internal constant ptr @_ZZ2f3vE8integers, align 8
+// CHECK: @_ZZ2f3vE6__end1 = internal constant ptr @_ZZ2f3vE8integers, align 8
+// CHECK: @_ZZ2f4vE1a = internal constant %struct.Array.1 { [2 x i32] [i32 1, i32 2] }, align 4
+// CHECK: @_ZZ2f4vE1b = internal constant %struct.Array.1 { [2 x i32] [i32 3, i32 4] }, align 4
+// CHECK: @_ZZ2f4vE8__range1 = internal constant ptr @_ZZ2f4vE1a, align 8
+// CHECK: @_ZZ2f4vE8__begin1 = internal constant ptr @_ZZ2f4vE1a, align 8
+// CHECK: @_ZZ2f4vE6__end1 = internal constant ptr getelementptr (i8, ptr @_ZZ2f4vE1a, i64 8), align 8
+// CHECK: @_ZZ2f4vE8__range2 = internal constant ptr @_ZZ2f4vE1b, align 8
+// CHECK: @_ZZ2f4vE8__begin2 = internal constant ptr @_ZZ2f4vE1b, align 8
+// CHECK: @_ZZ2f4vE6__end2 = internal constant ptr getelementptr (i8, ptr @_ZZ2f4vE1b, i64 8), align 8
+// CHECK: @_ZZ2f4vE8__range2_0 = internal constant ptr @_ZZ2f4vE1b, align 8
+// CHECK: @_ZZ2f4vE8__begin2_0 = internal constant ptr @_ZZ2f4vE1b, align 8
+// CHECK: @_ZZ2f4vE6__end2_0 = internal constant ptr getelementptr (i8, ptr @_ZZ2f4vE1b, i64 8), align 8
+// CHECK: @_ZZ2f4vE8__range1_0 = internal constant ptr @_ZZ2f4vE1a, align 8
+// CHECK: @_ZZ2f4vE8__begin1_0 = internal constant ptr @_ZZ2f4vE1a, align 8
+// CHECK: @_ZZ2f4vE6__end1_0 = internal constant ptr getelementptr (i8, ptr @_ZZ2f4vE1a, i64 8), align 8
+// CHECK: @_ZZ2f4vE8__range2_1 = internal constant ptr @_ZZ2f4vE1b, align 8
+// CHECK: @_ZZ2f4vE8__begin2_1 = internal constant ptr @_ZZ2f4vE1b, align 8
+// CHECK: @_ZZ2f4vE6__end2_1 = internal constant ptr getelementptr (i8, ptr @_ZZ2f4vE1b, i64 8), align 8
+// CHECK: @_ZZ2f4vE8__range2_2 = internal constant ptr @_ZZ2f4vE1b, align 8
+// CHECK: @_ZZ2f4vE8__begin2_2 = internal constant ptr @_ZZ2f4vE1b, align 8
+// CHECK: @_ZZ2f4vE6__end2_2 = internal constant ptr getelementptr (i8, ptr @_ZZ2f4vE1b, i64 8), align 8
+// CHECK: @_ZZN7Private11member_funcEvE2p1 = internal constant %struct.Private zeroinitializer, align 1
+// CHECK: @_ZZN7Private11member_funcEvE8__range1 = internal constant ptr @_ZZN7Private11member_funcEvE2p1, align 8
+// CHECK: @_ZZN7Private11member_funcEvE8__begin1 = internal constant ptr @_ZN7Private8integersE, align 8
+// CHECK: @_ZN7Private8integersE = {{.*}} constant %struct.Array { [3 x i32] [i32 1, i32 2, i32 3] }, comdat, align 4
+// CHECK: @_ZZN7Private11member_funcEvE6__end1 = internal constant ptr getelementptr (i8, ptr @_ZN7Private8integersE, i64 12), align 8
+// CHECK: @_ZZ15custom_iteratorvE1c = internal constant %struct.CustomIterator zeroinitializer, align 1
+// CHECK: @_ZZ15custom_iteratorvE8__range1 = internal constant ptr @_ZZ15custom_iteratorvE1c, align 8
+// CHECK: @_ZZ15custom_iteratorvE8__begin1 = internal constant %"struct.CustomIterator::iterator" { i32 1 }, align 4
+// CHECK: @_ZZ15custom_iteratorvE6__end1 = internal constant %"struct.CustomIterator::iterator" { i32 5 }, align 4
+// CHECK: @_ZZ15custom_iteratorvE8__range1_0 = internal constant ptr @_ZZ15custom_iteratorvE1c, align 8
+// CHECK: @_ZZ15custom_iteratorvE8__begin1_0 = internal constant %"struct.CustomIterator::iterator" { i32 1 }, align 4
+// CHECK: @_ZZ15custom_iteratorvE6__end1_0 = internal constant %"struct.CustomIterator::iterator" { i32 5 }, align 4
+
+
+// CHECK-LABEL: define {{.*}} i32 @_Z2f1v()
+// CHECK: entry:
+// CHECK-NEXT:   %sum = alloca i32, align 4
+// CHECK-NEXT:   %x = alloca i32, align 4
+// CHECK-NEXT:   %x1 = alloca i32, align 4
+// CHECK-NEXT:   %x4 = alloca i32, align 4
+// CHECK-NEXT:   store i32 0, ptr %sum, align 4
+// CHECK-NEXT:   %0 = load i32, ptr @_ZZ2f1vE8integers, align 4
+// CHECK-NEXT:   store i32 %0, ptr %x, align 4
+// CHECK-NEXT:   %1 = load i32, ptr %x, align 4
+// CHECK-NEXT:   %2 = load i32, ptr %sum, align 4
+// CHECK-NEXT:   %add = add nsw i32 %2, %1
+// CHECK-NEXT:   store i32 %add, ptr %sum, align 4
+// CHECK-NEXT:   br label %expand.next
+// CHECK: expand.next:
+// CHECK-NEXT:   %3 = load i32, ptr getelementptr inbounds (i32, ptr @_ZZ2f1vE8integers, i64 1), align 4
+// CHECK-NEXT:   store i32 %3, ptr %x1, align 4
+// CHECK-NEXT:   %4 = load i32, ptr %x1, align 4
+// CHECK-NEXT:   %5 = load i32, ptr %sum, align 4
+// CHECK-NEXT:   %add2 = add nsw i32 %5, %4
+// CHECK-NEXT:   store i32 %add2, ptr %sum, align 4
+// CHECK-NEXT:   br label %expand.next3
+// CHECK: expand.next3:
+// CHECK-NEXT:   %6 = load i32, ptr getelementptr inbounds (i32, ptr @_ZZ2f1vE8integers, i64 2), align 4
+// CHECK-NEXT:   store i32 %6, ptr %x4, align 4
+// CHECK-NEXT:   %7 = load i32, ptr %x4, align 4
+// CHECK-NEXT:   %8 = load i32, ptr %sum, align 4
+// CHECK-NEXT:   %add5 = add nsw i32 %8, %7
+// CHECK-NEXT:   store i32 %add5, ptr %sum, align 4
+// CHECK-NEXT:   br label %expand.end
+// CHECK: expand.end:
+// CHECK-NEXT:   %9 = load i32, ptr %sum, align 4
+// CHECK-NEXT:   ret i32 %9
+
+
+// CHECK-LABEL: define {{.*}} i32 @_Z2f2v()
+// CHECK: entry:
+// CHECK-NEXT:   %sum = alloca i32, align 4
+// CHECK-NEXT:   %x = alloca i32, align 4
+// CHECK-NEXT:   %x1 = alloca i32, align 4
+// CHECK-NEXT:   %x4 = alloca i32, align 4
+// CHECK-NEXT:   store i32 0, ptr %sum, align 4
+// CHECK-NEXT:   store i32 1, ptr %x, align 4
+// CHECK-NEXT:   %0 = load i32, ptr %sum, align 4
+// CHECK-NEXT:   %add = add nsw i32 %0, 1
+// CHECK-NEXT:   store i32 %add, ptr %sum, align 4
+// CHECK-NEXT:   br label %expand.next
+// CHECK: expand.next:
+// CHECK-NEXT:   store i32 2, ptr %x1, align 4
+// CHECK-NEXT:   %1 = load i32, ptr %sum, align 4
+// CHECK-NEXT:   %add2 = add nsw i32 %1, 2
+// CHECK-NEXT:   store i32 %add2, ptr %sum, align 4
+// CHECK-NEXT:   br label %expand.next3
+// CHECK: expand.next3:
+// CHECK-NEXT:   store i32 3, ptr %x4, align 4
+// CHECK-NEXT:   %2 = load i32, ptr %sum, align 4
+// CHECK-NEXT:   %add5 = add nsw i32 %2, 3
+// CHECK-NEXT:   store i32 %add5, ptr %sum, align 4
+// CHECK-NEXT:   br label %expand.end
+// CHECK: expand.end:
+// CHECK-NEXT:   %3 = load i32, ptr %sum, align 4
+// CHECK-NEXT:   ret i32 %3
+
+
+// CHECK-LABEL: define {{.*}} i32 @_Z2f3v()
+// CHECK: entry:
+// CHECK-NEXT:   %sum = alloca i32, align 4
+// CHECK-NEXT:   store i32 0, ptr %sum, align 4
+// CHECK-NEXT:   %0 = load i32, ptr %sum, align 4
+// CHECK-NEXT:   ret i32 %0
+
+
+// CHECK-LABEL: define {{.*}} i32 @_Z2f4v()
+// CHECK: entry:
+// CHECK-NEXT:   %sum = alloca i32, align 4
+// CHECK-NEXT:   %x = alloca i32, align 4
+// CHECK-NEXT:   %y = alloca i32, align 4
+// CHECK-NEXT:   %y2 = alloca i32, align 4
+// CHECK-NEXT:   %x6 = alloca i32, align 4
+// CHECK-NEXT:   %y7 = alloca i32, align 4
+// CHECK-NEXT:   %y11 = alloca i32, align 4
+// CHECK-NEXT:   %x16 = alloca i32, align 4
+// CHECK-NEXT:   %y17 = alloca i32, align 4
+// CHECK-NEXT:   %y20 = alloca i32, align 4
+// CHECK-NEXT:   %x24 = alloca i32, align 4
+// CHECK-NEXT:   %y25 = alloca i32, align 4
+// CHECK-NEXT:   %y28 = alloca i32, align 4
+// CHECK-NEXT:   store i32 0, ptr %sum, align 4
+// CHECK-NEXT:   %0 = load i32, ptr @_ZZ2f4vE1a, align 4
+// CHECK-NEXT:   store i32 %0, ptr %x, align 4
+// CHECK-NEXT:   %1 = load i32, ptr @_ZZ2f4vE1b, align 4
+// CHECK-NEXT:   store i32 %1, ptr %y, align 4
+// CHECK-NEXT:   %2 = load i32, ptr %x, align 4
+// CHECK-NEXT:   %3 = load i32, ptr %y, align 4
+// CHECK-NEXT:   %add = add nsw i32 %2, %3
+// CHECK-NEXT:   %4 = load i32, ptr %sum, align 4
+// CHECK-NEXT:   %add1 = add nsw i32 %4, %add
+// CHECK-NEXT:   store i32 %add1, ptr %sum, align 4
+// CHECK-NEXT:   br label %expand.next
+// CHECK: expand.next:
+// CHECK-NEXT:   %5 = load i32, ptr getelementptr inbounds (i32, ptr @_ZZ2f4vE1b, i64 1), align 4
+// CHECK-NEXT:   store i32 %5, ptr %y2, align 4
+// CHECK-NEXT:   %6 = load i32, ptr %x, align 4
+// CHECK-NEXT:   %7 = load i32, ptr %y2, align 4
+// CHECK-NEXT:   %add3 = add nsw i32 %6, %7
+// CHECK-NEXT:   %8 = load i32, ptr %sum, align 4
+// CHECK-NEXT:   %add4 = add nsw i32 %8, %add3
+// CHECK-NEXT:   store i32 %add4, ptr %sum, align 4
+// CHECK-NEXT:   br label %expand.end
+// CHECK: expand.end:
+// CHECK-NEXT:   br label %expand.next5
+// CHECK: expand.next5:
+// CHECK-NEXT:   %9 = load i32, ptr getelementptr inbounds (i32, ptr @_ZZ2f4vE1a, i64 1), align 4
+// CHECK-NEXT:   store i32 %9, ptr %x6, align 4
+// CHECK-NEXT:   %10 = load i32, ptr @_ZZ2f4vE1b, align 4
+// CHECK-NEXT:   store i32 %10, ptr %y7, align 4
+// CHECK-NEXT:   %11 = load i32, ptr %x6, align 4
+// CHECK-NEXT:   %12 = load i32, ptr %y7, align 4
+// CHECK-NEXT:   %add8 = add nsw i32 %11, %12
+// CHECK-NEXT:   %13 = load i32, ptr %sum, align 4
+// CHECK-NEXT:   %add9 = add nsw i32 %13, %add8
+// CHECK-NEXT:   store i32 %add9, ptr %sum, align 4
+// CHECK-NEXT:   br label %expand.next10
+// CHECK: expand.next10:
+// CHECK-NEXT:   %14 = load i32, ptr getelementptr inbounds (i32, ptr @_ZZ2f4vE1b, i64 1), align 4
+// CHECK-NEXT:   store i32 %14, ptr %y11, align 4
+// CHECK-NEXT:   %15 = load i32, ptr %x6, align 4
+// CHECK-NEXT:   %16 = load i32, ptr %y11, align 4
+// CHECK-NEXT:   %add12 = add nsw i32 %15, %16
+// CHECK-NEXT:   %17 = load i32, ptr %sum, align 4
+// CHECK-NEXT:   %add13 = add nsw i32 %17, %add12
+// CHECK-NEXT:   store i32 %add13, ptr %sum, align 4
+// CHECK-NEXT:   br label %expand.end14
+// CHECK: expand.end14:
+// CHECK-NEXT:   br label %expand.end15
+// CHECK: expand.end15:
+// CHECK-NEXT:   store i32 1, ptr %x16, align 4
+// CHECK-NEXT:   store i32 3, ptr %y17, align 4
+// CHECK-NEXT:   %18 = load i32, ptr %sum, align 4
+// CHECK-NEXT:   %add18 = add nsw i32 %18, 4
+// CHECK-NEXT:   store i32 %add18, ptr %sum, align 4
+// CHECK-NEXT:   br label %expand.next19
+// CHECK: expand.next19:
+// CHECK-NEXT:   store i32 4, ptr %y20, align 4
+// CHECK-NEXT:   %19 = load i32, ptr %sum, align 4
+// CHECK-NEXT:   %add21 = add nsw i32 %19, 5
+// CHECK-NEXT:   store i32 %add21, ptr %sum, align 4
+// CHECK-NEXT:   br label %expand.end22
+// CHECK: expand.end22:
+// CHECK-NEXT:   br label %expand.next23
+// CHECK: expand.next23:
+// CHECK-NEXT:   store i32 2, ptr %x24, align 4
+// CHECK-NEXT:   store i32 3, ptr %y25, align 4
+// CHECK-NEXT:   %20 = load i32, ptr %sum, align 4
+// CHECK-NEXT:   %add26 = add nsw i32 %20, 5
+// CHECK-NEXT:   store i32 %add26, ptr %sum, align 4
+// CHECK-NEXT:   br label %expand.next27
+// CHECK: expand.next27:
+// CHECK-NEXT:   store i32 4, ptr %y28, align 4
+// CHECK-NEXT:   %21 = load i32, ptr %sum, align 4
+// CHECK-NEXT:   %add29 = add nsw i32 %21, 6
+// CHECK-NEXT:   store i32 %add29, ptr %sum, align 4
+// CHECK-NEXT:   br label %expand.end30
+// CHECK: expand.end30:
+// CHECK-NEXT:   br label %expand.end31
+// CHECK: expand.end31:
+// CHECK-NEXT:   %22 = load i32, ptr %sum, align 4
+// CHECK-NEXT:   ret i32 %22
+
+
+// CHECK-LABEL: define {{.*}} i32 @_ZN7Private11member_funcEv()
+// CHECK: entry:
+// CHECK-NEXT:   %sum = alloca i32, align 4
+// CHECK-NEXT:   %x = alloca i32, align 4
+// CHECK-NEXT:   %x1 = alloca i32, align 4
+// CHECK-NEXT:   %x4 = alloca i32, align 4
+// CHECK-NEXT:   store i32 0, ptr %sum, align 4
+// CHECK-NEXT:   %0 = load i32, ptr @_ZN7Private8integersE, align 4
+// CHECK-NEXT:   store i32 %0, ptr %x, align 4
+// CHECK-NEXT:   %1 = load i32, ptr %x, align 4
+// CHECK-NEXT:   %2 = load i32, ptr %sum, align 4
+// CHECK-NEXT:   %add = add nsw i32 %2, %1
+// CHECK-NEXT:   store i32 %add, ptr %sum, align 4
+// CHECK-NEXT:   br label %expand.next
+// CHECK: expand.next:
+// CHECK-NEXT:   %3 = load i32, ptr getelementptr inbounds (i32, ptr @_ZN7Private8integersE, i64 1), align 4
+// CHECK-NEXT:   store i32 %3, ptr %x1, align 4
+// CHECK-NEXT:   %4 = load i32, ptr %x1, align 4
+// CHECK-NEXT:   %5 = load i32, ptr %sum, align 4
+// CHECK-NEXT:   %add2 = add nsw i32 %5, %4
+// CHECK-NEXT:   store i32 %add2, ptr %sum, align 4
+// CHECK-NEXT:   br label %expand.next3
+// CHECK: expand.next3:
+// CHECK-NEXT:   %6 = load i32, ptr getelementptr inbounds (i32, ptr @_ZN7Private8integersE, i64 2), align 4
+// CHECK-NEXT:   store i32 %6, ptr %x4, align 4
+// CHECK-NEXT:   %7 = load i32, ptr %x4, align 4
+// CHECK-NEXT:   %8 = load i32, ptr %sum, align 4
+// CHECK-NEXT:   %add5 = add nsw i32 %8, %7
+// CHECK-NEXT:   store i32 %add5, ptr %sum, align 4
+// CHECK-NEXT:   br label %expand.end
+// CHECK: expand.end:
+// CHECK-NEXT:   %9 = load i32, ptr %sum, align 4
+// CHECK-NEXT:   ret i32 %9
+
+
+// CHECK-LABEL: define {{.*}} i32 @_Z15custom_iteratorv()
+// CHECK: entry:
+// CHECK-NEXT:   %sum = alloca i32, align 4
+// CHECK-NEXT:   %x = alloca i32, align 4
+// CHECK: %ref.tmp = alloca %"struct.CustomIterator::iterator", align 4
+// CHECK-NEXT:   %x2 = alloca i32, align 4
+// CHECK: %ref.tmp3 = alloca %"struct.CustomIterator::iterator", align 4
+// CHECK-NEXT:   %x9 = alloca i32, align 4
+// CHECK: %ref.tmp10 = alloca %"struct.CustomIterator::iterator", align 4
+// CHECK-NEXT:   %x16 = alloca i32, align 4
+// CHECK: %ref.tmp17 = alloca %"struct.CustomIterator::iterator", align 4
+// CHECK-NEXT:   %x22 = alloca i32, align 4
+// CHECK-NEXT:   %x25 = alloca i32, align 4
+// CHECK-NEXT:   %x28 = alloca i32, align 4
+// CHECK-NEXT:   %x31 = alloca i32, align 4
+// CHECK-NEXT:   store i32 0, ptr %sum, align 4
+// CHECK-NEXT:   %call = call i32 @_ZNK14CustomIterator8iteratorplEi(ptr {{.*}} @_ZZ15custom_iteratorvE8__begin1, i32 {{.*}} 0)
+// CHECK: %coerce.dive = getelementptr inbounds nuw %"struct.CustomIterator::iterator", ptr %ref.tmp, i32 0, i32 0
+// CHECK-NEXT:   store i32 %call, ptr %coerce.dive, align 4
+// CHECK-NEXT:   %call1 = call {{.*}} i32 @_ZNK14CustomIterator8iteratordeEv(ptr {{.*}} %ref.tmp)
+// CHECK-NEXT:   store i32 %call1, ptr %x, align 4
+// CHECK-NEXT:   %0 = load i32, ptr %x, align 4
+// CHECK-NEXT:   %1 = load i32, ptr %sum, align 4
+// CHECK-NEXT:   %add = add nsw i32 %1, %0
+// CHECK-NEXT:   store i32 %add, ptr %sum, align 4
+// CHECK-NEXT:   br label %expand.next
+// CHECK: expand.next:
+// CHECK-NEXT:   %call4 = call i32 @_ZNK14CustomIterator8iteratorplEi(ptr {{.*}} @_ZZ15custom_iteratorvE8__begin1, i32 {{.*}} 1)
+// CHECK: %coerce.dive5 = getelementptr inbounds nuw %"struct.CustomIterator::iterator", ptr %ref.tmp3, i32 0, i32 0
+// CHECK-NEXT:   store i32 %call4, ptr %coerce.dive5, align 4
+// CHECK-NEXT:   %call6 = call {{.*}} i32 @_ZNK14CustomIterator8iteratordeEv(ptr {{.*}} %ref.tmp3)
+// CHECK-NEXT:   store i32 %call6, ptr %x2, align 4
+// CHECK-NEXT:   %2 = load i32, ptr %x2, align 4
+// CHECK-NEXT:   %3 = load i32, ptr %sum, align 4
+// CHECK-NEXT:   %add7 = add nsw i32 %3, %2
+// CHECK-NEXT:   store i32 %add7, ptr %sum, align 4
+// CHECK-NEXT:   br label %expand.next8
+// CHECK: expand.next8:
+// CHECK-NEXT:   %call11 = call i32 @_ZNK14CustomIterator8iteratorplEi(ptr {{.*}} @_ZZ15custom_iteratorvE8__begin1, i32 {{.*}} 2)
+// CHECK: %coerce.dive12 = getelementptr inbounds nuw %"struct.CustomIterator::iterator", ptr %ref.tmp10, i32 0, i32 0
+// CHECK-NEXT:   store i32 %call11, ptr %coerce.dive12, align 4
+// CHECK-NEXT:   %call13 = call {{.*}} i32 @_ZNK14CustomIterator8iteratordeEv(ptr {{.*}} %ref.tmp10)
+// CHECK-NEXT:   store i32 %call13, ptr %x9, align 4
+// CHECK-NEXT:   %4 = load i32, ptr %x9, align 4
+// CHECK-NEXT:   %5 = load i32, ptr %sum, align 4
+// CHECK-NEXT:   %add14 = add nsw i32 %5, %4
+// CHECK-NEXT:   store i32 %add14, ptr %sum, align 4
+// CHECK-NEXT:   br label %expand.next15
+// CHECK: expand.next15:
+// CHECK-NEXT:   %call18 = call i32 @_ZNK14CustomIterator8iteratorplEi(ptr {{.*}} @_ZZ15custom_iteratorvE8__begin1, i32 {{.*}} 3)
+// CHECK: %coerce.dive19 = getelementptr inbounds nuw %"struct.CustomIterator::iterator", ptr %ref.tmp17, i32 0, i32 0
+// CHECK-NEXT:   store i32 %call18, ptr %coerce.dive19, align 4
+// CHECK-NEXT:   %call20 = call {{.*}} i32 @_ZNK14CustomIterator8iteratordeEv(ptr {{.*}} %ref.tmp17)
+// CHECK-NEXT:   store i32 %call20, ptr %x16, align 4
+// CHECK-NEXT:   %6 = load i32, ptr %x16, align 4
+// CHECK-NEXT:   %7 = load i32, ptr %sum, align 4
+// CHECK-NEXT:   %add21 = add nsw i32 %7, %6
+// CHECK-NEXT:   store i32 %add21, ptr %sum, align 4
+// CHECK-NEXT:   br label %expand.end
+// CHECK: expand.end:
+// CHECK-NEXT:   store i32 1, ptr %x22, align 4
+// CHECK-NEXT:   %8 = load i32, ptr %sum, align 4
+// CHECK-NEXT:   %add23 = add nsw i32 %8, 1
+// CHECK-NEXT:   store i32 %add23, ptr %sum, align 4
+// CHECK-NEXT:   br label %expand.next24
+// CHECK: expand.next24:
+// CHECK-NEXT:   store i32 2, ptr %x25, align 4
+// CHECK-NEXT:   %9 = load i32, ptr %sum, align 4
+// CHECK-NEXT:   %add26 = add nsw i32 %9, 2
+// CHECK-NEXT:   store i32 %add26, ptr %sum, align 4
+// CHECK-NEXT:   br label %expand.next27
+// CHECK: expand.next27:
+// CHECK-NEXT:   store i32 3, ptr %x28, align 4
+// CHECK-NEXT:   %10 = load i32, ptr %sum, align 4
+// CHECK-NEXT:   %add29 = add nsw i32 %10, 3
+// CHECK-NEXT:   store i32 %add29, ptr %sum, align 4
+// CHECK-NEXT:   br label %expand.next30
+// CHECK: expand.next30:
+// CHECK-NEXT:   store i32 4, ptr %x31, align 4
+// CHECK-NEXT:   %11 = load i32, ptr %sum, align 4
+// CHECK-NEXT:   %add32 = add nsw i32 %11, 4
+// CHECK-NEXT:   store i32 %add32, ptr %sum, align 4
+// CHECK-NEXT:   br label %expand.end33
+// CHECK: expand.end33:
+// CHECK-NEXT:   %12 = load i32, ptr %sum, align 4
+// CHECK-NEXT:   ret i32 %12
diff --git a/clang/test/SemaCXX/cxx2c-expansion-statements.cpp b/clang/test/SemaCXX/cxx2c-expansion-statements.cpp
index 4991fdb7abd72..b975c954e46c6 100644
--- a/clang/test/SemaCXX/cxx2c-expansion-statements.cpp
+++ b/clang/test/SemaCXX/cxx2c-expansion-statements.cpp
@@ -102,3 +102,256 @@ void f2() {
   t2<S[1231]>();
   t2<S***>();
 }
+
+template <__SIZE_TYPE__ size>
+struct String {
+  char data[size];
+
+  template <__SIZE_TYPE__ n>
+  constexpr String(const char (&str)[n]) { __builtin_memcpy(data, str, n); }
+
+  constexpr const char* begin() const { return data; }
+  constexpr const char* end() const { return data + size - 1; }
+};
+
+template <__SIZE_TYPE__ n>
+String(const char (&str)[n]) -> String<n>;
+
+constexpr int f3() {
+  static constexpr String s{"abcd"};
+  int count = 0;
+  template for (constexpr auto x : s) count++;
+  return count;
+}
+
+static_assert(f3() == 4);
+
+void f4() {
+  static constexpr String empty{""};
+  static constexpr String s{"abcd"};
+  template for (auto x : empty) static_assert(false, "not expanded");
+  template for (constexpr auto x : s) g(x);
+  template for (auto x : s) g(x);
+}
+
+struct NegativeSize {
+  static constexpr const char* str = "123";
+  constexpr const char* begin() const { return str + 3; }
+  constexpr const char* end() const { return str; }
+};
+
+template <typename T, __SIZE_TYPE__ size>
+struct Array {
+  T data[size]{};
+  constexpr const T* begin() const { return data; }
+  constexpr const T* end() const { return data + size; }
+};
+
+void expansion_size() {
+  static constexpr Array<int, 32> almost_too_big;
+  template for (auto x : almost_too_big) g(x);
+  template for (constexpr auto x : almost_too_big) g(x);
+
+  static constexpr Array<int, 33> too_big;
+  template for (auto x : too_big) g(x); // expected-error {{expansion size 33 exceeds maximum configured size 32}} expected-note {{use -fexpansion-limit=N to adjust this limit}}
+  template for (constexpr auto x : too_big) g(x); // expected-error {{expansion size 33 exceeds maximum configured size 32}} expected-note {{use -fexpansion-limit=N to adjust this limit}}
+
+  static constexpr String big{"1234567890123456789012345678901234567890234567890"};
+  template for (auto x : big) g(x); // expected-error {{expansion size 49 exceeds maximum configured size 32}} expected-note {{use -fexpansion-limit=N to adjust this limit}}
+  template for (constexpr auto x : big) g(x); // expected-error {{expansion size 49 exceeds maximum configured size 32}} expected-note {{use -fexpansion-limit=N to adjust this limit}}
+
+  static constexpr NegativeSize n;
+  template for (auto x : n) g(x); // expected-error {{expansion size must not be negative (was -3)}}
+  template for (constexpr auto x : n) g(x); // expected-error {{expansion size must not be negative (was -3)}}
+
+  template for (auto x : {1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
+    11, 12, 13, 14, 15, 16, 17, 18, 19, 20,
+    21, 22, 23, 24, 25, 26, 27, 28, 29, 30,
+    31, 32}) g(x);
+  template for (constexpr auto x : {1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
+    11, 12, 13, 14, 15, 16, 17, 18, 19, 20,
+    21, 22, 23, 24, 25, 26, 27, 28, 29, 30,
+    31, 32}) g(x);
+
+  template for (auto x : {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, // expected-error {{expansion size 33 exceeds maximum configured size 32}} expected-note {{use -fexpansion-limit=N to adjust this limit}}
+    11, 12, 13, 14, 15, 16, 17, 18, 19, 20,
+    21, 22, 23, 24, 25, 26, 27, 28, 29, 30,
+    31, 32, 33}) g(x);
+  template for (constexpr auto x : {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, // expected-error {{expansion size 33 exceeds maximum configured size 32}} expected-note {{use -fexpansion-limit=N to adjust this limit}}
+    11, 12, 13, 14, 15, 16, 17, 18, 19, 20,
+    21, 22, 23, 24, 25, 26, 27, 28, 29, 30,
+    31, 32, 33}) g(x);
+}
+
+struct NotInt {
+  struct iterator {};
+  constexpr iterator begin() const { return {}; }
+  constexpr iterator end() const { return {}; }
+};
+
+void not_int() {
+  static constexpr NotInt ni;
+  template for (auto x : ni) g(x); // expected-error {{invalid operands to binary expression}}
+}
+
+static constexpr Array<int, 3> integers{1, 2, 3};
+
+constexpr int friend_func();
+
+struct Private {
+  friend constexpr int friend_func();
+
+private:
+  constexpr const int* begin() const { return integers.begin(); } // expected-note 3 {{declared private here}}
+  constexpr const int* end() const { return integers.end(); } // expected-note 1 {{declared private here}}
+
+public:
+  static constexpr int member_func() {
+    int sum = 0;
+    static constexpr Private p1;
+    template for (auto x : p1) sum += x;
+    return sum;
+  }
+};
+
+struct Protected {
+  friend constexpr int friend_func();
+
+protected:
+  constexpr const int* begin() const { return integers.begin(); } // expected-note 3 {{declared protected here}}
+  constexpr const int* end() const { return integers.end(); } // expected-note 1 {{declared protected here}}
+
+public:
+  static constexpr int member_func() {
+    int sum = 0;
+    static constexpr Protected p1;
+    template for (auto x : p1) sum += x;
+    return sum;
+  }
+};
+
+void access_control() {
+  static constexpr Private p1;
+  template for (auto x : p1) g(x); // expected-error 3 {{'begin' is a private member of 'Private'}} expected-error 1 {{'end' is a private member of 'Private'}}
+
+  static constexpr Protected p2;
+  template for (auto x : p2) g(x); // expected-error 3 {{'begin' is a protected member of 'Protected'}} expected-error 1 {{'end' is a protected member of 'Protected'}}
+}
+
+constexpr int friend_func() {
+  int sum = 0;
+  static constexpr Private p1;
+  template for (auto x : p1) sum += x;
+
+  static constexpr Protected p2;
+  template for (auto x : p2) sum += x;
+  return sum;
+}
+
+static_assert(friend_func() == 12);
+static_assert(Private::member_func() == 6);
+static_assert(Protected::member_func() == 6);
+
+struct SizeNotICE {
+  struct iterator {
+    friend constexpr iterator operator+(iterator a, __PTRDIFF_TYPE__) { return a; }
+    int constexpr operator*() const { return 7; }
+
+    // NOT constexpr!
+    friend int operator-(iterator, iterator) { return 7; } // expected-note {{declared here}}
+    friend int operator!=(iterator, iterator) { return 7; }
+  };
+  constexpr iterator begin() const { return {}; }
+  constexpr iterator end() const { return {}; }
+};
+
+struct PlusMissing {
+  struct iterator {
+    int constexpr operator*() const { return 7; }
+  };
+  constexpr iterator begin() const { return {}; }
+  constexpr iterator end() const { return {}; }
+};
+
+struct DerefMissing {
+  struct iterator {
+    friend constexpr iterator operator+(iterator a, __PTRDIFF_TYPE__) { return a; }
+  };
+  constexpr iterator begin() const { return {}; }
+  constexpr iterator end() const { return {}; }
+};
+
+void missing_funcs() {
+  static constexpr SizeNotICE s1;
+  static constexpr PlusMissing s2;
+  static constexpr DerefMissing s3;
+
+  // TODO: This message should start complaining about '!=' once we support the
+  // proper way of computing the size.
+  template for (auto x : s1) g(x); // expected-error {{expansion size is not a constant expression}} \
+                                      expected-note {{non-constexpr function 'operator-' cannot be used in a constant expression}}
+
+  template for (auto x : s2) g(x); // expected-error {{invalid operands to binary expression}}
+  template for (auto x : s3) g(x); // expected-error {{indirection requires pointer operand ('iterator' invalid)}}
+}
+
+namespace adl {
+struct ADL {
+
+};
+
+constexpr const int* begin(const ADL&) { return integers.begin(); }
+constexpr const int* end(const ADL&) { return integers.end(); }
+}
+
+namespace adl_error {
+struct ADLError1 {
+  constexpr const int* begin() const { return integers.begin(); } // expected-note {{member is not a candidate because range type 'const adl_error::ADLError1' has no 'end' member}}
+};
+
+struct ADLError2 {
+  constexpr const int* end() const { return integers.end(); } // expected-note {{member is not a candidate because range type 'const adl_error::ADLError2' has no 'begin' member}}
+};
+
+constexpr const int* begin(const ADLError2&) { return integers.begin(); } // expected-note {{candidate function not viable: no known conversion from 'const adl_error::ADLError1' to 'const ADLError2' for 1st argument}}
+constexpr const int* end(const ADLError1&) { return integers.end(); } // expected-note {{candidate function not viable: no known conversion from 'const adl_error::ADLError2' to 'const ADLError1' for 1st argument}}
+}
+
+namespace adl_both {
+static constexpr Array<int, 5> integers2{1, 2, 3, 4, 5};
+struct ADLBoth {
+  // Test that member begin/end are preferred over ADl begin/end. These return
+  // pointers to a different array.
+  constexpr const int* begin() const { return integers2.begin(); }
+  constexpr const int* end() const { return integers2.end(); }
+};
+
+constexpr const int* begin(const ADLBoth&) { return integers.begin(); }
+constexpr const int* end(const ADLBoth&) { return integers.end(); }
+}
+
+constexpr int adl_begin_end() {
+  static constexpr adl::ADL a;
+  int sum = 0;
+  template for (auto x : a) sum += x;
+  template for (constexpr auto x : a) sum += x;
+  return sum;
+}
+
+static_assert(adl_begin_end() == 12);
+
+void adl_mixed_error() {
+  static constexpr adl_error::ADLError1 a1;
+  static constexpr adl_error::ADLError2 a2;
+  template for (auto x : a1) g(x); // expected-error {{invalid range expression of type 'const adl_error::ADLError1'; no viable 'begin' function available}}
+  template for (auto x : a2) g(x); // expected-error {{invalid range expression of type 'const adl_error::ADLError2'; no viable 'end' function available}}
+}
+
+constexpr int adl_both_test() {
+  static constexpr adl_both::ADLBoth a;
+  int sum = 0;
+  template for (auto x : a) sum += x;
+  return sum;
+}
+
+static_assert(adl_both_test() == 15);
diff --git a/clang/tools/libclang/CXCursor.cpp b/clang/tools/libclang/CXCursor.cpp
index 999bd1c0d78be..150441e9b4202 100644
--- a/clang/tools/libclang/CXCursor.cpp
+++ b/clang/tools/libclang/CXCursor.cpp
@@ -291,6 +291,7 @@ CXCursor cxcursor::MakeCXCursor(const Stmt *S, const Decl *Parent,
   case Stmt::CoroutineBodyStmtClass:
   case Stmt::CoreturnStmtClass:
   case Stmt::CXXEnumeratingExpansionStmtClass:
+  case Stmt::CXXIteratingExpansionStmtClass:
   case Stmt::CXXExpansionInstantiationStmtClass:
     K = CXCursor_UnexposedStmt;
     break;

>From dbf5fecee25002ac23a6cb476cf51d700315b370 Mon Sep 17 00:00:00 2001
From: Sirraide <aeternalmail at gmail.com>
Date: Sat, 25 Oct 2025 22:29:21 +0200
Subject: [PATCH 05/33] Dependent expansion statements

---
 clang/include/clang/AST/RecursiveASTVisitor.h |   1 +
 clang/include/clang/AST/StmtCXX.h             |  40 +++++-
 clang/include/clang/Basic/StmtNodes.td        |   1 +
 clang/include/clang/Sema/Sema.h               |  14 ++-
 .../include/clang/Serialization/ASTBitCodes.h |   3 +
 clang/lib/AST/StmtCXX.cpp                     |  14 +++
 clang/lib/AST/StmtPrinter.cpp                 |  12 +-
 clang/lib/AST/StmtProfile.cpp                 |   5 +
 clang/lib/CodeGen/CGStmt.cpp                  |   1 +
 clang/lib/Parse/ParseStmt.cpp                 |   2 +-
 clang/lib/Sema/SemaDecl.cpp                   |   3 +-
 clang/lib/Sema/SemaExceptionSpec.cpp          |   1 +
 clang/lib/Sema/SemaExpand.cpp                 | 117 ++++++++----------
 clang/lib/Sema/SemaStmt.cpp                   |  26 ++--
 clang/lib/Sema/TreeTransform.h                |  27 ++++
 clang/lib/Serialization/ASTReaderStmt.cpp     |  10 ++
 clang/lib/Serialization/ASTWriterStmt.cpp     |   6 +
 clang/lib/StaticAnalyzer/Core/ExprEngine.cpp  |   1 +
 .../SemaCXX/cxx2c-expansion-statements.cpp    |  11 ++
 clang/tools/libclang/CXCursor.cpp             |   1 +
 20 files changed, 214 insertions(+), 82 deletions(-)

diff --git a/clang/include/clang/AST/RecursiveASTVisitor.h b/clang/include/clang/AST/RecursiveASTVisitor.h
index a729773581f72..de704bd9cdac1 100644
--- a/clang/include/clang/AST/RecursiveASTVisitor.h
+++ b/clang/include/clang/AST/RecursiveASTVisitor.h
@@ -3126,6 +3126,7 @@ DEF_TRAVERSE_STMT(RequiresExpr, {
 
 DEF_TRAVERSE_STMT(CXXEnumeratingExpansionStmt, {})
 DEF_TRAVERSE_STMT(CXXIteratingExpansionStmt, {})
+DEF_TRAVERSE_STMT(CXXDependentExpansionStmt, {})
 DEF_TRAVERSE_STMT(CXXExpansionInstantiationStmt, {})
 DEF_TRAVERSE_STMT(CXXExpansionInitListExpr, {})
 DEF_TRAVERSE_STMT(CXXExpansionInitListSelectExpr, {})
diff --git a/clang/include/clang/AST/StmtCXX.h b/clang/include/clang/AST/StmtCXX.h
index 856b2912b0e1e..561b9b6f276e8 100644
--- a/clang/include/clang/AST/StmtCXX.h
+++ b/clang/include/clang/AST/StmtCXX.h
@@ -621,6 +621,43 @@ class CXXEnumeratingExpansionStmt : public CXXExpansionStmt {
   }
 };
 
+/// Represents an expansion statement whose expansion-initializer is dependent.
+class CXXDependentExpansionStmt : public CXXExpansionStmt {
+  friend class ASTStmtReader;
+
+  Expr* ExpansionInitializer;
+
+public:
+  CXXDependentExpansionStmt(EmptyShell Empty);
+  CXXDependentExpansionStmt(ExpansionStmtDecl *ESD, Stmt *Init,
+                            DeclStmt *ExpansionVar, Expr *ExpansionInitializer,
+                            SourceLocation ForLoc, SourceLocation LParenLoc,
+                            SourceLocation ColonLoc, SourceLocation RParenLoc);
+
+  Expr *getExpansionInitializer() { return ExpansionInitializer; }
+  const Expr *getExpansionInitializer() const { return ExpansionInitializer; }
+  void setExpansionInitializer(Expr* S) { ExpansionInitializer = S; }
+
+  child_range children() {
+    const_child_range CCR =
+        const_cast<const CXXDependentExpansionStmt *>(this)->children();
+    return child_range(cast_away_const(CCR.begin()),
+                       cast_away_const(CCR.end()));
+  }
+
+  const_child_range children() const {
+    // See CXXIteratingExpansion statement for an explansion of this terrible
+    // hack.
+    Stmt *const *FirstParentSubStmt = CXXExpansionStmt::SubStmts;
+    unsigned Count = static_cast<unsigned>(CXXExpansionStmt::COUNT) + 1;
+    return const_child_range(FirstParentSubStmt, FirstParentSubStmt + Count);
+  }
+
+  static bool classof(const Stmt *T) {
+    return T->getStmtClass() == CXXDependentExpansionStmtClass;
+  }
+};
+
 /// Represents an unexpanded iterating expansion statement.
 ///
 /// The expression used to compute the size of the expansion is not stored in
@@ -697,7 +734,8 @@ class CXXIteratingExpansionStmt : public CXXExpansionStmt {
     // classes of CXXExpansionStmt instead or moving it into trailing data
     // would be quite a bit more complicated.
     //
-    // FIXME: There ought to be a better way of doing this.
+    // FIXME: There ought to be a better way of doing this. If we change this,
+    // we should also update CXXDependentExpansionStmt.
     Stmt *const *FirstParentSubStmt = CXXExpansionStmt::SubStmts;
     unsigned Count = static_cast<unsigned>(CXXExpansionStmt::COUNT) +
                      static_cast<unsigned>(CXXIteratingExpansionStmt::COUNT);
diff --git a/clang/include/clang/Basic/StmtNodes.td b/clang/include/clang/Basic/StmtNodes.td
index ecb2a6bdc5b39..1ca0193792338 100644
--- a/clang/include/clang/Basic/StmtNodes.td
+++ b/clang/include/clang/Basic/StmtNodes.td
@@ -62,6 +62,7 @@ def CoreturnStmt : StmtNode<Stmt>;
 def CXXExpansionStmt : StmtNode<Stmt, 1>;
 def CXXEnumeratingExpansionStmt : StmtNode<CXXExpansionStmt>;
 def CXXIteratingExpansionStmt : StmtNode<CXXExpansionStmt>;
+def CXXDependentExpansionStmt : StmtNode<CXXExpansionStmt>;
 def CXXExpansionInstantiationStmt : StmtNode<Stmt>; // *Not* derived from CXXExpansionStmt!
 
 // Expressions
diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index 1da1ddb189510..fe18f0c162550 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -11055,6 +11055,11 @@ class Sema final : public SemaBase {
       BuildForRangeKind Kind,
       ArrayRef<MaterializeTemporaryExpr *> LifetimeExtendTemps = {});
 
+  /// Set the type of a for-range declaration whose for-range or expansion
+  /// initialiser is dependent.
+  void ActOnDependentForRangeInitializer(VarDecl *LoopVar,
+                                         BuildForRangeKind BFRK);
+
   /// Holds the 'begin' and 'end' variables of a range-based for loop or
   /// expansion statement; begin-expr and end-expr are also provided; the
   /// latter are used in some diagnostics.
@@ -15676,7 +15681,7 @@ class Sema final : public SemaBase {
       ExpansionStmtDecl *ESD, Stmt *Init, Stmt *ExpansionVarStmt,
       Expr *ExpansionInitializer, SourceLocation ForLoc,
       SourceLocation LParenLoc, SourceLocation ColonLoc,
-      SourceLocation RParenLoc, BuildForRangeKind Kind,
+      SourceLocation RParenLoc,
       ArrayRef<MaterializeTemporaryExpr *> LifetimeExtendTemps);
 
   StmtResult FinishCXXExpansionStmt(Stmt *Expansion, Stmt *Body);
@@ -15693,6 +15698,13 @@ class Sema final : public SemaBase {
   BuildCXXExpansionInitListSelectExpr(CXXExpansionInitListExpr *Range,
                                       Expr *Idx);
 
+  StmtResult BuildNonEnumeratingCXXExpansionStmt(
+      ExpansionStmtDecl *ESD, Stmt *Init, DeclStmt *ExpansionVarStmt,
+      Expr *ExpansionInitializer, SourceLocation ForLoc,
+      SourceLocation LParenLoc, SourceLocation ColonLoc,
+      SourceLocation RParenLoc,
+      ArrayRef<MaterializeTemporaryExpr *> LifetimeExtendTemps = {});
+
   std::optional<uint64_t> ComputeExpansionSize(CXXExpansionStmt *Expansion);
   ///@}
 };
diff --git a/clang/include/clang/Serialization/ASTBitCodes.h b/clang/include/clang/Serialization/ASTBitCodes.h
index f3410f593c4af..8102d40200296 100644
--- a/clang/include/clang/Serialization/ASTBitCodes.h
+++ b/clang/include/clang/Serialization/ASTBitCodes.h
@@ -1842,6 +1842,9 @@ enum StmtCode {
   /// A CXXIteratingExpansionStmt.
   STMT_CXX_ITERATING_EXPANSION,
 
+  /// A CXXDependentExpansionStmt,
+  STMT_CXX_DEPENDENT_EXPANSION,
+
   /// A CXXExpansionInstantiationStmt.
   STMT_CXX_EXPANSION_INSTANTIATION,
 
diff --git a/clang/lib/AST/StmtCXX.cpp b/clang/lib/AST/StmtCXX.cpp
index 515b06bb0e917..49dd46b00517b 100644
--- a/clang/lib/AST/StmtCXX.cpp
+++ b/clang/lib/AST/StmtCXX.cpp
@@ -178,6 +178,9 @@ bool CXXExpansionStmt::hasDependentSize() const {
            End->isTypeDependent() || End->isValueDependent();
   }
 
+  if (isa<CXXDependentExpansionStmt>(this))
+    return true;
+
   llvm_unreachable("Invalid expansion statement class");
 }
 
@@ -195,6 +198,17 @@ CXXIteratingExpansionStmt::CXXIteratingExpansionStmt(
   SubStmts[RANGE] = Range;
 }
 
+CXXDependentExpansionStmt::CXXDependentExpansionStmt(EmptyShell Empty)
+  : CXXExpansionStmt(CXXDependentExpansionStmtClass, Empty) {}
+
+CXXDependentExpansionStmt::CXXDependentExpansionStmt(
+    ExpansionStmtDecl *ESD, Stmt *Init, DeclStmt *ExpansionVar,
+    Expr *ExpansionInitializer, SourceLocation ForLoc, SourceLocation LParenLoc,
+    SourceLocation ColonLoc, SourceLocation RParenLoc)
+    : CXXExpansionStmt(CXXDependentExpansionStmtClass, ESD, Init, ExpansionVar,
+                       ForLoc, LParenLoc, ColonLoc, RParenLoc),
+      ExpansionInitializer(ExpansionInitializer) {}
+
 CXXExpansionInstantiationStmt::CXXExpansionInstantiationStmt(
     EmptyShell Empty, unsigned NumInstantiations, unsigned NumSharedStmts)
     : Stmt(CXXExpansionInstantiationStmtClass, Empty),
diff --git a/clang/lib/AST/StmtPrinter.cpp b/clang/lib/AST/StmtPrinter.cpp
index 6de6894f2c5e3..ffb2fbcd5d0e0 100644
--- a/clang/lib/AST/StmtPrinter.cpp
+++ b/clang/lib/AST/StmtPrinter.cpp
@@ -160,7 +160,7 @@ namespace {
     }
 
     void VisitCXXNamedCastExpr(CXXNamedCastExpr *Node);
-    void VisitCXXExpansionStmt(CXXExpansionStmt* Node);
+    void VisitCXXExpansionStmt(CXXExpansionStmt* Node, Expr* Initializer = nullptr);
 
 #define ABSTRACT_STMT(CLASS)
 #define STMT(CLASS, PARENT) \
@@ -448,7 +448,7 @@ void StmtPrinter::VisitCXXForRangeStmt(CXXForRangeStmt *Node) {
   PrintControlledStmt(Node->getBody());
 }
 
-void StmtPrinter::VisitCXXExpansionStmt(CXXExpansionStmt *Node) {
+void StmtPrinter::VisitCXXExpansionStmt(CXXExpansionStmt *Node, Expr* Initializer) {
   Indent() << "template for (";
   if (Node->getInit())
     PrintInitStmt(Node->getInit(), 14);
@@ -456,7 +456,8 @@ void StmtPrinter::VisitCXXExpansionStmt(CXXExpansionStmt *Node) {
   SubPolicy.SuppressInitializers = true;
   Node->getExpansionVariable()->print(OS, SubPolicy, IndentLevel);
   OS << ":";
-  PrintExpr(Node->getExpansionVariable()->getInit());
+  PrintExpr(Initializer ? Initializer
+                        : Node->getExpansionVariable()->getInit());
   OS << ")";
   PrintControlledStmt(Node->getBody());
 }
@@ -471,6 +472,11 @@ void StmtPrinter::VisitCXXIteratingExpansionStmt(
   VisitCXXExpansionStmt(Node);
 }
 
+void StmtPrinter::VisitCXXDependentExpansionStmt(
+    CXXDependentExpansionStmt *Node) {
+  VisitCXXExpansionStmt(Node, Node->getExpansionInitializer());
+}
+
 void StmtPrinter::VisitCXXExpansionInstantiationStmt(
     CXXExpansionInstantiationStmt *) {
   llvm_unreachable("should never be printed");
diff --git a/clang/lib/AST/StmtProfile.cpp b/clang/lib/AST/StmtProfile.cpp
index a834a6eb61b97..8230cc3b774b7 100644
--- a/clang/lib/AST/StmtProfile.cpp
+++ b/clang/lib/AST/StmtProfile.cpp
@@ -378,6 +378,11 @@ void StmtProfiler::VisitCXXIteratingExpansionStmt(
   VisitCXXExpansionStmt(S);
 }
 
+void StmtProfiler::VisitCXXDependentExpansionStmt(
+    const CXXDependentExpansionStmt *S) {
+  VisitCXXExpansionStmt(S);
+}
+
 void StmtProfiler::VisitCXXExpansionInstantiationStmt(
     const CXXExpansionInstantiationStmt *S) {
   VisitStmt(S);
diff --git a/clang/lib/CodeGen/CGStmt.cpp b/clang/lib/CodeGen/CGStmt.cpp
index f1b18d7014c86..a240e209c5a63 100644
--- a/clang/lib/CodeGen/CGStmt.cpp
+++ b/clang/lib/CodeGen/CGStmt.cpp
@@ -206,6 +206,7 @@ void CodeGenFunction::EmitStmt(const Stmt *S, ArrayRef<const Attr *> Attrs) {
     break;
   case Stmt::CXXEnumeratingExpansionStmtClass:
   case Stmt::CXXIteratingExpansionStmtClass:
+  case Stmt::CXXDependentExpansionStmtClass:
     llvm_unreachable("unexpanded expansion statements should not be emitted");
   case Stmt::CXXExpansionInstantiationStmtClass:
     EmitCXXExpansionInstantiationStmt(cast<CXXExpansionInstantiationStmt>(*S));
diff --git a/clang/lib/Parse/ParseStmt.cpp b/clang/lib/Parse/ParseStmt.cpp
index 0f73d13dc9fe6..913a993823d27 100644
--- a/clang/lib/Parse/ParseStmt.cpp
+++ b/clang/lib/Parse/ParseStmt.cpp
@@ -2251,7 +2251,7 @@ StmtResult Parser::ParseForStatement(SourceLocation *TrailingElseLoc,
     ForRangeStmt = Actions.ActOnCXXExpansionStmt(
         ExpansionStmtDeclaration, FirstPart.get(), ForRangeInfo.LoopVar.get(),
         ForRangeInfo.RangeExpr.get(), ForLoc, T.getOpenLocation(),
-        ForRangeInfo.ColonLoc, T.getCloseLocation(), Sema::BFRK_Build,
+        ForRangeInfo.ColonLoc, T.getCloseLocation(),
         ForRangeInfo.LifetimeExtendTemps);
   } else if (ForRangeInfo.ParsedForRangeDecl()) {
     ForRangeStmt = Actions.ActOnCXXForRangeStmt(
diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp
index 873ddc1c3950d..338a026b46322 100644
--- a/clang/lib/Sema/SemaDecl.cpp
+++ b/clang/lib/Sema/SemaDecl.cpp
@@ -14603,8 +14603,7 @@ void Sema::ActOnCXXForRangeDecl(Decl *D, bool InExpansionStmt) {
     return;
   }
 
-  if (!InExpansionStmt)
-    VD->setCXXForRangeDecl(true);
+  VD->setCXXForRangeDecl(true);
 
   // for-range-declaration cannot be given a storage class specifier.
   int Error = -1;
diff --git a/clang/lib/Sema/SemaExceptionSpec.cpp b/clang/lib/Sema/SemaExceptionSpec.cpp
index 69c3dbc4eeb31..86dc25d0e38ee 100644
--- a/clang/lib/Sema/SemaExceptionSpec.cpp
+++ b/clang/lib/Sema/SemaExceptionSpec.cpp
@@ -1350,6 +1350,7 @@ CanThrowResult Sema::canThrow(const Stmt *S) {
   case Expr::DependentScopeDeclRefExprClass:
   case Expr::CXXFoldExprClass:
   case Expr::RecoveryExprClass:
+  case Expr::CXXDependentExpansionStmtClass:
     return CT_Dependent;
 
   case Expr::AsTypeExprClass:
diff --git a/clang/lib/Sema/SemaExpand.cpp b/clang/lib/Sema/SemaExpand.cpp
index dfb52b39d5ec7..4147b99f1c4d8 100644
--- a/clang/lib/Sema/SemaExpand.cpp
+++ b/clang/lib/Sema/SemaExpand.cpp
@@ -45,33 +45,23 @@ struct IterableExpansionStmtData {
 };
 } // namespace
 
-  /*
-// 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;
-// }()
-//
-// 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'.
-auto CreateBeginDRE = [&] {
-  return S.BuildDeclRefExpr(Info.BeginVar,
-                            Info.BeginVar->getType().getNonReferenceType(),
-                            VK_LValue, ColonLoc);
-};
+// Build a 'DeclRefExpr' designating the template parameter '__N'.
+static DeclRefExpr *BuildIndexDRE(Sema &S, ExpansionStmtDecl *ESD) {
+  return S.BuildDeclRefExpr(ESD->getIndexTemplateParm(),
+                            S.Context.getPointerDiffType(), VK_PRValue,
+                            ESD->getBeginLoc());
+}
 
-DeclRefExpr *Begin = CreateBeginDRE();
-DeclRefExpr *End = S.BuildDeclRefExpr(
-    Info.EndVar, Info.EndVar->getType().getNonReferenceType(), VK_LValue,
-    ColonLoc);
+static bool FinaliseExpansionVar(Sema &S, VarDecl *ExpansionVar,
+                                 ExprResult Initializer) {
+  if (Initializer.isInvalid()) {
+    S.ActOnInitializerError(ExpansionVar);
+    return true;
+  }
 
-ExprResult N = S.ActOnBinOp(Scope, ColonLoc, tok::minus, Begin, End);
-if (N.isInvalid())
-  return ExprError();
-*/
+  S.AddInitializerToDecl(ExpansionVar, Initializer.get(), /*DirectInit=*/false);
+  return ExpansionVar->isInvalidDecl();
+}
 
 static IterableExpansionStmtData
 TryBuildIterableExpansionStmtInitializer(Sema &S, Expr *ExpansionInitializer,
@@ -225,10 +215,9 @@ ExprResult Sema::ActOnCXXExpansionInitList(MultiExprArg SubExprs,
 StmtResult Sema::ActOnCXXExpansionStmt(
     ExpansionStmtDecl *ESD, Stmt *Init, Stmt *ExpansionVarStmt,
     Expr *ExpansionInitializer, SourceLocation ForLoc, SourceLocation LParenLoc,
-    SourceLocation ColonLoc, SourceLocation RParenLoc, BuildForRangeKind Kind,
+    SourceLocation ColonLoc, SourceLocation RParenLoc,
     ArrayRef<MaterializeTemporaryExpr *> LifetimeExtendTemps) {
-  // TODO: Do we actually need a BuildForRangeKind here at all?
-  if (!ExpansionInitializer || !ExpansionVarStmt || Kind == BFRK_Check)
+  if (!ExpansionInitializer || !ExpansionVarStmt)
     return StmtError();
 
   assert(CurContext->isExpansionStmt());
@@ -243,26 +232,12 @@ StmtResult Sema::ActOnCXXExpansionStmt(
       ExpansionInitializer->containsErrors())
     return StmtError();
 
-  auto FinaliseExpansionVar = [&](ExprResult Initializer) {
-    if (Initializer.isInvalid()) {
-      ActOnInitializerError(ExpansionVar);
-      return true;
-    }
-
-    AddInitializerToDecl(ExpansionVar, Initializer.get(), /*DirectInit=*/false);
-    return ExpansionVar->isInvalidDecl();
-  };
-
-  // Build a 'DeclRefExpr' designating the template parameter '__N'.
-  DeclRefExpr *Index = BuildDeclRefExpr(ESD->getIndexTemplateParm(),
-                                        Context.getPointerDiffType(),
-                                        VK_PRValue, ESD->getBeginLoc());
-
   // This is an enumerating expansion statement.
   if (auto *ILE = dyn_cast<CXXExpansionInitListExpr>(ExpansionInitializer)) {
 
-    ExprResult Initializer = BuildCXXExpansionInitListSelectExpr(ILE, Index);
-    if (FinaliseExpansionVar(Initializer))
+    ExprResult Initializer =
+        BuildCXXExpansionInitListSelectExpr(ILE, BuildIndexDRE(*this, ESD));
+    if (FinaliseExpansionVar(*this, ExpansionVar, Initializer))
       return StmtError();
 
     // Note that lifetime extension only applies to destructurable expansion
@@ -272,41 +247,57 @@ StmtResult Sema::ActOnCXXExpansionStmt(
                                             ColonLoc, RParenLoc);
   }
 
-  if (ExpansionInitializer->isTypeDependent())
-    llvm_unreachable("TODO: Dependent expansion initializer");
+  return BuildNonEnumeratingCXXExpansionStmt(
+      ESD, Init, DS, ExpansionInitializer, ForLoc, LParenLoc, ColonLoc,
+      RParenLoc, LifetimeExtendTemps);
+}
+
+StmtResult Sema::BuildCXXEnumeratingExpansionStmt(Decl *ESD, Stmt *Init,
+                                                  Stmt *ExpansionVar,
+                                                  SourceLocation ForLoc,
+                                                  SourceLocation LParenLoc,
+                                                  SourceLocation ColonLoc,
+                                                  SourceLocation RParenLoc) {
+  return new (Context) CXXEnumeratingExpansionStmt(
+      cast<ExpansionStmtDecl>(ESD), Init, cast<DeclStmt>(ExpansionVar), ForLoc,
+      LParenLoc, ColonLoc, RParenLoc);
+}
+
+StmtResult Sema::BuildNonEnumeratingCXXExpansionStmt(
+    ExpansionStmtDecl *ESD, Stmt *Init, DeclStmt *ExpansionVarStmt,
+    Expr *ExpansionInitializer, SourceLocation ForLoc, 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) CXXDependentExpansionStmt(
+        ESD, Init, ExpansionVarStmt, ExpansionInitializer, ForLoc, LParenLoc,
+        ColonLoc, RParenLoc);
+  }
 
   // Otherwise, if it can be an iterating expansion statement, it is one.
   IterableExpansionStmtData Data = TryBuildIterableExpansionStmtInitializer(
-      *this, ExpansionInitializer, Index, ColonLoc, ExpansionVar->isConstexpr());
+      *this, ExpansionInitializer, BuildIndexDRE(*this, ESD), ColonLoc,
+      ExpansionVar->isConstexpr());
   if (Data.hasError()) {
     ActOnInitializerError(ExpansionVar);
     return StmtError();
   }
 
   if (Data.isIterable()) {
-    if (FinaliseExpansionVar(Data.Initializer))
+    if (FinaliseExpansionVar(*this, ExpansionVar, Data.Initializer))
       return StmtError();
 
     return new (Context) CXXIteratingExpansionStmt(
-        ESD, Init, DS, Data.RangeDecl, Data.BeginDecl, Data.EndDecl, ForLoc,
-        LParenLoc, ColonLoc, RParenLoc);
+        ESD, Init, ExpansionVarStmt, Data.RangeDecl, Data.BeginDecl,
+        Data.EndDecl, ForLoc, LParenLoc, ColonLoc, RParenLoc);
   }
 
-
   llvm_unreachable("TODO: Destructuring expansion statement");
 }
 
-StmtResult Sema::BuildCXXEnumeratingExpansionStmt(Decl *ESD, Stmt *Init,
-                                                  Stmt *ExpansionVar,
-                                                  SourceLocation ForLoc,
-                                                  SourceLocation LParenLoc,
-                                                  SourceLocation ColonLoc,
-                                                  SourceLocation RParenLoc) {
-  return new (Context) CXXEnumeratingExpansionStmt(
-      cast<ExpansionStmtDecl>(ESD), Init, cast<DeclStmt>(ExpansionVar), ForLoc,
-      LParenLoc, ColonLoc, RParenLoc);
-}
-
 /*
 StmtResult Sema::BuildCXXExpansionStmt(
       ExpansionStmtDecl *ESD, Stmt *Init, Stmt *ExpansionVarStmt,
diff --git a/clang/lib/Sema/SemaStmt.cpp b/clang/lib/Sema/SemaStmt.cpp
index 56ac191f17d3a..3a6d68b2f65ae 100644
--- a/clang/lib/Sema/SemaStmt.cpp
+++ b/clang/lib/Sema/SemaStmt.cpp
@@ -2910,6 +2910,20 @@ Sema::ForRangeBeginEndInfo Sema::BuildCXXForRangeBeginEndVars(
   return {BeginVar, EndVar, BeginExpr.get(), EndExpr.get()};
 }
 
+void Sema::ActOnDependentForRangeInitializer(VarDecl *LoopVar,
+                                             BuildForRangeKind BFRK) {
+  // Deduce any 'auto's in the loop variable as 'DependentTy'. We'll fill
+  // them in properly when we instantiate the loop.
+  if (!LoopVar->isInvalidDecl() && BFRK != BFRK_Check) {
+    if (auto *DD = dyn_cast<DecompositionDecl>(LoopVar))
+      for (auto *Binding : DD->bindings()) {
+        if (!Binding->isParameterPack())
+          Binding->setType(Context.DependentTy);
+      }
+    LoopVar->setType(SubstAutoTypeDependent(LoopVar->getType()));
+  }
+}
+
 StmtResult Sema::BuildCXXForRangeStmt(
     SourceLocation ForLoc, SourceLocation CoawaitLoc, Stmt *InitStmt,
     SourceLocation ColonLoc, Stmt *RangeDecl, Stmt *Begin, Stmt *End,
@@ -2941,17 +2955,7 @@ StmtResult Sema::BuildCXXForRangeStmt(
   if (RangeVarType->isDependentType()) {
     // The range is implicitly used as a placeholder when it is dependent.
     RangeVar->markUsed(Context);
-
-    // Deduce any 'auto's in the loop variable as 'DependentTy'. We'll fill
-    // them in properly when we instantiate the loop.
-    if (!LoopVar->isInvalidDecl() && Kind != BFRK_Check) {
-      if (auto *DD = dyn_cast<DecompositionDecl>(LoopVar))
-        for (auto *Binding : DD->bindings()) {
-          if (!Binding->isParameterPack())
-            Binding->setType(Context.DependentTy);
-        }
-      LoopVar->setType(SubstAutoTypeDependent(LoopVar->getType()));
-    }
+    ActOnDependentForRangeInitializer(LoopVar, Kind);
   } else if (!BeginDeclStmt.get()) {
     StmtResult RebuildResult;
     auto RebuildWithDereference = [&] {
diff --git a/clang/lib/Sema/TreeTransform.h b/clang/lib/Sema/TreeTransform.h
index ee09910a8d370..4de479bb62b0d 100644
--- a/clang/lib/Sema/TreeTransform.h
+++ b/clang/lib/Sema/TreeTransform.h
@@ -9359,6 +9359,33 @@ StmtResult TreeTransform<Derived>::TransformCXXIteratingExpansionStmt(
   return SemaRef.FinishCXXExpansionStmt(Expansion, Body.get());
 }
 
+template <typename Derived>
+StmtResult TreeTransform<Derived>::TransformCXXDependentExpansionStmt(
+    CXXDependentExpansionStmt *S) {
+  TransformCXXExpansionStmtResult Common =
+      TransformCXXExpansionStmtCommonParts(S);
+  if (!Common.isValid())
+    return StmtError();
+
+  ExprResult ExpansionInitializer =
+      getDerived().TransformExpr(S->getExpansionInitializer());
+  if (ExpansionInitializer.isInvalid())
+    return StmtError();
+
+  StmtResult Expansion = SemaRef.BuildNonEnumeratingCXXExpansionStmt(
+      Common.NewESD, Common.NewInit, Common.NewExpansionVarDecl,
+      ExpansionInitializer.get(), S->getForLoc(), S->getLParenLoc(),
+      S->getColonLoc(), S->getRParenLoc());
+  if (Expansion.isInvalid())
+    return StmtError();
+
+  StmtResult Body = getDerived().TransformStmt(S->getBody());
+  if (Body.isInvalid())
+    return StmtError();
+
+  return SemaRef.FinishCXXExpansionStmt(Expansion.get(), Body.get());
+}
+
 template <typename Derived>
 ExprResult TreeTransform<Derived>::TransformCXXExpansionInitListExpr(
     CXXExpansionInitListExpr *E) {
diff --git a/clang/lib/Serialization/ASTReaderStmt.cpp b/clang/lib/Serialization/ASTReaderStmt.cpp
index c66b19e5bf752..cbe6378b45075 100644
--- a/clang/lib/Serialization/ASTReaderStmt.cpp
+++ b/clang/lib/Serialization/ASTReaderStmt.cpp
@@ -1764,6 +1764,12 @@ void ASTStmtReader::VisitCXXIteratingExpansionStmt(
   S->setEndVarStmt(cast<DeclStmt>(Record.readSubStmt()));
 }
 
+void ASTStmtReader::VisitCXXDependentExpansionStmt(
+    CXXDependentExpansionStmt *S) {
+  VisitCXXExpansionStmt(S);
+  S->setExpansionInitializer(Record.readSubExpr());
+}
+
 void ASTStmtReader::VisitCXXExpansionInitListExpr(CXXExpansionInitListExpr *E) {
   VisitExpr(E);
   assert(Record.peekInt() == E->getNumExprs() && "NumExprFields is wrong ?");
@@ -3625,6 +3631,10 @@ Stmt *ASTReader::ReadStmtFromStream(ModuleFile &F) {
       S = new (Context) CXXIteratingExpansionStmt(Empty);
       break;
 
+    case STMT_CXX_DEPENDENT_EXPANSION:
+      S = new (Context) CXXDependentExpansionStmt(Empty);
+      break;
+
     case STMT_CXX_EXPANSION_INSTANTIATION:
       S = CXXExpansionInstantiationStmt::CreateEmpty(
           Context, Empty, Record[ASTStmtReader::NumStmtFields],
diff --git a/clang/lib/Serialization/ASTWriterStmt.cpp b/clang/lib/Serialization/ASTWriterStmt.cpp
index bc04c23cb11f4..9c0b139698f12 100644
--- a/clang/lib/Serialization/ASTWriterStmt.cpp
+++ b/clang/lib/Serialization/ASTWriterStmt.cpp
@@ -1742,6 +1742,12 @@ void ASTStmtWriter::VisitCXXIteratingExpansionStmt(
   Code = serialization::STMT_CXX_ITERATING_EXPANSION;
 }
 
+void ASTStmtWriter::VisitCXXDependentExpansionStmt(
+    CXXDependentExpansionStmt *S) {
+  VisitCXXExpansionStmt(S);
+  Record.AddStmt(S->getExpansionInitializer());
+  Code = serialization::STMT_CXX_DEPENDENT_EXPANSION;
+}
 void ASTStmtWriter::VisitCXXExpansionInitListExpr(CXXExpansionInitListExpr *E) {
   VisitExpr(E);
   Record.push_back(E->getNumExprs());
diff --git a/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp b/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp
index 54b3ba11e698d..35881f3fe1c42 100644
--- a/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp
+++ b/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp
@@ -1749,6 +1749,7 @@ void ExprEngine::Visit(const Stmt *S, ExplodedNode *Pred,
     case Stmt::SEHFinallyStmtClass:
     case Stmt::CXXEnumeratingExpansionStmtClass:
     case Stmt::CXXIteratingExpansionStmtClass:
+    case Stmt::CXXDependentExpansionStmtClass:
     case Stmt::CXXExpansionInstantiationStmtClass:
     case Stmt::CXXExpansionInitListExprClass:
     case Stmt::CXXExpansionInitListSelectExprClass:
diff --git a/clang/test/SemaCXX/cxx2c-expansion-statements.cpp b/clang/test/SemaCXX/cxx2c-expansion-statements.cpp
index b975c954e46c6..7f25e4df48ae4 100644
--- a/clang/test/SemaCXX/cxx2c-expansion-statements.cpp
+++ b/clang/test/SemaCXX/cxx2c-expansion-statements.cpp
@@ -124,7 +124,18 @@ constexpr int f3() {
   return count;
 }
 
+template <String s>
+constexpr int tf3() {
+  int count = 0;
+  template for (constexpr auto x : s) count++;
+  return count;
+}
+
 static_assert(f3() == 4);
+static_assert(tf3<"1">() == 1);
+static_assert(tf3<"12">() == 2);
+static_assert(tf3<"123">() == 3);
+static_assert(tf3<"1234">() == 4);
 
 void f4() {
   static constexpr String empty{""};
diff --git a/clang/tools/libclang/CXCursor.cpp b/clang/tools/libclang/CXCursor.cpp
index 150441e9b4202..d6b197eb5f9eb 100644
--- a/clang/tools/libclang/CXCursor.cpp
+++ b/clang/tools/libclang/CXCursor.cpp
@@ -292,6 +292,7 @@ CXCursor cxcursor::MakeCXCursor(const Stmt *S, const Decl *Parent,
   case Stmt::CoreturnStmtClass:
   case Stmt::CXXEnumeratingExpansionStmtClass:
   case Stmt::CXXIteratingExpansionStmtClass:
+  case Stmt::CXXDependentExpansionStmtClass:
   case Stmt::CXXExpansionInstantiationStmtClass:
     K = CXCursor_UnexposedStmt;
     break;

>From a9e1d55e0072413f1f482a3818e2c80e1329bf66 Mon Sep 17 00:00:00 2001
From: Sirraide <aeternalmail at gmail.com>
Date: Sun, 26 Oct 2025 01:34:30 +0200
Subject: [PATCH 06/33] Sema for destructuring expansion statements

---
 clang/include/clang/AST/ExprCXX.h             |  47 +++-
 clang/include/clang/AST/RecursiveASTVisitor.h |   2 +
 clang/include/clang/AST/StmtCXX.h             |  44 +++-
 clang/include/clang/AST/TextNodeDumper.h      |   2 +
 .../clang/Basic/DiagnosticSemaKinds.td        |   4 +
 clang/include/clang/Basic/StmtNodes.td        |   3 +-
 clang/include/clang/Sema/Sema.h               |   4 +
 .../include/clang/Serialization/ASTBitCodes.h |   4 +
 clang/lib/AST/Expr.cpp                        |   1 +
 clang/lib/AST/ExprCXX.cpp                     |  24 +-
 clang/lib/AST/ExprClassification.cpp          |   1 +
 clang/lib/AST/ExprConstant.cpp                |   1 +
 clang/lib/AST/ItaniumMangle.cpp               |   1 +
 clang/lib/AST/StmtCXX.cpp                     |  19 ++
 clang/lib/AST/StmtPrinter.cpp                 |  10 +
 clang/lib/AST/StmtProfile.cpp                 |  11 +
 clang/lib/AST/TextNodeDumper.cpp              |  11 +-
 clang/lib/CodeGen/CGStmt.cpp                  |   1 +
 clang/lib/Sema/SemaExceptionSpec.cpp          |   2 +
 clang/lib/Sema/SemaExpand.cpp                 | 144 ++++++++----
 clang/lib/Sema/TreeTransform.h                |  41 ++++
 clang/lib/Serialization/ASTReaderStmt.cpp     |  21 ++
 clang/lib/Serialization/ASTWriterStmt.cpp     |  16 ++
 clang/lib/StaticAnalyzer/Core/ExprEngine.cpp  |   2 +
 .../SemaCXX/cxx2c-expansion-statements.cpp    | 214 +++++++++++++++++-
 clang/tools/libclang/CXCursor.cpp             |   2 +
 26 files changed, 571 insertions(+), 61 deletions(-)

diff --git a/clang/include/clang/AST/ExprCXX.h b/clang/include/clang/AST/ExprCXX.h
index 6532d0b8f7f36..33b00f2a760f3 100644
--- a/clang/include/clang/AST/ExprCXX.h
+++ b/clang/include/clang/AST/ExprCXX.h
@@ -5587,7 +5587,7 @@ class CXXExpansionInitListSelectExpr : public Expr {
   const Expr *getIndexExpr() const { return SubExprs[INDEX]; }
   void setIndexExpr(Expr* E) { SubExprs[INDEX] = E; }
 
-  SourceLocation getBeginLoc() const { return getRangeExpr()->getExprLoc(); }
+  SourceLocation getBeginLoc() const { return getRangeExpr()->getBeginLoc(); }
   SourceLocation getEndLoc() const { return getRangeExpr()->getEndLoc(); }
 
   child_range children() {
@@ -5606,6 +5606,51 @@ class CXXExpansionInitListSelectExpr : public Expr {
   }
 };
 
+class CXXDestructuringExpansionSelectExpr : public Expr {
+  friend class ASTStmtReader;
+
+  DecompositionDecl* Decomposition;
+  Expr* Index;
+
+public:
+  CXXDestructuringExpansionSelectExpr(EmptyShell Empty);
+  CXXDestructuringExpansionSelectExpr(const ASTContext &C,
+                                      DecompositionDecl *Decomposition,
+                                      Expr *Index);
+
+  DecompositionDecl *getDecompositionDecl() {
+    return cast<DecompositionDecl>(Decomposition);
+  }
+
+  const DecompositionDecl *getDecompositionDecl() const {
+    return cast<DecompositionDecl>(Decomposition);
+  }
+
+  void setDecompositionDecl(DecompositionDecl *E) { Decomposition = E; }
+
+  Expr *getIndexExpr() { return Index; }
+  const Expr *getIndexExpr() const { return Index; }
+  void setIndexExpr(Expr* E) { Index = E; }
+
+  SourceLocation getBeginLoc() const { return Decomposition->getBeginLoc(); }
+  SourceLocation getEndLoc() const { return Decomposition->getEndLoc(); }
+
+  child_range children() {
+    return child_range(reinterpret_cast<Stmt **>(&Index),
+                       reinterpret_cast<Stmt **>(&Index + 1));
+  }
+
+  const_child_range children() const {
+    return const_child_range(
+            reinterpret_cast<Stmt **>(const_cast<Expr **>(&Index)),
+            reinterpret_cast<Stmt **>(const_cast<Expr **>(&Index + 1)));
+  }
+
+  static bool classof(const Stmt *T) {
+    return T->getStmtClass() == CXXDestructuringExpansionSelectExprClass;
+  }
+};
+
 } // namespace clang
 
 #endif // LLVM_CLANG_AST_EXPRCXX_H
diff --git a/clang/include/clang/AST/RecursiveASTVisitor.h b/clang/include/clang/AST/RecursiveASTVisitor.h
index de704bd9cdac1..33413f8a742fc 100644
--- a/clang/include/clang/AST/RecursiveASTVisitor.h
+++ b/clang/include/clang/AST/RecursiveASTVisitor.h
@@ -3126,10 +3126,12 @@ DEF_TRAVERSE_STMT(RequiresExpr, {
 
 DEF_TRAVERSE_STMT(CXXEnumeratingExpansionStmt, {})
 DEF_TRAVERSE_STMT(CXXIteratingExpansionStmt, {})
+DEF_TRAVERSE_STMT(CXXDestructuringExpansionStmt, {})
 DEF_TRAVERSE_STMT(CXXDependentExpansionStmt, {})
 DEF_TRAVERSE_STMT(CXXExpansionInstantiationStmt, {})
 DEF_TRAVERSE_STMT(CXXExpansionInitListExpr, {})
 DEF_TRAVERSE_STMT(CXXExpansionInitListSelectExpr, {})
+DEF_TRAVERSE_STMT(CXXDestructuringExpansionSelectExpr, {})
 
 // These literals (all of them) do not need any action.
 DEF_TRAVERSE_STMT(IntegerLiteral, {})
diff --git a/clang/include/clang/AST/StmtCXX.h b/clang/include/clang/AST/StmtCXX.h
index 561b9b6f276e8..fa992666b825f 100644
--- a/clang/include/clang/AST/StmtCXX.h
+++ b/clang/include/clang/AST/StmtCXX.h
@@ -646,7 +646,7 @@ class CXXDependentExpansionStmt : public CXXExpansionStmt {
   }
 
   const_child_range children() const {
-    // See CXXIteratingExpansion statement for an explansion of this terrible
+    // See CXXIteratingExpansion statement for an explanation of this terrible
     // hack.
     Stmt *const *FirstParentSubStmt = CXXExpansionStmt::SubStmts;
     unsigned Count = static_cast<unsigned>(CXXExpansionStmt::COUNT) + 1;
@@ -747,6 +747,48 @@ class CXXIteratingExpansionStmt : public CXXExpansionStmt {
   }
 };
 
+/// Represents an expansion statement whose expansion-initializer is dependent.
+class CXXDestructuringExpansionStmt : public CXXExpansionStmt {
+  friend class ASTStmtReader;
+
+  Stmt* DecompositionDeclStmt;
+
+public:
+  CXXDestructuringExpansionStmt(EmptyShell Empty);
+  CXXDestructuringExpansionStmt(ExpansionStmtDecl *ESD, Stmt *Init,
+                            DeclStmt *ExpansionVar, Stmt *DecompositionDeclStmt,
+                            SourceLocation ForLoc, SourceLocation LParenLoc,
+                            SourceLocation ColonLoc, SourceLocation RParenLoc);
+
+  Stmt *getDecompositionDeclStmt() { return DecompositionDeclStmt; }
+  const Stmt *getDecompositionDeclStmt() const { return DecompositionDeclStmt; }
+  void setDecompositionDeclStmt(Stmt* S) { DecompositionDeclStmt = S; }
+
+  DecompositionDecl* getDecompositionDecl();
+  const DecompositionDecl* getDecompositionDecl() const {
+    return const_cast<CXXDestructuringExpansionStmt *>(this)->getDecompositionDecl();
+  }
+
+  child_range children() {
+    const_child_range CCR =
+        const_cast<const CXXDestructuringExpansionStmt *>(this)->children();
+    return child_range(cast_away_const(CCR.begin()),
+                       cast_away_const(CCR.end()));
+  }
+
+  const_child_range children() const {
+    // See CXXIteratingExpansion statement for an explanation of this terrible
+    // hack.
+    Stmt *const *FirstParentSubStmt = CXXExpansionStmt::SubStmts;
+    unsigned Count = static_cast<unsigned>(CXXExpansionStmt::COUNT) + 1;
+    return const_child_range(FirstParentSubStmt, FirstParentSubStmt + Count);
+  }
+
+  static bool classof(const Stmt *T) {
+    return T->getStmtClass() == CXXDestructuringExpansionStmtClass;
+  }
+};
+
 /// Represents the code generated for an instantiated expansion statement.
 ///
 /// This holds 'shared statements' and 'instantiations'; these encode the
diff --git a/clang/include/clang/AST/TextNodeDumper.h b/clang/include/clang/AST/TextNodeDumper.h
index 3da9c5076fb1f..a9756d975787d 100644
--- a/clang/include/clang/AST/TextNodeDumper.h
+++ b/clang/include/clang/AST/TextNodeDumper.h
@@ -310,6 +310,8 @@ class TextNodeDumper
   void VisitSizeOfPackExpr(const SizeOfPackExpr *Node);
   void
   VisitCXXDependentScopeMemberExpr(const CXXDependentScopeMemberExpr *Node);
+  void VisitCXXDestructuringExpansionSelectExpr(
+      const CXXDestructuringExpansionSelectExpr *Node);
   void VisitCXXExpansionInitListExpr(const CXXExpansionInitListExpr *Node);
   void VisitObjCAtCatchStmt(const ObjCAtCatchStmt *Node);
   void VisitObjCEncodeExpr(const ObjCEncodeExpr *Node);
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index fb4e98726b8d1..284db2dc8e4a3 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -3686,6 +3686,10 @@ def err_conflicting_codeseg_attribute : Error<
 def warn_duplicate_codeseg_attribute : Warning<
   "duplicate code segment specifiers">, InGroup<Section>;
 
+def err_expansion_stmt_invalid_init : Error<
+  "cannot expand expression of type %0">;
+def err_expansion_stmt_lambda : Error<
+  "cannot expand lambda closure type">;
 def err_expanded_identifier_label : Error<
   "identifier labels are not allowed in expansion statements">;
 
diff --git a/clang/include/clang/Basic/StmtNodes.td b/clang/include/clang/Basic/StmtNodes.td
index 1ca0193792338..0141b84cac583 100644
--- a/clang/include/clang/Basic/StmtNodes.td
+++ b/clang/include/clang/Basic/StmtNodes.td
@@ -62,6 +62,7 @@ def CoreturnStmt : StmtNode<Stmt>;
 def CXXExpansionStmt : StmtNode<Stmt, 1>;
 def CXXEnumeratingExpansionStmt : StmtNode<CXXExpansionStmt>;
 def CXXIteratingExpansionStmt : StmtNode<CXXExpansionStmt>;
+def CXXDestructuringExpansionStmt : StmtNode<CXXExpansionStmt>;
 def CXXDependentExpansionStmt : StmtNode<CXXExpansionStmt>;
 def CXXExpansionInstantiationStmt : StmtNode<Stmt>; // *Not* derived from CXXExpansionStmt!
 
@@ -187,7 +188,7 @@ def RequiresExpr : StmtNode<Expr>;
 // C++26 Expansion statement support expressions
 def CXXExpansionInitListExpr : StmtNode<Expr>;
 def CXXExpansionInitListSelectExpr : StmtNode<Expr>;
-//def CXXDestructurableExpansionSelectExpr : StmtNode<Expr>;
+def CXXDestructuringExpansionSelectExpr : StmtNode<Expr>;
 
 // Obj-C Expressions.
 def ObjCStringLiteral : StmtNode<Expr>;
diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index fe18f0c162550..bf58fcd7a8af7 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -15698,6 +15698,10 @@ class Sema final : public SemaBase {
   BuildCXXExpansionInitListSelectExpr(CXXExpansionInitListExpr *Range,
                                       Expr *Idx);
 
+  ExprResult
+  BuildCXXDestructuringExpansionSelectExpr(DecompositionDecl *DD,
+                                                 Expr *Idx);
+
   StmtResult BuildNonEnumeratingCXXExpansionStmt(
       ExpansionStmtDecl *ESD, Stmt *Init, DeclStmt *ExpansionVarStmt,
       Expr *ExpansionInitializer, SourceLocation ForLoc,
diff --git a/clang/include/clang/Serialization/ASTBitCodes.h b/clang/include/clang/Serialization/ASTBitCodes.h
index 8102d40200296..2dc4116929f3e 100644
--- a/clang/include/clang/Serialization/ASTBitCodes.h
+++ b/clang/include/clang/Serialization/ASTBitCodes.h
@@ -1842,6 +1842,9 @@ enum StmtCode {
   /// A CXXIteratingExpansionStmt.
   STMT_CXX_ITERATING_EXPANSION,
 
+  /// A CXXDestructuringExpansionStmt.
+  STMT_CXX_DESTRUCTURING_EXPANSION,
+
   /// A CXXDependentExpansionStmt,
   STMT_CXX_DEPENDENT_EXPANSION,
 
@@ -1941,6 +1944,7 @@ enum StmtCode {
   EXPR_REQUIRES,                          // RequiresExpr
   EXPR_CXX_EXPANSION_INIT_LIST,           // CXXExpansionInitListExpr
   EXPR_CXX_EXPANSION_INIT_LIST_SELECT,    // CXXExpansionInitListSelectExpr
+  EXPR_CXX_DESTRUCTURING_EXPANSION_SELECT, // CXXDestructuringExpansionSelectExpr
 
   // CUDA
   EXPR_CUDA_KERNEL_CALL, // CUDAKernelCallExpr
diff --git a/clang/lib/AST/Expr.cpp b/clang/lib/AST/Expr.cpp
index dea07cbde39cd..c61660c90513f 100644
--- a/clang/lib/AST/Expr.cpp
+++ b/clang/lib/AST/Expr.cpp
@@ -3690,6 +3690,7 @@ bool Expr::HasSideEffects(const ASTContext &Ctx,
   case CXXFoldExprClass:
   case CXXExpansionInitListSelectExprClass:
   case CXXExpansionInitListExprClass:
+  case CXXDestructuringExpansionSelectExprClass:
     // Make a conservative assumption for dependent nodes.
     return IncludePossibleEffects;
 
diff --git a/clang/lib/AST/ExprCXX.cpp b/clang/lib/AST/ExprCXX.cpp
index 4215c964032a3..824c3ac0a3db5 100644
--- a/clang/lib/AST/ExprCXX.cpp
+++ b/clang/lib/AST/ExprCXX.cpp
@@ -2050,12 +2050,6 @@ CXXExpansionInitListExpr::CreateEmpty(const ASTContext &C, EmptyShell Empty,
   return new (Mem) CXXExpansionInitListExpr(Empty, NumExprs);
 }
 
-bool CXXExpansionInitListExpr::containsPackExpansion() const {
-  return llvm::any_of(getExprs(), [](const Expr* E) {
-    return isa<PackExpansionExpr>(E);
-  });
-}
-
 CXXExpansionInitListSelectExpr::CXXExpansionInitListSelectExpr(EmptyShell Empty)
     : Expr(CXXExpansionInitListSelectExprClass, Empty) {
 }
@@ -2068,3 +2062,21 @@ CXXExpansionInitListSelectExpr::CXXExpansionInitListSelectExpr(
   SubExprs[RANGE] = Range;
   SubExprs[INDEX] = Idx;
 }
+
+bool CXXExpansionInitListExpr::containsPackExpansion() const {
+  return llvm::any_of(getExprs(), [](const Expr* E) {
+    return isa<PackExpansionExpr>(E);
+  });
+}
+
+CXXDestructuringExpansionSelectExpr::CXXDestructuringExpansionSelectExpr(
+    EmptyShell Empty)
+    : Expr(CXXDestructuringExpansionSelectExprClass, Empty) {}
+
+CXXDestructuringExpansionSelectExpr::CXXDestructuringExpansionSelectExpr(
+    const ASTContext &C, DecompositionDecl *Decomposition, Expr *Index)
+    : Expr(CXXDestructuringExpansionSelectExprClass, C.DependentTy, VK_PRValue,
+           OK_Ordinary),
+      Decomposition(Decomposition), Index(Index) {
+  setDependence(ExprDependence::TypeValueInstantiation);
+}
diff --git a/clang/lib/AST/ExprClassification.cpp b/clang/lib/AST/ExprClassification.cpp
index f676b2d95d26a..5521cdf9d04c9 100644
--- a/clang/lib/AST/ExprClassification.cpp
+++ b/clang/lib/AST/ExprClassification.cpp
@@ -218,6 +218,7 @@ static Cl::Kinds ClassifyInternal(ASTContext &Ctx, const Expr *E) {
   case Expr::RequiresExprClass:
   case Expr::CXXExpansionInitListExprClass:
   case Expr::CXXExpansionInitListSelectExprClass:
+  case Expr::CXXDestructuringExpansionSelectExprClass:
     return Cl::CL_PRValue;
 
   case Expr::EmbedExprClass:
diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp
index 2bb49860fcf73..f4479f222840c 100644
--- a/clang/lib/AST/ExprConstant.cpp
+++ b/clang/lib/AST/ExprConstant.cpp
@@ -19063,6 +19063,7 @@ static ICEDiag CheckICE(const Expr* E, const ASTContext &Ctx) {
   case Expr::HLSLOutArgExprClass:
   case Expr::CXXExpansionInitListExprClass:
   case Expr::CXXExpansionInitListSelectExprClass:
+  case Expr::CXXDestructuringExpansionSelectExprClass:
     return ICEDiag(IK_NotICE, E->getBeginLoc());
 
   case Expr::InitListExprClass: {
diff --git a/clang/lib/AST/ItaniumMangle.cpp b/clang/lib/AST/ItaniumMangle.cpp
index 2fe8e0400e141..2223347ba3b10 100644
--- a/clang/lib/AST/ItaniumMangle.cpp
+++ b/clang/lib/AST/ItaniumMangle.cpp
@@ -4947,6 +4947,7 @@ void CXXNameMangler::mangleExpression(const Expr *E, unsigned Arity,
   case Expr::PackIndexingExprClass:
   case Expr::CXXExpansionInitListSelectExprClass:
   case Expr::CXXExpansionInitListExprClass:
+  case Expr::CXXDestructuringExpansionSelectExprClass:
     llvm_unreachable("unexpected statement kind");
 
   case Expr::ConstantExprClass:
diff --git a/clang/lib/AST/StmtCXX.cpp b/clang/lib/AST/StmtCXX.cpp
index 49dd46b00517b..793ba67103ed6 100644
--- a/clang/lib/AST/StmtCXX.cpp
+++ b/clang/lib/AST/StmtCXX.cpp
@@ -178,6 +178,9 @@ bool CXXExpansionStmt::hasDependentSize() const {
            End->isTypeDependent() || End->isValueDependent();
   }
 
+  if (isa<CXXDestructuringExpansionStmt>(this))
+    return false;
+
   if (isa<CXXDependentExpansionStmt>(this))
     return true;
 
@@ -198,6 +201,22 @@ CXXIteratingExpansionStmt::CXXIteratingExpansionStmt(
   SubStmts[RANGE] = Range;
 }
 
+CXXDestructuringExpansionStmt::CXXDestructuringExpansionStmt(EmptyShell Empty)
+    : CXXExpansionStmt(CXXDestructuringExpansionStmtClass, Empty) {}
+
+CXXDestructuringExpansionStmt::CXXDestructuringExpansionStmt(
+    ExpansionStmtDecl *ESD, Stmt *Init, DeclStmt *ExpansionVar,
+    Stmt *DecompositionDeclStmt, SourceLocation ForLoc,
+    SourceLocation LParenLoc, SourceLocation ColonLoc, SourceLocation RParenLoc)
+    : CXXExpansionStmt(CXXDestructuringExpansionStmtClass, ESD, Init, ExpansionVar,
+                       ForLoc, LParenLoc, ColonLoc, RParenLoc),
+      DecompositionDeclStmt(DecompositionDeclStmt) {}
+
+DecompositionDecl* CXXDestructuringExpansionStmt::getDecompositionDecl() {
+  return cast<DecompositionDecl>(
+      cast<DeclStmt>(DecompositionDeclStmt)->getSingleDecl());
+}
+
 CXXDependentExpansionStmt::CXXDependentExpansionStmt(EmptyShell Empty)
   : CXXExpansionStmt(CXXDependentExpansionStmtClass, Empty) {}
 
diff --git a/clang/lib/AST/StmtPrinter.cpp b/clang/lib/AST/StmtPrinter.cpp
index ffb2fbcd5d0e0..d0e44b6fbf14c 100644
--- a/clang/lib/AST/StmtPrinter.cpp
+++ b/clang/lib/AST/StmtPrinter.cpp
@@ -472,6 +472,11 @@ void StmtPrinter::VisitCXXIteratingExpansionStmt(
   VisitCXXExpansionStmt(Node);
 }
 
+void StmtPrinter::VisitCXXDestructuringExpansionStmt(
+    CXXDestructuringExpansionStmt *Node) {
+  VisitCXXExpansionStmt(Node);
+}
+
 void StmtPrinter::VisitCXXDependentExpansionStmt(
     CXXDependentExpansionStmt *Node) {
   VisitCXXExpansionStmt(Node, Node->getExpansionInitializer());
@@ -494,6 +499,11 @@ void StmtPrinter::VisitCXXExpansionInitListSelectExpr(
   PrintExpr(Node->getRangeExpr());
 }
 
+void StmtPrinter::VisitCXXDestructuringExpansionSelectExpr(
+    CXXDestructuringExpansionSelectExpr *Node) {
+  PrintExpr(Node->getDecompositionDecl()->getInit());
+}
+
 void StmtPrinter::VisitMSDependentExistsStmt(MSDependentExistsStmt *Node) {
   Indent();
   if (Node->isIfExists())
diff --git a/clang/lib/AST/StmtProfile.cpp b/clang/lib/AST/StmtProfile.cpp
index 8230cc3b774b7..a2470f41a8ec7 100644
--- a/clang/lib/AST/StmtProfile.cpp
+++ b/clang/lib/AST/StmtProfile.cpp
@@ -378,6 +378,11 @@ void StmtProfiler::VisitCXXIteratingExpansionStmt(
   VisitCXXExpansionStmt(S);
 }
 
+void StmtProfiler::VisitCXXDestructuringExpansionStmt(
+    const CXXDestructuringExpansionStmt *S) {
+  VisitCXXExpansionStmt(S);
+}
+
 void StmtProfiler::VisitCXXDependentExpansionStmt(
     const CXXDependentExpansionStmt *S) {
   VisitCXXExpansionStmt(S);
@@ -2426,6 +2431,12 @@ void StmtProfiler::VisitCXXExpansionInitListSelectExpr(
   VisitExpr(E);
 }
 
+void StmtProfiler::VisitCXXDestructuringExpansionSelectExpr(
+    const CXXDestructuringExpansionSelectExpr *E) {
+  VisitExpr(E);
+  VisitDecl(E->getDecompositionDecl());
+}
+
 void StmtProfiler::VisitRecoveryExpr(const RecoveryExpr *E) { VisitExpr(E); }
 
 void StmtProfiler::VisitObjCStringLiteral(const ObjCStringLiteral *S) {
diff --git a/clang/lib/AST/TextNodeDumper.cpp b/clang/lib/AST/TextNodeDumper.cpp
index 614c21ac7f5e0..ea31f13af99da 100644
--- a/clang/lib/AST/TextNodeDumper.cpp
+++ b/clang/lib/AST/TextNodeDumper.cpp
@@ -951,7 +951,11 @@ void TextNodeDumper::dumpBareDeclRef(const Decl *D) {
       switch (ND->getKind()) {
       case Decl::Decomposition: {
         auto *DD = cast<DecompositionDecl>(ND);
-        OS << " first_binding '" << DD->bindings()[0]->getDeclName() << '\'';
+
+        // Empty decomposition decls can occur in destructuring expansion
+        // statements.
+        if (!DD->bindings().empty())
+          OS << " first_binding '" << DD->bindings()[0]->getDeclName() << '\'';
         break;
       }
       case Decl::Field: {
@@ -1831,6 +1835,11 @@ void TextNodeDumper::VisitCXXExpansionInitListExpr(const CXXExpansionInitListExp
     OS << " contains_pack";
 }
 
+void TextNodeDumper::VisitCXXDestructuringExpansionSelectExpr(
+    const CXXDestructuringExpansionSelectExpr *Node) {
+  dumpDeclRef(Node->getDecompositionDecl());
+}
+
 void TextNodeDumper::VisitObjCMessageExpr(const ObjCMessageExpr *Node) {
   OS << " selector=";
   Node->getSelector().print(OS);
diff --git a/clang/lib/CodeGen/CGStmt.cpp b/clang/lib/CodeGen/CGStmt.cpp
index a240e209c5a63..bed89a965baac 100644
--- a/clang/lib/CodeGen/CGStmt.cpp
+++ b/clang/lib/CodeGen/CGStmt.cpp
@@ -206,6 +206,7 @@ void CodeGenFunction::EmitStmt(const Stmt *S, ArrayRef<const Attr *> Attrs) {
     break;
   case Stmt::CXXEnumeratingExpansionStmtClass:
   case Stmt::CXXIteratingExpansionStmtClass:
+  case Stmt::CXXDestructuringExpansionStmtClass:
   case Stmt::CXXDependentExpansionStmtClass:
     llvm_unreachable("unexpanded expansion statements should not be emitted");
   case Stmt::CXXExpansionInstantiationStmtClass:
diff --git a/clang/lib/Sema/SemaExceptionSpec.cpp b/clang/lib/Sema/SemaExceptionSpec.cpp
index 86dc25d0e38ee..dda6123bd25fc 100644
--- a/clang/lib/Sema/SemaExceptionSpec.cpp
+++ b/clang/lib/Sema/SemaExceptionSpec.cpp
@@ -1290,6 +1290,7 @@ CanThrowResult Sema::canThrow(const Stmt *S) {
   case Expr::CXXParenListInitExprClass:
   case Expr::CXXExpansionInitListSelectExprClass:
   case Expr::CXXExpansionInitListExprClass:
+  case Expr::CXXDestructuringExpansionSelectExprClass:
     return canSubStmtsThrow(*this, S);
 
   case Expr::CompoundLiteralExprClass:
@@ -1543,6 +1544,7 @@ CanThrowResult Sema::canThrow(const Stmt *S) {
   case Stmt::WhileStmtClass:
   case Stmt::CXXEnumeratingExpansionStmtClass:
   case Stmt::CXXIteratingExpansionStmtClass:
+  case Stmt::CXXDestructuringExpansionStmtClass:
   case Stmt::CXXExpansionInstantiationStmtClass:
     return canSubStmtsThrow(*this, S);
 
diff --git a/clang/lib/Sema/SemaExpand.cpp b/clang/lib/Sema/SemaExpand.cpp
index 4147b99f1c4d8..01c5403b2f124 100644
--- a/clang/lib/Sema/SemaExpand.cpp
+++ b/clang/lib/Sema/SemaExpand.cpp
@@ -86,7 +86,7 @@ TryBuildIterableExpansionStmtInitializer(Sema &S, Expr *ExpansionInitializer,
   bool FoundBeginEnd = false;
   if (auto *Record = Ty->getAsCXXRecordDecl()) {
     LookupResult BeginLR(S, BeginName, Sema::LookupMemberName);
-    LookupResult EndLR(S, BeginName, Sema::LookupMemberName);
+    LookupResult EndLR(S, EndName, Sema::LookupMemberName);
     FoundBeginEnd = S.LookupQualifiedName(BeginLR, Record) &&
                     S.LookupQualifiedName(EndLR, Record);
   }
@@ -173,6 +173,45 @@ TryBuildIterableExpansionStmtInitializer(Sema &S, Expr *ExpansionInitializer,
   return Data;
 }
 
+static StmtResult BuildDestructuringExpansionStmtDecl(
+    Sema &S, Expr *ExpansionInitializer, SourceLocation ColonLoc,
+    bool VarIsConstexpr,
+    ArrayRef<MaterializeTemporaryExpr *> LifetimeExtendTemps) {
+  auto Ctx = Sema::ExpressionEvaluationContext::PotentiallyEvaluated;
+  if (VarIsConstexpr)
+    Ctx = Sema::ExpressionEvaluationContext::ImmediateFunctionContext;
+  EnterExpressionEvaluationContext ExprEvalCtx(S, Ctx);
+
+  UnsignedOrNone Arity =
+      S.GetDecompositionElementCount(ExpansionInitializer->getType(), ColonLoc);
+
+  if (!Arity) {
+    S.Diag(ExpansionInitializer->getBeginLoc(),
+           diag::err_expansion_stmt_invalid_init)
+        << ExpansionInitializer->getType()
+        << ExpansionInitializer->getSourceRange();
+    return StmtError();
+  }
+
+  QualType AutoRRef = S.Context.getAutoRRefDeductType();
+  SmallVector<BindingDecl *> Bindings;
+  for (unsigned I = 0; I < *Arity; ++I)
+    Bindings.push_back(BindingDecl::Create(S.Context, S.CurContext, ColonLoc,
+                                           /*Id=*/nullptr, AutoRRef));
+
+  TypeSourceInfo *TSI = S.Context.getTrivialTypeSourceInfo(AutoRRef);
+  auto *DD =
+      DecompositionDecl::Create(S.Context, S.CurContext, ColonLoc, ColonLoc,
+                                AutoRRef, TSI, SC_Auto, Bindings);
+
+  if (VarIsConstexpr)
+    DD->setConstexpr(true);
+
+  S.ApplyForRangeOrExpansionStatementLifetimeExtension(DD, LifetimeExtendTemps);
+  S.AddInitializerToDecl(DD, ExpansionInitializer, false);
+  return S.ActOnDeclStmt(S.ConvertDeclToDeclGroup(DD), ColonLoc, ColonLoc);
+}
+
 ExpansionStmtDecl *Sema::ActOnExpansionStmtDecl(unsigned TemplateDepth,
                                                 SourceLocation TemplateKWLoc) {
   // Create a template parameter '__N'. This will be used to denote the index
@@ -247,6 +286,20 @@ StmtResult Sema::ActOnCXXExpansionStmt(
                                             ColonLoc, RParenLoc);
   }
 
+  if (ExpansionInitializer->hasPlaceholderType()) {
+    ExprResult R = CheckPlaceholderExpr(ExpansionInitializer);
+    if (R.isInvalid())
+      return StmtError();
+    ExpansionInitializer = R.get();
+  }
+
+  // Reject lambdas early.
+  if (auto *RD = ExpansionInitializer->getType()->getAsCXXRecordDecl();
+      RD && RD->isLambda()) {
+    Diag(ExpansionInitializer->getBeginLoc(), diag::err_expansion_stmt_lambda);
+    return StmtError();
+  }
+
   return BuildNonEnumeratingCXXExpansionStmt(
       ESD, Init, DS, ExpansionInitializer, ForLoc, LParenLoc, ColonLoc,
       RParenLoc, LifetimeExtendTemps);
@@ -278,8 +331,9 @@ StmtResult Sema::BuildNonEnumeratingCXXExpansionStmt(
   }
 
   // Otherwise, if it can be an iterating expansion statement, it is one.
+  DeclRefExpr *Index = BuildIndexDRE(*this, ESD);
   IterableExpansionStmtData Data = TryBuildIterableExpansionStmtInitializer(
-      *this, ExpansionInitializer, BuildIndexDRE(*this, ESD), ColonLoc,
+      *this, ExpansionInitializer, Index, ColonLoc,
       ExpansionVar->isConstexpr());
   if (Data.hasError()) {
     ActOnInitializerError(ExpansionVar);
@@ -295,48 +349,29 @@ StmtResult Sema::BuildNonEnumeratingCXXExpansionStmt(
         Data.EndDecl, ForLoc, LParenLoc, ColonLoc, RParenLoc);
   }
 
-  llvm_unreachable("TODO: Destructuring expansion statement");
-}
+  // If not, try destructuring.
+  StmtResult DecompDeclStmt = BuildDestructuringExpansionStmtDecl(
+      *this, ExpansionInitializer, ColonLoc, ExpansionVar->isConstexpr(),
+      LifetimeExtendTemps);
+  if (DecompDeclStmt.isInvalid()) {
+    ActOnInitializerError(ExpansionVar);
+    return StmtError();
+  }
 
-/*
-StmtResult Sema::BuildCXXExpansionStmt(
-      ExpansionStmtDecl *ESD, Stmt *Init, Stmt *ExpansionVarStmt,
-      Expr *ExpansionInitializer, SourceLocation ForLoc,
-      SourceLocation LParenLoc, SourceLocation ColonLoc,
-      SourceLocation RParenLoc,
-      ArrayRef<MaterializeTemporaryExpr *> LifetimeExtendTemps) {
-  auto *ExpansionVar = cast<DeclStmt>(ExpansionVarStmt);
-  Expr *Initializer = cast<VarDecl>(ExpansionVar->getSingleDecl())->getInit();
-  assert(Initializer);
-
-  if (auto *WithCleanups = dyn_cast<ExprWithCleanups>(Initializer))
-    Initializer = WithCleanups->getSubExpr();
-
-  if (Initializer->isTypeDependent())
-    llvm_unreachable("TODO");
-
-  if (isa<CXXDependentExpansionInitListSelectExpr>(Initializer))
-    return CXXExpansionStmt::Create(Context, Init, ExpansionVar,
-                                    ESD->getLocation(), ForLoc, LParenLoc,
-                                    ColonLoc, RParenLoc);
-
-  llvm_unreachable("TODO");
-  /*else if (isa<CXXDestructurableExpansionSelectExpr>(Initializer)) {
-    return BuildCXXDestructurableExpansionStmt(TemplateKWLoc, ForLoc, LParenLoc,
-                                               Init, ExpansionVarStmt, ColonLoc,
-                                               RParenLoc, Index);
-  } else if (auto *IESE = dyn_cast<CXXIterableExpansionSelectExpr>(Initializer))
-  { ExprResult Size = makeIterableExpansionSizeExpr(*this, IESE->getRangeVar());
-    if (Size.isInvalid()) {
-      Diag(IESE->getExprLoc(), diag::err_compute_expansion_size_index) << 0;
-      return StmtError();
-    }
-    return BuildCXXIterableExpansionStmt(TemplateKWLoc, ForLoc, LParenLoc, Init,
-                                         ExpansionVarStmt, ColonLoc, RParenLoc,
-                                         Index, Size.get());
+  auto *DS = DecompDeclStmt.getAs<DeclStmt>();
+  auto *DD = cast<DecompositionDecl>(DS->getSingleDecl());
+  ExprResult Select = BuildCXXDestructuringExpansionSelectExpr(DD, Index);
+  if (Select.isInvalid()) {
+    ActOnInitializerError(ExpansionVar);
+    return StmtError();
   }
-  llvm_unreachable("unknown expansion select expression");#1#
-}*/
+
+  if (FinaliseExpansionVar(*this, ExpansionVar, Select))
+    return StmtError();
+
+  return new (Context) CXXDestructuringExpansionStmt(
+      ESD, Init, ExpansionVarStmt, DS, ForLoc, LParenLoc, ColonLoc, RParenLoc);
+}
 
 StmtResult Sema::FinishCXXExpansionStmt(Stmt* Exp, Stmt *Body) {
   if (!Exp || !Body)
@@ -389,6 +424,9 @@ StmtResult Sema::FinishCXXExpansionStmt(Stmt* Exp, Stmt *Body) {
     Shared.push_back(Iter->getRangeVarStmt());
     Shared.push_back(Iter->getBeginVarStmt());
     Shared.push_back(Iter->getEndVarStmt());
+  } else if (auto *Destructuring =
+                 dyn_cast<CXXDestructuringExpansionStmt>(Expansion)) {
+    Shared.push_back(Destructuring->getDecompositionDeclStmt());
   }
 
   // Return an empty statement if the range is empty.
@@ -449,12 +487,29 @@ Sema::BuildCXXExpansionInitListSelectExpr(CXXExpansionInitListExpr *Range,
   // fail to evaluate it.
   Expr::EvalResult ER;
   if (!Idx->EvaluateAsInt(ER, Context))
-    llvm_unreachable("Failed to evaluate expansion init list index");
+    llvm_unreachable("Failed to evaluate expansion index");
 
   uint64_t I = ER.Val.getInt().getZExtValue();
   return Range->getExprs()[I];
 }
 
+ExprResult Sema::BuildCXXDestructuringExpansionSelectExpr(DecompositionDecl *DD,
+                                                          Expr *Idx) {
+  if (Idx->isValueDependent())
+    return new (Context) CXXDestructuringExpansionSelectExpr(Context, DD, Idx);
+
+  Expr::EvalResult ER;
+  if (!Idx->EvaluateAsInt(ER, Context))
+    llvm_unreachable("Failed to evaluate expansion index");
+
+  uint64_t I = ER.Val.getInt().getZExtValue();
+  MarkAnyDeclReferenced(Idx->getBeginLoc(), DD, true); // TODO: Do we need this?
+  if (auto *BD = DD->bindings()[I]; auto *HVD = BD->getHoldingVar())
+    return HVD->getInit();
+  else
+    return BD->getBinding();
+}
+
 std::optional<uint64_t>  Sema::ComputeExpansionSize(CXXExpansionStmt *Expansion) {
   assert(!Expansion->hasDependentSize());
 
@@ -468,6 +523,9 @@ std::optional<uint64_t>  Sema::ComputeExpansionSize(CXXExpansionStmt *Expansion)
     return Size;
   }
 
+  if (auto *Destructuring = dyn_cast<CXXDestructuringExpansionStmt>(Expansion))
+    return Destructuring->getDecompositionDecl()->bindings().size();
+
   // By [stmt.expand]5.2, N is the result of evaluating the expression
   //
   // [] consteval {
diff --git a/clang/lib/Sema/TreeTransform.h b/clang/lib/Sema/TreeTransform.h
index 4de479bb62b0d..c958672d23798 100644
--- a/clang/lib/Sema/TreeTransform.h
+++ b/clang/lib/Sema/TreeTransform.h
@@ -9386,6 +9386,31 @@ StmtResult TreeTransform<Derived>::TransformCXXDependentExpansionStmt(
   return SemaRef.FinishCXXExpansionStmt(Expansion.get(), Body.get());
 }
 
+template <typename Derived>
+StmtResult TreeTransform<Derived>::TransformCXXDestructuringExpansionStmt(
+    CXXDestructuringExpansionStmt *S) {
+  TransformCXXExpansionStmtResult Common =
+      TransformCXXExpansionStmtCommonParts(S);
+  if (!Common.isValid())
+    return StmtError();
+
+  StmtResult DecompositionDeclStmt =
+      getDerived().TransformStmt(S->getDecompositionDeclStmt());
+  if (DecompositionDeclStmt.isInvalid())
+    return StmtError();
+
+  auto *Expansion = new (SemaRef.Context) CXXDestructuringExpansionStmt(
+      Common.NewESD, Common.NewInit, Common.NewExpansionVarDecl,
+      DecompositionDeclStmt.get(), S->getForLoc(), S->getLParenLoc(),
+      S->getColonLoc(), S->getRParenLoc());
+
+  StmtResult Body = getDerived().TransformStmt(S->getBody());
+  if (Body.isInvalid())
+    return StmtError();
+
+  return SemaRef.FinishCXXExpansionStmt(Expansion, Body.get());
+}
+
 template <typename Derived>
 ExprResult TreeTransform<Derived>::TransformCXXExpansionInitListExpr(
     CXXExpansionInitListExpr *E) {
@@ -9450,6 +9475,22 @@ ExprResult TreeTransform<Derived>::TransformCXXExpansionInitListSelectExpr(
           Range.getAs<CXXExpansionInitListExpr>(), Idx.get());
 }
 
+template <typename Derived>
+ExprResult TreeTransform<Derived>::TransformCXXDestructuringExpansionSelectExpr(
+    CXXDestructuringExpansionSelectExpr *E) {
+  Decl *DD = getDerived().TransformDecl(
+      E->getDecompositionDecl()->getLocation(), E->getDecompositionDecl());
+  ExprResult Idx = getDerived().TransformExpr(E->getIndexExpr());
+  if (!DD || Idx.isInvalid())
+    return ExprError();
+
+  if (!getDerived().AlwaysRebuild() && DD == E->getDecompositionDecl() &&
+      Idx.get() == E->getIndexExpr())
+    return E;
+
+  return SemaRef.BuildCXXDestructuringExpansionSelectExpr(
+      cast<DecompositionDecl>(DD), Idx.get());
+}
 
 template<typename Derived>
 StmtResult
diff --git a/clang/lib/Serialization/ASTReaderStmt.cpp b/clang/lib/Serialization/ASTReaderStmt.cpp
index cbe6378b45075..3c6c6bc37d616 100644
--- a/clang/lib/Serialization/ASTReaderStmt.cpp
+++ b/clang/lib/Serialization/ASTReaderStmt.cpp
@@ -1764,6 +1764,12 @@ void ASTStmtReader::VisitCXXIteratingExpansionStmt(
   S->setEndVarStmt(cast<DeclStmt>(Record.readSubStmt()));
 }
 
+void ASTStmtReader::VisitCXXDestructuringExpansionStmt(
+    CXXDestructuringExpansionStmt *S) {
+  VisitCXXExpansionStmt(S);
+  S->setDecompositionDeclStmt(Record.readSubStmt());
+}
+
 void ASTStmtReader::VisitCXXDependentExpansionStmt(
     CXXDependentExpansionStmt *S) {
   VisitCXXExpansionStmt(S);
@@ -1787,6 +1793,13 @@ void ASTStmtReader::VisitCXXExpansionInitListSelectExpr(
   E->setIndexExpr(Record.readSubExpr());
 }
 
+void ASTStmtReader::VisitCXXDestructuringExpansionSelectExpr(
+    CXXDestructuringExpansionSelectExpr *E) {
+  VisitExpr(E);
+  E->setDecompositionDecl(cast<DecompositionDecl>(Record.readDeclRef()));
+  E->setIndexExpr(Record.readSubExpr());
+}
+
 void ASTStmtReader::VisitMSDependentExistsStmt(MSDependentExistsStmt *S) {
   VisitStmt(S);
   S->KeywordLoc = readSourceLocation();
@@ -3631,6 +3644,10 @@ Stmt *ASTReader::ReadStmtFromStream(ModuleFile &F) {
       S = new (Context) CXXIteratingExpansionStmt(Empty);
       break;
 
+    case STMT_CXX_DESTRUCTURING_EXPANSION:
+      S = new (Context) CXXDestructuringExpansionStmt(Empty);
+      break;
+
     case STMT_CXX_DEPENDENT_EXPANSION:
       S = new (Context) CXXDependentExpansionStmt(Empty);
       break;
@@ -4528,6 +4545,10 @@ Stmt *ASTReader::ReadStmtFromStream(ModuleFile &F) {
       S = new (Context) CXXExpansionInitListSelectExpr(Empty);
       break;
 
+    case EXPR_CXX_DESTRUCTURING_EXPANSION_SELECT:
+      S = new (Context) CXXDestructuringExpansionSelectExpr(Empty);
+      break;
+
     case STMT_OPENACC_COMPUTE_CONSTRUCT: {
       unsigned NumClauses = Record[ASTStmtReader::NumStmtFields];
       S = OpenACCComputeConstruct::CreateEmpty(Context, NumClauses);
diff --git a/clang/lib/Serialization/ASTWriterStmt.cpp b/clang/lib/Serialization/ASTWriterStmt.cpp
index 9c0b139698f12..17d16b0a6326f 100644
--- a/clang/lib/Serialization/ASTWriterStmt.cpp
+++ b/clang/lib/Serialization/ASTWriterStmt.cpp
@@ -1742,12 +1742,20 @@ void ASTStmtWriter::VisitCXXIteratingExpansionStmt(
   Code = serialization::STMT_CXX_ITERATING_EXPANSION;
 }
 
+void ASTStmtWriter::VisitCXXDestructuringExpansionStmt(
+    CXXDestructuringExpansionStmt *S) {
+  VisitCXXExpansionStmt(S);
+  Record.AddStmt(S->getDecompositionDeclStmt());
+  Code = serialization::STMT_CXX_DESTRUCTURING_EXPANSION;
+}
+
 void ASTStmtWriter::VisitCXXDependentExpansionStmt(
     CXXDependentExpansionStmt *S) {
   VisitCXXExpansionStmt(S);
   Record.AddStmt(S->getExpansionInitializer());
   Code = serialization::STMT_CXX_DEPENDENT_EXPANSION;
 }
+
 void ASTStmtWriter::VisitCXXExpansionInitListExpr(CXXExpansionInitListExpr *E) {
   VisitExpr(E);
   Record.push_back(E->getNumExprs());
@@ -1766,6 +1774,14 @@ void ASTStmtWriter::VisitCXXExpansionInitListSelectExpr(
   Code = serialization::EXPR_CXX_EXPANSION_INIT_LIST_SELECT;
 }
 
+void ASTStmtWriter::VisitCXXDestructuringExpansionSelectExpr(
+    CXXDestructuringExpansionSelectExpr *E) {
+  VisitExpr(E);
+  Record.AddDeclRef(E->getDecompositionDecl());
+  Record.AddStmt(E->getIndexExpr());
+  Code = serialization::EXPR_CXX_DESTRUCTURING_EXPANSION_SELECT;
+}
+
 void ASTStmtWriter::VisitMSDependentExistsStmt(MSDependentExistsStmt *S) {
   VisitStmt(S);
   Record.AddSourceLocation(S->getKeywordLoc());
diff --git a/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp b/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp
index 35881f3fe1c42..f9286a50b1437 100644
--- a/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp
+++ b/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp
@@ -1749,10 +1749,12 @@ void ExprEngine::Visit(const Stmt *S, ExplodedNode *Pred,
     case Stmt::SEHFinallyStmtClass:
     case Stmt::CXXEnumeratingExpansionStmtClass:
     case Stmt::CXXIteratingExpansionStmtClass:
+    case Stmt::CXXDestructuringExpansionStmtClass:
     case Stmt::CXXDependentExpansionStmtClass:
     case Stmt::CXXExpansionInstantiationStmtClass:
     case Stmt::CXXExpansionInitListExprClass:
     case Stmt::CXXExpansionInitListSelectExprClass:
+    case Stmt::CXXDestructuringExpansionSelectExprClass:
     case Stmt::OMPCanonicalLoopClass:
     case Stmt::OMPParallelDirectiveClass:
     case Stmt::OMPSimdDirectiveClass:
diff --git a/clang/test/SemaCXX/cxx2c-expansion-statements.cpp b/clang/test/SemaCXX/cxx2c-expansion-statements.cpp
index 7f25e4df48ae4..834903cc57c95 100644
--- a/clang/test/SemaCXX/cxx2c-expansion-statements.cpp
+++ b/clang/test/SemaCXX/cxx2c-expansion-statements.cpp
@@ -1,4 +1,4 @@
-// RUN: %clang_cc1 %s -std=c++2c -fsyntax-only -verify
+// RUN: %clang_cc1 %s -std=c++2c -fsyntax-only -fdeclspec -verify
 namespace std {
 template <typename T>
 struct initializer_list {
@@ -13,7 +13,7 @@ struct S {
   constexpr S(int x) : x{x} {}
 };
 
-void g(int);
+void g(int); // #g
 template <int n> constexpr int tg() { return n; }
 
 void f1() {
@@ -213,8 +213,8 @@ struct Private {
   friend constexpr int friend_func();
 
 private:
-  constexpr const int* begin() const { return integers.begin(); } // expected-note 3 {{declared private here}}
-  constexpr const int* end() const { return integers.end(); } // expected-note 1 {{declared private here}}
+  constexpr const int* begin() const { return integers.begin(); } // expected-note 2 {{declared private here}}
+  constexpr const int* end() const { return integers.end(); } // expected-note 2 {{declared private here}}
 
 public:
   static constexpr int member_func() {
@@ -229,8 +229,8 @@ struct Protected {
   friend constexpr int friend_func();
 
 protected:
-  constexpr const int* begin() const { return integers.begin(); } // expected-note 3 {{declared protected here}}
-  constexpr const int* end() const { return integers.end(); } // expected-note 1 {{declared protected here}}
+  constexpr const int* begin() const { return integers.begin(); } // expected-note 2 {{declared protected here}}
+  constexpr const int* end() const { return integers.end(); } // expected-note 2 {{declared protected here}}
 
 public:
   static constexpr int member_func() {
@@ -243,10 +243,10 @@ struct Protected {
 
 void access_control() {
   static constexpr Private p1;
-  template for (auto x : p1) g(x); // expected-error 3 {{'begin' is a private member of 'Private'}} expected-error 1 {{'end' is a private member of 'Private'}}
+  template for (auto x : p1) g(x); // expected-error 2 {{'begin' is a private member of 'Private'}} expected-error 2 {{'end' is a private member of 'Private'}}
 
   static constexpr Protected p2;
-  template for (auto x : p2) g(x); // expected-error 3 {{'begin' is a protected member of 'Protected'}} expected-error 1 {{'end' is a protected member of 'Protected'}}
+  template for (auto x : p2) g(x); // expected-error 2 {{'begin' is a protected member of 'Protected'}} expected-error 2 {{'end' is a protected member of 'Protected'}}
 }
 
 constexpr int friend_func() {
@@ -366,3 +366,201 @@ constexpr int adl_both_test() {
 }
 
 static_assert(adl_both_test() == 15);
+
+struct A {};
+struct B { int x = 1; };
+struct C { int a = 1, b = 2, c = 3; };
+struct D {
+  int a = 1;
+  int* b = nullptr;
+  const char* c = "3";
+};
+
+struct Nested {
+  A a;
+  B b;
+  C c;
+};
+
+struct PrivateDestructurable {
+  friend void destructurable_friend();
+private:
+  int a, b; // expected-note 4 {{declared private here}}
+};
+
+struct ProtectedDestructurable {
+  friend void destructurable_friend();
+protected:
+  int a, b; // expected-note 4 {{declared protected here}}
+};
+
+void destructuring() {
+  static constexpr A a;
+  static constexpr B b;
+  static constexpr C c;
+  static constexpr D d;
+
+  template for (auto x : a) static_assert(false, "not expanded");
+  template for (constexpr auto x : a) static_assert(false, "not expanded");
+
+  template for (auto x : b) g(x);
+  template for (constexpr auto x : b) g(x);
+
+  template for (auto x : c) g(x);
+  template for (constexpr auto x : c) g(x);
+
+  template for (auto x : d) { // expected-note 2 {{in instantiation of expansion statement requested here}}
+    // expected-note@#g {{candidate function not viable: no known conversion from 'int *' to 'int' for 1st argument}}
+    // expected-note@#g {{candidate function not viable: no known conversion from 'const char *' to 'int' for 1st argument}}
+    g(x); // expected-error 2 {{no matching function for call to 'g'}}
+
+  }
+
+  template for (constexpr auto x : d) { // expected-note 2 {{in instantiation of expansion statement requested here}}
+    // expected-note@#g {{candidate function not viable: no known conversion from 'int *const' to 'int' for 1st argument}}
+    // expected-note@#g {{candidate function not viable: no known conversion from 'const char *const' to 'int' for 1st argument}}
+    g(x); // expected-error 2 {{no matching function for call to 'g'}}
+  }
+}
+
+constexpr int array() {
+  static constexpr int x[4]{1, 2, 3, 4};
+  int sum = 0;
+  template for (auto y : x) sum += y;
+  template for (constexpr auto y : x) sum += y;
+  return sum;
+}
+
+static_assert(array() == 20);
+
+void array_too_big() {
+  int ok[32];
+  int too_big[33];
+
+  template for (auto x : ok) {}
+  template for (auto x : too_big) {} // expected-error {{expansion size 33 exceeds maximum configured size 32}} \
+                                        expected-note {{use -fexpansion-limit=N to adjust this limit}}
+}
+
+template <auto v>
+constexpr int destructure() {
+  int sum = 0;
+  template for (auto x : v) sum += x;
+  template for (constexpr auto x : v) sum += x;
+  return sum;
+}
+
+static_assert(destructure<B{10}>() == 20);
+static_assert(destructure<C{}>() == 12);
+static_assert(destructure<C{3, 4, 5}>() == 24);
+
+constexpr int nested() {
+  static constexpr Nested n;
+  int sum = 0;
+  template for (constexpr auto x : n) {
+    static constexpr auto val = x;
+    template for (auto y : val) {
+      sum += y;
+    }
+  }
+  template for (constexpr auto x : n) {
+    static constexpr auto val = x;
+    template for (constexpr auto y : val) {
+      sum += y;
+    }
+  }
+  return sum;
+}
+
+static_assert(nested() == 14);
+
+void access_control_destructurable() {
+  template for (auto x : PrivateDestructurable()) {} // expected-error 2 {{cannot bind private member 'a' of 'PrivateDestructurable'}} \
+                                                        expected-error 2 {{cannot bind private member 'b' of 'PrivateDestructurable'}}
+
+  template for (auto x : ProtectedDestructurable()) {} // expected-error 2 {{cannot bind protected member 'a' of 'ProtectedDestructurable'}} \
+                                                          expected-error 2 {{cannot bind protected member 'b' of 'ProtectedDestructurable'}}
+}
+
+void destructurable_friend() {
+  template for (auto x : PrivateDestructurable()) {}
+  template for (auto x : ProtectedDestructurable()) {}
+}
+
+struct Placeholder {
+  A get_value() const { return {}; }
+  __declspec(property(get = get_value)) A a;
+};
+
+void placeholder() {
+  template for (auto x: Placeholder().a) {}
+}
+
+union Union { int a; long b;};
+
+struct MemberPtr {
+  void f() {}
+};
+
+void overload_set(int); // expected-note 2 {{possible target for call}}
+void overload_set(long); // expected-note 2 {{possible target for call}}
+
+void invalid_types() {
+  template for (auto x : void()) {} // expected-error {{cannot expand expression of type 'void'}}
+  template for (auto x : 1) {} // expected-error {{cannot expand expression of type 'int'}}
+  template for (auto x : 1.f) {} // expected-error {{cannot expand expression of type 'float'}}
+  template for (auto x : 'c') {} // expected-error {{cannot expand expression of type 'char'}}
+  template for (auto x : invalid_types) {} // expected-error {{cannot expand expression of type 'void ()'}}
+  template for (auto x : &invalid_types) {} // expected-error {{cannot expand expression of type 'void (*)()'}}
+  template for (auto x : &MemberPtr::f) {} // expected-error {{cannot expand expression of type 'void (MemberPtr::*)()'}}
+  template for (auto x : overload_set) {} // expected-error{{reference to overloaded function could not be resolved; did you mean to call it?}}
+  template for (auto x : &overload_set) {} // expected-error{{reference to overloaded function could not be resolved; did you mean to call it?}}
+  template for (auto x : nullptr) {} // expected-error {{cannot expand expression of type 'std::nullptr_t'}}
+  template for (auto x : __builtin_strlen) {} // expected-error {{builtin functions must be directly called}}
+  template for (auto x : Union()) {} // expected-error {{cannot expand expression of type 'Union'}}
+  template for (auto x : (char*)nullptr) {} // expected-error {{cannot expand expression of type 'char *'}}
+  template for (auto x : []{}) {} // expected-error {{cannot expand lambda closure type}}
+  template for (auto x : [x=3]{}) {} // expected-error {{cannot expand lambda closure type}}
+}
+
+struct BeginOnly {
+  int x{1};
+  constexpr const int* begin() const { return nullptr; }
+};
+
+struct EndOnly {
+  int x{2};
+  constexpr const int* end() const { return nullptr; }
+};
+
+namespace adl1 {
+struct BeginOnly {
+  int x{3};
+};
+constexpr const int* begin(const BeginOnly&) { return nullptr; }
+}
+
+namespace adl2 {
+struct EndOnly {
+  int x{4};
+};
+constexpr const int* end(const EndOnly&) { return nullptr; }
+}
+
+constexpr int unpaired_begin_end() {
+  static constexpr BeginOnly b1;
+  static constexpr EndOnly e1;
+  static constexpr adl1::BeginOnly b2;
+  static constexpr adl2::EndOnly e2;
+  int sum = 0;
+
+  template for (auto x : b1) sum += x;
+  template for (auto x : e1) sum += x;
+
+  template for (auto x : b2) sum += x;
+  template for (auto x : e2) sum += x;
+
+  return sum;
+}
+
+static_assert(unpaired_begin_end() == 10);
diff --git a/clang/tools/libclang/CXCursor.cpp b/clang/tools/libclang/CXCursor.cpp
index d6b197eb5f9eb..c9cdaa6ee1285 100644
--- a/clang/tools/libclang/CXCursor.cpp
+++ b/clang/tools/libclang/CXCursor.cpp
@@ -292,6 +292,7 @@ CXCursor cxcursor::MakeCXCursor(const Stmt *S, const Decl *Parent,
   case Stmt::CoreturnStmtClass:
   case Stmt::CXXEnumeratingExpansionStmtClass:
   case Stmt::CXXIteratingExpansionStmtClass:
+  case Stmt::CXXDestructuringExpansionStmtClass:
   case Stmt::CXXDependentExpansionStmtClass:
   case Stmt::CXXExpansionInstantiationStmtClass:
     K = CXCursor_UnexposedStmt;
@@ -344,6 +345,7 @@ CXCursor cxcursor::MakeCXCursor(const Stmt *S, const Decl *Parent,
   case Stmt::OpenACCAsteriskSizeExprClass:
   case Stmt::CXXExpansionInitListExprClass:
   case Stmt::CXXExpansionInitListSelectExprClass:
+  case Stmt::CXXDestructuringExpansionSelectExprClass:
     K = CXCursor_UnexposedExpr;
     break;
 

>From 86cab85f90c59ef03aa46a9429a0f05e2228ea45 Mon Sep 17 00:00:00 2001
From: Sirraide <aeternalmail at gmail.com>
Date: Sun, 26 Oct 2025 02:38:48 +0100
Subject: [PATCH 07/33] Implement CWG 3044

---
 clang/lib/Sema/SemaExpand.cpp                 |  16 +-
 clang/lib/Sema/SemaStmt.cpp                   |   5 +-
 .../cxx2c-iterating-expansion-stmt.cpp        | 255 ++++++++++--------
 .../SemaCXX/cxx2c-expansion-statements.cpp    |  80 ++++++
 4 files changed, 242 insertions(+), 114 deletions(-)

diff --git a/clang/lib/Sema/SemaExpand.cpp b/clang/lib/Sema/SemaExpand.cpp
index 01c5403b2f124..bae7bad6660ae 100644
--- a/clang/lib/Sema/SemaExpand.cpp
+++ b/clang/lib/Sema/SemaExpand.cpp
@@ -182,6 +182,11 @@ static StmtResult BuildDestructuringExpansionStmtDecl(
     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);
+
   UnsignedOrNone Arity =
       S.GetDecompositionElementCount(ExpansionInitializer->getType(), ColonLoc);
 
@@ -196,8 +201,10 @@ static StmtResult BuildDestructuringExpansionStmtDecl(
   QualType AutoRRef = S.Context.getAutoRRefDeductType();
   SmallVector<BindingDecl *> Bindings;
   for (unsigned I = 0; I < *Arity; ++I)
-    Bindings.push_back(BindingDecl::Create(S.Context, S.CurContext, ColonLoc,
-                                           /*Id=*/nullptr, AutoRRef));
+    Bindings.push_back(BindingDecl::Create(
+        S.Context, S.CurContext, ColonLoc,
+        S.getPreprocessor().getIdentifierInfo("__u" + std::to_string(I)),
+        AutoRRef));
 
   TypeSourceInfo *TSI = S.Context.getTrivialTypeSourceInfo(AutoRRef);
   auto *DD =
@@ -360,6 +367,9 @@ StmtResult Sema::BuildNonEnumeratingCXXExpansionStmt(
 
   auto *DS = DecompDeclStmt.getAs<DeclStmt>();
   auto *DD = cast<DecompositionDecl>(DS->getSingleDecl());
+  if (DD->isInvalidDecl())
+    return StmtError();
+
   ExprResult Select = BuildCXXDestructuringExpansionSelectExpr(DD, Index);
   if (Select.isInvalid()) {
     ActOnInitializerError(ExpansionVar);
@@ -530,7 +540,7 @@ std::optional<uint64_t>  Sema::ComputeExpansionSize(CXXExpansionStmt *Expansion)
   //
   // [] consteval {
   //    std::ptrdiff_t result = 0;
-  //    for (auto i = begin; i != end; ++i, ++result);
+  //    for (auto i = begin; i != end; ++i) ++result;
   //    return result;
   // }()
   if (auto *Iterating = dyn_cast<CXXIteratingExpansionStmt>(Expansion)) {
diff --git a/clang/lib/Sema/SemaStmt.cpp b/clang/lib/Sema/SemaStmt.cpp
index 3a6d68b2f65ae..2fc92c73392f6 100644
--- a/clang/lib/Sema/SemaStmt.cpp
+++ b/clang/lib/Sema/SemaStmt.cpp
@@ -2423,10 +2423,9 @@ VarDecl *BuildForRangeVarDecl(Sema &SemaRef, SourceLocation Loc, QualType Type,
                                   TInfo, SC_None);
   Decl->setImplicit();
   Decl->setCXXForRangeImplicitVar(true);
-  if (ForExpansionStmt) {
+  if (ForExpansionStmt)
+    // CWG 3044: Do not make the variable 'static'.
     Decl->setConstexpr(true);
-    Decl->setStorageClass(SC_Static);
-  }
   return Decl;
 }
 
diff --git a/clang/test/CodeGenCXX/cxx2c-iterating-expansion-stmt.cpp b/clang/test/CodeGenCXX/cxx2c-iterating-expansion-stmt.cpp
index 0175216ded0b7..ad73972740749 100644
--- a/clang/test/CodeGenCXX/cxx2c-iterating-expansion-stmt.cpp
+++ b/clang/test/CodeGenCXX/cxx2c-iterating-expansion-stmt.cpp
@@ -97,58 +97,31 @@ int custom_iterator() {
 }
 
 // CHECK: @_ZZ2f1vE8integers = internal constant %struct.Array { [3 x i32] [i32 1, i32 2, i32 3] }, align 4
-// CHECK: @_ZZ2f1vE8__range1 = internal constant ptr @_ZZ2f1vE8integers, align 8
-// CHECK: @_ZZ2f1vE8__begin1 = internal constant ptr @_ZZ2f1vE8integers, align 8
-// CHECK: @_ZZ2f1vE6__end1 = internal constant ptr getelementptr (i8, ptr @_ZZ2f1vE8integers, i64 12), align 8
 // CHECK: @_ZZ2f2vE8integers = internal constant %struct.Array { [3 x i32] [i32 1, i32 2, i32 3] }, align 4
-// CHECK: @_ZZ2f2vE8__range1 = internal constant ptr @_ZZ2f2vE8integers, align 8
-// CHECK: @_ZZ2f2vE8__begin1 = internal constant ptr @_ZZ2f2vE8integers, align 8
-// CHECK: @_ZZ2f2vE6__end1 = internal constant ptr getelementptr (i8, ptr @_ZZ2f2vE8integers, i64 12), align 8
 // CHECK: @_ZZ2f3vE8integers = internal constant %struct.Array.0 zeroinitializer, align 4
-// CHECK: @_ZZ2f3vE8__range1 = internal constant ptr @_ZZ2f3vE8integers, align 8
-// CHECK: @_ZZ2f3vE8__begin1 = internal constant ptr @_ZZ2f3vE8integers, align 8
-// CHECK: @_ZZ2f3vE6__end1 = internal constant ptr @_ZZ2f3vE8integers, align 8
 // CHECK: @_ZZ2f4vE1a = internal constant %struct.Array.1 { [2 x i32] [i32 1, i32 2] }, align 4
 // CHECK: @_ZZ2f4vE1b = internal constant %struct.Array.1 { [2 x i32] [i32 3, i32 4] }, align 4
-// CHECK: @_ZZ2f4vE8__range1 = internal constant ptr @_ZZ2f4vE1a, align 8
-// CHECK: @_ZZ2f4vE8__begin1 = internal constant ptr @_ZZ2f4vE1a, align 8
-// CHECK: @_ZZ2f4vE6__end1 = internal constant ptr getelementptr (i8, ptr @_ZZ2f4vE1a, i64 8), align 8
-// CHECK: @_ZZ2f4vE8__range2 = internal constant ptr @_ZZ2f4vE1b, align 8
-// CHECK: @_ZZ2f4vE8__begin2 = internal constant ptr @_ZZ2f4vE1b, align 8
-// CHECK: @_ZZ2f4vE6__end2 = internal constant ptr getelementptr (i8, ptr @_ZZ2f4vE1b, i64 8), align 8
-// CHECK: @_ZZ2f4vE8__range2_0 = internal constant ptr @_ZZ2f4vE1b, align 8
-// CHECK: @_ZZ2f4vE8__begin2_0 = internal constant ptr @_ZZ2f4vE1b, align 8
-// CHECK: @_ZZ2f4vE6__end2_0 = internal constant ptr getelementptr (i8, ptr @_ZZ2f4vE1b, i64 8), align 8
-// CHECK: @_ZZ2f4vE8__range1_0 = internal constant ptr @_ZZ2f4vE1a, align 8
-// CHECK: @_ZZ2f4vE8__begin1_0 = internal constant ptr @_ZZ2f4vE1a, align 8
-// CHECK: @_ZZ2f4vE6__end1_0 = internal constant ptr getelementptr (i8, ptr @_ZZ2f4vE1a, i64 8), align 8
-// CHECK: @_ZZ2f4vE8__range2_1 = internal constant ptr @_ZZ2f4vE1b, align 8
-// CHECK: @_ZZ2f4vE8__begin2_1 = internal constant ptr @_ZZ2f4vE1b, align 8
-// CHECK: @_ZZ2f4vE6__end2_1 = internal constant ptr getelementptr (i8, ptr @_ZZ2f4vE1b, i64 8), align 8
-// CHECK: @_ZZ2f4vE8__range2_2 = internal constant ptr @_ZZ2f4vE1b, align 8
-// CHECK: @_ZZ2f4vE8__begin2_2 = internal constant ptr @_ZZ2f4vE1b, align 8
-// CHECK: @_ZZ2f4vE6__end2_2 = internal constant ptr getelementptr (i8, ptr @_ZZ2f4vE1b, i64 8), align 8
 // CHECK: @_ZZN7Private11member_funcEvE2p1 = internal constant %struct.Private zeroinitializer, align 1
-// CHECK: @_ZZN7Private11member_funcEvE8__range1 = internal constant ptr @_ZZN7Private11member_funcEvE2p1, align 8
-// CHECK: @_ZZN7Private11member_funcEvE8__begin1 = internal constant ptr @_ZN7Private8integersE, align 8
 // CHECK: @_ZN7Private8integersE = {{.*}} constant %struct.Array { [3 x i32] [i32 1, i32 2, i32 3] }, comdat, align 4
-// CHECK: @_ZZN7Private11member_funcEvE6__end1 = internal constant ptr getelementptr (i8, ptr @_ZN7Private8integersE, i64 12), align 8
 // CHECK: @_ZZ15custom_iteratorvE1c = internal constant %struct.CustomIterator zeroinitializer, align 1
-// CHECK: @_ZZ15custom_iteratorvE8__range1 = internal constant ptr @_ZZ15custom_iteratorvE1c, align 8
-// CHECK: @_ZZ15custom_iteratorvE8__begin1 = internal constant %"struct.CustomIterator::iterator" { i32 1 }, align 4
-// CHECK: @_ZZ15custom_iteratorvE6__end1 = internal constant %"struct.CustomIterator::iterator" { i32 5 }, align 4
-// CHECK: @_ZZ15custom_iteratorvE8__range1_0 = internal constant ptr @_ZZ15custom_iteratorvE1c, align 8
-// CHECK: @_ZZ15custom_iteratorvE8__begin1_0 = internal constant %"struct.CustomIterator::iterator" { i32 1 }, align 4
-// CHECK: @_ZZ15custom_iteratorvE6__end1_0 = internal constant %"struct.CustomIterator::iterator" { i32 5 }, align 4
-
+// CHECK: @__const._Z15custom_iteratorv.__begin1 = private {{.*}} constant %"struct.CustomIterator::iterator" { i32 1 }, align 4
+// CHECK: @__const._Z15custom_iteratorv.__end1 = private {{.*}} constant %"struct.CustomIterator::iterator" { i32 5 }, align 4
+// CHECK: @__const._Z15custom_iteratorv.__begin1.1 = private {{.*}} constant %"struct.CustomIterator::iterator" { i32 1 }, align 4
+// CHECK: @__const._Z15custom_iteratorv.__end1.2 = private {{.*}} constant %"struct.CustomIterator::iterator" { i32 5 }, align 4
 
 // CHECK-LABEL: define {{.*}} i32 @_Z2f1v()
 // CHECK: entry:
 // CHECK-NEXT:   %sum = alloca i32, align 4
+// CHECK-NEXT:   %__range1 = alloca ptr, align 8
+// CHECK-NEXT:   %__begin1 = alloca ptr, align 8
+// CHECK-NEXT:   %__end1 = alloca ptr, align 8
 // CHECK-NEXT:   %x = alloca i32, align 4
 // CHECK-NEXT:   %x1 = alloca i32, align 4
 // CHECK-NEXT:   %x4 = alloca i32, align 4
 // CHECK-NEXT:   store i32 0, ptr %sum, align 4
+// CHECK-NEXT:   store ptr @_ZZ2f1vE8integers, ptr %__range1, align 8
+// CHECK-NEXT:   store ptr @_ZZ2f1vE8integers, ptr %__begin1, align 8
+// CHECK-NEXT:   store ptr getelementptr (i8, ptr @_ZZ2f1vE8integers, i64 12), ptr %__end1, align 8
 // CHECK-NEXT:   %0 = load i32, ptr @_ZZ2f1vE8integers, align 4
 // CHECK-NEXT:   store i32 %0, ptr %x, align 4
 // CHECK-NEXT:   %1 = load i32, ptr %x, align 4
@@ -180,10 +153,16 @@ int custom_iterator() {
 // CHECK-LABEL: define {{.*}} i32 @_Z2f2v()
 // CHECK: entry:
 // CHECK-NEXT:   %sum = alloca i32, align 4
+// CHECK-NEXT:   %__range1 = alloca ptr, align 8
+// CHECK-NEXT:   %__begin1 = alloca ptr, align 8
+// CHECK-NEXT:   %__end1 = alloca ptr, align 8
 // CHECK-NEXT:   %x = alloca i32, align 4
 // CHECK-NEXT:   %x1 = alloca i32, align 4
 // CHECK-NEXT:   %x4 = alloca i32, align 4
 // CHECK-NEXT:   store i32 0, ptr %sum, align 4
+// CHECK-NEXT:   store ptr @_ZZ2f2vE8integers, ptr %__range1, align 8
+// CHECK-NEXT:   store ptr @_ZZ2f2vE8integers, ptr %__begin1, align 8
+// CHECK-NEXT:   store ptr getelementptr (i8, ptr @_ZZ2f2vE8integers, i64 12), ptr %__end1, align 8
 // CHECK-NEXT:   store i32 1, ptr %x, align 4
 // CHECK-NEXT:   %0 = load i32, ptr %sum, align 4
 // CHECK-NEXT:   %add = add nsw i32 %0, 1
@@ -209,7 +188,13 @@ int custom_iterator() {
 // CHECK-LABEL: define {{.*}} i32 @_Z2f3v()
 // CHECK: entry:
 // CHECK-NEXT:   %sum = alloca i32, align 4
+// CHECK-NEXT:   %__range1 = alloca ptr, align 8
+// CHECK-NEXT:   %__begin1 = alloca ptr, align 8
+// CHECK-NEXT:   %__end1 = alloca ptr, align 8
 // CHECK-NEXT:   store i32 0, ptr %sum, align 4
+// CHECK-NEXT:   store ptr @_ZZ2f3vE8integers, ptr %__range1, align 8
+// CHECK-NEXT:   store ptr @_ZZ2f3vE8integers, ptr %__begin1, align 8
+// CHECK-NEXT:   store ptr @_ZZ2f3vE8integers, ptr %__end1, align 8
 // CHECK-NEXT:   %0 = load i32, ptr %sum, align 4
 // CHECK-NEXT:   ret i32 %0
 
@@ -217,21 +202,45 @@ int custom_iterator() {
 // CHECK-LABEL: define {{.*}} i32 @_Z2f4v()
 // CHECK: entry:
 // CHECK-NEXT:   %sum = alloca i32, align 4
+// CHECK-NEXT:   %__range1 = alloca ptr, align 8
+// CHECK-NEXT:   %__begin1 = alloca ptr, align 8
+// CHECK-NEXT:   %__end1 = alloca ptr, align 8
 // CHECK-NEXT:   %x = alloca i32, align 4
+// CHECK-NEXT:   %__range2 = alloca ptr, align 8
+// CHECK-NEXT:   %__begin2 = alloca ptr, align 8
+// CHECK-NEXT:   %__end2 = alloca ptr, align 8
 // CHECK-NEXT:   %y = alloca i32, align 4
 // CHECK-NEXT:   %y2 = alloca i32, align 4
 // CHECK-NEXT:   %x6 = alloca i32, align 4
-// CHECK-NEXT:   %y7 = alloca i32, align 4
-// CHECK-NEXT:   %y11 = alloca i32, align 4
-// CHECK-NEXT:   %x16 = alloca i32, align 4
-// CHECK-NEXT:   %y17 = alloca i32, align 4
-// CHECK-NEXT:   %y20 = alloca i32, align 4
-// CHECK-NEXT:   %x24 = alloca i32, align 4
-// CHECK-NEXT:   %y25 = alloca i32, align 4
-// CHECK-NEXT:   %y28 = alloca i32, align 4
+// CHECK-NEXT:   %__range27 = alloca ptr, align 8
+// CHECK-NEXT:   %__begin28 = alloca ptr, align 8
+// CHECK-NEXT:   %__end29 = alloca ptr, align 8
+// CHECK-NEXT:   %y10 = alloca i32, align 4
+// CHECK-NEXT:   %y14 = alloca i32, align 4
+// CHECK-NEXT:   %__range119 = alloca ptr, align 8
+// CHECK-NEXT:   %__begin120 = alloca ptr, align 8
+// CHECK-NEXT:   %__end121 = alloca ptr, align 8
+// CHECK-NEXT:   %x22 = alloca i32, align 4
+// CHECK-NEXT:   %__range223 = alloca ptr, align 8
+// CHECK-NEXT:   %__begin224 = alloca ptr, align 8
+// CHECK-NEXT:   %__end225 = alloca ptr, align 8
+// CHECK-NEXT:   %y26 = alloca i32, align 4
+// CHECK-NEXT:   %y29 = alloca i32, align 4
+// CHECK-NEXT:   %x33 = alloca i32, align 4
+// CHECK-NEXT:   %__range234 = alloca ptr, align 8
+// CHECK-NEXT:   %__begin235 = alloca ptr, align 8
+// CHECK-NEXT:   %__end236 = alloca ptr, align 8
+// CHECK-NEXT:   %y37 = alloca i32, align 4
+// CHECK-NEXT:   %y40 = alloca i32, align 4
 // CHECK-NEXT:   store i32 0, ptr %sum, align 4
+// CHECK-NEXT:   store ptr @_ZZ2f4vE1a, ptr %__range1, align 8
+// CHECK-NEXT:   store ptr @_ZZ2f4vE1a, ptr %__begin1, align 8
+// CHECK-NEXT:   store ptr getelementptr (i8, ptr @_ZZ2f4vE1a, i64 8), ptr %__end1, align 8
 // CHECK-NEXT:   %0 = load i32, ptr @_ZZ2f4vE1a, align 4
 // CHECK-NEXT:   store i32 %0, ptr %x, align 4
+// CHECK-NEXT:   store ptr @_ZZ2f4vE1b, ptr %__range2, align 8
+// CHECK-NEXT:   store ptr @_ZZ2f4vE1b, ptr %__begin2, align 8
+// CHECK-NEXT:   store ptr getelementptr (i8, ptr @_ZZ2f4vE1b, i64 8), ptr %__end2, align 8
 // CHECK-NEXT:   %1 = load i32, ptr @_ZZ2f4vE1b, align 4
 // CHECK-NEXT:   store i32 %1, ptr %y, align 4
 // CHECK-NEXT:   %2 = load i32, ptr %x, align 4
@@ -256,58 +265,70 @@ int custom_iterator() {
 // CHECK: expand.next5:
 // CHECK-NEXT:   %9 = load i32, ptr getelementptr inbounds (i32, ptr @_ZZ2f4vE1a, i64 1), align 4
 // CHECK-NEXT:   store i32 %9, ptr %x6, align 4
+// CHECK-NEXT:   store ptr @_ZZ2f4vE1b, ptr %__range27, align 8
+// CHECK-NEXT:   store ptr @_ZZ2f4vE1b, ptr %__begin28, align 8
+// CHECK-NEXT:   store ptr getelementptr (i8, ptr @_ZZ2f4vE1b, i64 8), ptr %__end29, align 8
 // CHECK-NEXT:   %10 = load i32, ptr @_ZZ2f4vE1b, align 4
-// CHECK-NEXT:   store i32 %10, ptr %y7, align 4
+// CHECK-NEXT:   store i32 %10, ptr %y10, align 4
 // CHECK-NEXT:   %11 = load i32, ptr %x6, align 4
-// CHECK-NEXT:   %12 = load i32, ptr %y7, align 4
-// CHECK-NEXT:   %add8 = add nsw i32 %11, %12
+// CHECK-NEXT:   %12 = load i32, ptr %y10, align 4
+// CHECK-NEXT:   %add11 = add nsw i32 %11, %12
 // CHECK-NEXT:   %13 = load i32, ptr %sum, align 4
-// CHECK-NEXT:   %add9 = add nsw i32 %13, %add8
-// CHECK-NEXT:   store i32 %add9, ptr %sum, align 4
-// CHECK-NEXT:   br label %expand.next10
-// CHECK: expand.next10:
+// CHECK-NEXT:   %add12 = add nsw i32 %13, %add11
+// CHECK-NEXT:   store i32 %add12, ptr %sum, align 4
+// CHECK-NEXT:   br label %expand.next13
+// CHECK: expand.next13:
 // CHECK-NEXT:   %14 = load i32, ptr getelementptr inbounds (i32, ptr @_ZZ2f4vE1b, i64 1), align 4
-// CHECK-NEXT:   store i32 %14, ptr %y11, align 4
+// CHECK-NEXT:   store i32 %14, ptr %y14, align 4
 // CHECK-NEXT:   %15 = load i32, ptr %x6, align 4
-// CHECK-NEXT:   %16 = load i32, ptr %y11, align 4
-// CHECK-NEXT:   %add12 = add nsw i32 %15, %16
+// CHECK-NEXT:   %16 = load i32, ptr %y14, align 4
+// CHECK-NEXT:   %add15 = add nsw i32 %15, %16
 // CHECK-NEXT:   %17 = load i32, ptr %sum, align 4
-// CHECK-NEXT:   %add13 = add nsw i32 %17, %add12
-// CHECK-NEXT:   store i32 %add13, ptr %sum, align 4
-// CHECK-NEXT:   br label %expand.end14
-// CHECK: expand.end14:
-// CHECK-NEXT:   br label %expand.end15
-// CHECK: expand.end15:
-// CHECK-NEXT:   store i32 1, ptr %x16, align 4
-// CHECK-NEXT:   store i32 3, ptr %y17, align 4
+// CHECK-NEXT:   %add16 = add nsw i32 %17, %add15
+// CHECK-NEXT:   store i32 %add16, ptr %sum, align 4
+// CHECK-NEXT:   br label %expand.end17
+// CHECK: expand.end17:
+// CHECK-NEXT:   br label %expand.end18
+// CHECK: expand.end18:
+// CHECK-NEXT:   store ptr @_ZZ2f4vE1a, ptr %__range119, align 8
+// CHECK-NEXT:   store ptr @_ZZ2f4vE1a, ptr %__begin120, align 8
+// CHECK-NEXT:   store ptr getelementptr (i8, ptr @_ZZ2f4vE1a, i64 8), ptr %__end121, align 8
+// CHECK-NEXT:   store i32 1, ptr %x22, align 4
+// CHECK-NEXT:   store ptr @_ZZ2f4vE1b, ptr %__range223, align 8
+// CHECK-NEXT:   store ptr @_ZZ2f4vE1b, ptr %__begin224, align 8
+// CHECK-NEXT:   store ptr getelementptr (i8, ptr @_ZZ2f4vE1b, i64 8), ptr %__end225, align 8
+// CHECK-NEXT:   store i32 3, ptr %y26, align 4
 // CHECK-NEXT:   %18 = load i32, ptr %sum, align 4
-// CHECK-NEXT:   %add18 = add nsw i32 %18, 4
-// CHECK-NEXT:   store i32 %add18, ptr %sum, align 4
-// CHECK-NEXT:   br label %expand.next19
-// CHECK: expand.next19:
-// CHECK-NEXT:   store i32 4, ptr %y20, align 4
+// CHECK-NEXT:   %add27 = add nsw i32 %18, 4
+// CHECK-NEXT:   store i32 %add27, ptr %sum, align 4
+// CHECK-NEXT:   br label %expand.next28
+// CHECK: expand.next28:
+// CHECK-NEXT:   store i32 4, ptr %y29, align 4
 // CHECK-NEXT:   %19 = load i32, ptr %sum, align 4
-// CHECK-NEXT:   %add21 = add nsw i32 %19, 5
-// CHECK-NEXT:   store i32 %add21, ptr %sum, align 4
-// CHECK-NEXT:   br label %expand.end22
-// CHECK: expand.end22:
-// CHECK-NEXT:   br label %expand.next23
-// CHECK: expand.next23:
-// CHECK-NEXT:   store i32 2, ptr %x24, align 4
-// CHECK-NEXT:   store i32 3, ptr %y25, align 4
-// CHECK-NEXT:   %20 = load i32, ptr %sum, align 4
-// CHECK-NEXT:   %add26 = add nsw i32 %20, 5
-// CHECK-NEXT:   store i32 %add26, ptr %sum, align 4
-// CHECK-NEXT:   br label %expand.next27
-// CHECK: expand.next27:
-// CHECK-NEXT:   store i32 4, ptr %y28, align 4
-// CHECK-NEXT:   %21 = load i32, ptr %sum, align 4
-// CHECK-NEXT:   %add29 = add nsw i32 %21, 6
-// CHECK-NEXT:   store i32 %add29, ptr %sum, align 4
-// CHECK-NEXT:   br label %expand.end30
-// CHECK: expand.end30:
+// CHECK-NEXT:   %add30 = add nsw i32 %19, 5
+// CHECK-NEXT:   store i32 %add30, ptr %sum, align 4
 // CHECK-NEXT:   br label %expand.end31
 // CHECK: expand.end31:
+// CHECK-NEXT:   br label %expand.next32
+// CHECK: expand.next32:
+// CHECK-NEXT:   store i32 2, ptr %x33, align 4
+// CHECK-NEXT:   store ptr @_ZZ2f4vE1b, ptr %__range234, align 8
+// CHECK-NEXT:   store ptr @_ZZ2f4vE1b, ptr %__begin235, align 8
+// CHECK-NEXT:   store ptr getelementptr (i8, ptr @_ZZ2f4vE1b, i64 8), ptr %__end236, align 8
+// CHECK-NEXT:   store i32 3, ptr %y37, align 4
+// CHECK-NEXT:   %20 = load i32, ptr %sum, align 4
+// CHECK-NEXT:   %add38 = add nsw i32 %20, 5
+// CHECK-NEXT:   store i32 %add38, ptr %sum, align 4
+// CHECK-NEXT:   br label %expand.next39
+// CHECK: expand.next39:
+// CHECK-NEXT:   store i32 4, ptr %y40, align 4
+// CHECK-NEXT:   %21 = load i32, ptr %sum, align 4
+// CHECK-NEXT:   %add41 = add nsw i32 %21, 6
+// CHECK-NEXT:   store i32 %add41, ptr %sum, align 4
+// CHECK-NEXT:   br label %expand.end42
+// CHECK: expand.end42:
+// CHECK-NEXT:   br label %expand.end43
+// CHECK: expand.end43:
 // CHECK-NEXT:   %22 = load i32, ptr %sum, align 4
 // CHECK-NEXT:   ret i32 %22
 
@@ -315,10 +336,16 @@ int custom_iterator() {
 // CHECK-LABEL: define {{.*}} i32 @_ZN7Private11member_funcEv()
 // CHECK: entry:
 // CHECK-NEXT:   %sum = alloca i32, align 4
+// CHECK-NEXT:   %__range1 = alloca ptr, align 8
+// CHECK-NEXT:   %__begin1 = alloca ptr, align 8
+// CHECK-NEXT:   %__end1 = alloca ptr, align 8
 // CHECK-NEXT:   %x = alloca i32, align 4
 // CHECK-NEXT:   %x1 = alloca i32, align 4
 // CHECK-NEXT:   %x4 = alloca i32, align 4
 // CHECK-NEXT:   store i32 0, ptr %sum, align 4
+// CHECK-NEXT:   store ptr @_ZZN7Private11member_funcEvE2p1, ptr %__range1, align 8
+// CHECK-NEXT:   store ptr @_ZN7Private8integersE, ptr %__begin1, align 8
+// CHECK-NEXT:   store ptr getelementptr (i8, ptr @_ZN7Private8integersE, i64 12), ptr %__end1, align 8
 // CHECK-NEXT:   %0 = load i32, ptr @_ZN7Private8integersE, align 4
 // CHECK-NEXT:   store i32 %0, ptr %x, align 4
 // CHECK-NEXT:   %1 = load i32, ptr %x, align 4
@@ -350,6 +377,9 @@ int custom_iterator() {
 // CHECK-LABEL: define {{.*}} i32 @_Z15custom_iteratorv()
 // CHECK: entry:
 // CHECK-NEXT:   %sum = alloca i32, align 4
+// CHECK-NEXT:   %__range1 = alloca ptr, align 8
+// CHECK: %__begin1 = alloca %"struct.CustomIterator::iterator", align 4
+// CHECK: %__end1 = alloca %"struct.CustomIterator::iterator", align 4
 // CHECK-NEXT:   %x = alloca i32, align 4
 // CHECK: %ref.tmp = alloca %"struct.CustomIterator::iterator", align 4
 // CHECK-NEXT:   %x2 = alloca i32, align 4
@@ -358,12 +388,18 @@ int custom_iterator() {
 // CHECK: %ref.tmp10 = alloca %"struct.CustomIterator::iterator", align 4
 // CHECK-NEXT:   %x16 = alloca i32, align 4
 // CHECK: %ref.tmp17 = alloca %"struct.CustomIterator::iterator", align 4
-// CHECK-NEXT:   %x22 = alloca i32, align 4
+// CHECK-NEXT:   %__range122 = alloca ptr, align 8
+// CHECK: %__begin123 = alloca %"struct.CustomIterator::iterator", align 4
+// CHECK: %__end124 = alloca %"struct.CustomIterator::iterator", align 4
 // CHECK-NEXT:   %x25 = alloca i32, align 4
 // CHECK-NEXT:   %x28 = alloca i32, align 4
 // CHECK-NEXT:   %x31 = alloca i32, align 4
+// CHECK-NEXT:   %x34 = alloca i32, align 4
 // CHECK-NEXT:   store i32 0, ptr %sum, align 4
-// CHECK-NEXT:   %call = call i32 @_ZNK14CustomIterator8iteratorplEi(ptr {{.*}} @_ZZ15custom_iteratorvE8__begin1, i32 {{.*}} 0)
+// CHECK-NEXT:   store ptr @_ZZ15custom_iteratorvE1c, ptr %__range1, align 8
+// CHECK-NEXT:   call void @llvm.memcpy.p0.p0.i64(ptr align 4 %__begin1, ptr align 4 @__const._Z15custom_iteratorv.__begin1, i64 4, i1 false)
+// CHECK-NEXT:   call void @llvm.memcpy.p0.p0.i64(ptr align 4 %__end1, ptr align 4 @__const._Z15custom_iteratorv.__end1, i64 4, i1 false)
+// CHECK-NEXT:   %call = call i32 @_ZNK14CustomIterator8iteratorplEi(ptr {{.*}} %__begin1, i32 {{.*}} 0)
 // CHECK: %coerce.dive = getelementptr inbounds nuw %"struct.CustomIterator::iterator", ptr %ref.tmp, i32 0, i32 0
 // CHECK-NEXT:   store i32 %call, ptr %coerce.dive, align 4
 // CHECK-NEXT:   %call1 = call {{.*}} i32 @_ZNK14CustomIterator8iteratordeEv(ptr {{.*}} %ref.tmp)
@@ -374,7 +410,7 @@ int custom_iterator() {
 // CHECK-NEXT:   store i32 %add, ptr %sum, align 4
 // CHECK-NEXT:   br label %expand.next
 // CHECK: expand.next:
-// CHECK-NEXT:   %call4 = call i32 @_ZNK14CustomIterator8iteratorplEi(ptr {{.*}} @_ZZ15custom_iteratorvE8__begin1, i32 {{.*}} 1)
+// CHECK-NEXT:   %call4 = call i32 @_ZNK14CustomIterator8iteratorplEi(ptr {{.*}} %__begin1, i32 {{.*}} 1)
 // CHECK: %coerce.dive5 = getelementptr inbounds nuw %"struct.CustomIterator::iterator", ptr %ref.tmp3, i32 0, i32 0
 // CHECK-NEXT:   store i32 %call4, ptr %coerce.dive5, align 4
 // CHECK-NEXT:   %call6 = call {{.*}} i32 @_ZNK14CustomIterator8iteratordeEv(ptr {{.*}} %ref.tmp3)
@@ -385,7 +421,7 @@ int custom_iterator() {
 // CHECK-NEXT:   store i32 %add7, ptr %sum, align 4
 // CHECK-NEXT:   br label %expand.next8
 // CHECK: expand.next8:
-// CHECK-NEXT:   %call11 = call i32 @_ZNK14CustomIterator8iteratorplEi(ptr {{.*}} @_ZZ15custom_iteratorvE8__begin1, i32 {{.*}} 2)
+// CHECK-NEXT:   %call11 = call i32 @_ZNK14CustomIterator8iteratorplEi(ptr {{.*}} %__begin1, i32 {{.*}} 2)
 // CHECK: %coerce.dive12 = getelementptr inbounds nuw %"struct.CustomIterator::iterator", ptr %ref.tmp10, i32 0, i32 0
 // CHECK-NEXT:   store i32 %call11, ptr %coerce.dive12, align 4
 // CHECK-NEXT:   %call13 = call {{.*}} i32 @_ZNK14CustomIterator8iteratordeEv(ptr {{.*}} %ref.tmp10)
@@ -396,7 +432,7 @@ int custom_iterator() {
 // CHECK-NEXT:   store i32 %add14, ptr %sum, align 4
 // CHECK-NEXT:   br label %expand.next15
 // CHECK: expand.next15:
-// CHECK-NEXT:   %call18 = call i32 @_ZNK14CustomIterator8iteratorplEi(ptr {{.*}} @_ZZ15custom_iteratorvE8__begin1, i32 {{.*}} 3)
+// CHECK-NEXT:   %call18 = call i32 @_ZNK14CustomIterator8iteratorplEi(ptr {{.*}} %__begin1, i32 {{.*}} 3)
 // CHECK: %coerce.dive19 = getelementptr inbounds nuw %"struct.CustomIterator::iterator", ptr %ref.tmp17, i32 0, i32 0
 // CHECK-NEXT:   store i32 %call18, ptr %coerce.dive19, align 4
 // CHECK-NEXT:   %call20 = call {{.*}} i32 @_ZNK14CustomIterator8iteratordeEv(ptr {{.*}} %ref.tmp17)
@@ -407,29 +443,32 @@ int custom_iterator() {
 // CHECK-NEXT:   store i32 %add21, ptr %sum, align 4
 // CHECK-NEXT:   br label %expand.end
 // CHECK: expand.end:
-// CHECK-NEXT:   store i32 1, ptr %x22, align 4
+// CHECK-NEXT:   store ptr @_ZZ15custom_iteratorvE1c, ptr %__range122, align 8
+// CHECK-NEXT:   call void @llvm.memcpy.p0.p0.i64(ptr align 4 %__begin123, ptr align 4 @__const._Z15custom_iteratorv.__begin1.1, i64 4, i1 false)
+// CHECK-NEXT:   call void @llvm.memcpy.p0.p0.i64(ptr align 4 %__end124, ptr align 4 @__const._Z15custom_iteratorv.__end1.2, i64 4, i1 false)
+// CHECK-NEXT:   store i32 1, ptr %x25, align 4
 // CHECK-NEXT:   %8 = load i32, ptr %sum, align 4
-// CHECK-NEXT:   %add23 = add nsw i32 %8, 1
-// CHECK-NEXT:   store i32 %add23, ptr %sum, align 4
-// CHECK-NEXT:   br label %expand.next24
-// CHECK: expand.next24:
-// CHECK-NEXT:   store i32 2, ptr %x25, align 4
-// CHECK-NEXT:   %9 = load i32, ptr %sum, align 4
-// CHECK-NEXT:   %add26 = add nsw i32 %9, 2
+// CHECK-NEXT:   %add26 = add nsw i32 %8, 1
 // CHECK-NEXT:   store i32 %add26, ptr %sum, align 4
 // CHECK-NEXT:   br label %expand.next27
 // CHECK: expand.next27:
-// CHECK-NEXT:   store i32 3, ptr %x28, align 4
-// CHECK-NEXT:   %10 = load i32, ptr %sum, align 4
-// CHECK-NEXT:   %add29 = add nsw i32 %10, 3
+// CHECK-NEXT:   store i32 2, ptr %x28, align 4
+// CHECK-NEXT:   %9 = load i32, ptr %sum, align 4
+// CHECK-NEXT:   %add29 = add nsw i32 %9, 2
 // CHECK-NEXT:   store i32 %add29, ptr %sum, align 4
 // CHECK-NEXT:   br label %expand.next30
 // CHECK: expand.next30:
-// CHECK-NEXT:   store i32 4, ptr %x31, align 4
-// CHECK-NEXT:   %11 = load i32, ptr %sum, align 4
-// CHECK-NEXT:   %add32 = add nsw i32 %11, 4
+// CHECK-NEXT:   store i32 3, ptr %x31, align 4
+// CHECK-NEXT:   %10 = load i32, ptr %sum, align 4
+// CHECK-NEXT:   %add32 = add nsw i32 %10, 3
 // CHECK-NEXT:   store i32 %add32, ptr %sum, align 4
-// CHECK-NEXT:   br label %expand.end33
-// CHECK: expand.end33:
+// CHECK-NEXT:   br label %expand.next33
+// CHECK: expand.next33:
+// CHECK-NEXT:   store i32 4, ptr %x34, align 4
+// CHECK-NEXT:   %11 = load i32, ptr %sum, align 4
+// CHECK-NEXT:   %add35 = add nsw i32 %11, 4
+// CHECK-NEXT:   store i32 %add35, ptr %sum, align 4
+// CHECK-NEXT:   br label %expand.end36
+// CHECK: expand.end36:
 // CHECK-NEXT:   %12 = load i32, ptr %sum, align 4
 // CHECK-NEXT:   ret i32 %12
diff --git a/clang/test/SemaCXX/cxx2c-expansion-statements.cpp b/clang/test/SemaCXX/cxx2c-expansion-statements.cpp
index 834903cc57c95..3cdf8512639bf 100644
--- a/clang/test/SemaCXX/cxx2c-expansion-statements.cpp
+++ b/clang/test/SemaCXX/cxx2c-expansion-statements.cpp
@@ -564,3 +564,83 @@ constexpr int unpaired_begin_end() {
 }
 
 static_assert(unpaired_begin_end() == 10);
+
+// Examples taken from [stmt.expand].
+namespace stmt_expand_examples {
+consteval int f(auto const&... Containers) {
+  int result = 0;
+  template for (auto const& c : {Containers...}) {      // OK, enumerating expansion statement
+    result += c[0];
+  }
+  return result;
+}
+constexpr int c1[] = {1, 2, 3};
+constexpr int c2[] = {4, 3, 2, 1};
+static_assert(f(c1, c2) == 5);
+
+// TODO: This entire example should work without issuing any diagnostics once
+// we have full support for references to constexpr variables (P2686).
+consteval int f() {
+  constexpr Array<int, 3> arr {1, 2, 3}; // expected-note{{add 'static' to give it a constant address}}
+
+  int result = 0;
+
+  // expected-error@#invalid-ref {{constexpr variable '__range1' must be initialized by a constant expression}}
+  // expected-error@#invalid-ref {{constexpr variable '__begin1' must be initialized by a constant expression}}
+  // expected-error@#invalid-ref {{constexpr variable '__end1' must be initialized by a constant expression}}
+  // expected-error@#invalid-ref {{expansion size is not a constant expression}}
+  // expected-note@#invalid-ref 2 {{member call on variable '__range1' whose value is not known}}
+  // expected-note@#invalid-ref 1 {{initializer of '__end1' is not a constant expression}}
+  // expected-note@#invalid-ref 3 {{declared here}}
+  // expected-note@#invalid-ref {{reference to 'arr' is not a constant expression}}
+  template for (constexpr int s : arr) { // #invalid-ref                // OK, iterating expansion statement
+    result += sizeof(char[s]);
+  }
+  return result;
+}
+static_assert(f() == 6); // expected-error {{static assertion failed due to requirement 'f() == 6'}} expected-note {{expression evaluates to '0 == 6'}}
+
+struct S {
+  int i;
+  short s;
+};
+
+consteval long f(S s) {
+  long result = 0;
+  template for (auto x : s) {                           // OK, destructuring expansion statement
+    result += sizeof(x);
+  }
+  return result;
+}
+static_assert(f(S{}) == sizeof(int) + sizeof(short));
+}
+
+void not_constant_expression() {
+  template for (constexpr auto x : B()) { // expected-error {{constexpr variable '[__u0]' must be initialized by a constant expression}} \
+                                             expected-note {{reference to temporary is not a constant expression}} \
+                                             expected-note {{temporary created here}} \
+                                             expected-error {{constexpr variable 'x' must be initialized by a constant expression}} \
+                                             expected-note {{in instantiation of expansion statement requested here}} \
+                                             expected-note {{read of variable '[__u0]' whose value is not known}} \
+                                             expected-note {{declared here}}
+    g(x);
+  }
+}
+
+constexpr int references_enumerating() {
+  int x = 1, y = 2, z = 3;
+  template for (auto& x : {x, y, z}) { ++x; }
+  template for (auto&& x : {x, y, z}) { ++x; }
+  return x + y + z;
+}
+
+static_assert(references_enumerating() == 12);
+
+constexpr int references_destructuring() {
+  C c;
+  template for (auto& x : c) { ++x; }
+  template for (auto&& x : c) { ++x; }
+  return c.a + c.b + c.c;
+}
+
+static_assert(references_destructuring() == 12);

>From 1deea2be6afaaeb2b83b9136feeb30a7589ed708 Mon Sep 17 00:00:00 2001
From: Sirraide <aeternalmail at gmail.com>
Date: Sun, 26 Oct 2025 02:51:31 +0100
Subject: [PATCH 08/33] Add codegen tests for destructuring expansion
 statements

---
 .../cxx2c-destructuring-expansion-stmt.cpp    | 284 ++++++++++++++++++
 ...cxx2c-enumerating-expansion-statements.cpp |  70 +++++
 2 files changed, 354 insertions(+)
 create mode 100644 clang/test/CodeGenCXX/cxx2c-destructuring-expansion-stmt.cpp

diff --git a/clang/test/CodeGenCXX/cxx2c-destructuring-expansion-stmt.cpp b/clang/test/CodeGenCXX/cxx2c-destructuring-expansion-stmt.cpp
new file mode 100644
index 0000000000000..411e1e3824009
--- /dev/null
+++ b/clang/test/CodeGenCXX/cxx2c-destructuring-expansion-stmt.cpp
@@ -0,0 +1,284 @@
+// RUN: %clang_cc1 -std=c++2c -triple x86_64-unknown-linux-gnu -emit-llvm -o - %s | FileCheck %s
+
+struct A {};
+struct B { int x = 1; };
+struct C { int a = 1, b = 2, c = 3; };
+
+void g(int);
+
+int references_destructuring() {
+  C c;
+  template for (auto& x : c) { ++x; }
+  template for (auto&& x : c) { ++x; }
+  return c.a + c.b + c.c;
+}
+
+template <auto v>
+int destructure() {
+  int sum = 0;
+  template for (auto x : v) sum += x;
+  template for (constexpr auto x : v) sum += x;
+  return sum;
+}
+
+void f() {
+  destructure<B{10}>();
+  destructure<C{}>();
+  destructure<C{3, 4, 5}>();
+}
+
+void empty() {
+  static constexpr A a;
+  template for (auto x : A()) g(x);
+  template for (auto& x : a) g(x);
+  template for (auto&& x : A()) g(x);
+  template for (constexpr auto x : a) g(x);
+}
+
+// CHECK: @_ZZ5emptyvE1a = internal constant %struct.A zeroinitializer, align 1
+// CHECK: @_ZTAXtl1BLi10EEE = {{.*}} constant %struct.B { i32 10 }, comdat
+// CHECK: @_ZTAXtl1CLi1ELi2ELi3EEE = {{.*}} constant %struct.C { i32 1, i32 2, i32 3 }, comdat
+// CHECK: @_ZTAXtl1CLi3ELi4ELi5EEE = {{.*}} constant %struct.C { i32 3, i32 4, i32 5 }, comdat
+
+
+// CHECK-LABEL: define {{.*}} i32 @_Z24references_destructuringv()
+// CHECK: entry:
+// CHECK-NEXT:   %c = alloca %struct.C, align 4
+// CHECK-NEXT:   %0 = alloca ptr, align 8
+// CHECK-NEXT:   %x = alloca ptr, align 8
+// CHECK-NEXT:   %x1 = alloca ptr, align 8
+// CHECK-NEXT:   %x4 = alloca ptr, align 8
+// CHECK-NEXT:   %1 = alloca ptr, align 8
+// CHECK-NEXT:   %x7 = alloca ptr, align 8
+// CHECK-NEXT:   %x11 = alloca ptr, align 8
+// CHECK-NEXT:   %x15 = alloca ptr, align 8
+// CHECK-NEXT:   call void @_ZN1CC1Ev(ptr {{.*}} %c)
+// CHECK-NEXT:   store ptr %c, ptr %0, align 8
+// CHECK-NEXT:   %2 = load ptr, ptr %0, align 8
+// CHECK-NEXT:   %a = getelementptr inbounds nuw %struct.C, ptr %2, i32 0, i32 0
+// CHECK-NEXT:   store ptr %a, ptr %x, align 8
+// CHECK-NEXT:   %3 = load ptr, ptr %x, align 8
+// CHECK-NEXT:   %4 = load i32, ptr %3, align 4
+// CHECK-NEXT:   %inc = add nsw i32 %4, 1
+// CHECK-NEXT:   store i32 %inc, ptr %3, align 4
+// CHECK-NEXT:   br label %expand.next
+// CHECK: expand.next:
+// CHECK-NEXT:   %5 = load ptr, ptr %0, align 8
+// CHECK-NEXT:   %b = getelementptr inbounds nuw %struct.C, ptr %5, i32 0, i32 1
+// CHECK-NEXT:   store ptr %b, ptr %x1, align 8
+// CHECK-NEXT:   %6 = load ptr, ptr %x1, align 8
+// CHECK-NEXT:   %7 = load i32, ptr %6, align 4
+// CHECK-NEXT:   %inc2 = add nsw i32 %7, 1
+// CHECK-NEXT:   store i32 %inc2, ptr %6, align 4
+// CHECK-NEXT:   br label %expand.next3
+// CHECK: expand.next3:
+// CHECK-NEXT:   %8 = load ptr, ptr %0, align 8
+// CHECK-NEXT:   %c5 = getelementptr inbounds nuw %struct.C, ptr %8, i32 0, i32 2
+// CHECK-NEXT:   store ptr %c5, ptr %x4, align 8
+// CHECK-NEXT:   %9 = load ptr, ptr %x4, align 8
+// CHECK-NEXT:   %10 = load i32, ptr %9, align 4
+// CHECK-NEXT:   %inc6 = add nsw i32 %10, 1
+// CHECK-NEXT:   store i32 %inc6, ptr %9, align 4
+// CHECK-NEXT:   br label %expand.end
+// CHECK: expand.end:
+// CHECK-NEXT:   store ptr %c, ptr %1, align 8
+// CHECK-NEXT:   %11 = load ptr, ptr %1, align 8
+// CHECK-NEXT:   %a8 = getelementptr inbounds nuw %struct.C, ptr %11, i32 0, i32 0
+// CHECK-NEXT:   store ptr %a8, ptr %x7, align 8
+// CHECK-NEXT:   %12 = load ptr, ptr %x7, align 8
+// CHECK-NEXT:   %13 = load i32, ptr %12, align 4
+// CHECK-NEXT:   %inc9 = add nsw i32 %13, 1
+// CHECK-NEXT:   store i32 %inc9, ptr %12, align 4
+// CHECK-NEXT:   br label %expand.next10
+// CHECK: expand.next10:
+// CHECK-NEXT:   %14 = load ptr, ptr %1, align 8
+// CHECK-NEXT:   %b12 = getelementptr inbounds nuw %struct.C, ptr %14, i32 0, i32 1
+// CHECK-NEXT:   store ptr %b12, ptr %x11, align 8
+// CHECK-NEXT:   %15 = load ptr, ptr %x11, align 8
+// CHECK-NEXT:   %16 = load i32, ptr %15, align 4
+// CHECK-NEXT:   %inc13 = add nsw i32 %16, 1
+// CHECK-NEXT:   store i32 %inc13, ptr %15, align 4
+// CHECK-NEXT:   br label %expand.next14
+// CHECK: expand.next14:
+// CHECK-NEXT:   %17 = load ptr, ptr %1, align 8
+// CHECK-NEXT:   %c16 = getelementptr inbounds nuw %struct.C, ptr %17, i32 0, i32 2
+// CHECK-NEXT:   store ptr %c16, ptr %x15, align 8
+// CHECK-NEXT:   %18 = load ptr, ptr %x15, align 8
+// CHECK-NEXT:   %19 = load i32, ptr %18, align 4
+// CHECK-NEXT:   %inc17 = add nsw i32 %19, 1
+// CHECK-NEXT:   store i32 %inc17, ptr %18, align 4
+// CHECK-NEXT:   br label %expand.end18
+// CHECK: expand.end18:
+// CHECK-NEXT:   %a19 = getelementptr inbounds nuw %struct.C, ptr %c, i32 0, i32 0
+// CHECK-NEXT:   %20 = load i32, ptr %a19, align 4
+// CHECK-NEXT:   %b20 = getelementptr inbounds nuw %struct.C, ptr %c, i32 0, i32 1
+// CHECK-NEXT:   %21 = load i32, ptr %b20, align 4
+// CHECK-NEXT:   %add = add nsw i32 %20, %21
+// CHECK-NEXT:   %c21 = getelementptr inbounds nuw %struct.C, ptr %c, i32 0, i32 2
+// CHECK-NEXT:   %22 = load i32, ptr %c21, align 4
+// CHECK-NEXT:   %add22 = add nsw i32 %add, %22
+// CHECK-NEXT:   ret i32 %add22
+
+
+// CHECK-LABEL: define {{.*}} void @_Z1fv()
+// CHECK: entry:
+// CHECK-NEXT:   %call = call {{.*}} i32 @_Z11destructureITnDaXtl1BLi10EEEEiv()
+// CHECK-NEXT:   %call1 = call {{.*}} i32 @_Z11destructureITnDaXtl1CLi1ELi2ELi3EEEEiv()
+// CHECK-NEXT:   %call2 = call {{.*}} i32 @_Z11destructureITnDaXtl1CLi3ELi4ELi5EEEEiv()
+// CHECK-NEXT:   ret void
+
+
+// CHECK-LABEL: define {{.*}} i32 @_Z11destructureITnDaXtl1BLi10EEEEiv()
+// CHECK: entry:
+// CHECK-NEXT:   %sum = alloca i32, align 4
+// CHECK-NEXT:   %0 = alloca ptr, align 8
+// CHECK-NEXT:   %x = alloca i32, align 4
+// CHECK-NEXT:   %1 = alloca ptr, align 8
+// CHECK-NEXT:   %x1 = alloca i32, align 4
+// CHECK-NEXT:   store i32 0, ptr %sum, align 4
+// CHECK-NEXT:   store ptr @_ZTAXtl1BLi10EEE, ptr %0, align 8
+// CHECK-NEXT:   store i32 10, ptr %x, align 4
+// CHECK-NEXT:   %2 = load i32, ptr %x, align 4
+// CHECK-NEXT:   %3 = load i32, ptr %sum, align 4
+// CHECK-NEXT:   %add = add nsw i32 %3, %2
+// CHECK-NEXT:   store i32 %add, ptr %sum, align 4
+// CHECK-NEXT:   br label %expand.end
+// CHECK: expand.end:
+// CHECK-NEXT:   store ptr @_ZTAXtl1BLi10EEE, ptr %1, align 8
+// CHECK-NEXT:   store i32 10, ptr %x1, align 4
+// CHECK-NEXT:   %4 = load i32, ptr %sum, align 4
+// CHECK-NEXT:   %add2 = add nsw i32 %4, 10
+// CHECK-NEXT:   store i32 %add2, ptr %sum, align 4
+// CHECK-NEXT:   br label %expand.end3
+// CHECK: expand.end3:
+// CHECK-NEXT:   %5 = load i32, ptr %sum, align 4
+// CHECK-NEXT:   ret i32 %5
+
+
+// CHECK-LABEL: define {{.*}} i32 @_Z11destructureITnDaXtl1CLi1ELi2ELi3EEEEiv()
+// CHECK: entry:
+// CHECK-NEXT:   %sum = alloca i32, align 4
+// CHECK-NEXT:   %0 = alloca ptr, align 8
+// CHECK-NEXT:   %x = alloca i32, align 4
+// CHECK-NEXT:   %x1 = alloca i32, align 4
+// CHECK-NEXT:   %x4 = alloca i32, align 4
+// CHECK-NEXT:   %1 = alloca ptr, align 8
+// CHECK-NEXT:   %x6 = alloca i32, align 4
+// CHECK-NEXT:   %x9 = alloca i32, align 4
+// CHECK-NEXT:   %x12 = alloca i32, align 4
+// CHECK-NEXT:   store i32 0, ptr %sum, align 4
+// CHECK-NEXT:   store ptr @_ZTAXtl1CLi1ELi2ELi3EEE, ptr %0, align 8
+// CHECK-NEXT:   store i32 1, ptr %x, align 4
+// CHECK-NEXT:   %2 = load i32, ptr %x, align 4
+// CHECK-NEXT:   %3 = load i32, ptr %sum, align 4
+// CHECK-NEXT:   %add = add nsw i32 %3, %2
+// CHECK-NEXT:   store i32 %add, ptr %sum, align 4
+// CHECK-NEXT:   br label %expand.next
+// CHECK: expand.next:
+// CHECK-NEXT:   store i32 2, ptr %x1, align 4
+// CHECK-NEXT:   %4 = load i32, ptr %x1, align 4
+// CHECK-NEXT:   %5 = load i32, ptr %sum, align 4
+// CHECK-NEXT:   %add2 = add nsw i32 %5, %4
+// CHECK-NEXT:   store i32 %add2, ptr %sum, align 4
+// CHECK-NEXT:   br label %expand.next3
+// CHECK: expand.next3:
+// CHECK-NEXT:   store i32 3, ptr %x4, align 4
+// CHECK-NEXT:   %6 = load i32, ptr %x4, align 4
+// CHECK-NEXT:   %7 = load i32, ptr %sum, align 4
+// CHECK-NEXT:   %add5 = add nsw i32 %7, %6
+// CHECK-NEXT:   store i32 %add5, ptr %sum, align 4
+// CHECK-NEXT:   br label %expand.end
+// CHECK: expand.end:
+// CHECK-NEXT:   store ptr @_ZTAXtl1CLi1ELi2ELi3EEE, ptr %1, align 8
+// CHECK-NEXT:   store i32 1, ptr %x6, align 4
+// CHECK-NEXT:   %8 = load i32, ptr %sum, align 4
+// CHECK-NEXT:   %add7 = add nsw i32 %8, 1
+// CHECK-NEXT:   store i32 %add7, ptr %sum, align 4
+// CHECK-NEXT:   br label %expand.next8
+// CHECK: expand.next8:
+// CHECK-NEXT:   store i32 2, ptr %x9, align 4
+// CHECK-NEXT:   %9 = load i32, ptr %sum, align 4
+// CHECK-NEXT:   %add10 = add nsw i32 %9, 2
+// CHECK-NEXT:   store i32 %add10, ptr %sum, align 4
+// CHECK-NEXT:   br label %expand.next11
+// CHECK: expand.next11:
+// CHECK-NEXT:   store i32 3, ptr %x12, align 4
+// CHECK-NEXT:   %10 = load i32, ptr %sum, align 4
+// CHECK-NEXT:   %add13 = add nsw i32 %10, 3
+// CHECK-NEXT:   store i32 %add13, ptr %sum, align 4
+// CHECK-NEXT:   br label %expand.end14
+// CHECK: expand.end14:
+// CHECK-NEXT:   %11 = load i32, ptr %sum, align 4
+// CHECK-NEXT:   ret i32 %11
+
+
+// CHECK-LABEL: define {{.*}} i32 @_Z11destructureITnDaXtl1CLi3ELi4ELi5EEEEiv()
+// CHECK: entry:
+// CHECK-NEXT:   %sum = alloca i32, align 4
+// CHECK-NEXT:   %0 = alloca ptr, align 8
+// CHECK-NEXT:   %x = alloca i32, align 4
+// CHECK-NEXT:   %x1 = alloca i32, align 4
+// CHECK-NEXT:   %x4 = alloca i32, align 4
+// CHECK-NEXT:   %1 = alloca ptr, align 8
+// CHECK-NEXT:   %x6 = alloca i32, align 4
+// CHECK-NEXT:   %x9 = alloca i32, align 4
+// CHECK-NEXT:   %x12 = alloca i32, align 4
+// CHECK-NEXT:   store i32 0, ptr %sum, align 4
+// CHECK-NEXT:   store ptr @_ZTAXtl1CLi3ELi4ELi5EEE, ptr %0, align 8
+// CHECK-NEXT:   store i32 3, ptr %x, align 4
+// CHECK-NEXT:   %2 = load i32, ptr %x, align 4
+// CHECK-NEXT:   %3 = load i32, ptr %sum, align 4
+// CHECK-NEXT:   %add = add nsw i32 %3, %2
+// CHECK-NEXT:   store i32 %add, ptr %sum, align 4
+// CHECK-NEXT:   br label %expand.next
+// CHECK: expand.next:
+// CHECK-NEXT:   store i32 4, ptr %x1, align 4
+// CHECK-NEXT:   %4 = load i32, ptr %x1, align 4
+// CHECK-NEXT:   %5 = load i32, ptr %sum, align 4
+// CHECK-NEXT:   %add2 = add nsw i32 %5, %4
+// CHECK-NEXT:   store i32 %add2, ptr %sum, align 4
+// CHECK-NEXT:   br label %expand.next3
+// CHECK: expand.next3:
+// CHECK-NEXT:   store i32 5, ptr %x4, align 4
+// CHECK-NEXT:   %6 = load i32, ptr %x4, align 4
+// CHECK-NEXT:   %7 = load i32, ptr %sum, align 4
+// CHECK-NEXT:   %add5 = add nsw i32 %7, %6
+// CHECK-NEXT:   store i32 %add5, ptr %sum, align 4
+// CHECK-NEXT:   br label %expand.end
+// CHECK: expand.end:
+// CHECK-NEXT:   store ptr @_ZTAXtl1CLi3ELi4ELi5EEE, ptr %1, align 8
+// CHECK-NEXT:   store i32 3, ptr %x6, align 4
+// CHECK-NEXT:   %8 = load i32, ptr %sum, align 4
+// CHECK-NEXT:   %add7 = add nsw i32 %8, 3
+// CHECK-NEXT:   store i32 %add7, ptr %sum, align 4
+// CHECK-NEXT:   br label %expand.next8
+// CHECK: expand.next8:
+// CHECK-NEXT:   store i32 4, ptr %x9, align 4
+// CHECK-NEXT:   %9 = load i32, ptr %sum, align 4
+// CHECK-NEXT:   %add10 = add nsw i32 %9, 4
+// CHECK-NEXT:   store i32 %add10, ptr %sum, align 4
+// CHECK-NEXT:   br label %expand.next11
+// CHECK: expand.next11:
+// CHECK-NEXT:   store i32 5, ptr %x12, align 4
+// CHECK-NEXT:   %10 = load i32, ptr %sum, align 4
+// CHECK-NEXT:   %add13 = add nsw i32 %10, 5
+// CHECK-NEXT:   store i32 %add13, ptr %sum, align 4
+// CHECK-NEXT:   br label %expand.end14
+// CHECK: expand.end14:
+// CHECK-NEXT:   %11 = load i32, ptr %sum, align 4
+// CHECK-NEXT:   ret i32 %11
+
+
+// CHECK-LABEL: define {{.*}} void @_Z5emptyv()
+// CHECK: entry:
+// CHECK-NEXT:   %0 = alloca ptr, align 8
+// CHECK-NEXT:   %ref.tmp = alloca %struct.A, align 1
+// CHECK-NEXT:   %1 = alloca ptr, align 8
+// CHECK-NEXT:   %2 = alloca ptr, align 8
+// CHECK-NEXT:   %ref.tmp1 = alloca %struct.A, align 1
+// CHECK-NEXT:   %3 = alloca ptr, align 8
+// CHECK-NEXT:   store ptr %ref.tmp, ptr %0, align 8
+// CHECK-NEXT:   store ptr @_ZZ5emptyvE1a, ptr %1, align 8
+// CHECK-NEXT:   store ptr %ref.tmp1, ptr %2, align 8
+// CHECK-NEXT:   store ptr @_ZZ5emptyvE1a, ptr %3, align 8
+// CHECK-NEXT:   ret void
diff --git a/clang/test/CodeGenCXX/cxx2c-enumerating-expansion-statements.cpp b/clang/test/CodeGenCXX/cxx2c-enumerating-expansion-statements.cpp
index a30f453263bbf..c82b345de206b 100644
--- a/clang/test/CodeGenCXX/cxx2c-enumerating-expansion-statements.cpp
+++ b/clang/test/CodeGenCXX/cxx2c-enumerating-expansion-statements.cpp
@@ -172,6 +172,12 @@ void f8() {
   t5<1, 2, 3>();
 }
 
+int references_enumerating() {
+  int x = 1, y = 2, z = 3;
+  template for (auto& v : {x, y, z}) { ++v; }
+  template for (auto&& v : {x, y, z}) { ++v; }
+  return x + y + z;
+}
 
 // CHECK-LABEL: define {{.*}} void @_Z2f1v()
 // CHECK: entry:
@@ -1406,6 +1412,70 @@ void f8() {
 // CHECK-NEXT:   ret void
 
 
+// CHECK-LABEL: define {{.*}} i32 @_Z22references_enumeratingv()
+// CHECK: entry:
+// CHECK-NEXT:   %x = alloca i32, align 4
+// CHECK-NEXT:   %y = alloca i32, align 4
+// CHECK-NEXT:   %z = alloca i32, align 4
+// CHECK-NEXT:   %v = alloca ptr, align 8
+// CHECK-NEXT:   %v1 = alloca ptr, align 8
+// CHECK-NEXT:   %v4 = alloca ptr, align 8
+// CHECK-NEXT:   %v6 = alloca ptr, align 8
+// CHECK-NEXT:   %v9 = alloca ptr, align 8
+// CHECK-NEXT:   %v12 = alloca ptr, align 8
+// CHECK-NEXT:   store i32 1, ptr %x, align 4
+// CHECK-NEXT:   store i32 2, ptr %y, align 4
+// CHECK-NEXT:   store i32 3, ptr %z, align 4
+// CHECK-NEXT:   store ptr %x, ptr %v, align 8
+// CHECK-NEXT:   %0 = load ptr, ptr %v, align 8
+// CHECK-NEXT:   %1 = load i32, ptr %0, align 4
+// CHECK-NEXT:   %inc = add nsw i32 %1, 1
+// CHECK-NEXT:   store i32 %inc, ptr %0, align 4
+// CHECK-NEXT:   br label %expand.next
+// CHECK: expand.next:
+// CHECK-NEXT:   store ptr %y, ptr %v1, align 8
+// CHECK-NEXT:   %2 = load ptr, ptr %v1, align 8
+// CHECK-NEXT:   %3 = load i32, ptr %2, align 4
+// CHECK-NEXT:   %inc2 = add nsw i32 %3, 1
+// CHECK-NEXT:   store i32 %inc2, ptr %2, align 4
+// CHECK-NEXT:   br label %expand.next3
+// CHECK: expand.next3:
+// CHECK-NEXT:   store ptr %z, ptr %v4, align 8
+// CHECK-NEXT:   %4 = load ptr, ptr %v4, align 8
+// CHECK-NEXT:   %5 = load i32, ptr %4, align 4
+// CHECK-NEXT:   %inc5 = add nsw i32 %5, 1
+// CHECK-NEXT:   store i32 %inc5, ptr %4, align 4
+// CHECK-NEXT:   br label %expand.end
+// CHECK: expand.end:
+// CHECK-NEXT:   store ptr %x, ptr %v6, align 8
+// CHECK-NEXT:   %6 = load ptr, ptr %v6, align 8
+// CHECK-NEXT:   %7 = load i32, ptr %6, align 4
+// CHECK-NEXT:   %inc7 = add nsw i32 %7, 1
+// CHECK-NEXT:   store i32 %inc7, ptr %6, align 4
+// CHECK-NEXT:   br label %expand.next8
+// CHECK: expand.next8:
+// CHECK-NEXT:   store ptr %y, ptr %v9, align 8
+// CHECK-NEXT:   %8 = load ptr, ptr %v9, align 8
+// CHECK-NEXT:   %9 = load i32, ptr %8, align 4
+// CHECK-NEXT:   %inc10 = add nsw i32 %9, 1
+// CHECK-NEXT:   store i32 %inc10, ptr %8, align 4
+// CHECK-NEXT:   br label %expand.next11
+// CHECK: expand.next11:
+// CHECK-NEXT:   store ptr %z, ptr %v12, align 8
+// CHECK-NEXT:   %10 = load ptr, ptr %v12, align 8
+// CHECK-NEXT:   %11 = load i32, ptr %10, align 4
+// CHECK-NEXT:   %inc13 = add nsw i32 %11, 1
+// CHECK-NEXT:   store i32 %inc13, ptr %10, align 4
+// CHECK-NEXT:   br label %expand.end14
+// CHECK: expand.end14:
+// CHECK-NEXT:   %12 = load i32, ptr %x, align 4
+// CHECK-NEXT:   %13 = load i32, ptr %y, align 4
+// CHECK-NEXT:   %add = add nsw i32 %12, %13
+// CHECK-NEXT:   %14 = load i32, ptr %z, align 4
+// CHECK-NEXT:   %add15 = add nsw i32 %add, %14
+// CHECK-NEXT:   ret i32 %add15
+
+
 // CHECK-LABEL: define {{.*}} void @_ZZ2t5IJLi1ELi2ELi3EEEvvENKUlvE1_clEv(ptr {{.*}} %this)
 // CHECK: entry:
 // CHECK-NEXT:   %this.addr = alloca ptr, align 8

>From a365f3e92bc1291d0023715335822c377fb24ab6 Mon Sep 17 00:00:00 2001
From: Sirraide <aeternalmail at gmail.com>
Date: Sun, 26 Oct 2025 03:43:40 +0100
Subject: [PATCH 09/33] Add tests for break/continue

---
 clang/lib/CodeGen/CGStmt.cpp                  |   4 +-
 .../cxx2c-expansion-stmts-control-flow.cpp    | 264 ++++++++++++++++++
 .../SemaCXX/cxx2c-expansion-statements.cpp    |  51 ++++
 3 files changed, 318 insertions(+), 1 deletion(-)
 create mode 100644 clang/test/CodeGenCXX/cxx2c-expansion-stmts-control-flow.cpp

diff --git a/clang/lib/CodeGen/CGStmt.cpp b/clang/lib/CodeGen/CGStmt.cpp
index bed89a965baac..d03f216152dff 100644
--- a/clang/lib/CodeGen/CGStmt.cpp
+++ b/clang/lib/CodeGen/CGStmt.cpp
@@ -1580,8 +1580,10 @@ void CodeGenFunction::EmitCXXExpansionInstantiationStmt(
   JumpDest ExpandExit = getJumpDestInCurrentScope("expand.end");
   JumpDest ContinueDest;
   for (auto [N, Inst] : enumerate(S.getInstantiations())) {
-    if (!HaveInsertPoint())
+    if (!HaveInsertPoint()) {
+      EmitBlock(ExpandExit.getBlock(), true);
       return;
+    }
 
     if (N == S.getInstantiations().size() - 1)
       ContinueDest = ExpandExit;
diff --git a/clang/test/CodeGenCXX/cxx2c-expansion-stmts-control-flow.cpp b/clang/test/CodeGenCXX/cxx2c-expansion-stmts-control-flow.cpp
new file mode 100644
index 0000000000000..18d90b744340c
--- /dev/null
+++ b/clang/test/CodeGenCXX/cxx2c-expansion-stmts-control-flow.cpp
@@ -0,0 +1,264 @@
+// RUN: %clang_cc1 -std=c++2c -triple x86_64-unknown-linux-gnu -emit-llvm -o - %s | FileCheck %s
+
+void h(int, int);
+
+void break_continue() {
+  template for (auto x : {1, 2}) {
+    break;
+    h(1, x);
+  }
+
+  template for (auto x : {3, 4}) {
+    continue;
+    h(2, x);
+  }
+
+  template for (auto x : {5, 6}) {
+    if (x == 2) break;
+    h(3, x);
+  }
+
+  template for (auto x : {7, 8}) {
+    if (x == 2) continue;
+    h(4, x);
+  }
+}
+
+int break_continue_nested() {
+  int sum = 0;
+
+  template for (auto x : {1, 2}) {
+    template for (auto y : {3, 4}) {
+      if (x == 2) break;
+      sum += y;
+    }
+    sum += x;
+  }
+
+  template for (auto x : {5, 6}) {
+    template for (auto y : {7, 8}) {
+      if (x == 6) continue;
+      sum += y;
+    }
+    sum += x;
+  }
+
+  return sum;
+}
+
+
+// CHECK-LABEL: define {{.*}} void @_Z14break_continuev()
+// CHECK: entry:
+// CHECK-NEXT:   %x = alloca i32, align 4
+// CHECK-NEXT:   %x1 = alloca i32, align 4
+// CHECK-NEXT:   %x2 = alloca i32, align 4
+// CHECK-NEXT:   %x4 = alloca i32, align 4
+// CHECK-NEXT:   %x6 = alloca i32, align 4
+// CHECK-NEXT:   %x11 = alloca i32, align 4
+// CHECK-NEXT:   %x16 = alloca i32, align 4
+// CHECK-NEXT:   store i32 1, ptr %x, align 4
+// CHECK-NEXT:   br label %expand.end
+// CHECK: expand.end:
+// CHECK-NEXT:   store i32 3, ptr %x1, align 4
+// CHECK-NEXT:   br label %expand.next
+// CHECK: expand.next:
+// CHECK-NEXT:   store i32 4, ptr %x2, align 4
+// CHECK-NEXT:   br label %expand.end3
+// CHECK: expand.end3:
+// CHECK-NEXT:   store i32 5, ptr %x4, align 4
+// CHECK-NEXT:   %0 = load i32, ptr %x4, align 4
+// CHECK-NEXT:   %cmp = icmp eq i32 %0, 2
+// CHECK-NEXT:   br i1 %cmp, label %if.then, label %if.end
+// CHECK: if.then:
+// CHECK-NEXT:   br label %expand.end10
+// CHECK: if.end:
+// CHECK-NEXT:   %1 = load i32, ptr %x4, align 4
+// CHECK-NEXT:   call void @_Z1hii(i32 {{.*}} 3, i32 {{.*}} %1)
+// CHECK-NEXT:   br label %expand.next5
+// CHECK: expand.next5:
+// CHECK-NEXT:   store i32 6, ptr %x6, align 4
+// CHECK-NEXT:   %2 = load i32, ptr %x6, align 4
+// CHECK-NEXT:   %cmp7 = icmp eq i32 %2, 2
+// CHECK-NEXT:   br i1 %cmp7, label %if.then8, label %if.end9
+// CHECK: if.then8:
+// CHECK-NEXT:   br label %expand.end10
+// CHECK: if.end9:
+// CHECK-NEXT:   %3 = load i32, ptr %x6, align 4
+// CHECK-NEXT:   call void @_Z1hii(i32 {{.*}} 3, i32 {{.*}} %3)
+// CHECK-NEXT:   br label %expand.end10
+// CHECK: expand.end10:
+// CHECK-NEXT:   store i32 7, ptr %x11, align 4
+// CHECK-NEXT:   %4 = load i32, ptr %x11, align 4
+// CHECK-NEXT:   %cmp12 = icmp eq i32 %4, 2
+// CHECK-NEXT:   br i1 %cmp12, label %if.then13, label %if.end14
+// CHECK: if.then13:
+// CHECK-NEXT:   br label %expand.next15
+// CHECK: if.end14:
+// CHECK-NEXT:   %5 = load i32, ptr %x11, align 4
+// CHECK-NEXT:   call void @_Z1hii(i32 {{.*}} 4, i32 {{.*}} %5)
+// CHECK-NEXT:   br label %expand.next15
+// CHECK: expand.next15:
+// CHECK-NEXT:   store i32 8, ptr %x16, align 4
+// CHECK-NEXT:   %6 = load i32, ptr %x16, align 4
+// CHECK-NEXT:   %cmp17 = icmp eq i32 %6, 2
+// CHECK-NEXT:   br i1 %cmp17, label %if.then18, label %if.end19
+// CHECK: if.then18:
+// CHECK-NEXT:   br label %expand.end20
+// CHECK: if.end19:
+// CHECK-NEXT:   %7 = load i32, ptr %x16, align 4
+// CHECK-NEXT:   call void @_Z1hii(i32 {{.*}} 4, i32 {{.*}} %7)
+// CHECK-NEXT:   br label %expand.end20
+// CHECK: expand.end20:
+// CHECK-NEXT:   ret void
+
+
+// CHECK-LABEL: define {{.*}} i32 @_Z21break_continue_nestedv()
+// CHECK: entry:
+// CHECK-NEXT:   %sum = alloca i32, align 4
+// CHECK-NEXT:   %x = alloca i32, align 4
+// CHECK-NEXT:   %y = alloca i32, align 4
+// CHECK-NEXT:   %y1 = alloca i32, align 4
+// CHECK-NEXT:   %x8 = alloca i32, align 4
+// CHECK-NEXT:   %y9 = alloca i32, align 4
+// CHECK-NEXT:   %y15 = alloca i32, align 4
+// CHECK-NEXT:   %x23 = alloca i32, align 4
+// CHECK-NEXT:   %y24 = alloca i32, align 4
+// CHECK-NEXT:   %y30 = alloca i32, align 4
+// CHECK-NEXT:   %x38 = alloca i32, align 4
+// CHECK-NEXT:   %y39 = alloca i32, align 4
+// CHECK-NEXT:   %y45 = alloca i32, align 4
+// CHECK-NEXT:   store i32 0, ptr %sum, align 4
+// CHECK-NEXT:   store i32 1, ptr %x, align 4
+// CHECK-NEXT:   store i32 3, ptr %y, align 4
+// CHECK-NEXT:   %0 = load i32, ptr %x, align 4
+// CHECK-NEXT:   %cmp = icmp eq i32 %0, 2
+// CHECK-NEXT:   br i1 %cmp, label %if.then, label %if.end
+// CHECK: if.then:
+// CHECK-NEXT:   br label %expand.end
+// CHECK: if.end:
+// CHECK-NEXT:   %1 = load i32, ptr %y, align 4
+// CHECK-NEXT:   %2 = load i32, ptr %sum, align 4
+// CHECK-NEXT:   %add = add nsw i32 %2, %1
+// CHECK-NEXT:   store i32 %add, ptr %sum, align 4
+// CHECK-NEXT:   br label %expand.next
+// CHECK: expand.next:
+// CHECK-NEXT:   store i32 4, ptr %y1, align 4
+// CHECK-NEXT:   %3 = load i32, ptr %x, align 4
+// CHECK-NEXT:   %cmp2 = icmp eq i32 %3, 2
+// CHECK-NEXT:   br i1 %cmp2, label %if.then3, label %if.end4
+// CHECK: if.then3:
+// CHECK-NEXT:   br label %expand.end
+// CHECK: if.end4:
+// CHECK-NEXT:   %4 = load i32, ptr %y1, align 4
+// CHECK-NEXT:   %5 = load i32, ptr %sum, align 4
+// CHECK-NEXT:   %add5 = add nsw i32 %5, %4
+// CHECK-NEXT:   store i32 %add5, ptr %sum, align 4
+// CHECK-NEXT:   br label %expand.end
+// CHECK: expand.end:
+// CHECK-NEXT:   %6 = load i32, ptr %x, align 4
+// CHECK-NEXT:   %7 = load i32, ptr %sum, align 4
+// CHECK-NEXT:   %add6 = add nsw i32 %7, %6
+// CHECK-NEXT:   store i32 %add6, ptr %sum, align 4
+// CHECK-NEXT:   br label %expand.next7
+// CHECK: expand.next7:
+// CHECK-NEXT:   store i32 2, ptr %x8, align 4
+// CHECK-NEXT:   store i32 3, ptr %y9, align 4
+// CHECK-NEXT:   %8 = load i32, ptr %x8, align 4
+// CHECK-NEXT:   %cmp10 = icmp eq i32 %8, 2
+// CHECK-NEXT:   br i1 %cmp10, label %if.then11, label %if.end12
+// CHECK: if.then11:
+// CHECK-NEXT:   br label %expand.end20
+// CHECK: if.end12:
+// CHECK-NEXT:   %9 = load i32, ptr %y9, align 4
+// CHECK-NEXT:   %10 = load i32, ptr %sum, align 4
+// CHECK-NEXT:   %add13 = add nsw i32 %10, %9
+// CHECK-NEXT:   store i32 %add13, ptr %sum, align 4
+// CHECK-NEXT:   br label %expand.next14
+// CHECK: expand.next14:
+// CHECK-NEXT:   store i32 4, ptr %y15, align 4
+// CHECK-NEXT:   %11 = load i32, ptr %x8, align 4
+// CHECK-NEXT:   %cmp16 = icmp eq i32 %11, 2
+// CHECK-NEXT:   br i1 %cmp16, label %if.then17, label %if.end18
+// CHECK: if.then17:
+// CHECK-NEXT:   br label %expand.end20
+// CHECK: if.end18:
+// CHECK-NEXT:   %12 = load i32, ptr %y15, align 4
+// CHECK-NEXT:   %13 = load i32, ptr %sum, align 4
+// CHECK-NEXT:   %add19 = add nsw i32 %13, %12
+// CHECK-NEXT:   store i32 %add19, ptr %sum, align 4
+// CHECK-NEXT:   br label %expand.end20
+// CHECK: expand.end20:
+// CHECK-NEXT:   %14 = load i32, ptr %x8, align 4
+// CHECK-NEXT:   %15 = load i32, ptr %sum, align 4
+// CHECK-NEXT:   %add21 = add nsw i32 %15, %14
+// CHECK-NEXT:   store i32 %add21, ptr %sum, align 4
+// CHECK-NEXT:   br label %expand.end22
+// CHECK: expand.end22:
+// CHECK-NEXT:   store i32 5, ptr %x23, align 4
+// CHECK-NEXT:   store i32 7, ptr %y24, align 4
+// CHECK-NEXT:   %16 = load i32, ptr %x23, align 4
+// CHECK-NEXT:   %cmp25 = icmp eq i32 %16, 6
+// CHECK-NEXT:   br i1 %cmp25, label %if.then26, label %if.end27
+// CHECK: if.then26:
+// CHECK-NEXT:   br label %expand.next29
+// CHECK: if.end27:
+// CHECK-NEXT:   %17 = load i32, ptr %y24, align 4
+// CHECK-NEXT:   %18 = load i32, ptr %sum, align 4
+// CHECK-NEXT:   %add28 = add nsw i32 %18, %17
+// CHECK-NEXT:   store i32 %add28, ptr %sum, align 4
+// CHECK-NEXT:   br label %expand.next29
+// CHECK: expand.next29:
+// CHECK-NEXT:   store i32 8, ptr %y30, align 4
+// CHECK-NEXT:   %19 = load i32, ptr %x23, align 4
+// CHECK-NEXT:   %cmp31 = icmp eq i32 %19, 6
+// CHECK-NEXT:   br i1 %cmp31, label %if.then32, label %if.end33
+// CHECK: if.then32:
+// CHECK-NEXT:   br label %expand.end35
+// CHECK: if.end33:
+// CHECK-NEXT:   %20 = load i32, ptr %y30, align 4
+// CHECK-NEXT:   %21 = load i32, ptr %sum, align 4
+// CHECK-NEXT:   %add34 = add nsw i32 %21, %20
+// CHECK-NEXT:   store i32 %add34, ptr %sum, align 4
+// CHECK-NEXT:   br label %expand.end35
+// CHECK: expand.end35:
+// CHECK-NEXT:   %22 = load i32, ptr %x23, align 4
+// CHECK-NEXT:   %23 = load i32, ptr %sum, align 4
+// CHECK-NEXT:   %add36 = add nsw i32 %23, %22
+// CHECK-NEXT:   store i32 %add36, ptr %sum, align 4
+// CHECK-NEXT:   br label %expand.next37
+// CHECK: expand.next37:
+// CHECK-NEXT:   store i32 6, ptr %x38, align 4
+// CHECK-NEXT:   store i32 7, ptr %y39, align 4
+// CHECK-NEXT:   %24 = load i32, ptr %x38, align 4
+// CHECK-NEXT:   %cmp40 = icmp eq i32 %24, 6
+// CHECK-NEXT:   br i1 %cmp40, label %if.then41, label %if.end42
+// CHECK: if.then41:
+// CHECK-NEXT:   br label %expand.next44
+// CHECK: if.end42:
+// CHECK-NEXT:   %25 = load i32, ptr %y39, align 4
+// CHECK-NEXT:   %26 = load i32, ptr %sum, align 4
+// CHECK-NEXT:   %add43 = add nsw i32 %26, %25
+// CHECK-NEXT:   store i32 %add43, ptr %sum, align 4
+// CHECK-NEXT:   br label %expand.next44
+// CHECK: expand.next44:
+// CHECK-NEXT:   store i32 8, ptr %y45, align 4
+// CHECK-NEXT:   %27 = load i32, ptr %x38, align 4
+// CHECK-NEXT:   %cmp46 = icmp eq i32 %27, 6
+// CHECK-NEXT:   br i1 %cmp46, label %if.then47, label %if.end48
+// CHECK: if.then47:
+// CHECK-NEXT:   br label %expand.end50
+// CHECK: if.end48:
+// CHECK-NEXT:   %28 = load i32, ptr %y45, align 4
+// CHECK-NEXT:   %29 = load i32, ptr %sum, align 4
+// CHECK-NEXT:   %add49 = add nsw i32 %29, %28
+// CHECK-NEXT:   store i32 %add49, ptr %sum, align 4
+// CHECK-NEXT:   br label %expand.end50
+// CHECK: expand.end50:
+// CHECK-NEXT:   %30 = load i32, ptr %x38, align 4
+// CHECK-NEXT:   %31 = load i32, ptr %sum, align 4
+// CHECK-NEXT:   %add51 = add nsw i32 %31, %30
+// CHECK-NEXT:   store i32 %add51, ptr %sum, align 4
+// CHECK-NEXT:   br label %expand.end52
+// CHECK: expand.end52:
+// CHECK-NEXT:   %32 = load i32, ptr %sum, align 4
+// CHECK-NEXT:   ret i32 %32
diff --git a/clang/test/SemaCXX/cxx2c-expansion-statements.cpp b/clang/test/SemaCXX/cxx2c-expansion-statements.cpp
index 3cdf8512639bf..dd5b800e10d12 100644
--- a/clang/test/SemaCXX/cxx2c-expansion-statements.cpp
+++ b/clang/test/SemaCXX/cxx2c-expansion-statements.cpp
@@ -644,3 +644,54 @@ constexpr int references_destructuring() {
 }
 
 static_assert(references_destructuring() == 12);
+
+constexpr int break_continue() {
+  int sum = 0;
+  template for (auto x : {1, 2}) {
+    break;
+    sum += x;
+  }
+
+  template for (auto x : {3, 4}) {
+    continue;
+    sum += x;
+  }
+
+  template for (auto x : {5, 6}) {
+    if (x == 6) break;
+    sum += x;
+  }
+
+  template for (auto x : {7, 8, 9}) {
+    if (x == 8) continue;
+    sum += x;
+  }
+
+  return sum;
+}
+
+static_assert(break_continue() == 21);
+
+constexpr int break_continue_nested() {
+  int sum = 0;
+
+  template for (auto x : {1, 2}) {
+    template for (auto y : {3, 4}) {
+      if (x == 2) break;
+      sum += y;
+    }
+    sum += x;
+  }
+
+  template for (auto x : {5, 6}) {
+    template for (auto y : {7, 8}) {
+      if (x == 6) continue;
+      sum += y;
+    }
+    sum += x;
+  }
+
+  return sum;
+}
+
+static_assert(break_continue_nested() == 36);

>From 355b761f393d1e4c247f503468960d3c760ba2e8 Mon Sep 17 00:00:00 2001
From: Sirraide <aeternalmail at gmail.com>
Date: Sun, 26 Oct 2025 04:04:09 +0100
Subject: [PATCH 10/33] Disallow (non-local) labels in expansion statements

---
 .../clang/Basic/DiagnosticParseKinds.td       |  2 +
 clang/lib/Parse/ParseStmt.cpp                 |  9 +++
 clang/lib/Sema/SemaExpand.cpp                 | 13 -----
 .../cxx2c-expansion-stmts-control-flow.cpp    | 57 +++++++++++++++++++
 .../SemaCXX/cxx2c-expansion-statements.cpp    | 40 ++++++++++++-
 5 files changed, 107 insertions(+), 14 deletions(-)

diff --git a/clang/include/clang/Basic/DiagnosticParseKinds.td b/clang/include/clang/Basic/DiagnosticParseKinds.td
index e194c22bdb614..6b2ca94b64188 100644
--- a/clang/include/clang/Basic/DiagnosticParseKinds.td
+++ b/clang/include/clang/Basic/DiagnosticParseKinds.td
@@ -452,6 +452,8 @@ 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 range-based">;
+def err_expansion_stmt_label : Error<
+  "labels are not allowed in expansion statements">;
 
 def err_expected_case_before_expression: Error<
   "expected 'case' keyword before expression">;
diff --git a/clang/lib/Parse/ParseStmt.cpp b/clang/lib/Parse/ParseStmt.cpp
index 913a993823d27..73b1f95a61fba 100644
--- a/clang/lib/Parse/ParseStmt.cpp
+++ b/clang/lib/Parse/ParseStmt.cpp
@@ -749,6 +749,15 @@ StmtResult Parser::ParseLabeledStatement(ParsedAttributes &Attrs,
 
   DiagnoseLabelFollowedByDecl(*this, SubStmt.get());
 
+  // C++26 [stmt.label]p4 An identifier label shall not be enclosed by an
+  // expansion-statement.
+  //
+  // As an extension, we allow GNU local labels since they are logically
+  // scoped to the containing block, which prevents us from ending up with
+  // multiple copies of the same label in a function after instantiation.
+  if (Actions.CurContext->isExpansionStmt() && !LD->isGnuLocal())
+    Diag(LD->getLocation(), diag::err_expansion_stmt_label);
+
   Actions.ProcessDeclAttributeList(Actions.CurScope, LD, Attrs);
   Attrs.clear();
 
diff --git a/clang/lib/Sema/SemaExpand.cpp b/clang/lib/Sema/SemaExpand.cpp
index bae7bad6660ae..398cc60c676a7 100644
--- a/clang/lib/Sema/SemaExpand.cpp
+++ b/clang/lib/Sema/SemaExpand.cpp
@@ -391,19 +391,6 @@ StmtResult Sema::FinishCXXExpansionStmt(Stmt* Exp, Stmt *Body) {
   assert(!Expansion->getDecl()->getInstantiations() &&
          "should not rebuild expansion statement after instantiation");
 
-  // Diagnose identifier labels.
-  // TODO: Do this somewhere, somehow, but not every time we instantiate this.
-  /*struct DiagnoseLabels : DynamicRecursiveASTVisitor {
-    Sema &SemaRef;
-    DiagnoseLabels(Sema &S) : SemaRef(S) {}
-    bool VisitLabelStmt(LabelStmt *S) override {
-      SemaRef.Diag(S->getIdentLoc(), diag::err_expanded_identifier_label);
-      return false;
-    }
-  } Visitor(*this);
-  if (!Visitor.TraverseStmt(Body))
-    return StmtError();*/
-
   Expansion->setBody(Body);
   if (Expansion->hasDependentSize())
     return Expansion;
diff --git a/clang/test/CodeGenCXX/cxx2c-expansion-stmts-control-flow.cpp b/clang/test/CodeGenCXX/cxx2c-expansion-stmts-control-flow.cpp
index 18d90b744340c..5e824c05209bb 100644
--- a/clang/test/CodeGenCXX/cxx2c-expansion-stmts-control-flow.cpp
+++ b/clang/test/CodeGenCXX/cxx2c-expansion-stmts-control-flow.cpp
@@ -46,6 +46,16 @@ int break_continue_nested() {
   return sum;
 }
 
+void label() {
+  // Only local labels are allowed in expansion statements.
+  template for (auto x : {1, 2, 3}) {
+    __label__ a;
+    if (x == 1) goto a;
+    h(1, x);
+    a:;
+  }
+}
+
 
 // CHECK-LABEL: define {{.*}} void @_Z14break_continuev()
 // CHECK: entry:
@@ -262,3 +272,50 @@ int break_continue_nested() {
 // CHECK: expand.end52:
 // CHECK-NEXT:   %32 = load i32, ptr %sum, align 4
 // CHECK-NEXT:   ret i32 %32
+
+
+// CHECK-LABEL: define {{.*}} void @_Z5labelv()
+// CHECK: entry:
+// CHECK-NEXT:   %x = alloca i32, align 4
+// CHECK-NEXT:   %x1 = alloca i32, align 4
+// CHECK-NEXT:   %x7 = alloca i32, align 4
+// CHECK-NEXT:   store i32 1, ptr %x, align 4
+// CHECK-NEXT:   %0 = load i32, ptr %x, align 4
+// CHECK-NEXT:   %cmp = icmp eq i32 %0, 1
+// CHECK-NEXT:   br i1 %cmp, label %if.then, label %if.end
+// CHECK: if.then:
+// CHECK-NEXT:   br label %a
+// CHECK: if.end:
+// CHECK-NEXT:   %1 = load i32, ptr %x, align 4
+// CHECK-NEXT:   call void @_Z1hii(i32 {{.*}} 1, i32 {{.*}} %1)
+// CHECK-NEXT:   br label %a
+// CHECK: a:
+// CHECK-NEXT:   br label %expand.next
+// CHECK: expand.next:
+// CHECK-NEXT:   store i32 2, ptr %x1, align 4
+// CHECK-NEXT:   %2 = load i32, ptr %x1, align 4
+// CHECK-NEXT:   %cmp2 = icmp eq i32 %2, 1
+// CHECK-NEXT:   br i1 %cmp2, label %if.then3, label %if.end4
+// CHECK: if.then3:
+// CHECK-NEXT:   br label %a5
+// CHECK: if.end4:
+// CHECK-NEXT:   %3 = load i32, ptr %x1, align 4
+// CHECK-NEXT:   call void @_Z1hii(i32 {{.*}} 1, i32 {{.*}} %3)
+// CHECK-NEXT:   br label %a5
+// CHECK: a5:
+// CHECK-NEXT:   br label %expand.next6
+// CHECK: expand.next6:
+// CHECK-NEXT:   store i32 3, ptr %x7, align 4
+// CHECK-NEXT:   %4 = load i32, ptr %x7, align 4
+// CHECK-NEXT:   %cmp8 = icmp eq i32 %4, 1
+// CHECK-NEXT:   br i1 %cmp8, label %if.then9, label %if.end10
+// CHECK: if.then9:
+// CHECK-NEXT:   br label %a11
+// CHECK: if.end10:
+// CHECK-NEXT:   %5 = load i32, ptr %x7, align 4
+// CHECK-NEXT:   call void @_Z1hii(i32 {{.*}} 1, i32 {{.*}} %5)
+// CHECK-NEXT:   br label %a11
+// CHECK: a11:
+// CHECK-NEXT:   br label %expand.end
+// CHECK: expand.end:
+// CHECK-NEXT:   ret void
diff --git a/clang/test/SemaCXX/cxx2c-expansion-statements.cpp b/clang/test/SemaCXX/cxx2c-expansion-statements.cpp
index dd5b800e10d12..103cc3d141e9a 100644
--- a/clang/test/SemaCXX/cxx2c-expansion-statements.cpp
+++ b/clang/test/SemaCXX/cxx2c-expansion-statements.cpp
@@ -1,4 +1,4 @@
-// RUN: %clang_cc1 %s -std=c++2c -fsyntax-only -fdeclspec -verify
+// RUN: %clang_cc1 %s -std=c++2c -fsyntax-only -fdeclspec -fblocks -verify
 namespace std {
 template <typename T>
 struct initializer_list {
@@ -695,3 +695,41 @@ constexpr int break_continue_nested() {
 }
 
 static_assert(break_continue_nested() == 36);
+
+
+void label() {
+  template for (auto x : {1, 2}) {
+    invalid1:; // expected-error {{labels are not allowed in expansion statements}}
+    invalid2:; // expected-error {{labels are not allowed in expansion statements}}
+    goto invalid1;
+  }
+
+  template for (auto x : {1, 2}) {
+    (void) [] {
+      template for (auto x : {1, 2}) {
+        invalid3:; // expected-error {{labels are not allowed in expansion statements}}
+      }
+      ok:;
+    };
+
+    (void) ^{
+      template for (auto x : {1, 2}) {
+        invalid4:; // expected-error {{labels are not allowed in expansion statements}}
+      }
+      ok:;
+    };
+
+    struct X {
+      void f() {
+        ok:;
+      }
+    };
+  }
+
+  // GNU local labels are allowed.
+  template for (auto x : {1, 2}) {
+    __label__ a;
+    a:;
+    if (x == 1) goto a;
+  }
+}

>From 3603a5425a0d83746a1f77de5c0783e454a714c3 Mon Sep 17 00:00:00 2001
From: Sirraide <aeternalmail at gmail.com>
Date: Sun, 26 Oct 2025 04:33:30 +0100
Subject: [PATCH 11/33] Disallow case/default statements in expansion
 statements

---
 .../clang/Basic/DiagnosticSemaKinds.td        |  6 +++--
 clang/include/clang/Sema/ScopeInfo.h          |  6 ++++-
 clang/lib/Sema/SemaStmt.cpp                   | 25 +++++++++++++++--
 .../SemaCXX/cxx2c-expansion-statements.cpp    | 27 +++++++++++++++++++
 4 files changed, 59 insertions(+), 5 deletions(-)

diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index 284db2dc8e4a3..94b0558d7ff38 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -3690,8 +3690,10 @@ def err_expansion_stmt_invalid_init : Error<
   "cannot expand expression of type %0">;
 def err_expansion_stmt_lambda : Error<
   "cannot expand lambda closure type">;
-def err_expanded_identifier_label : Error<
-  "identifier labels are not allowed in expansion statements">;
+def err_expansion_stmt_case : Error<
+  "%select{'case'|'default'}0 belongs to 'switch' outside enclosing expansion statement">;
+def note_enclosing_switch_statement_here : Note<
+  "switch statement is here">;
 
 def err_attribute_patchable_function_entry_invalid_section
     : Error<"section argument to 'patchable_function_entry' attribute is not "
diff --git a/clang/include/clang/Sema/ScopeInfo.h b/clang/include/clang/Sema/ScopeInfo.h
index 4f4d38c961140..ad81dee64f8cd 100644
--- a/clang/include/clang/Sema/ScopeInfo.h
+++ b/clang/include/clang/Sema/ScopeInfo.h
@@ -202,7 +202,11 @@ class FunctionScopeInfo {
 public:
   /// A SwitchStmt, along with a flag indicating if its list of case statements
   /// is incomplete (because we dropped an invalid one while parsing).
-  using SwitchInfo = llvm::PointerIntPair<SwitchStmt*, 1, bool>;
+  struct SwitchInfo : llvm::PointerIntPair<SwitchStmt*, 1, bool> {
+    DeclContext* EnclosingDC;
+    SwitchInfo(SwitchStmt *Switch, DeclContext *DC)
+        : PointerIntPair(Switch, false), EnclosingDC(DC) {}
+  };
 
   /// SwitchStack - This is the current set of active switch statements in the
   /// block.
diff --git a/clang/lib/Sema/SemaStmt.cpp b/clang/lib/Sema/SemaStmt.cpp
index 2fc92c73392f6..5028e052546ac 100644
--- a/clang/lib/Sema/SemaStmt.cpp
+++ b/clang/lib/Sema/SemaStmt.cpp
@@ -528,6 +528,20 @@ Sema::ActOnCaseExpr(SourceLocation CaseLoc, ExprResult Val) {
   return CheckAndFinish(Val.get());
 }
 
+static bool DiagnoseSwitchCaseInExpansionStmt(Sema &S, SourceLocation KwLoc,
+                                              bool IsDefault) {
+  // C++26 [stmt.expand] The compound-statement of an expansion-statement is a
+  // control-flow-limited statement.
+  if (S.CurContext->isExpansionStmt() &&
+      S.getCurFunction()->SwitchStack.back().EnclosingDC != S.CurContext) {
+    S.Diag(KwLoc, diag::err_expansion_stmt_case) << IsDefault;
+    S.Diag(S.getCurFunction()->SwitchStack.back().getPointer()->getSwitchLoc(),
+           diag::note_enclosing_switch_statement_here);
+    return true;
+  }
+  return false;
+}
+
 StmtResult
 Sema::ActOnCaseStmt(SourceLocation CaseLoc, ExprResult LHSVal,
                     SourceLocation DotDotDotLoc, ExprResult RHSVal,
@@ -547,6 +561,9 @@ Sema::ActOnCaseStmt(SourceLocation CaseLoc, ExprResult LHSVal,
     return StmtError();
   }
 
+  if (DiagnoseSwitchCaseInExpansionStmt(*this, CaseLoc, false))
+    return StmtError();
+
   if (LangOpts.OpenACC &&
       getCurScope()->isInOpenACCComputeConstructScope(Scope::SwitchScope)) {
     Diag(CaseLoc, diag::err_acc_branch_in_out_compute_construct)
@@ -572,6 +589,9 @@ Sema::ActOnDefaultStmt(SourceLocation DefaultLoc, SourceLocation ColonLoc,
     return SubStmt;
   }
 
+  if (DiagnoseSwitchCaseInExpansionStmt(*this, DefaultLoc, true))
+    return StmtError();
+
   if (LangOpts.OpenACC &&
       getCurScope()->isInOpenACCComputeConstructScope(Scope::SwitchScope)) {
     Diag(DefaultLoc, diag::err_acc_branch_in_out_compute_construct)
@@ -1196,8 +1216,9 @@ StmtResult Sema::ActOnStartOfSwitchStmt(SourceLocation SwitchLoc,
 
   auto *SS = SwitchStmt::Create(Context, InitStmt, Cond.get().first, CondExpr,
                                 LParenLoc, RParenLoc);
+  SS->setSwitchLoc(SwitchLoc);
   getCurFunction()->SwitchStack.push_back(
-      FunctionScopeInfo::SwitchInfo(SS, false));
+      FunctionScopeInfo::SwitchInfo(SS, CurContext));
   return SS;
 }
 
@@ -1313,7 +1334,7 @@ Sema::ActOnFinishSwitchStmt(SourceLocation SwitchLoc, Stmt *Switch,
     BodyStmt = new (Context) NullStmt(BodyStmt->getBeginLoc());
   }
 
-  SS->setBody(BodyStmt, SwitchLoc);
+  SS->setBody(BodyStmt);
 
   Expr *CondExpr = SS->getCond();
   if (!CondExpr) return StmtError();
diff --git a/clang/test/SemaCXX/cxx2c-expansion-statements.cpp b/clang/test/SemaCXX/cxx2c-expansion-statements.cpp
index 103cc3d141e9a..925e1d0bfb247 100644
--- a/clang/test/SemaCXX/cxx2c-expansion-statements.cpp
+++ b/clang/test/SemaCXX/cxx2c-expansion-statements.cpp
@@ -733,3 +733,30 @@ void label() {
     if (x == 1) goto a;
   }
 }
+
+
+void case_default(int i) {
+  switch (i) { // expected-note 3 {{switch statement is here}}
+    template for (auto x : {1, 2}) {
+      case 1:; // expected-error {{'case' belongs to 'switch' outside enclosing expansion statement}}
+        template for (auto x : {1, 2}) {
+          case 2:; // expected-error {{'case' belongs to 'switch' outside enclosing expansion statement}}
+        }
+      default: // expected-error {{'default' belongs to 'switch' outside enclosing expansion statement}}
+        switch (i) {  // expected-note {{switch statement is here}}
+          case 3:;
+          default:
+            template for (auto x : {1, 2}) {
+              case 4:; // expected-error {{'case' belongs to 'switch' outside enclosing expansion statement}}
+            }
+        }
+    }
+  }
+
+  template for (auto x : {1, 2}) {
+    switch (i) {
+      case 1:;
+      default:
+    }
+  }
+}

>From 316f81acb31749c30abb1ca127eaf7fc4f08dc8a Mon Sep 17 00:00:00 2001
From: Sirraide <aeternalmail at gmail.com>
Date: Sun, 26 Oct 2025 04:35:02 +0100
Subject: [PATCH 12/33] Add another case/default test

---
 clang/test/SemaCXX/cxx2c-expansion-statements.cpp | 8 ++++++++
 1 file changed, 8 insertions(+)

diff --git a/clang/test/SemaCXX/cxx2c-expansion-statements.cpp b/clang/test/SemaCXX/cxx2c-expansion-statements.cpp
index 925e1d0bfb247..bbfe6dae4bc2f 100644
--- a/clang/test/SemaCXX/cxx2c-expansion-statements.cpp
+++ b/clang/test/SemaCXX/cxx2c-expansion-statements.cpp
@@ -759,4 +759,12 @@ void case_default(int i) {
       default:
     }
   }
+
+  // Ensure that we diagnose this even if the statements would be discarded.
+  switch (i) { // expected-note 2 {{switch statement is here}}
+    template for (auto x : {}) {
+      case 1:; // expected-error {{'case' belongs to 'switch' outside enclosing expansion statement}}
+      default:; // expected-error {{'default' belongs to 'switch' outside enclosing expansion statement}}
+    }
+  }
 }

>From 9e43b8cdd438b0bb905bc90b443ff2f3190dbd43 Mon Sep 17 00:00:00 2001
From: Sirraide <aeternalmail at gmail.com>
Date: Sun, 26 Oct 2025 04:37:47 +0100
Subject: [PATCH 13/33] Add comment explaining why we diagnose this here

---
 clang/lib/Sema/SemaStmt.cpp | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/clang/lib/Sema/SemaStmt.cpp b/clang/lib/Sema/SemaStmt.cpp
index 5028e052546ac..63c4c5e33fa53 100644
--- a/clang/lib/Sema/SemaStmt.cpp
+++ b/clang/lib/Sema/SemaStmt.cpp
@@ -532,6 +532,11 @@ static bool DiagnoseSwitchCaseInExpansionStmt(Sema &S, SourceLocation KwLoc,
                                               bool IsDefault) {
   // C++26 [stmt.expand] The compound-statement of an expansion-statement is a
   // control-flow-limited statement.
+  //
+  // We diagnose this here rather than in JumpDiagnostics because those run
+  // after the expansion statement is instantiated, at which point we will have
+  // have already complained about duplicate case labels, which is not exactly
+  // great QOI.
   if (S.CurContext->isExpansionStmt() &&
       S.getCurFunction()->SwitchStack.back().EnclosingDC != S.CurContext) {
     S.Diag(KwLoc, diag::err_expansion_stmt_case) << IsDefault;

>From 66ca06c3b79f37e4c77bc6b4ed256021888449bd Mon Sep 17 00:00:00 2001
From: Sirraide <aeternalmail at gmail.com>
Date: Sun, 26 Oct 2025 05:42:56 +0100
Subject: [PATCH 14/33] Proper support for local labels

---
 .../clang/Basic/DiagnosticParseKinds.td       |   2 -
 .../clang/Basic/DiagnosticSemaKinds.td        |   2 +
 clang/include/clang/Sema/Sema.h               |   3 +-
 clang/lib/Parse/ParseStmt.cpp                 |  18 ++-
 clang/lib/Sema/SemaLookup.cpp                 |  51 +++++++--
 .../cxx2c-expansion-stmts-control-flow.cpp    | 108 ++++++++++++++++++
 .../SemaCXX/cxx2c-expansion-statements.cpp    |  45 +++++++-
 7 files changed, 205 insertions(+), 24 deletions(-)

diff --git a/clang/include/clang/Basic/DiagnosticParseKinds.td b/clang/include/clang/Basic/DiagnosticParseKinds.td
index 6b2ca94b64188..e194c22bdb614 100644
--- a/clang/include/clang/Basic/DiagnosticParseKinds.td
+++ b/clang/include/clang/Basic/DiagnosticParseKinds.td
@@ -452,8 +452,6 @@ 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 range-based">;
-def err_expansion_stmt_label : Error<
-  "labels are not allowed in expansion statements">;
 
 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 94b0558d7ff38..008b097a12bba 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -3694,6 +3694,8 @@ def err_expansion_stmt_case : Error<
   "%select{'case'|'default'}0 belongs to 'switch' outside enclosing expansion statement">;
 def note_enclosing_switch_statement_here : Note<
   "switch statement is here">;
+def err_expansion_stmt_label : Error<
+  "labels are not allowed in expansion statements">;
 
 def err_attribute_patchable_function_entry_invalid_section
     : Error<"section argument to 'patchable_function_entry' attribute is not "
diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index bf58fcd7a8af7..a765bf12ef62e 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -9508,7 +9508,8 @@ class Sema final : public SemaBase {
   /// of an __label__ label name, otherwise it is a normal label definition
   /// or use.
   LabelDecl *LookupOrCreateLabel(IdentifierInfo *II, SourceLocation IdentLoc,
-                                 SourceLocation GnuLabelLoc = SourceLocation());
+                                 SourceLocation GnuLabelLoc = SourceLocation(),
+                                 bool ForLabelStmt = false);
 
   /// Perform a name lookup for a label with the specified name; this does not
   /// create a new label if the lookup fails.
diff --git a/clang/lib/Parse/ParseStmt.cpp b/clang/lib/Parse/ParseStmt.cpp
index 73b1f95a61fba..ae3acd202bf9b 100644
--- a/clang/lib/Parse/ParseStmt.cpp
+++ b/clang/lib/Parse/ParseStmt.cpp
@@ -704,8 +704,9 @@ StmtResult Parser::ParseLabeledStatement(ParsedAttributes &Attrs,
   // identifier ':' statement
   SourceLocation ColonLoc = ConsumeToken();
 
-  LabelDecl *LD = Actions.LookupOrCreateLabel(IdentTok.getIdentifierInfo(),
-                                              IdentTok.getLocation());
+  LabelDecl *LD = Actions.LookupOrCreateLabel(
+      IdentTok.getIdentifierInfo(), IdentTok.getLocation(), /*GnuLabelLoc=*/{},
+      /*ForLabelStmt=*/true);
 
   // Read label attributes, if present.
   StmtResult SubStmt;
@@ -749,14 +750,11 @@ StmtResult Parser::ParseLabeledStatement(ParsedAttributes &Attrs,
 
   DiagnoseLabelFollowedByDecl(*this, SubStmt.get());
 
-  // C++26 [stmt.label]p4 An identifier label shall not be enclosed by an
-  // expansion-statement.
-  //
-  // As an extension, we allow GNU local labels since they are logically
-  // scoped to the containing block, which prevents us from ending up with
-  // multiple copies of the same label in a function after instantiation.
-  if (Actions.CurContext->isExpansionStmt() && !LD->isGnuLocal())
-    Diag(LD->getLocation(), diag::err_expansion_stmt_label);
+  // If a label cannot appear here, just return the underlying statement.
+  if (!LD) {
+    Attrs.clear();
+    return SubStmt.get();
+  }
 
   Actions.ProcessDeclAttributeList(Actions.CurScope, LD, Attrs);
   Attrs.clear();
diff --git a/clang/lib/Sema/SemaLookup.cpp b/clang/lib/Sema/SemaLookup.cpp
index 5915d6e57d893..576ec6c80770e 100644
--- a/clang/lib/Sema/SemaLookup.cpp
+++ b/clang/lib/Sema/SemaLookup.cpp
@@ -4455,13 +4455,16 @@ LabelDecl *Sema::LookupExistingLabel(IdentifierInfo *II, SourceLocation Loc) {
                                     RedeclarationKind::NotForRedeclaration);
   // If we found a label, check to see if it is in the same context as us.
   // When in a Block, we don't want to reuse a label in an enclosing function.
-  if (!Res || Res->getDeclContext() != CurContext)
+  if (!Res ||
+      Res->getDeclContext()->getEnclosingNonExpansionStatementContext() !=
+          CurContext->getEnclosingNonExpansionStatementContext())
     return nullptr;
   return cast<LabelDecl>(Res);
 }
 
 LabelDecl *Sema::LookupOrCreateLabel(IdentifierInfo *II, SourceLocation Loc,
-                                     SourceLocation GnuLabelLoc) {
+                                     SourceLocation GnuLabelLoc,
+                                     bool ForLabelStmt) {
   if (GnuLabelLoc.isValid()) {
     // Local label definitions always shadow existing labels.
     auto *Res = LabelDecl::Create(Context, CurContext, Loc, II, GnuLabelLoc);
@@ -4470,15 +4473,43 @@ LabelDecl *Sema::LookupOrCreateLabel(IdentifierInfo *II, SourceLocation Loc,
     return cast<LabelDecl>(Res);
   }
 
-  // Not a GNU local label.
-  LabelDecl *Res = LookupExistingLabel(II, Loc);
-  if (!Res) {
-    // If not forward referenced or defined already, create the backing decl.
-    Res = LabelDecl::Create(Context, CurContext, Loc, II);
-    Scope *S = CurScope->getFnParent();
-    assert(S && "Not in a function?");
-    PushOnScopeChains(Res, S, true);
+  LabelDecl *Existing = LookupExistingLabel(II, Loc);
+
+  // C++26 [stmt.label]p4 An identifier label shall not be enclosed by an
+  // expansion-statement.
+  //
+  // As an extension, we allow GNU local labels since they are logically
+  // scoped to the containing block, which prevents us from ending up with
+  // multiple copies of the same label in a function after instantiation.
+  //
+  // While allowing this is slightly more complicated, it also has the nice
+  // side-effect of avoiding otherwise rather horrible diagnostics you'd get
+  // when trying to use '__label__' if we didn't support this.
+  if (ForLabelStmt && CurContext->isExpansionStmt()) {
+    if (Existing && Existing->isGnuLocal())
+      return Existing;
+
+    // Drop the label from the AST as creating it anyway would cause us to
+    // either issue various unhelpful diagnostics (if we were to declare
+    // it in the function decl context) or shadow a valid label with the
+    // same name outside the expansion statement.
+    Diag(Loc, diag::err_expansion_stmt_label);
+    return nullptr;
   }
+
+  if (Existing)
+    return Existing;
+
+  // Declare non-local labels outside any expansion statements; this is required
+  // to support jumping out of an expansion statement.
+  ContextRAII Ctx{*this, CurContext->getEnclosingNonExpansionStatementContext(),
+                  /*NewThisContext=*/false};
+
+  // Not a GNU local label. Create the backing decl.
+  auto *Res = LabelDecl::Create(Context, CurContext, Loc, II);
+  Scope *S = CurScope->getFnParent();
+  assert(S && "Not in a function?");
+  PushOnScopeChains(Res, S, true);
   return Res;
 }
 
diff --git a/clang/test/CodeGenCXX/cxx2c-expansion-stmts-control-flow.cpp b/clang/test/CodeGenCXX/cxx2c-expansion-stmts-control-flow.cpp
index 5e824c05209bb..f94b580290500 100644
--- a/clang/test/CodeGenCXX/cxx2c-expansion-stmts-control-flow.cpp
+++ b/clang/test/CodeGenCXX/cxx2c-expansion-stmts-control-flow.cpp
@@ -56,6 +56,19 @@ void label() {
   }
 }
 
+void nested_label() {
+  template for (auto x : {1, 2}) {
+    __label__ a;
+    template for (auto y : {3, 4}) {
+      if (y == 3) goto a;
+      if (y == 4) goto end;
+      h(x, y);
+    }
+    a:;
+  }
+  end:
+}
+
 
 // CHECK-LABEL: define {{.*}} void @_Z14break_continuev()
 // CHECK: entry:
@@ -319,3 +332,98 @@ void label() {
 // CHECK-NEXT:   br label %expand.end
 // CHECK: expand.end:
 // CHECK-NEXT:   ret void
+
+
+// CHECK-LABEL: define {{.*}} void @_Z12nested_labelv()
+// CHECK: entry:
+// CHECK-NEXT:   %x = alloca i32, align 4
+// CHECK-NEXT:   %y = alloca i32, align 4
+// CHECK-NEXT:   %y4 = alloca i32, align 4
+// CHECK-NEXT:   %x12 = alloca i32, align 4
+// CHECK-NEXT:   %y13 = alloca i32, align 4
+// CHECK-NEXT:   %y21 = alloca i32, align 4
+// CHECK-NEXT:   store i32 1, ptr %x, align 4
+// CHECK-NEXT:   store i32 3, ptr %y, align 4
+// CHECK-NEXT:   %0 = load i32, ptr %y, align 4
+// CHECK-NEXT:   %cmp = icmp eq i32 %0, 3
+// CHECK-NEXT:   br i1 %cmp, label %if.then, label %if.end
+// CHECK: if.then:
+// CHECK-NEXT:   br label %a
+// CHECK: if.end:
+// CHECK-NEXT:   %1 = load i32, ptr %y, align 4
+// CHECK-NEXT:   %cmp1 = icmp eq i32 %1, 4
+// CHECK-NEXT:   br i1 %cmp1, label %if.then2, label %if.end3
+// CHECK: if.then2:
+// CHECK-NEXT:   br label %end
+// CHECK: if.end3:
+// CHECK-NEXT:   %2 = load i32, ptr %x, align 4
+// CHECK-NEXT:   %3 = load i32, ptr %y, align 4
+// CHECK-NEXT:   call void @_Z1hii(i32 {{.*}} %2, i32 {{.*}} %3)
+// CHECK-NEXT:   br label %expand.next
+// CHECK: expand.next:
+// CHECK-NEXT:   store i32 4, ptr %y4, align 4
+// CHECK-NEXT:   %4 = load i32, ptr %y4, align 4
+// CHECK-NEXT:   %cmp5 = icmp eq i32 %4, 3
+// CHECK-NEXT:   br i1 %cmp5, label %if.then6, label %if.end7
+// CHECK: if.then6:
+// CHECK-NEXT:   br label %a
+// CHECK: if.end7:
+// CHECK-NEXT:   %5 = load i32, ptr %y4, align 4
+// CHECK-NEXT:   %cmp8 = icmp eq i32 %5, 4
+// CHECK-NEXT:   br i1 %cmp8, label %if.then9, label %if.end10
+// CHECK: if.then9:
+// CHECK-NEXT:   br label %end
+// CHECK: if.end10:
+// CHECK-NEXT:   %6 = load i32, ptr %x, align 4
+// CHECK-NEXT:   %7 = load i32, ptr %y4, align 4
+// CHECK-NEXT:   call void @_Z1hii(i32 {{.*}} %6, i32 {{.*}} %7)
+// CHECK-NEXT:   br label %expand.end
+// CHECK: expand.end:
+// CHECK-NEXT:   br label %a
+// CHECK: a:
+// CHECK-NEXT:   br label %expand.next11
+// CHECK: expand.next11:
+// CHECK-NEXT:   store i32 2, ptr %x12, align 4
+// CHECK-NEXT:   store i32 3, ptr %y13, align 4
+// CHECK-NEXT:   %8 = load i32, ptr %y13, align 4
+// CHECK-NEXT:   %cmp14 = icmp eq i32 %8, 3
+// CHECK-NEXT:   br i1 %cmp14, label %if.then15, label %if.end16
+// CHECK: if.then15:
+// CHECK-NEXT:   br label %a29
+// CHECK: if.end16:
+// CHECK-NEXT:   %9 = load i32, ptr %y13, align 4
+// CHECK-NEXT:   %cmp17 = icmp eq i32 %9, 4
+// CHECK-NEXT:   br i1 %cmp17, label %if.then18, label %if.end19
+// CHECK: if.then18:
+// CHECK-NEXT:   br label %end
+// CHECK: if.end19:
+// CHECK-NEXT:   %10 = load i32, ptr %x12, align 4
+// CHECK-NEXT:   %11 = load i32, ptr %y13, align 4
+// CHECK-NEXT:   call void @_Z1hii(i32 {{.*}} %10, i32 {{.*}} %11)
+// CHECK-NEXT:   br label %expand.next20
+// CHECK: expand.next20:
+// CHECK-NEXT:   store i32 4, ptr %y21, align 4
+// CHECK-NEXT:   %12 = load i32, ptr %y21, align 4
+// CHECK-NEXT:   %cmp22 = icmp eq i32 %12, 3
+// CHECK-NEXT:   br i1 %cmp22, label %if.then23, label %if.end24
+// CHECK: if.then23:
+// CHECK-NEXT:   br label %a29
+// CHECK: if.end24:
+// CHECK-NEXT:   %13 = load i32, ptr %y21, align 4
+// CHECK-NEXT:   %cmp25 = icmp eq i32 %13, 4
+// CHECK-NEXT:   br i1 %cmp25, label %if.then26, label %if.end27
+// CHECK: if.then26:
+// CHECK-NEXT:   br label %end
+// CHECK: if.end27:
+// CHECK-NEXT:   %14 = load i32, ptr %x12, align 4
+// CHECK-NEXT:   %15 = load i32, ptr %y21, align 4
+// CHECK-NEXT:   call void @_Z1hii(i32 {{.*}} %14, i32 {{.*}} %15)
+// CHECK-NEXT:   br label %expand.end28
+// CHECK: expand.end28:
+// CHECK-NEXT:   br label %a29
+// CHECK: a29:
+// CHECK-NEXT:   br label %expand.end30
+// CHECK: expand.end30:
+// CHECK-NEXT:   br label %end
+// CHECK: end:
+// CHECK-NEXT:   ret void
diff --git a/clang/test/SemaCXX/cxx2c-expansion-statements.cpp b/clang/test/SemaCXX/cxx2c-expansion-statements.cpp
index bbfe6dae4bc2f..d060b418335bd 100644
--- a/clang/test/SemaCXX/cxx2c-expansion-statements.cpp
+++ b/clang/test/SemaCXX/cxx2c-expansion-statements.cpp
@@ -701,7 +701,7 @@ void label() {
   template for (auto x : {1, 2}) {
     invalid1:; // expected-error {{labels are not allowed in expansion statements}}
     invalid2:; // expected-error {{labels are not allowed in expansion statements}}
-    goto invalid1;
+    goto invalid1; // expected-error {{use of undeclared label 'invalid1'}}
   }
 
   template for (auto x : {1, 2}) {
@@ -729,9 +729,52 @@ void label() {
   // GNU local labels are allowed.
   template for (auto x : {1, 2}) {
     __label__ a;
+    if (x == 1) goto a;
     a:;
     if (x == 1) goto a;
   }
+
+  // Likewise, jumping *out* of an expansion statement is fine.
+  template for (auto x : {1, 2}) {
+    if (x == 1) goto lbl;
+    g(x);
+  }
+  lbl:;
+  template for (auto x : {1, 2}) {
+    if (x == 1) goto lbl;
+    g(x);
+  }
+
+  // Jumping into one is not possible, as local labels aren't visible
+  // outside the block that declares them, and non-local labels are invalid.
+  goto exp1; // expected-error {{use of undeclared label 'exp1'}}
+  goto exp3; // expected-error {{use of undeclared label 'exp3'}}
+  template for (auto x : {1, 2}) {
+    __label__ exp1, exp2;
+    exp1:;
+    exp2:;
+    exp3:; // expected-error {{labels are not allowed in expansion statements}}
+  }
+  goto exp2; // expected-error {{use of undeclared label 'exp2'}}
+
+  // Allow jumping from inside an expansion statement to a local label in
+  // one of its parents; this requires walking up the stack of expansion
+  // statement context.
+  out1:;
+  template for (auto x : {1, 2}) {
+    __label__ x, y;
+    x:
+    goto out1;
+    goto out2;
+    template for (auto x : {3, 4}) {
+      goto x;
+      goto y;
+      goto out1;
+      goto out2;
+    }
+    y:
+  }
+  out2:;
 }
 
 

>From 278977a636f9a28d309934c4c57492ea36da817a Mon Sep 17 00:00:00 2001
From: Sirraide <aeternalmail at gmail.com>
Date: Sun, 26 Oct 2025 06:40:23 +0100
Subject: [PATCH 15/33] Make substatement memory representation less horrible

---
 clang/include/clang/AST/StmtCXX.h | 130 ++++++++++++++----------------
 clang/lib/AST/StmtCXX.cpp         |  25 +++---
 2 files changed, 73 insertions(+), 82 deletions(-)

diff --git a/clang/include/clang/AST/StmtCXX.h b/clang/include/clang/AST/StmtCXX.h
index fa992666b825f..59f7c7a387dc3 100644
--- a/clang/include/clang/AST/StmtCXX.h
+++ b/clang/include/clang/AST/StmtCXX.h
@@ -19,11 +19,10 @@
 #include "clang/AST/Stmt.h"
 #include "llvm/Support/Compiler.h"
 
-#include <clang/AST/ExprCXX.h>
-
 namespace clang {
 
 class VarDecl;
+class ExpansionStmtDecl;
 
 /// CXXCatchStmt - This represents a C++ catch block.
 ///
@@ -541,11 +540,30 @@ class CXXExpansionStmt : public Stmt {
     INIT,
     VAR,
     BODY,
-    COUNT
+    FIRST_CHILD_STMT,
+
+    // CXXDependentExpansionStmt
+    EXPANSION_INITIALIZER = FIRST_CHILD_STMT,
+    COUNT_CXXDependentExpansionStmt,
+
+    // CXXDestructuringExpansionStmt
+    DECOMP_DECL = FIRST_CHILD_STMT,
+    COUNT_CXXDestructuringExpansionStmt,
+
+    // CXXIteratingExpansionStmt
+    RANGE = FIRST_CHILD_STMT,
+    BEGIN,
+    END,
+    COUNT_CXXIteratingExpansionStmt,
+
+    MAX_COUNT = COUNT_CXXIteratingExpansionStmt,
   };
 
-  // This must be the last member of this class.
-  Stmt* SubStmts[COUNT];
+  // Managing the memory for this properly would be rather complicated, and
+  // expansion statements are fairly uncommon, so just allocate space for the
+  // maximum amount of substatements we could possibly have.
+  Stmt* SubStmts[MAX_COUNT];
+
 
   CXXExpansionStmt(StmtClass SC, EmptyShell Empty);
   CXXExpansionStmt(StmtClass SC, ExpansionStmtDecl *ESD, Stmt *Init,
@@ -595,11 +613,11 @@ class CXXExpansionStmt : public Stmt {
   }
 
   child_range children() {
-    return child_range(SubStmts, SubStmts + COUNT);
+    return child_range(SubStmts, SubStmts + FIRST_CHILD_STMT);
   }
 
   const_child_range children() const {
-    return const_child_range(SubStmts, SubStmts + COUNT);
+    return const_child_range(SubStmts, SubStmts + FIRST_CHILD_STMT);
   }
 };
 
@@ -625,8 +643,6 @@ class CXXEnumeratingExpansionStmt : public CXXExpansionStmt {
 class CXXDependentExpansionStmt : public CXXExpansionStmt {
   friend class ASTStmtReader;
 
-  Expr* ExpansionInitializer;
-
 public:
   CXXDependentExpansionStmt(EmptyShell Empty);
   CXXDependentExpansionStmt(ExpansionStmtDecl *ESD, Stmt *Init,
@@ -634,23 +650,21 @@ class CXXDependentExpansionStmt : public CXXExpansionStmt {
                             SourceLocation ForLoc, SourceLocation LParenLoc,
                             SourceLocation ColonLoc, SourceLocation RParenLoc);
 
-  Expr *getExpansionInitializer() { return ExpansionInitializer; }
-  const Expr *getExpansionInitializer() const { return ExpansionInitializer; }
-  void setExpansionInitializer(Expr* S) { ExpansionInitializer = S; }
+  Expr *getExpansionInitializer() {
+    return cast<Expr>(SubStmts[EXPANSION_INITIALIZER]);
+  }
+  const Expr *getExpansionInitializer() const {
+    return cast<Expr>(SubStmts[EXPANSION_INITIALIZER]);
+  }
+  void setExpansionInitializer(Expr *S) { SubStmts[EXPANSION_INITIALIZER] = S; }
 
   child_range children() {
-    const_child_range CCR =
-        const_cast<const CXXDependentExpansionStmt *>(this)->children();
-    return child_range(cast_away_const(CCR.begin()),
-                       cast_away_const(CCR.end()));
+    return child_range(SubStmts, SubStmts + COUNT_CXXDependentExpansionStmt);
   }
 
   const_child_range children() const {
-    // See CXXIteratingExpansion statement for an explanation of this terrible
-    // hack.
-    Stmt *const *FirstParentSubStmt = CXXExpansionStmt::SubStmts;
-    unsigned Count = static_cast<unsigned>(CXXExpansionStmt::COUNT) + 1;
-    return const_child_range(FirstParentSubStmt, FirstParentSubStmt + Count);
+    return const_child_range(SubStmts,
+                             SubStmts + COUNT_CXXDependentExpansionStmt);
   }
 
   static bool classof(const Stmt *T) {
@@ -665,16 +679,6 @@ class CXXDependentExpansionStmt : public CXXExpansionStmt {
 class CXXIteratingExpansionStmt : public CXXExpansionStmt {
   friend class ASTStmtReader;
 
-  enum SubStmt {
-    RANGE,
-    BEGIN,
-    END,
-    COUNT
-  };
-
-  // This must be the first member of this class.
-  DeclStmt* SubStmts[COUNT];
-
 public:
   CXXIteratingExpansionStmt(EmptyShell Empty);
   CXXIteratingExpansionStmt(ExpansionStmtDecl *ESD, Stmt *Init,
@@ -683,9 +687,11 @@ class CXXIteratingExpansionStmt : public CXXExpansionStmt {
                             SourceLocation ForLoc, SourceLocation LParenLoc,
                             SourceLocation ColonLoc, SourceLocation RParenLoc);
 
-  const DeclStmt* getRangeVarStmt() const { return SubStmts[RANGE]; }
-  DeclStmt* getRangeVarStmt() { return SubStmts[RANGE]; }
-  void setRangeVarStmt(DeclStmt* S) { SubStmts[RANGE] = S; }
+  const DeclStmt *getRangeVarStmt() const {
+    return cast<DeclStmt>(SubStmts[RANGE]);
+  }
+  DeclStmt *getRangeVarStmt() { return cast<DeclStmt>(SubStmts[RANGE]); }
+  void setRangeVarStmt(DeclStmt *S) { SubStmts[RANGE] = S; }
 
   const VarDecl* getRangeVar() const {
     return cast<VarDecl>(getRangeVarStmt()->getSingleDecl());
@@ -695,9 +701,11 @@ class CXXIteratingExpansionStmt : public CXXExpansionStmt {
     return cast<VarDecl>(getRangeVarStmt()->getSingleDecl());
   }
 
-  const DeclStmt* getBeginVarStmt() const { return SubStmts[BEGIN]; }
-  DeclStmt* getBeginVarStmt() { return SubStmts[BEGIN]; }
-  void setBeginVarStmt(DeclStmt* S) { SubStmts[BEGIN] = S; }
+  const DeclStmt *getBeginVarStmt() const {
+    return cast<DeclStmt>(SubStmts[BEGIN]);
+  }
+  DeclStmt *getBeginVarStmt() { return cast<DeclStmt>(SubStmts[BEGIN]); }
+  void setBeginVarStmt(DeclStmt *S) { SubStmts[BEGIN] = S; }
 
   const VarDecl* getBeginVar() const {
     return cast<VarDecl>(getBeginVarStmt()->getSingleDecl());
@@ -707,9 +715,11 @@ class CXXIteratingExpansionStmt : public CXXExpansionStmt {
     return cast<VarDecl>(getBeginVarStmt()->getSingleDecl());
   }
 
-  const DeclStmt* getEndVarStmt() const { return SubStmts[END]; }
-  DeclStmt* getEndVarStmt() { return SubStmts[END]; }
-  void setEndVarStmt(DeclStmt* S) { SubStmts[END] = S; }
+  const DeclStmt *getEndVarStmt() const {
+    return cast<DeclStmt>(SubStmts[END]);
+  }
+  DeclStmt *getEndVarStmt() { return cast<DeclStmt>(SubStmts[END]); }
+  void setEndVarStmt(DeclStmt *S) { SubStmts[END] = S; }
 
   const VarDecl* getEndVar() const {
     return cast<VarDecl>(getEndVarStmt()->getSingleDecl());
@@ -720,26 +730,12 @@ class CXXIteratingExpansionStmt : public CXXExpansionStmt {
   }
 
   child_range children() {
-    const_child_range CCR =
-        const_cast<const CXXIteratingExpansionStmt *>(this)->children();
-    return child_range(cast_away_const(CCR.begin()),
-                       cast_away_const(CCR.end()));
+    return child_range(SubStmts, SubStmts + COUNT_CXXIteratingExpansionStmt);
   }
 
   const_child_range children() const {
-    // Build a contiguous range consisting of the end of the base
-    // CXXExpansionStmt’s SubStmts and ours.
-    //
-    // This is rather terrible, but allocating all this state in the derived
-    // classes of CXXExpansionStmt instead or moving it into trailing data
-    // would be quite a bit more complicated.
-    //
-    // FIXME: There ought to be a better way of doing this. If we change this,
-    // we should also update CXXDependentExpansionStmt.
-    Stmt *const *FirstParentSubStmt = CXXExpansionStmt::SubStmts;
-    unsigned Count = static_cast<unsigned>(CXXExpansionStmt::COUNT) +
-                     static_cast<unsigned>(CXXIteratingExpansionStmt::COUNT);
-    return const_child_range(FirstParentSubStmt, FirstParentSubStmt + Count);
+    return const_child_range(SubStmts,
+                             SubStmts + COUNT_CXXIteratingExpansionStmt);
   }
 
   static bool classof(const Stmt *T) {
@@ -751,8 +747,6 @@ class CXXIteratingExpansionStmt : public CXXExpansionStmt {
 class CXXDestructuringExpansionStmt : public CXXExpansionStmt {
   friend class ASTStmtReader;
 
-  Stmt* DecompositionDeclStmt;
-
 public:
   CXXDestructuringExpansionStmt(EmptyShell Empty);
   CXXDestructuringExpansionStmt(ExpansionStmtDecl *ESD, Stmt *Init,
@@ -760,9 +754,9 @@ class CXXDestructuringExpansionStmt : public CXXExpansionStmt {
                             SourceLocation ForLoc, SourceLocation LParenLoc,
                             SourceLocation ColonLoc, SourceLocation RParenLoc);
 
-  Stmt *getDecompositionDeclStmt() { return DecompositionDeclStmt; }
-  const Stmt *getDecompositionDeclStmt() const { return DecompositionDeclStmt; }
-  void setDecompositionDeclStmt(Stmt* S) { DecompositionDeclStmt = S; }
+  Stmt *getDecompositionDeclStmt() { return SubStmts[DECOMP_DECL]; }
+  const Stmt *getDecompositionDeclStmt() const { return SubStmts[DECOMP_DECL]; }
+  void setDecompositionDeclStmt(Stmt* S) { SubStmts[DECOMP_DECL] = S; }
 
   DecompositionDecl* getDecompositionDecl();
   const DecompositionDecl* getDecompositionDecl() const {
@@ -770,18 +764,12 @@ class CXXDestructuringExpansionStmt : public CXXExpansionStmt {
   }
 
   child_range children() {
-    const_child_range CCR =
-        const_cast<const CXXDestructuringExpansionStmt *>(this)->children();
-    return child_range(cast_away_const(CCR.begin()),
-                       cast_away_const(CCR.end()));
+    return child_range(SubStmts, SubStmts + COUNT_CXXDestructuringExpansionStmt);
   }
 
   const_child_range children() const {
-    // See CXXIteratingExpansion statement for an explanation of this terrible
-    // hack.
-    Stmt *const *FirstParentSubStmt = CXXExpansionStmt::SubStmts;
-    unsigned Count = static_cast<unsigned>(CXXExpansionStmt::COUNT) + 1;
-    return const_child_range(FirstParentSubStmt, FirstParentSubStmt + Count);
+    return const_child_range(SubStmts,
+                             SubStmts + COUNT_CXXDestructuringExpansionStmt);
   }
 
   static bool classof(const Stmt *T) {
diff --git a/clang/lib/AST/StmtCXX.cpp b/clang/lib/AST/StmtCXX.cpp
index 793ba67103ed6..696accf786d78 100644
--- a/clang/lib/AST/StmtCXX.cpp
+++ b/clang/lib/AST/StmtCXX.cpp
@@ -11,6 +11,7 @@
 //===----------------------------------------------------------------------===//
 
 #include "clang/AST/StmtCXX.h"
+#include "clang/AST/ExprCXX.h"
 
 #include "clang/AST/ASTContext.h"
 
@@ -137,9 +138,9 @@ CXXExpansionStmt::CXXExpansionStmt(StmtClass SC, ExpansionStmtDecl *ESD,
                                    SourceLocation RParenLoc)
     : Stmt(SC), ParentDecl(ESD), ForLoc(ForLoc), LParenLoc(LParenLoc),
       ColonLoc(ColonLoc), RParenLoc(RParenLoc) {
-  SubStmts[INIT] = Init;
-  SubStmts[VAR] = ExpansionVar;
-  SubStmts[BODY] = nullptr;
+  setInit(Init);
+  setExpansionVarStmt(ExpansionVar);
+  setBody(nullptr);
 }
 
 CXXEnumeratingExpansionStmt::CXXEnumeratingExpansionStmt(EmptyShell Empty)
@@ -196,9 +197,9 @@ CXXIteratingExpansionStmt::CXXIteratingExpansionStmt(
     SourceLocation LParenLoc, SourceLocation ColonLoc, SourceLocation RParenLoc)
     : CXXExpansionStmt(CXXIteratingExpansionStmtClass, ESD, Init, ExpansionVar,
                        ForLoc, LParenLoc, ColonLoc, RParenLoc) {
-  SubStmts[BEGIN] = Begin;
-  SubStmts[END] = End;
-  SubStmts[RANGE] = Range;
+  setRangeVarStmt(Range);
+  setBeginVarStmt(Begin);
+  setEndVarStmt(End);
 }
 
 CXXDestructuringExpansionStmt::CXXDestructuringExpansionStmt(EmptyShell Empty)
@@ -209,12 +210,13 @@ CXXDestructuringExpansionStmt::CXXDestructuringExpansionStmt(
     Stmt *DecompositionDeclStmt, SourceLocation ForLoc,
     SourceLocation LParenLoc, SourceLocation ColonLoc, SourceLocation RParenLoc)
     : CXXExpansionStmt(CXXDestructuringExpansionStmtClass, ESD, Init, ExpansionVar,
-                       ForLoc, LParenLoc, ColonLoc, RParenLoc),
-      DecompositionDeclStmt(DecompositionDeclStmt) {}
+                       ForLoc, LParenLoc, ColonLoc, RParenLoc) {
+  setDecompositionDeclStmt(DecompositionDeclStmt);
+}
 
 DecompositionDecl* CXXDestructuringExpansionStmt::getDecompositionDecl() {
   return cast<DecompositionDecl>(
-      cast<DeclStmt>(DecompositionDeclStmt)->getSingleDecl());
+      cast<DeclStmt>(getDecompositionDeclStmt())->getSingleDecl());
 }
 
 CXXDependentExpansionStmt::CXXDependentExpansionStmt(EmptyShell Empty)
@@ -225,8 +227,9 @@ CXXDependentExpansionStmt::CXXDependentExpansionStmt(
     Expr *ExpansionInitializer, SourceLocation ForLoc, SourceLocation LParenLoc,
     SourceLocation ColonLoc, SourceLocation RParenLoc)
     : CXXExpansionStmt(CXXDependentExpansionStmtClass, ESD, Init, ExpansionVar,
-                       ForLoc, LParenLoc, ColonLoc, RParenLoc),
-      ExpansionInitializer(ExpansionInitializer) {}
+                       ForLoc, LParenLoc, ColonLoc, RParenLoc) {
+  setExpansionInitializer(ExpansionInitializer);
+}
 
 CXXExpansionInstantiationStmt::CXXExpansionInstantiationStmt(
     EmptyShell Empty, unsigned NumInstantiations, unsigned NumSharedStmts)

>From 7ba15b018813f98554f5e6ba52a6d098828d7d2c Mon Sep 17 00:00:00 2001
From: Sirraide <aeternalmail at gmail.com>
Date: Sun, 26 Oct 2025 06:55:19 +0100
Subject: [PATCH 16/33] Update out-of-date comment

---
 clang/test/SemaCXX/cxx2c-expansion-statements.cpp | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/clang/test/SemaCXX/cxx2c-expansion-statements.cpp b/clang/test/SemaCXX/cxx2c-expansion-statements.cpp
index d060b418335bd..c902bac16fe84 100644
--- a/clang/test/SemaCXX/cxx2c-expansion-statements.cpp
+++ b/clang/test/SemaCXX/cxx2c-expansion-statements.cpp
@@ -758,8 +758,7 @@ void label() {
   goto exp2; // expected-error {{use of undeclared label 'exp2'}}
 
   // Allow jumping from inside an expansion statement to a local label in
-  // one of its parents; this requires walking up the stack of expansion
-  // statement context.
+  // one of its parents.
   out1:;
   template for (auto x : {1, 2}) {
     __label__ x, y;

>From fb7a000f2bb1140019bdd8991804470497fd8e24 Mon Sep 17 00:00:00 2001
From: Sirraide <aeternalmail at gmail.com>
Date: Sun, 26 Oct 2025 20:52:40 +0100
Subject: [PATCH 17/33] Check for unexpanded parameter packs

---
 clang/lib/Sema/SemaExpand.cpp                 |   3 +
 clang/lib/Sema/SemaExprCXX.cpp                |   1 -
 clang/lib/Sema/SemaLambda.cpp                 |   5 -
 .../cxx2c-expansion-stmts-templates.cpp       | 208 ++++++++++++++++++
 .../SemaCXX/cxx2c-expansion-statements.cpp    |  22 ++
 5 files changed, 233 insertions(+), 6 deletions(-)
 create mode 100644 clang/test/CodeGenCXX/cxx2c-expansion-stmts-templates.cpp

diff --git a/clang/lib/Sema/SemaExpand.cpp b/clang/lib/Sema/SemaExpand.cpp
index 398cc60c676a7..57d8d1a1f5c87 100644
--- a/clang/lib/Sema/SemaExpand.cpp
+++ b/clang/lib/Sema/SemaExpand.cpp
@@ -300,6 +300,9 @@ StmtResult Sema::ActOnCXXExpansionStmt(
     ExpansionInitializer = R.get();
   }
 
+  if (DiagnoseUnexpandedParameterPack(ExpansionInitializer))
+    return StmtError();
+
   // Reject lambdas early.
   if (auto *RD = ExpansionInitializer->getType()->getAsCXXRecordDecl();
       RD && RD->isLambda()) {
diff --git a/clang/lib/Sema/SemaExprCXX.cpp b/clang/lib/Sema/SemaExprCXX.cpp
index fe1f89b7a5dfa..e93d4291fbf6f 100644
--- a/clang/lib/Sema/SemaExprCXX.cpp
+++ b/clang/lib/Sema/SemaExprCXX.cpp
@@ -7600,7 +7600,6 @@ static void CheckIfAnyEnclosingLambdasMustCaptureAnyPotentialCaptures(
     Expr *const FE, LambdaScopeInfo *const CurrentLSI, Sema &S) {
 
   assert(!S.isUnevaluatedContext());
-  assert(S.CurContext->isDependentContext());
 #ifndef NDEBUG
   DeclContext *DC = S.CurContext;
   while (isa_and_nonnull<CapturedDecl>(DC))
diff --git a/clang/lib/Sema/SemaLambda.cpp b/clang/lib/Sema/SemaLambda.cpp
index d2effd4ebc16b..16cb865e0b0b7 100644
--- a/clang/lib/Sema/SemaLambda.cpp
+++ b/clang/lib/Sema/SemaLambda.cpp
@@ -190,11 +190,6 @@ UnsignedOrNone clang::getStackIndexOfNearestEnclosingCaptureCapableLambda(
     return NoLambdaIsCaptureCapable;
 
   const unsigned IndexOfCaptureReadyLambda = *OptionalStackIndex;
-  assert(((IndexOfCaptureReadyLambda != (FunctionScopes.size() - 1)) ||
-          S.getCurGenericLambda()) &&
-         "The capture ready lambda for a potential capture can only be the "
-         "current lambda if it is a generic lambda");
-
   const sema::LambdaScopeInfo *const CaptureReadyLambdaLSI =
       cast<sema::LambdaScopeInfo>(FunctionScopes[IndexOfCaptureReadyLambda]);
 
diff --git a/clang/test/CodeGenCXX/cxx2c-expansion-stmts-templates.cpp b/clang/test/CodeGenCXX/cxx2c-expansion-stmts-templates.cpp
new file mode 100644
index 0000000000000..e0de7ced5baee
--- /dev/null
+++ b/clang/test/CodeGenCXX/cxx2c-expansion-stmts-templates.cpp
@@ -0,0 +1,208 @@
+// RUN: %clang_cc1 -std=c++2c -triple x86_64-unknown-linux-gnu -emit-llvm -o - %s | FileCheck %s
+
+struct E {
+  int x, y;
+  constexpr E(int x, int y) : x{x}, y{y} {}
+};
+
+template <typename ...Es>
+int unexpanded_pack_good(Es ...es) {
+  int sum = 0;
+  ([&] {
+    template for (auto x : es) sum += x;
+    template for (Es e : {{5, 6}, {7, 8}}) sum += e.x + e.y;
+  }(), ...);
+  return sum;
+}
+
+int unexpanded_pack() {
+  return unexpanded_pack_good(E{1, 2}, E{3, 4});
+}
+
+
+// CHECK: %struct.E = type { i32, i32 }
+// CHECK: %class.anon = type { ptr, ptr }
+// CHECK: %class.anon.0 = type { ptr, ptr }
+
+
+// CHECK-LABEL: define {{.*}} i32 @_Z15unexpanded_packv()
+// CHECK: entry:
+// CHECK-NEXT:   %agg.tmp = alloca %struct.E, align 4
+// CHECK-NEXT:   %agg.tmp1 = alloca %struct.E, align 4
+// CHECK-NEXT:   call void @_ZN1EC1Eii(ptr {{.*}} %agg.tmp, i32 {{.*}} 1, i32 {{.*}} 2)
+// CHECK-NEXT:   call void @_ZN1EC1Eii(ptr {{.*}} %agg.tmp1, i32 {{.*}} 3, i32 {{.*}} 4)
+// CHECK-NEXT:   %0 = load i64, ptr %agg.tmp, align 4
+// CHECK-NEXT:   %1 = load i64, ptr %agg.tmp1, align 4
+// CHECK-NEXT:   %call = call {{.*}} i32 @_Z20unexpanded_pack_goodIJ1ES0_EEiDpT_(i64 %0, i64 %1)
+// CHECK-NEXT:   ret i32 %call
+
+
+// CHECK-LABEL: define {{.*}} i32 @_Z20unexpanded_pack_goodIJ1ES0_EEiDpT_(i64 %es.coerce, i64 %es.coerce2)
+// CHECK: entry:
+// CHECK-NEXT:   %es = alloca %struct.E, align 4
+// CHECK-NEXT:   %es3 = alloca %struct.E, align 4
+// CHECK-NEXT:   %sum = alloca i32, align 4
+// CHECK-NEXT:   %ref.tmp = alloca %class.anon, align 8
+// CHECK-NEXT:   %ref.tmp4 = alloca %class.anon.0, align 8
+// CHECK-NEXT:   store i64 %es.coerce, ptr %es, align 4
+// CHECK-NEXT:   store i64 %es.coerce2, ptr %es3, align 4
+// CHECK-NEXT:   store i32 0, ptr %sum, align 4
+// CHECK-NEXT:   %0 = getelementptr inbounds nuw %class.anon, ptr %ref.tmp, i32 0, i32 0
+// CHECK-NEXT:   store ptr %es, ptr %0, align 8
+// CHECK-NEXT:   %1 = getelementptr inbounds nuw %class.anon, ptr %ref.tmp, i32 0, i32 1
+// CHECK-NEXT:   store ptr %sum, ptr %1, align 8
+// CHECK-NEXT:   call void @_ZZ20unexpanded_pack_goodIJ1ES0_EEiDpT_ENKUlvE0_clEv(ptr {{.*}} %ref.tmp)
+// CHECK-NEXT:   %2 = getelementptr inbounds nuw %class.anon.0, ptr %ref.tmp4, i32 0, i32 0
+// CHECK-NEXT:   store ptr %es3, ptr %2, align 8
+// CHECK-NEXT:   %3 = getelementptr inbounds nuw %class.anon.0, ptr %ref.tmp4, i32 0, i32 1
+// CHECK-NEXT:   store ptr %sum, ptr %3, align 8
+// CHECK-NEXT:   call void @_ZZ20unexpanded_pack_goodIJ1ES0_EEiDpT_ENKUlvE_clEv(ptr {{.*}} %ref.tmp4)
+// CHECK-NEXT:   %4 = load i32, ptr %sum, align 4
+// CHECK-NEXT:   ret i32 %4
+
+
+// CHECK-LABEL: define {{.*}} void @_ZN1EC1Eii(ptr {{.*}} %this, i32 {{.*}} %x, i32 {{.*}} %y) {{.*}}
+// CHECK: entry:
+// CHECK-NEXT:   %this.addr = alloca ptr, align 8
+// CHECK-NEXT:   %x.addr = alloca i32, align 4
+// CHECK-NEXT:   %y.addr = alloca i32, align 4
+// CHECK-NEXT:   store ptr %this, ptr %this.addr, align 8
+// CHECK-NEXT:   store i32 %x, ptr %x.addr, align 4
+// CHECK-NEXT:   store i32 %y, ptr %y.addr, align 4
+// CHECK-NEXT:   %this1 = load ptr, ptr %this.addr, align 8
+// CHECK-NEXT:   %0 = load i32, ptr %x.addr, align 4
+// CHECK-NEXT:   %1 = load i32, ptr %y.addr, align 4
+// CHECK-NEXT:   call void @_ZN1EC2Eii(ptr {{.*}} %this1, i32 {{.*}} %0, i32 {{.*}} %1)
+// CHECK-NEXT:   ret void
+
+
+// CHECK-LABEL: define {{.*}} void @_ZZ20unexpanded_pack_goodIJ1ES0_EEiDpT_ENKUlvE0_clEv(ptr {{.*}} %this)
+// CHECK: entry:
+// CHECK-NEXT:   %this.addr = alloca ptr, align 8
+// CHECK-NEXT:   %0 = alloca ptr, align 8
+// CHECK-NEXT:   %x = alloca i32, align 4
+// CHECK-NEXT:   %x3 = alloca i32, align 4
+// CHECK-NEXT:   %e = alloca %struct.E, align 4
+// CHECK-NEXT:   %e10 = alloca %struct.E, align 4
+// CHECK-NEXT:   store ptr %this, ptr %this.addr, align 8
+// CHECK-NEXT:   %this1 = load ptr, ptr %this.addr, align 8
+// CHECK-NEXT:   %1 = getelementptr inbounds nuw %class.anon, ptr %this1, i32 0, i32 0
+// CHECK-NEXT:   %2 = load ptr, ptr %1, align 8
+// CHECK-NEXT:   store ptr %2, ptr %0, align 8
+// CHECK-NEXT:   %3 = load ptr, ptr %0, align 8
+// CHECK-NEXT:   %x2 = getelementptr inbounds nuw %struct.E, ptr %3, i32 0, i32 0
+// CHECK-NEXT:   %4 = load i32, ptr %x2, align 4
+// CHECK-NEXT:   store i32 %4, ptr %x, align 4
+// CHECK-NEXT:   %5 = load i32, ptr %x, align 4
+// CHECK-NEXT:   %6 = getelementptr inbounds nuw %class.anon, ptr %this1, i32 0, i32 1
+// CHECK-NEXT:   %7 = load ptr, ptr %6, align 8
+// CHECK-NEXT:   %8 = load i32, ptr %7, align 4
+// CHECK-NEXT:   %add = add nsw i32 %8, %5
+// CHECK-NEXT:   store i32 %add, ptr %7, align 4
+// CHECK-NEXT:   br label %expand.next
+// CHECK: expand.next:
+// CHECK-NEXT:   %9 = load ptr, ptr %0, align 8
+// CHECK-NEXT:   %y = getelementptr inbounds nuw %struct.E, ptr %9, i32 0, i32 1
+// CHECK-NEXT:   %10 = load i32, ptr %y, align 4
+// CHECK-NEXT:   store i32 %10, ptr %x3, align 4
+// CHECK-NEXT:   %11 = load i32, ptr %x3, align 4
+// CHECK-NEXT:   %12 = getelementptr inbounds nuw %class.anon, ptr %this1, i32 0, i32 1
+// CHECK-NEXT:   %13 = load ptr, ptr %12, align 8
+// CHECK-NEXT:   %14 = load i32, ptr %13, align 4
+// CHECK-NEXT:   %add4 = add nsw i32 %14, %11
+// CHECK-NEXT:   store i32 %add4, ptr %13, align 4
+// CHECK-NEXT:   br label %expand.end
+// CHECK: expand.end:
+// CHECK-NEXT:   call void @_ZN1EC1Eii(ptr {{.*}} %e, i32 {{.*}} 5, i32 {{.*}} 6)
+// CHECK-NEXT:   %x5 = getelementptr inbounds nuw %struct.E, ptr %e, i32 0, i32 0
+// CHECK-NEXT:   %15 = load i32, ptr %x5, align 4
+// CHECK-NEXT:   %y6 = getelementptr inbounds nuw %struct.E, ptr %e, i32 0, i32 1
+// CHECK-NEXT:   %16 = load i32, ptr %y6, align 4
+// CHECK-NEXT:   %add7 = add nsw i32 %15, %16
+// CHECK-NEXT:   %17 = getelementptr inbounds nuw %class.anon, ptr %this1, i32 0, i32 1
+// CHECK-NEXT:   %18 = load ptr, ptr %17, align 8
+// CHECK-NEXT:   %19 = load i32, ptr %18, align 4
+// CHECK-NEXT:   %add8 = add nsw i32 %19, %add7
+// CHECK-NEXT:   store i32 %add8, ptr %18, align 4
+// CHECK-NEXT:   br label %expand.next9
+// CHECK: expand.next9:
+// CHECK-NEXT:   call void @_ZN1EC1Eii(ptr {{.*}} %e10, i32 {{.*}} 7, i32 {{.*}} 8)
+// CHECK-NEXT:   %x11 = getelementptr inbounds nuw %struct.E, ptr %e10, i32 0, i32 0
+// CHECK-NEXT:   %20 = load i32, ptr %x11, align 4
+// CHECK-NEXT:   %y12 = getelementptr inbounds nuw %struct.E, ptr %e10, i32 0, i32 1
+// CHECK-NEXT:   %21 = load i32, ptr %y12, align 4
+// CHECK-NEXT:   %add13 = add nsw i32 %20, %21
+// CHECK-NEXT:   %22 = getelementptr inbounds nuw %class.anon, ptr %this1, i32 0, i32 1
+// CHECK-NEXT:   %23 = load ptr, ptr %22, align 8
+// CHECK-NEXT:   %24 = load i32, ptr %23, align 4
+// CHECK-NEXT:   %add14 = add nsw i32 %24, %add13
+// CHECK-NEXT:   store i32 %add14, ptr %23, align 4
+// CHECK-NEXT:   br label %expand.end15
+// CHECK: expand.end15:
+// CHECK-NEXT:   ret void
+
+
+// CHECK-LABEL: define {{.*}} void @_ZZ20unexpanded_pack_goodIJ1ES0_EEiDpT_ENKUlvE_clEv(ptr {{.*}} %this)
+// CHECK: entry:
+// CHECK-NEXT:   %this.addr = alloca ptr, align 8
+// CHECK-NEXT:   %0 = alloca ptr, align 8
+// CHECK-NEXT:   %x = alloca i32, align 4
+// CHECK-NEXT:   %x3 = alloca i32, align 4
+// CHECK-NEXT:   %e = alloca %struct.E, align 4
+// CHECK-NEXT:   %e10 = alloca %struct.E, align 4
+// CHECK-NEXT:   store ptr %this, ptr %this.addr, align 8
+// CHECK-NEXT:   %this1 = load ptr, ptr %this.addr, align 8
+// CHECK-NEXT:   %1 = getelementptr inbounds nuw %class.anon.0, ptr %this1, i32 0, i32 0
+// CHECK-NEXT:   %2 = load ptr, ptr %1, align 8
+// CHECK-NEXT:   store ptr %2, ptr %0, align 8
+// CHECK-NEXT:   %3 = load ptr, ptr %0, align 8
+// CHECK-NEXT:   %x2 = getelementptr inbounds nuw %struct.E, ptr %3, i32 0, i32 0
+// CHECK-NEXT:   %4 = load i32, ptr %x2, align 4
+// CHECK-NEXT:   store i32 %4, ptr %x, align 4
+// CHECK-NEXT:   %5 = load i32, ptr %x, align 4
+// CHECK-NEXT:   %6 = getelementptr inbounds nuw %class.anon.0, ptr %this1, i32 0, i32 1
+// CHECK-NEXT:   %7 = load ptr, ptr %6, align 8
+// CHECK-NEXT:   %8 = load i32, ptr %7, align 4
+// CHECK-NEXT:   %add = add nsw i32 %8, %5
+// CHECK-NEXT:   store i32 %add, ptr %7, align 4
+// CHECK-NEXT:   br label %expand.next
+// CHECK: expand.next:
+// CHECK-NEXT:   %9 = load ptr, ptr %0, align 8
+// CHECK-NEXT:   %y = getelementptr inbounds nuw %struct.E, ptr %9, i32 0, i32 1
+// CHECK-NEXT:   %10 = load i32, ptr %y, align 4
+// CHECK-NEXT:   store i32 %10, ptr %x3, align 4
+// CHECK-NEXT:   %11 = load i32, ptr %x3, align 4
+// CHECK-NEXT:   %12 = getelementptr inbounds nuw %class.anon.0, ptr %this1, i32 0, i32 1
+// CHECK-NEXT:   %13 = load ptr, ptr %12, align 8
+// CHECK-NEXT:   %14 = load i32, ptr %13, align 4
+// CHECK-NEXT:   %add4 = add nsw i32 %14, %11
+// CHECK-NEXT:   store i32 %add4, ptr %13, align 4
+// CHECK-NEXT:   br label %expand.end
+// CHECK: expand.end:
+// CHECK-NEXT:   call void @_ZN1EC1Eii(ptr {{.*}} %e, i32 {{.*}} 5, i32 {{.*}} 6)
+// CHECK-NEXT:   %x5 = getelementptr inbounds nuw %struct.E, ptr %e, i32 0, i32 0
+// CHECK-NEXT:   %15 = load i32, ptr %x5, align 4
+// CHECK-NEXT:   %y6 = getelementptr inbounds nuw %struct.E, ptr %e, i32 0, i32 1
+// CHECK-NEXT:   %16 = load i32, ptr %y6, align 4
+// CHECK-NEXT:   %add7 = add nsw i32 %15, %16
+// CHECK-NEXT:   %17 = getelementptr inbounds nuw %class.anon.0, ptr %this1, i32 0, i32 1
+// CHECK-NEXT:   %18 = load ptr, ptr %17, align 8
+// CHECK-NEXT:   %19 = load i32, ptr %18, align 4
+// CHECK-NEXT:   %add8 = add nsw i32 %19, %add7
+// CHECK-NEXT:   store i32 %add8, ptr %18, align 4
+// CHECK-NEXT:   br label %expand.next9
+// CHECK: expand.next9:
+// CHECK-NEXT:   call void @_ZN1EC1Eii(ptr {{.*}} %e10, i32 {{.*}} 7, i32 {{.*}} 8)
+// CHECK-NEXT:   %x11 = getelementptr inbounds nuw %struct.E, ptr %e10, i32 0, i32 0
+// CHECK-NEXT:   %20 = load i32, ptr %x11, align 4
+// CHECK-NEXT:   %y12 = getelementptr inbounds nuw %struct.E, ptr %e10, i32 0, i32 1
+// CHECK-NEXT:   %21 = load i32, ptr %y12, align 4
+// CHECK-NEXT:   %add13 = add nsw i32 %20, %21
+// CHECK-NEXT:   %22 = getelementptr inbounds nuw %class.anon.0, ptr %this1, i32 0, i32 1
+// CHECK-NEXT:   %23 = load ptr, ptr %22, align 8
+// CHECK-NEXT:   %24 = load i32, ptr %23, align 4
+// CHECK-NEXT:   %add14 = add nsw i32 %24, %add13
+// CHECK-NEXT:   store i32 %add14, ptr %23, align 4
+// CHECK-NEXT:   br label %expand.end15
+// CHECK: expand.end15:
+// CHECK-NEXT:   ret void
diff --git a/clang/test/SemaCXX/cxx2c-expansion-statements.cpp b/clang/test/SemaCXX/cxx2c-expansion-statements.cpp
index c902bac16fe84..c2ea2c9dd71b1 100644
--- a/clang/test/SemaCXX/cxx2c-expansion-statements.cpp
+++ b/clang/test/SemaCXX/cxx2c-expansion-statements.cpp
@@ -810,3 +810,25 @@ void case_default(int i) {
     }
   }
 }
+
+template <typename ...Ts>
+void unexpanded_pack_bad(Ts ...ts) {
+  template for (auto x : ts) {} // expected-error {{expression contains unexpanded parameter pack 'ts'}}
+  template for (Ts x : {1, 2}) {} // expected-error {{declaration type contains unexpanded parameter pack 'Ts'}}
+  template for (auto x : {ts}) {} // expected-error {{initializer contains unexpanded parameter pack}} \
+  // expected-note {{in instantiation of expansion statement requested here}}
+}
+
+struct E { int x, y; constexpr E(int x, int y) : x{x}, y{y} {}};
+
+template <typename ...Es>
+constexpr int unexpanded_pack_good(Es ...es) {
+  int sum = 0;
+  ([&] {
+    template for (auto x : es) sum += x;
+    template for (Es e : {{5, 6}, {7, 8}}) sum += e.x + e.y;
+  }(), ...);
+  return sum;
+}
+
+static_assert(unexpanded_pack_good(E{1, 2}, E{3, 4}) == 62);

>From 9264ff0d06d1ba7ae0fb7b3985de2f47c4f55e50 Mon Sep 17 00:00:00 2001
From: Sirraide <aeternalmail at gmail.com>
Date: Sun, 26 Oct 2025 21:17:44 +0100
Subject: [PATCH 18/33] Add -fexpansion-limit

---
 clang/include/clang/Basic/LangOptions.def     |  1 +
 clang/include/clang/Driver/Options.td         |  4 +++
 clang/lib/Driver/ToolChains/Clang.cpp         |  1 +
 clang/lib/Sema/SemaExpand.cpp                 | 26 ++++++++++++-------
 .../SemaCXX/cxx2c-expansion-statements.cpp    |  5 +++-
 .../SemaCXX/cxx2c-fexpansion-statements.cpp   |  9 +++++++
 6 files changed, 35 insertions(+), 11 deletions(-)
 create mode 100644 clang/test/SemaCXX/cxx2c-fexpansion-statements.cpp

diff --git a/clang/include/clang/Basic/LangOptions.def b/clang/include/clang/Basic/LangOptions.def
index 8d6b8a14740ce..d69a1712e4f0e 100644
--- a/clang/include/clang/Basic/LangOptions.def
+++ b/clang/include/clang/Basic/LangOptions.def
@@ -376,6 +376,7 @@ LANGOPT(ConstexprCallDepth, 32, 512, Benign,
         "maximum constexpr call depth")
 LANGOPT(ConstexprStepLimit, 32, 1048576, Benign,
         "maximum constexpr evaluation steps")
+LANGOPT(MaxTemplateForExpansions, 32, 256, Benign, "maximum template for expansions")
 LANGOPT(EnableNewConstInterp, 1, 0, Benign,
         "enable the experimental new constant interpreter")
 LANGOPT(BracketDepth, 32, 256, Benign,
diff --git a/clang/include/clang/Driver/Options.td b/clang/include/clang/Driver/Options.td
index 0c9584f1b479f..34659cb903632 100644
--- a/clang/include/clang/Driver/Options.td
+++ b/clang/include/clang/Driver/Options.td
@@ -2049,6 +2049,10 @@ def fconstexpr_steps_EQ : Joined<["-"], "fconstexpr-steps=">, Group<f_Group>,
   Visibility<[ClangOption, CC1Option]>,
   HelpText<"Set the maximum number of steps in constexpr function evaluation (0 = no limit)">,
   MarshallingInfoInt<LangOpts<"ConstexprStepLimit">, "1048576">;
+def fexpansion_limit_EQ : Joined<["-"], "fexpansion-limit=">, Group<f_Group>,
+  Visibility<[ClangOption, CC1Option]>,
+  HelpText<"Set the maximum number of times a single expansion statement may be expanded (0 = no limit)">,
+  MarshallingInfoInt<LangOpts<"MaxTemplateForExpansions">, "256">;
 def fexperimental_new_constant_interpreter : Flag<["-"], "fexperimental-new-constant-interpreter">, Group<f_Group>,
   HelpText<"Enable the experimental new constant interpreter">,
   Visibility<[ClangOption, CC1Option]>,
diff --git a/clang/lib/Driver/ToolChains/Clang.cpp b/clang/lib/Driver/ToolChains/Clang.cpp
index caf74786aebea..2381fa2a49f94 100644
--- a/clang/lib/Driver/ToolChains/Clang.cpp
+++ b/clang/lib/Driver/ToolChains/Clang.cpp
@@ -6399,6 +6399,7 @@ void Clang::ConstructJob(Compilation &C, const JobAction &JA,
   Args.AddLastArg(CmdArgs, options::OPT_foperator_arrow_depth_EQ);
   Args.AddLastArg(CmdArgs, options::OPT_fconstexpr_depth_EQ);
   Args.AddLastArg(CmdArgs, options::OPT_fconstexpr_steps_EQ);
+  Args.AddLastArg(CmdArgs, options::OPT_fexpansion_limit_EQ);
 
   Args.AddLastArg(CmdArgs, options::OPT_fexperimental_library);
 
diff --git a/clang/lib/Sema/SemaExpand.cpp b/clang/lib/Sema/SemaExpand.cpp
index 57d8d1a1f5c87..4c522a52dd749 100644
--- a/clang/lib/Sema/SemaExpand.cpp
+++ b/clang/lib/Sema/SemaExpand.cpp
@@ -45,6 +45,18 @@ struct IterableExpansionStmtData {
 };
 } // namespace
 
+static bool CheckExpansionSize(Sema &S, uint64_t NumInstantiations,
+                               SourceLocation Loc) {
+  unsigned Max = S.LangOpts.MaxTemplateForExpansions;
+  if (Max != 0 && NumInstantiations > Max) {
+    S.Diag(Loc, diag::err_expansion_too_big) << NumInstantiations << Max;
+    S.Diag(Loc, diag::note_use_fexpansion_limit);
+    return true;
+  }
+
+  return false;
+}
+
 // Build a 'DeclRefExpr' designating the template parameter '__N'.
 static DeclRefExpr *BuildIndexDRE(Sema &S, ExpansionStmtDecl *ESD) {
   return S.BuildDeclRefExpr(ESD->getIndexTemplateParm(),
@@ -198,6 +210,9 @@ static StmtResult BuildDestructuringExpansionStmtDecl(
     return StmtError();
   }
 
+  if (CheckExpansionSize(S, *Arity, ColonLoc))
+    return StmtError();
+
   QualType AutoRRef = S.Context.getAutoRRefDeductType();
   SmallVector<BindingDecl *> Bindings;
   for (unsigned I = 0; I < *Arity; ++I)
@@ -403,17 +418,8 @@ StmtResult Sema::FinishCXXExpansionStmt(Stmt* Exp, Stmt *Body) {
   if (!NumInstantiations)
     return StmtError();
 
-  // TODO: Actually make this configurable. It is set to 32 for now so our
-  // tests don't take for ever to run; we should pick a larger default value
-  // once we add an option for this and then pass '-fexpansion-limit=32' to
-  // the tests.
-  static constexpr uint64_t MaxExpansionSize = 32;
-  if (MaxExpansionSize != 0 && *NumInstantiations > MaxExpansionSize) {
-    Diag(Expansion->getColonLoc(), diag::err_expansion_too_big)
-        << *NumInstantiations << MaxExpansionSize;
-    Diag(Expansion->getColonLoc(), diag::note_use_fexpansion_limit);
+  if (CheckExpansionSize(*this, *NumInstantiations, Expansion->getColonLoc()))
     return StmtError();
-  }
 
   // Collect shared statements.
   SmallVector<Stmt*, 1> Shared;
diff --git a/clang/test/SemaCXX/cxx2c-expansion-statements.cpp b/clang/test/SemaCXX/cxx2c-expansion-statements.cpp
index c2ea2c9dd71b1..d7f6a37cc66f6 100644
--- a/clang/test/SemaCXX/cxx2c-expansion-statements.cpp
+++ b/clang/test/SemaCXX/cxx2c-expansion-statements.cpp
@@ -1,4 +1,4 @@
-// RUN: %clang_cc1 %s -std=c++2c -fsyntax-only -fdeclspec -fblocks -verify
+// RUN: %clang_cc1 %s -std=c++2c -fsyntax-only -fdeclspec -fblocks -fexpansion-limit=32 -verify
 namespace std {
 template <typename T>
 struct initializer_list {
@@ -192,6 +192,9 @@ void expansion_size() {
     11, 12, 13, 14, 15, 16, 17, 18, 19, 20,
     21, 22, 23, 24, 25, 26, 27, 28, 29, 30,
     31, 32, 33}) g(x);
+
+  int huge[1'000'000'000];
+  template for (auto x : huge) {} // expected-error {{expansion size 1000000000 exceeds maximum configured size 32}} expected-note {{use -fexpansion-limit=N to adjust this limit}}
 }
 
 struct NotInt {
diff --git a/clang/test/SemaCXX/cxx2c-fexpansion-statements.cpp b/clang/test/SemaCXX/cxx2c-fexpansion-statements.cpp
new file mode 100644
index 0000000000000..2c80c392e400d
--- /dev/null
+++ b/clang/test/SemaCXX/cxx2c-fexpansion-statements.cpp
@@ -0,0 +1,9 @@
+// RUN: %clang_cc1 %s -std=c++2c -fsyntax-only -fexpansion-limit=0 -verify
+// expected-no-diagnostics
+
+// Test that passing =0 disables the limit.
+
+void big() {
+  int ok[500];
+  template for (auto x : ok) {}
+}

>From b0468c7564b6b6e991d7bc1e66a1d1cd86975d25 Mon Sep 17 00:00:00 2001
From: Sirraide <aeternalmail at gmail.com>
Date: Sun, 26 Oct 2025 21:57:16 +0100
Subject: [PATCH 19/33] Add release note

---
 clang/docs/ReleaseNotes.rst | 2 ++
 clang/www/cxx_status.html   | 2 +-
 2 files changed, 3 insertions(+), 1 deletion(-)

diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index e6e33e7a9a280..b247493752a7e 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -171,6 +171,8 @@ C++2c Feature Support
   At this timem, references to constexpr and decomposition of *tuple-like* types are not supported
   (only arrays and aggregates are).
 
+- Implemented `P1306R5 <https://wg21.link/P1306R5>`_ Expansion Statements.
+
 C++23 Feature Support
 ^^^^^^^^^^^^^^^^^^^^^
 
diff --git a/clang/www/cxx_status.html b/clang/www/cxx_status.html
index 2618ff930a0e4..0bb2d5440775a 100755
--- a/clang/www/cxx_status.html
+++ b/clang/www/cxx_status.html
@@ -317,7 +317,7 @@ <h2 id="cxx26">C++2c implementation status</h2>
  <tr>
   <td>Expansion Statements</td>
   <td><a href="https://wg21.link/P1306">P1306R5</a></td>
-  <td class="none" align="center">No</td>
+  <td class="unreleased" align="center">Clang 22</td>
   </tr>
   <tr>
    <td>constexpr virtual inheritance</td>

>From a2b72d1bb65f1612c3a409ad03c16d8e4821cdb2 Mon Sep 17 00:00:00 2001
From: Sirraide <aeternalmail at gmail.com>
Date: Sun, 26 Oct 2025 21:57:43 +0100
Subject: [PATCH 20/33] Add StmtPrinter/TextNodeDumper/Serialisation tests

---
 clang/lib/AST/StmtPrinter.cpp                |   8 +-
 clang/lib/Serialization/ASTReaderDecl.cpp    |   2 +-
 clang/test/AST/ast-dump-expansion-stmt.cpp   |  49 +++++++++
 clang/test/AST/ast-print-expansion-stmts.cpp | 104 +++++++++++++++++++
 4 files changed, 158 insertions(+), 5 deletions(-)
 create mode 100644 clang/test/AST/ast-dump-expansion-stmt.cpp
 create mode 100644 clang/test/AST/ast-print-expansion-stmts.cpp

diff --git a/clang/lib/AST/StmtPrinter.cpp b/clang/lib/AST/StmtPrinter.cpp
index d0e44b6fbf14c..e8bc7ca02dce2 100644
--- a/clang/lib/AST/StmtPrinter.cpp
+++ b/clang/lib/AST/StmtPrinter.cpp
@@ -264,7 +264,7 @@ void StmtPrinter::VisitDeclStmt(DeclStmt *Node) {
   PrintRawDeclStmt(Node);
   // Certain pragma declarations shouldn't have a semi-colon after them.
   if (!Node->isSingleDecl() ||
-      !isa<OpenACCDeclareDecl, OpenACCRoutineDecl>(Node->getSingleDecl()))
+      !isa<ExpansionStmtDecl, OpenACCDeclareDecl, OpenACCRoutineDecl>(Node->getSingleDecl()))
     OS << ";";
   OS << NL;
 }
@@ -449,13 +449,13 @@ void StmtPrinter::VisitCXXForRangeStmt(CXXForRangeStmt *Node) {
 }
 
 void StmtPrinter::VisitCXXExpansionStmt(CXXExpansionStmt *Node, Expr* Initializer) {
-  Indent() << "template for (";
+  OS << "template for (";
   if (Node->getInit())
     PrintInitStmt(Node->getInit(), 14);
   PrintingPolicy SubPolicy(Policy);
   SubPolicy.SuppressInitializers = true;
   Node->getExpansionVariable()->print(OS, SubPolicy, IndentLevel);
-  OS << ":";
+  OS << " : ";
   PrintExpr(Initializer ? Initializer
                         : Node->getExpansionVariable()->getInit());
   OS << ")";
@@ -469,7 +469,7 @@ void StmtPrinter::VisitCXXEnumeratingExpansionStmt(
 
 void StmtPrinter::VisitCXXIteratingExpansionStmt(
     CXXIteratingExpansionStmt *Node) {
-  VisitCXXExpansionStmt(Node);
+  VisitCXXExpansionStmt(Node, Node->getRangeVar()->getInit());
 }
 
 void StmtPrinter::VisitCXXDestructuringExpansionStmt(
diff --git a/clang/lib/Serialization/ASTReaderDecl.cpp b/clang/lib/Serialization/ASTReaderDecl.cpp
index d530a09ae36ea..f1d4f4c1ae659 100644
--- a/clang/lib/Serialization/ASTReaderDecl.cpp
+++ b/clang/lib/Serialization/ASTReaderDecl.cpp
@@ -2773,7 +2773,7 @@ void ASTDeclReader::VisitStaticAssertDecl(StaticAssertDecl *D) {
 void ASTDeclReader::VisitExpansionStmtDecl(ExpansionStmtDecl *D) {
   VisitDecl(D);
   D->Expansion = cast<CXXExpansionStmt>(Record.readStmt());
-  D->Instantiations = cast<CXXExpansionInstantiationStmt>(Record.readStmt());
+  D->Instantiations = cast_or_null<CXXExpansionInstantiationStmt>(Record.readStmt());
   D->TParams = Record.readTemplateParameterList();
 }
 
diff --git a/clang/test/AST/ast-dump-expansion-stmt.cpp b/clang/test/AST/ast-dump-expansion-stmt.cpp
new file mode 100644
index 0000000000000..1b947408f617e
--- /dev/null
+++ b/clang/test/AST/ast-dump-expansion-stmt.cpp
@@ -0,0 +1,49 @@
+// Test without serialization:
+// RUN: %clang_cc1 -std=c++26 -triple x86_64-unknown-unknown -ast-dump %s
+//
+// Test with serialization:
+// RUN: %clang_cc1 -std=c++26 -triple x86_64-unknown-unknown -emit-pch -o %t %s
+// RUN: %clang_cc1 -x c++ -std=c++26 -triple x86_64-unknown-unknown -include-pch %t -ast-dump-all /dev/null \
+// RUN: | sed -e "s/ <undeserialized declarations>//" -e "s/ imported//"
+
+template <typename T, __SIZE_TYPE__ size>
+struct Array {
+  T data[size]{};
+  constexpr const T* begin() const { return data; }
+  constexpr const T* end() const { return data + size; }
+};
+
+void foo(int);
+
+template <typename T>
+void test(T t) {
+  // CHECK:      ExpansionStmtDecl
+  // CHECK-NEXT:   CXXEnumeratingExpansionStmt
+  // CHECK:        CXXExpansionInstantiationStmt
+  template for (auto x : {1, 2, 3}) {
+    foo(x);
+  }
+
+  // CHECK:      ExpansionStmtDecl
+  // CHECK-NEXT:   CXXIteratingExpansionStmt
+  // CHECK:        CXXExpansionInstantiationStmt
+  static constexpr Array<int, 3> a;
+  template for (auto x : a) {
+    foo(x);
+  }
+
+  // CHECK:      ExpansionStmtDecl
+  // CHECK-NEXT:   CXXDestructuringExpansionStmt
+  // CHECK:        CXXExpansionInstantiationStmt
+  int arr[3]{1, 2, 3};
+  template for (auto x : arr) {
+    foo(x);
+  }
+
+  // CHECK:      ExpansionStmtDecl
+  // CHECK-NEXT:   CXXDependentExpansionStmt
+  // CHECK-NOT:    CXXExpansionInstantiationStmt
+  template for (auto x : t) {
+    foo(x);
+  }
+}
diff --git a/clang/test/AST/ast-print-expansion-stmts.cpp b/clang/test/AST/ast-print-expansion-stmts.cpp
new file mode 100644
index 0000000000000..bb9f79c6644c0
--- /dev/null
+++ b/clang/test/AST/ast-print-expansion-stmts.cpp
@@ -0,0 +1,104 @@
+// Without serialization:
+// RUN: %clang_cc1 -std=c++26 -ast-print %s | FileCheck %s
+//
+// With serialization:
+// RUN: %clang_cc1 -std=c++26 -emit-pch -o %t %s
+// RUN: %clang_cc1 -x c++ -std=c++26 -include-pch %t -ast-print  /dev/null | FileCheck %s
+
+template <typename T, __SIZE_TYPE__ size>
+struct Array {
+  T data[size]{};
+  constexpr const T* begin() const { return data; }
+  constexpr const T* end() const { return data + size; }
+};
+
+// CHECK: void foo(int);
+void foo(int);
+
+// CHECK: template <typename T> void test(T t) {
+template <typename T>
+void test(T t) {
+  // Enumerating expansion statement.
+  //
+  // CHECK:      template for (auto x : { 1, 2, 3 }) {
+  // CHECK-NEXT:     foo(x);
+  // CHECK-NEXT: }
+  template for (auto x : {1, 2, 3}) {
+    foo(x);
+  }
+
+  // Iterating expansion statement.
+  //
+  // CHECK:      static constexpr Array<int, 3> a;
+  // CHECK-NEXT: template for (auto x : a) {
+  // CHECK-NEXT:   foo(x);
+  // CHECK-NEXT: }
+  static constexpr Array<int, 3> a;
+  template for (auto x : a) {
+    foo(x);
+  }
+
+  // Destructuring expansion statement.
+  //
+  // CHECK:      int arr[3]{1, 2, 3};
+  // CHECK-NEXT: template for (auto x : arr) {
+  // CHECK-NEXT:   foo(x);
+  // CHECK-NEXT: }
+  int arr[3]{1, 2, 3};
+  template for (auto x : arr) {
+    foo(x);
+  }
+
+  // Dependent expansion statement.
+  //
+  // CHECK:      template for (auto x : t) {
+  // CHECK-NEXT:   foo(x);
+  // CHECK-NEXT: }
+  template for (auto x : t) {
+    foo(x);
+  }
+}
+
+// CHECK: template <typename T> void test2(T t) {
+template <typename T>
+void test2(T t) {
+  // Enumerating expansion statement.
+  //
+  // CHECK:      template for (int x : { 1, 2, 3 }) {
+  // CHECK-NEXT:     foo(x);
+  // CHECK-NEXT: }
+  template for (int x : {1, 2, 3}) {
+    foo(x);
+  }
+
+  // Iterating expansion statement.
+  //
+  // CHECK:      static constexpr Array<int, 3> a;
+  // CHECK-NEXT: template for (int x : a) {
+  // CHECK-NEXT:   foo(x);
+  // CHECK-NEXT: }
+  static constexpr Array<int, 3> a;
+  template for (int x : a) {
+    foo(x);
+  }
+
+  // Destructuring expansion statement.
+  //
+  // CHECK:      int arr[3]{1, 2, 3};
+  // CHECK-NEXT: template for (int x : arr) {
+  // CHECK-NEXT:   foo(x);
+  // CHECK-NEXT: }
+  int arr[3]{1, 2, 3};
+  template for (int x : arr) {
+    foo(x);
+  }
+
+  // Dependent expansion statement.
+  //
+  // CHECK:      template for (int x : t) {
+  // CHECK-NEXT:   foo(x);
+  // CHECK-NEXT: }
+  template for (int x : t) {
+    foo(x);
+  }
+}

>From a217f904fbfcbf311ab063003e7135b48ffb9e65 Mon Sep 17 00:00:00 2001
From: Sirraide <aeternalmail at gmail.com>
Date: Sun, 26 Oct 2025 22:24:01 +0100
Subject: [PATCH 21/33] Implement CWG 3061

---
 clang/include/clang/Parse/Parser.h               |  6 ++++--
 clang/lib/Parse/ParseExpr.cpp                    | 13 +++++++++++--
 clang/lib/Parse/ParseInit.cpp                    |  9 ++++++++-
 clang/test/Parser/cxx2c-expansion-statements.cpp |  7 ++++++-
 4 files changed, 29 insertions(+), 6 deletions(-)

diff --git a/clang/include/clang/Parse/Parser.h b/clang/include/clang/Parse/Parser.h
index a006ff24ea7ae..5d5612da661de 100644
--- a/clang/include/clang/Parse/Parser.h
+++ b/clang/include/clang/Parse/Parser.h
@@ -4188,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.
@@ -5257,7 +5258,8 @@ class Parser : public CodeCompletionHandler {
   ///
   /// \verbatim
   ///       expansion-init-list: [C++26 [stmt.expand]]
-  ///          '{' expression-list[opt] '}'
+  ///          '{' expression-list ','[opt] '}'
+  ///          '{' '}'
   /// \endverbatim
   ExprResult ParseExpansionInitList();
 
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 7f010493a477b..89b8fb80565a3 100644
--- a/clang/lib/Parse/ParseInit.cpp
+++ b/clang/lib/Parse/ParseInit.cpp
@@ -521,8 +521,15 @@ ExprResult Parser::ParseExpansionInitList() {
   T.consumeOpen();
 
   ExprVector InitExprs;
-  if (!Tok.is(tok::r_brace) && ParseExpressionList(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(),
diff --git a/clang/test/Parser/cxx2c-expansion-statements.cpp b/clang/test/Parser/cxx2c-expansion-statements.cpp
index ae1808b88c3a7..f2eee4d89d817 100644
--- a/clang/test/Parser/cxx2c-expansion-statements.cpp
+++ b/clang/test/Parser/cxx2c-expansion-statements.cpp
@@ -37,7 +37,7 @@ void bad() {
   template for (int x; static __thread auto y : {1, 2}); // expected-error {{expansion variable 'y' may not be declared 'static'}}
   template for (int x; constinit auto y : {1, 2}); // expected-error {{local variable cannot be declared 'constinit'}}
   template for (int x; consteval auto y : {1, 2});  // expected-error {{consteval can only be used in function declarations}}
-  template for (auto y : {abc, -+, }); // expected-error {{use of undeclared identifier 'abc'}} expected-error 2 {{expected expression}}
+  template for (auto y : {abc, -+, }); // expected-error {{use of undeclared identifier 'abc'}} expected-error {{expected expression}}
   template while (true) {} // expected-error {{expected '<' after 'template'}}
   template for (auto y : {{1}, {2}, {3, {4}}, {{{5}}}});
 }
@@ -55,3 +55,8 @@ void good() {
     }
   }
 }
+
+void trailing_comma() {
+  template for (int x : {1, 2,}) {}
+  template for (int x : {,}) {} // expected-error {{expected expression}}
+}

>From a9f42449e70ece917303700050fe56fa7497822f Mon Sep 17 00:00:00 2001
From: Sirraide <aeternalmail at gmail.com>
Date: Sun, 26 Oct 2025 22:37:10 +0100
Subject: [PATCH 22/33] Add a test for CWG 3048

---
 .../SemaCXX/cxx2c-expansion-statements.cpp    | 20 +++++++++++++++++++
 1 file changed, 20 insertions(+)

diff --git a/clang/test/SemaCXX/cxx2c-expansion-statements.cpp b/clang/test/SemaCXX/cxx2c-expansion-statements.cpp
index d7f6a37cc66f6..d1e319885dbea 100644
--- a/clang/test/SemaCXX/cxx2c-expansion-statements.cpp
+++ b/clang/test/SemaCXX/cxx2c-expansion-statements.cpp
@@ -835,3 +835,23 @@ constexpr int unexpanded_pack_good(Es ...es) {
 }
 
 static_assert(unexpanded_pack_good(E{1, 2}, E{3, 4}) == 62);
+
+// Ensure that the expansion-initializer is evaluated even if it expands
+// to nothing.
+//
+// This is related to CWG 3048. Note that we currently still model this as
+// a DecompositionDecl w/ zero bindings.
+constexpr bool empty_side_effect() {
+  struct A {
+    bool& b;
+    constexpr A(bool& b) : b{b} {
+      b = true;
+    }
+  };
+
+  bool constructed = false;
+  template for (auto x : A(constructed)) {}
+  return constructed;
+}
+
+static_assert(empty_side_effect());

>From bfdb5ed74c861c6215f15102a577965365b2ba52 Mon Sep 17 00:00:00 2001
From: Sirraide <aeternalmail at gmail.com>
Date: Sun, 26 Oct 2025 23:32:45 +0100
Subject: [PATCH 23/33] Add a test for lifetime extension (CWG 3034)

---
 clang/lib/Parse/ParseStmt.cpp                 |  1 -
 .../cxx2c-destructuring-expansion-stmt.cpp    | 55 +++++++++++++++++++
 .../SemaCXX/cxx2c-expansion-statements.cpp    | 27 +++++++++
 3 files changed, 82 insertions(+), 1 deletion(-)

diff --git a/clang/lib/Parse/ParseStmt.cpp b/clang/lib/Parse/ParseStmt.cpp
index ae3acd202bf9b..cf977bf3d6fbd 100644
--- a/clang/lib/Parse/ParseStmt.cpp
+++ b/clang/lib/Parse/ParseStmt.cpp
@@ -1905,7 +1905,6 @@ void Parser::ParseForRangeInitializerAfterColon(ForRangeInit& FRI, ParsingDeclSp
   // constexpr variable in an expansion statement.
   auto Ctx = Sema::ExpressionEvaluationContext::PotentiallyEvaluated;
   if (FRI.ExpansionStmt && VarDeclSpec && VarDeclSpec->hasConstexprSpecifier())
-    // TODO: Shouldn't this be 'ConstantEvaluated'?
     Ctx = Sema::ExpressionEvaluationContext::ImmediateFunctionContext;
 
   EnterExpressionEvaluationContext InitContext(
diff --git a/clang/test/CodeGenCXX/cxx2c-destructuring-expansion-stmt.cpp b/clang/test/CodeGenCXX/cxx2c-destructuring-expansion-stmt.cpp
index 411e1e3824009..107cfe7b2f4d2 100644
--- a/clang/test/CodeGenCXX/cxx2c-destructuring-expansion-stmt.cpp
+++ b/clang/test/CodeGenCXX/cxx2c-destructuring-expansion-stmt.cpp
@@ -35,6 +35,31 @@ void empty() {
   template for (constexpr auto x : a) g(x);
 }
 
+namespace apply_lifetime_extension {
+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); }
+
+// 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).
+int lifetime_extension() {
+  int x = 5;
+  int sum  = 0;
+  template for (auto e : f(g(x))) {
+    sum += x;
+  }
+  return sum + x;
+}
+}
+
 // CHECK: @_ZZ5emptyvE1a = internal constant %struct.A zeroinitializer, align 1
 // CHECK: @_ZTAXtl1BLi10EEE = {{.*}} constant %struct.B { i32 10 }, comdat
 // CHECK: @_ZTAXtl1CLi1ELi2ELi3EEE = {{.*}} constant %struct.C { i32 1, i32 2, i32 3 }, comdat
@@ -282,3 +307,33 @@ void empty() {
 // CHECK-NEXT:   store ptr %ref.tmp1, ptr %2, align 8
 // CHECK-NEXT:   store ptr @_ZZ5emptyvE1a, ptr %3, align 8
 // CHECK-NEXT:   ret void
+
+
+// CHECK-LABEL: define {{.*}} i32 @_ZN24apply_lifetime_extension18lifetime_extensionEv()
+// CHECK: entry:
+// CHECK-NEXT:   %x = alloca i32, align 4
+// CHECK-NEXT:   %sum = alloca i32, align 4
+// CHECK-NEXT:   %0 = alloca ptr, align 8
+// CHECK: %ref.tmp = alloca %"struct.apply_lifetime_extension::T", align 8
+// CHECK-NEXT:   %e = alloca i32, align 4
+// CHECK-NEXT:   store i32 5, ptr %x, align 4
+// CHECK-NEXT:   store i32 0, ptr %sum, align 4
+// CHECK: call void @_ZN24apply_lifetime_extension1gERi(ptr dead_on_unwind writable sret(%"struct.apply_lifetime_extension::T") align 8 %ref.tmp, ptr {{.*}} %x)
+// CHECK-NEXT:   %call = call {{.*}} ptr @_ZN24apply_lifetime_extension1fERKNS_1TE(ptr {{.*}} %ref.tmp)
+// CHECK-NEXT:   store ptr %call, ptr %0, align 8
+// CHECK-NEXT:   %1 = load ptr, ptr %0, align 8
+// CHECK: %x1 = getelementptr inbounds nuw %"struct.apply_lifetime_extension::T", ptr %1, i32 0, i32 0
+// CHECK-NEXT:   %2 = load ptr, ptr %x1, align 8
+// CHECK-NEXT:   %3 = load i32, ptr %2, align 4
+// CHECK-NEXT:   store i32 %3, ptr %e, align 4
+// CHECK-NEXT:   %4 = load i32, ptr %x, align 4
+// CHECK-NEXT:   %5 = load i32, ptr %sum, align 4
+// CHECK-NEXT:   %add = add nsw i32 %5, %4
+// CHECK-NEXT:   store i32 %add, ptr %sum, align 4
+// CHECK-NEXT:   br label %expand.end
+// CHECK: expand.end:
+// CHECK-NEXT:   call void @_ZN24apply_lifetime_extension1TD1Ev(ptr {{.*}} %ref.tmp)
+// CHECK-NEXT:   %6 = load i32, ptr %sum, align 4
+// CHECK-NEXT:   %7 = load i32, ptr %x, align 4
+// CHECK-NEXT:   %add2 = add nsw i32 %6, %7
+// CHECK-NEXT:   ret i32 %add2
diff --git a/clang/test/SemaCXX/cxx2c-expansion-statements.cpp b/clang/test/SemaCXX/cxx2c-expansion-statements.cpp
index d1e319885dbea..2ddb2d1a34e0f 100644
--- a/clang/test/SemaCXX/cxx2c-expansion-statements.cpp
+++ b/clang/test/SemaCXX/cxx2c-expansion-statements.cpp
@@ -855,3 +855,30 @@ constexpr bool empty_side_effect() {
 }
 
 static_assert(empty_side_effect());
+
+namespace apply_lifetime_extension {
+struct T {
+  int& x;
+  constexpr T(int& x) noexcept : x(x) {}
+  constexpr ~T() noexcept { x = 42; }
+};
+
+constexpr const T& f(const T& t) noexcept { return t; }
+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).
+constexpr int lifetime_extension() {
+  int x = 5;
+  int sum  = 0;
+  template for (auto e : f(g(x))) {
+    sum += x;
+  }
+  return sum + x;
+}
+
+static_assert(lifetime_extension() == 47);
+}

>From 6739929d1f633fc5d0be9cdeaee214415fc28189 Mon Sep 17 00:00:00 2001
From: Sirraide <aeternalmail at gmail.com>
Date: Mon, 27 Oct 2025 03:48:37 +0100
Subject: [PATCH 24/33] Fix lifetime extension in templates

---
 clang/include/clang/AST/StmtCXX.h             |  29 ++--
 clang/include/clang/AST/TextNodeDumper.h      |   4 +
 clang/lib/AST/StmtCXX.cpp                     |  29 ++--
 clang/lib/AST/StmtProfile.cpp                 |   1 +
 clang/lib/AST/TextNodeDumper.cpp              |   6 +
 clang/lib/CodeGen/CGStmt.cpp                  |   7 +
 clang/lib/Sema/SemaExpand.cpp                 |  11 +-
 clang/lib/Sema/TreeTransform.h                |  71 ++++++++--
 clang/lib/Serialization/ASTReaderStmt.cpp     |   4 +-
 clang/lib/Serialization/ASTWriterStmt.cpp     |   2 +
 .../cxx2c-destructuring-expansion-stmt.cpp    | 132 ++++++++++++++++++
 .../SemaCXX/cxx2c-expansion-statements.cpp    |  36 +++++
 12 files changed, 292 insertions(+), 40 deletions(-)

diff --git a/clang/include/clang/AST/StmtCXX.h b/clang/include/clang/AST/StmtCXX.h
index 59f7c7a387dc3..7e24677608781 100644
--- a/clang/include/clang/AST/StmtCXX.h
+++ b/clang/include/clang/AST/StmtCXX.h
@@ -800,22 +800,27 @@ class CXXExpansionInstantiationStmt final
   friend class ASTStmtReader;
   friend TrailingObjects;
 
-  SourceLocation Loc;
+  SourceLocation BeginLoc;
+  SourceLocation EndLoc;
 
   // Instantiations are stored first, then shared statements.
   const unsigned NumInstantiations : 20;
   const unsigned NumSharedStmts : 3;
+  unsigned ShouldApplyLifetimeExtensionToSharedStmts : 1;
 
   CXXExpansionInstantiationStmt(EmptyShell Empty, unsigned NumInstantiations,
                                 unsigned NumSharedStmts);
-  CXXExpansionInstantiationStmt(SourceLocation Loc,
-                                ArrayRef<Stmt *> Instantiations,
-                                ArrayRef<Stmt *> SharedStmts);
+  CXXExpansionInstantiationStmt(
+      SourceLocation BeginLoc,
+      SourceLocation EndLoc, ArrayRef<Stmt *> Instantiations,
+      ArrayRef<Stmt *> SharedStmts,
+      bool ShouldApplyLifetimeExtensionToSharedStmts);
 
 public:
   static CXXExpansionInstantiationStmt *
-  Create(ASTContext &C, SourceLocation Loc, ArrayRef<Stmt *> Instantiations,
-         ArrayRef<Stmt *> SharedStmts);
+  Create(ASTContext &C, SourceLocation BeginLoc, SourceLocation EndLoc,
+         ArrayRef<Stmt *> Instantiations, ArrayRef<Stmt *> SharedStmts,
+         bool ShouldApplyLifetimeExtensionToSharedStmts);
 
   static CXXExpansionInstantiationStmt *CreateEmpty(ASTContext &C,
                                                     EmptyShell Empty,
@@ -842,8 +847,16 @@ class CXXExpansionInstantiationStmt final
     return getAllSubStmts().drop_front(NumInstantiations);
   }
 
-  SourceLocation getBeginLoc() const { return Loc; }
-  SourceLocation getEndLoc() const { return Loc; }
+  bool shouldApplyLifetimeExtensionToSharedStmts() const {
+    return ShouldApplyLifetimeExtensionToSharedStmts;
+  }
+
+  void setShouldApplyLifetimeExtensionToSharedStmts(bool Apply) {
+    ShouldApplyLifetimeExtensionToSharedStmts = Apply;
+  }
+
+  SourceLocation getBeginLoc() const { return BeginLoc; }
+  SourceLocation getEndLoc() const { return EndLoc; }
 
   child_range children() {
     Stmt **S = getTrailingObjects();
diff --git a/clang/include/clang/AST/TextNodeDumper.h b/clang/include/clang/AST/TextNodeDumper.h
index a9756d975787d..584ebddd5c0ac 100644
--- a/clang/include/clang/AST/TextNodeDumper.h
+++ b/clang/include/clang/AST/TextNodeDumper.h
@@ -125,6 +125,8 @@ class TextTreeStructure {
       : OS(OS), ShowColors(ShowColors) {}
 };
 
+/// Dumps additional data associated with a single AST node; recursive traversal
+/// of AST nodes is handled via 'children()' or manually in ASTNodeTraverser.h.
 class TextNodeDumper
     : public TextTreeStructure,
       public comments::ConstCommentVisitor<TextNodeDumper, void,
@@ -266,6 +268,8 @@ class TextNodeDumper
   void VisitCoawaitExpr(const CoawaitExpr *Node);
   void VisitCoreturnStmt(const CoreturnStmt *Node);
   void VisitCompoundStmt(const CompoundStmt *Node);
+  void VisitCXXExpansionInstantiationStmt(
+      const CXXExpansionInstantiationStmt *Node);
   void VisitConstantExpr(const ConstantExpr *Node);
   void VisitCallExpr(const CallExpr *Node);
   void VisitCXXOperatorCallExpr(const CXXOperatorCallExpr *Node);
diff --git a/clang/lib/AST/StmtCXX.cpp b/clang/lib/AST/StmtCXX.cpp
index 696accf786d78..08a87bfe9cfd3 100644
--- a/clang/lib/AST/StmtCXX.cpp
+++ b/clang/lib/AST/StmtCXX.cpp
@@ -239,25 +239,30 @@ CXXExpansionInstantiationStmt::CXXExpansionInstantiationStmt(
 }
 
 CXXExpansionInstantiationStmt::CXXExpansionInstantiationStmt(
-    SourceLocation Loc, ArrayRef<Stmt *> Instantiations,
-    ArrayRef<Stmt *> SharedStmts)
-    : Stmt(CXXExpansionInstantiationStmtClass), Loc(Loc),
-      NumInstantiations(unsigned(Instantiations.size())),
-      NumSharedStmts(unsigned(SharedStmts.size())) {
+    SourceLocation BeginLoc, SourceLocation EndLoc,
+    ArrayRef<Stmt *> Instantiations, ArrayRef<Stmt *> SharedStmts,
+    bool ShouldApplyLifetimeExtensionToSharedStmts)
+    : Stmt(CXXExpansionInstantiationStmtClass), BeginLoc(BeginLoc),
+      EndLoc(EndLoc), NumInstantiations(unsigned(Instantiations.size())),
+      NumSharedStmts(unsigned(SharedStmts.size())),
+      ShouldApplyLifetimeExtensionToSharedStmts(
+          ShouldApplyLifetimeExtensionToSharedStmts) {
   assert(NumSharedStmts <= 4 && "might have to allocate more bits for this");
   llvm::uninitialized_copy(Instantiations, getTrailingObjects());
-  llvm::uninitialized_copy(SharedStmts, getTrailingObjects() + NumInstantiations);
+  llvm::uninitialized_copy(SharedStmts,
+                           getTrailingObjects() + NumInstantiations);
 }
 
-CXXExpansionInstantiationStmt *
-CXXExpansionInstantiationStmt::Create(ASTContext &C, SourceLocation Loc,
-                                      ArrayRef<Stmt *> Instantiations,
-                                      ArrayRef<Stmt *> SharedStmts) {
+CXXExpansionInstantiationStmt *CXXExpansionInstantiationStmt::Create(
+    ASTContext &C, SourceLocation BeginLoc, SourceLocation EndLoc,
+    ArrayRef<Stmt *> Instantiations, ArrayRef<Stmt *> SharedStmts,
+    bool ShouldApplyLifetimeExtensionToSharedStmts) {
   void *Mem = C.Allocate(
       totalSizeToAlloc<Stmt *>(Instantiations.size() + SharedStmts.size()),
       alignof(CXXExpansionInstantiationStmt));
-  return new (Mem)
-      CXXExpansionInstantiationStmt(Loc, Instantiations, SharedStmts);
+  return new (Mem) CXXExpansionInstantiationStmt(
+      BeginLoc, EndLoc, Instantiations, SharedStmts,
+      ShouldApplyLifetimeExtensionToSharedStmts);
 }
 
 CXXExpansionInstantiationStmt *
diff --git a/clang/lib/AST/StmtProfile.cpp b/clang/lib/AST/StmtProfile.cpp
index a2470f41a8ec7..dc4553882c843 100644
--- a/clang/lib/AST/StmtProfile.cpp
+++ b/clang/lib/AST/StmtProfile.cpp
@@ -391,6 +391,7 @@ void StmtProfiler::VisitCXXDependentExpansionStmt(
 void StmtProfiler::VisitCXXExpansionInstantiationStmt(
     const CXXExpansionInstantiationStmt *S) {
   VisitStmt(S);
+  ID.AddBoolean(S->shouldApplyLifetimeExtensionToSharedStmts());
 }
 
 void StmtProfiler::VisitMSDependentExistsStmt(const MSDependentExistsStmt *S) {
diff --git a/clang/lib/AST/TextNodeDumper.cpp b/clang/lib/AST/TextNodeDumper.cpp
index ea31f13af99da..43037ff688679 100644
--- a/clang/lib/AST/TextNodeDumper.cpp
+++ b/clang/lib/AST/TextNodeDumper.cpp
@@ -1499,6 +1499,12 @@ void clang::TextNodeDumper::VisitCoreturnStmt(const CoreturnStmt *Node) {
     OS << " implicit";
 }
 
+void TextNodeDumper::VisitCXXExpansionInstantiationStmt(
+    const CXXExpansionInstantiationStmt *Node) {
+  if (Node->shouldApplyLifetimeExtensionToSharedStmts())
+    OS << " applies_lifetime_extension";
+}
+
 void TextNodeDumper::VisitConstantExpr(const ConstantExpr *Node) {
   if (Node->hasAPValueResult())
     AddChild("value",
diff --git a/clang/lib/CodeGen/CGStmt.cpp b/clang/lib/CodeGen/CGStmt.cpp
index d03f216152dff..a89d65960b364 100644
--- a/clang/lib/CodeGen/CGStmt.cpp
+++ b/clang/lib/CodeGen/CGStmt.cpp
@@ -1569,7 +1569,14 @@ CodeGenFunction::EmitCXXForRangeStmt(const CXXForRangeStmt &S,
 
 void CodeGenFunction::EmitCXXExpansionInstantiationStmt(
     const CXXExpansionInstantiationStmt &S) {
+  // FIXME: For reasons beyond my understanding, two scopes are required to emit
+  // the destructors of lifetime-extended temporaries in the right place, but
+  // only in some templates. There are some other issues with lifetime-extended
+  // temporaries currently (https://github.com/llvm/llvm-project/issues/165182);
+  // perhaps resolving those will allow us to remove the second scope here
+  // because there really ought to be a better way of doing this.
   LexicalScope Scope(*this, S.getSourceRange());
+  LexicalScope Scope2(*this, S.getSourceRange());
 
   for (const Stmt* DS : S.getSharedStmts())
     EmitStmt(DS);
diff --git a/clang/lib/Sema/SemaExpand.cpp b/clang/lib/Sema/SemaExpand.cpp
index 4c522a52dd749..f2a642d70b729 100644
--- a/clang/lib/Sema/SemaExpand.cpp
+++ b/clang/lib/Sema/SemaExpand.cpp
@@ -438,8 +438,10 @@ StmtResult Sema::FinishCXXExpansionStmt(Stmt* Exp, Stmt *Body) {
   // Return an empty statement if the range is empty.
   if (*NumInstantiations == 0) {
     Expansion->getDecl()->setInstantiations(
-        CXXExpansionInstantiationStmt::Create(Context, Expansion->getBeginLoc(),
-                                              /*Instantiations=*/{}, Shared));
+        CXXExpansionInstantiationStmt::Create(
+            Context, Expansion->getBeginLoc(), Expansion->getEndLoc(),
+            /*Instantiations=*/{}, Shared,
+            isa<CXXDestructuringExpansionStmt>(Expansion)));
     return Expansion;
   }
 
@@ -447,7 +449,7 @@ StmtResult Sema::FinishCXXExpansionStmt(Stmt* Exp, Stmt *Body) {
   Stmt *VarAndBody[] = {Expansion->getExpansionVarStmt(), Body};
   Stmt *CombinedBody =
       CompoundStmt::Create(Context, VarAndBody, FPOptionsOverride(),
-                           Expansion->getBeginLoc(), Expansion->getEndLoc());
+                           Body->getBeginLoc(), Body->getEndLoc());
 
   // Expand the body for each instantiation.
   SmallVector<Stmt *, 4> Instantiations;
@@ -477,7 +479,8 @@ StmtResult Sema::FinishCXXExpansionStmt(Stmt* Exp, Stmt *Body) {
   }
 
   auto *InstantiationsStmt = CXXExpansionInstantiationStmt::Create(
-      Context, Expansion->getBeginLoc(), Instantiations, Shared);
+      Context, Expansion->getBeginLoc(), Expansion->getEndLoc(), Instantiations,
+      Shared, isa<CXXDestructuringExpansionStmt>(Expansion));
 
   Expansion->getDecl()->setInstantiations(InstantiationsStmt);
   return Expansion;
diff --git a/clang/lib/Sema/TreeTransform.h b/clang/lib/Sema/TreeTransform.h
index c958672d23798..a78e992799d2a 100644
--- a/clang/lib/Sema/TreeTransform.h
+++ b/clang/lib/Sema/TreeTransform.h
@@ -9362,20 +9362,36 @@ StmtResult TreeTransform<Derived>::TransformCXXIteratingExpansionStmt(
 template <typename Derived>
 StmtResult TreeTransform<Derived>::TransformCXXDependentExpansionStmt(
     CXXDependentExpansionStmt *S) {
-  TransformCXXExpansionStmtResult Common =
-      TransformCXXExpansionStmtCommonParts(S);
-  if (!Common.isValid())
-    return StmtError();
+  TransformCXXExpansionStmtResult Common;
+  ExprResult ExpansionInitializer;
+  SmallVector<MaterializeTemporaryExpr*, 8> LifetimeExtendTemps;
 
-  ExprResult ExpansionInitializer =
-      getDerived().TransformExpr(S->getExpansionInitializer());
-  if (ExpansionInitializer.isInvalid())
-    return StmtError();
+  // Apply lifetime extension in case this ends up begin a destructuring
+  // expansion statement.
+  {
+    EnterExpressionEvaluationContext ExprEvalCtx(
+       SemaRef, SemaRef.currentEvaluationContext().Context);
+    SemaRef.currentEvaluationContext().InLifetimeExtendingContext = true;
+    SemaRef.currentEvaluationContext().RebuildDefaultArgOrDefaultInit = true;
+
+    Common =
+        TransformCXXExpansionStmtCommonParts(S);
+    if (!Common.isValid())
+      return StmtError();
+
+    ExpansionInitializer =
+        getDerived().TransformExpr(S->getExpansionInitializer());
+    if (ExpansionInitializer.isInvalid())
+      return StmtError();
+
+    LifetimeExtendTemps =
+        SemaRef.currentEvaluationContext().ForRangeLifetimeExtendTemps;
+  }
 
   StmtResult Expansion = SemaRef.BuildNonEnumeratingCXXExpansionStmt(
       Common.NewESD, Common.NewInit, Common.NewExpansionVarDecl,
       ExpansionInitializer.get(), S->getForLoc(), S->getLParenLoc(),
-      S->getColonLoc(), S->getRParenLoc());
+      S->getColonLoc(), S->getRParenLoc(), LifetimeExtendTemps);
   if (Expansion.isInvalid())
     return StmtError();
 
@@ -9388,8 +9404,13 @@ StmtResult TreeTransform<Derived>::TransformCXXDependentExpansionStmt(
 
 template <typename Derived>
 StmtResult TreeTransform<Derived>::TransformCXXDestructuringExpansionStmt(
-    CXXDestructuringExpansionStmt *S) {
-  TransformCXXExpansionStmtResult Common =
+    CXXDestructuringExpansionStmt *) {
+  // The only time we instantiate an expansion statement is if its expansion
+  // size is dependent (otherwise, we only instantiate the expansions and
+  // leave the underlying CXXExpansionStmt as-is). Since destructuring expansion
+  // statements never have a dependent size, we should never get here.
+  llvm_unreachable("Should never be instantiated");
+  /*TransformCXXExpansionStmtResult Common =
       TransformCXXExpansionStmtCommonParts(S);
   if (!Common.isValid())
     return StmtError();
@@ -9408,7 +9429,7 @@ StmtResult TreeTransform<Derived>::TransformCXXDestructuringExpansionStmt(
   if (Body.isInvalid())
     return StmtError();
 
-  return SemaRef.FinishCXXExpansionStmt(Expansion, Body.get());
+  return SemaRef.FinishCXXExpansionStmt(Expansion, Body.get());*/
 }
 
 template <typename Derived>
@@ -9448,15 +9469,35 @@ StmtResult TreeTransform<Derived>::TransformCXXExpansionInstantiationStmt(
 
   SmallVector<Stmt*> SharedStmts;
   SmallVector<Stmt*> Instantiations;
-  if (TransformStmts(SharedStmts, S->getSharedStmts()) ||
-      TransformStmts(Instantiations, S->getInstantiations()))
+
+  // Apply lifetime extension to the shared statements in case this is a
+  // destructuring expansion statement (for other kinds of expansion
+  // statements, this should make no difference).
+  {
+    EnterExpressionEvaluationContext ExprEvalCtx(
+        SemaRef, SemaRef.currentEvaluationContext().Context);
+    SemaRef.currentEvaluationContext().InLifetimeExtendingContext = true;
+    SemaRef.currentEvaluationContext().RebuildDefaultArgOrDefaultInit = true;
+    if (TransformStmts(SharedStmts, S->getSharedStmts()))
+      return StmtError();
+
+    if (S->shouldApplyLifetimeExtensionToSharedStmts()) {
+      auto *VD =
+          cast<VarDecl>(cast<DeclStmt>(SharedStmts.front())->getSingleDecl());
+      SemaRef.ApplyForRangeOrExpansionStatementLifetimeExtension(
+          VD, SemaRef.currentEvaluationContext().ForRangeLifetimeExtendTemps);
+    }
+  }
+
+  if (TransformStmts(Instantiations, S->getInstantiations()))
     return StmtError();
 
   if (!getDerived().AlwaysRebuild() && !SubStmtChanged)
     return S;
 
   return CXXExpansionInstantiationStmt::Create(
-      SemaRef.Context, S->getBeginLoc(), Instantiations, SharedStmts);
+      SemaRef.Context, S->getBeginLoc(), S->getEndLoc(), Instantiations, SharedStmts,
+      S->shouldApplyLifetimeExtensionToSharedStmts());
 }
 
 template <typename Derived>
diff --git a/clang/lib/Serialization/ASTReaderStmt.cpp b/clang/lib/Serialization/ASTReaderStmt.cpp
index 3c6c6bc37d616..715e6e5513e01 100644
--- a/clang/lib/Serialization/ASTReaderStmt.cpp
+++ b/clang/lib/Serialization/ASTReaderStmt.cpp
@@ -1746,9 +1746,11 @@ void ASTStmtReader::VisitCXXExpansionInstantiationStmt(
     CXXExpansionInstantiationStmt *S) {
   VisitStmt(S);
   Record.skipInts(2);
-  S->Loc = readSourceLocation();
+  S->BeginLoc = readSourceLocation();
+  S->EndLoc = readSourceLocation();
   for (unsigned I = 0; I < S->getNumSubStmts(); ++I)
     S->getAllSubStmts()[I] = Record.readSubStmt();
+  S->setShouldApplyLifetimeExtensionToSharedStmts(Record.readBool());
 }
 
 void ASTStmtReader::VisitCXXEnumeratingExpansionStmt(
diff --git a/clang/lib/Serialization/ASTWriterStmt.cpp b/clang/lib/Serialization/ASTWriterStmt.cpp
index 17d16b0a6326f..a2d4daaeedcd8 100644
--- a/clang/lib/Serialization/ASTWriterStmt.cpp
+++ b/clang/lib/Serialization/ASTWriterStmt.cpp
@@ -1722,8 +1722,10 @@ void ASTStmtWriter::VisitCXXExpansionInstantiationStmt(
   Record.push_back(S->getInstantiations().size());
   Record.push_back(S->getSharedStmts().size());
   Record.AddSourceLocation(S->getBeginLoc());
+  Record.AddSourceLocation(S->getEndLoc());
   for (Stmt *St : S->getAllSubStmts())
     Record.AddStmt(St);
+  Record.push_back(S->shouldApplyLifetimeExtensionToSharedStmts());
   Code = serialization::STMT_CXX_EXPANSION_INSTANTIATION;
 }
 
diff --git a/clang/test/CodeGenCXX/cxx2c-destructuring-expansion-stmt.cpp b/clang/test/CodeGenCXX/cxx2c-destructuring-expansion-stmt.cpp
index 107cfe7b2f4d2..16d8c370a9d3f 100644
--- a/clang/test/CodeGenCXX/cxx2c-destructuring-expansion-stmt.cpp
+++ b/clang/test/CodeGenCXX/cxx2c-destructuring-expansion-stmt.cpp
@@ -58,6 +58,45 @@ int lifetime_extension() {
   }
   return sum + x;
 }
+
+template <typename T>
+int lifetime_extension_instantiate_expansions() {
+  int x = 5;
+  int sum  = 0;
+  template for (T e : f(g(x))) {
+    sum += x;
+  }
+  return sum + x;
+}
+
+template <typename T>
+int lifetime_extension_dependent_expansion_stmt() {
+  int x = 5;
+  int sum  = 0;
+  template for (int e : f(g((T&)x))) {
+    sum += x;
+  }
+  return sum + x;
+}
+
+template <typename U>
+struct foo {
+  template <typename T>
+  int lifetime_extension_multiple_instantiations() {
+    int x = 5;
+    int sum  = 0;
+    template for (T e : f(g((U&)x))) {
+      sum += x;
+    }
+    return sum + x;
+  }
+};
+
+void instantiate() {
+  lifetime_extension_instantiate_expansions<int>();
+  lifetime_extension_dependent_expansion_stmt<int>();
+  foo<int>().lifetime_extension_multiple_instantiations<int>();
+}
 }
 
 // CHECK: @_ZZ5emptyvE1a = internal constant %struct.A zeroinitializer, align 1
@@ -337,3 +376,96 @@ int lifetime_extension() {
 // CHECK-NEXT:   %7 = load i32, ptr %x, align 4
 // CHECK-NEXT:   %add2 = add nsw i32 %6, %7
 // CHECK-NEXT:   ret i32 %add2
+
+
+// CHECK-LABEL: define {{.*}} i32 @_ZN24apply_lifetime_extension41lifetime_extension_instantiate_expansionsIiEEiv()
+// CHECK: entry:
+// CHECK-NEXT:   %x = alloca i32, align 4
+// CHECK-NEXT:   %sum = alloca i32, align 4
+// CHECK-NEXT:   %0 = alloca ptr, align 8
+// CHECK: %ref.tmp = alloca %"struct.apply_lifetime_extension::T", align 8
+// CHECK-NEXT:   %e = alloca i32, align 4
+// CHECK-NEXT:   store i32 5, ptr %x, align 4
+// CHECK-NEXT:   store i32 0, ptr %sum, align 4
+// CHECK: call void @_ZN24apply_lifetime_extension1gERi(ptr dead_on_unwind writable sret(%"struct.apply_lifetime_extension::T") align 8 %ref.tmp, ptr {{.*}} %x)
+// CHECK-NEXT:   %call = call {{.*}} ptr @_ZN24apply_lifetime_extension1fERKNS_1TE(ptr {{.*}} %ref.tmp)
+// CHECK-NEXT:   store ptr %call, ptr %0, align 8
+// CHECK-NEXT:   %1 = load ptr, ptr %0, align 8
+// CHECK: %x1 = getelementptr inbounds nuw %"struct.apply_lifetime_extension::T", ptr %1, i32 0, i32 0
+// CHECK-NEXT:   %2 = load ptr, ptr %x1, align 8
+// CHECK-NEXT:   %3 = load i32, ptr %2, align 4
+// CHECK-NEXT:   store i32 %3, ptr %e, align 4
+// CHECK-NEXT:   %4 = load i32, ptr %x, align 4
+// CHECK-NEXT:   %5 = load i32, ptr %sum, align 4
+// CHECK-NEXT:   %add = add nsw i32 %5, %4
+// CHECK-NEXT:   store i32 %add, ptr %sum, align 4
+// CHECK-NEXT:   br label %expand.end
+// CHECK: expand.end:
+// CHECK-NEXT:   call void @_ZN24apply_lifetime_extension1TD1Ev(ptr {{.*}} %ref.tmp)
+// CHECK-NEXT:   %6 = load i32, ptr %sum, align 4
+// CHECK-NEXT:   %7 = load i32, ptr %x, align 4
+// CHECK-NEXT:   %add2 = add nsw i32 %6, %7
+// CHECK-NEXT:   ret i32 %add2
+
+
+// CHECK-LABEL: define {{.*}} i32 @_ZN24apply_lifetime_extension43lifetime_extension_dependent_expansion_stmtIiEEiv()
+// CHECK: entry:
+// CHECK-NEXT:   %x = alloca i32, align 4
+// CHECK-NEXT:   %sum = alloca i32, align 4
+// CHECK-NEXT:   %0 = alloca ptr, align 8
+// CHECK: %ref.tmp = alloca %"struct.apply_lifetime_extension::T", align 8
+// CHECK-NEXT:   %e = alloca i32, align 4
+// CHECK-NEXT:   store i32 5, ptr %x, align 4
+// CHECK-NEXT:   store i32 0, ptr %sum, align 4
+// CHECK: call void @_ZN24apply_lifetime_extension1gERi(ptr dead_on_unwind writable sret(%"struct.apply_lifetime_extension::T") align 8 %ref.tmp, ptr {{.*}} %x)
+// CHECK-NEXT:   %call = call {{.*}} ptr @_ZN24apply_lifetime_extension1fERKNS_1TE(ptr {{.*}} %ref.tmp)
+// CHECK-NEXT:   store ptr %call, ptr %0, align 8
+// CHECK-NEXT:   %1 = load ptr, ptr %0, align 8
+// CHECK: %x1 = getelementptr inbounds nuw %"struct.apply_lifetime_extension::T", ptr %1, i32 0, i32 0
+// CHECK-NEXT:   %2 = load ptr, ptr %x1, align 8
+// CHECK-NEXT:   %3 = load i32, ptr %2, align 4
+// CHECK-NEXT:   store i32 %3, ptr %e, align 4
+// CHECK-NEXT:   %4 = load i32, ptr %x, align 4
+// CHECK-NEXT:   %5 = load i32, ptr %sum, align 4
+// CHECK-NEXT:   %add = add nsw i32 %5, %4
+// CHECK-NEXT:   store i32 %add, ptr %sum, align 4
+// CHECK-NEXT:   br label %expand.end
+// CHECK: expand.end:
+// CHECK-NEXT:   call void @_ZN24apply_lifetime_extension1TD1Ev(ptr {{.*}} %ref.tmp)
+// CHECK-NEXT:   %6 = load i32, ptr %sum, align 4
+// CHECK-NEXT:   %7 = load i32, ptr %x, align 4
+// CHECK-NEXT:   %add2 = add nsw i32 %6, %7
+// CHECK-NEXT:   ret i32 %add2
+
+
+// CHECK-LABEL: define {{.*}} i32 @_ZN24apply_lifetime_extension3fooIiE42lifetime_extension_multiple_instantiationsIiEEiv(ptr {{.*}} %this)
+// CHECK: entry:
+// CHECK-NEXT:   %this.addr = alloca ptr, align 8
+// CHECK-NEXT:   %x = alloca i32, align 4
+// CHECK-NEXT:   %sum = alloca i32, align 4
+// CHECK-NEXT:   %0 = alloca ptr, align 8
+// CHECK: %ref.tmp = alloca %"struct.apply_lifetime_extension::T", align 8
+// CHECK-NEXT:   %e = alloca i32, align 4
+// CHECK-NEXT:   store ptr %this, ptr %this.addr, align 8
+// CHECK-NEXT:   %this1 = load ptr, ptr %this.addr, align 8
+// CHECK-NEXT:   store i32 5, ptr %x, align 4
+// CHECK-NEXT:   store i32 0, ptr %sum, align 4
+// CHECK: call void @_ZN24apply_lifetime_extension1gERi(ptr dead_on_unwind writable sret(%"struct.apply_lifetime_extension::T") align 8 %ref.tmp, ptr {{.*}} %x)
+// CHECK-NEXT:   %call = call {{.*}} ptr @_ZN24apply_lifetime_extension1fERKNS_1TE(ptr {{.*}} %ref.tmp)
+// CHECK-NEXT:   store ptr %call, ptr %0, align 8
+// CHECK-NEXT:   %1 = load ptr, ptr %0, align 8
+// CHECK: %x2 = getelementptr inbounds nuw %"struct.apply_lifetime_extension::T", ptr %1, i32 0, i32 0
+// CHECK-NEXT:   %2 = load ptr, ptr %x2, align 8
+// CHECK-NEXT:   %3 = load i32, ptr %2, align 4
+// CHECK-NEXT:   store i32 %3, ptr %e, align 4
+// CHECK-NEXT:   %4 = load i32, ptr %x, align 4
+// CHECK-NEXT:   %5 = load i32, ptr %sum, align 4
+// CHECK-NEXT:   %add = add nsw i32 %5, %4
+// CHECK-NEXT:   store i32 %add, ptr %sum, align 4
+// CHECK-NEXT:   br label %expand.end
+// CHECK: expand.end:
+// CHECK-NEXT:   call void @_ZN24apply_lifetime_extension1TD1Ev(ptr {{.*}} %ref.tmp)
+// CHECK-NEXT:   %6 = load i32, ptr %sum, align 4
+// CHECK-NEXT:   %7 = load i32, ptr %x, align 4
+// CHECK-NEXT:   %add3 = add nsw i32 %6, %7
+// CHECK-NEXT:   ret i32 %add3
diff --git a/clang/test/SemaCXX/cxx2c-expansion-statements.cpp b/clang/test/SemaCXX/cxx2c-expansion-statements.cpp
index 2ddb2d1a34e0f..592b22c92de86 100644
--- a/clang/test/SemaCXX/cxx2c-expansion-statements.cpp
+++ b/clang/test/SemaCXX/cxx2c-expansion-statements.cpp
@@ -880,5 +880,41 @@ constexpr int lifetime_extension() {
   return sum + x;
 }
 
+template <typename T>
+constexpr int lifetime_extension_instantiate_expansions() {
+  int x = 5;
+  int sum  = 0;
+  template for (T e : f(g(x))) {
+    sum += x;
+  }
+  return sum + x;
+}
+
+template <typename T>
+constexpr int lifetime_extension_dependent_expansion_stmt() {
+  int x = 5;
+  int sum  = 0;
+  template for (int e : f(g((T&)x))) {
+    sum += x;
+  }
+  return sum + x;
+}
+
+template <typename U>
+struct foo {
+  template <typename T>
+  constexpr int lifetime_extension_multiple_instantiations() {
+      int x = 5;
+      int sum  = 0;
+      template for (T e : f(g((U&)x))) {
+        sum += x;
+      }
+      return sum + x;
+  }
+};
+
 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);
 }

>From ea2a347c1d2b5627b876e97346ef630a319414df Mon Sep 17 00:00:00 2001
From: Sirraide <aeternalmail at gmail.com>
Date: Mon, 27 Oct 2025 03:50:01 +0100
Subject: [PATCH 25/33] Remove commented-out code

---
 clang/lib/Sema/TreeTransform.h | 20 --------------------
 1 file changed, 20 deletions(-)

diff --git a/clang/lib/Sema/TreeTransform.h b/clang/lib/Sema/TreeTransform.h
index a78e992799d2a..4d5c05d66306b 100644
--- a/clang/lib/Sema/TreeTransform.h
+++ b/clang/lib/Sema/TreeTransform.h
@@ -9410,26 +9410,6 @@ StmtResult TreeTransform<Derived>::TransformCXXDestructuringExpansionStmt(
   // leave the underlying CXXExpansionStmt as-is). Since destructuring expansion
   // statements never have a dependent size, we should never get here.
   llvm_unreachable("Should never be instantiated");
-  /*TransformCXXExpansionStmtResult Common =
-      TransformCXXExpansionStmtCommonParts(S);
-  if (!Common.isValid())
-    return StmtError();
-
-  StmtResult DecompositionDeclStmt =
-      getDerived().TransformStmt(S->getDecompositionDeclStmt());
-  if (DecompositionDeclStmt.isInvalid())
-    return StmtError();
-
-  auto *Expansion = new (SemaRef.Context) CXXDestructuringExpansionStmt(
-      Common.NewESD, Common.NewInit, Common.NewExpansionVarDecl,
-      DecompositionDeclStmt.get(), S->getForLoc(), S->getLParenLoc(),
-      S->getColonLoc(), S->getRParenLoc());
-
-  StmtResult Body = getDerived().TransformStmt(S->getBody());
-  if (Body.isInvalid())
-    return StmtError();
-
-  return SemaRef.FinishCXXExpansionStmt(Expansion, Body.get());*/
 }
 
 template <typename Derived>

>From d10d82aabc2cfb3e85f13459ce388e37d09b19b4 Mon Sep 17 00:00:00 2001
From: Sirraide <aeternalmail at gmail.com>
Date: Mon, 27 Oct 2025 04:20:15 +0100
Subject: [PATCH 26/33] ASTImporter

---
 clang/lib/AST/ASTImporter.cpp | 165 ++++++++++++++++++++++++++++++++++
 1 file changed, 165 insertions(+)

diff --git a/clang/lib/AST/ASTImporter.cpp b/clang/lib/AST/ASTImporter.cpp
index bf51c3e42719c..e208be7eb90f4 100644
--- a/clang/lib/AST/ASTImporter.cpp
+++ b/clang/lib/AST/ASTImporter.cpp
@@ -516,6 +516,7 @@ namespace clang {
     ExpectedDecl VisitEmptyDecl(EmptyDecl *D);
     ExpectedDecl VisitAccessSpecDecl(AccessSpecDecl *D);
     ExpectedDecl VisitStaticAssertDecl(StaticAssertDecl *D);
+    ExpectedDecl VisitExpansionStmtDecl(ExpansionStmtDecl* D);
     ExpectedDecl VisitTranslationUnitDecl(TranslationUnitDecl *D);
     ExpectedDecl VisitBindingDecl(BindingDecl *D);
     ExpectedDecl VisitNamespaceDecl(NamespaceDecl *D);
@@ -608,6 +609,11 @@ namespace clang {
     ExpectedStmt VisitCXXCatchStmt(CXXCatchStmt *S);
     ExpectedStmt VisitCXXTryStmt(CXXTryStmt *S);
     ExpectedStmt VisitCXXForRangeStmt(CXXForRangeStmt *S);
+    ExpectedStmt VisitCXXEnumeratingExpansionStmt(CXXEnumeratingExpansionStmt *S);
+    ExpectedStmt VisitCXXIteratingExpansionStmt(CXXIteratingExpansionStmt *S);
+    ExpectedStmt VisitCXXDestructuringExpansionStmt(CXXDestructuringExpansionStmt *S);
+    ExpectedStmt VisitCXXDependentExpansionStmt(CXXDependentExpansionStmt *S);
+    ExpectedStmt VisitCXXExpansionInstantiationStmt(CXXExpansionInstantiationStmt *S);
     // FIXME: MSDependentExistsStmt
     ExpectedStmt VisitObjCForCollectionStmt(ObjCForCollectionStmt *S);
     ExpectedStmt VisitObjCAtCatchStmt(ObjCAtCatchStmt *S);
@@ -696,6 +702,10 @@ namespace clang {
     ExpectedStmt VisitCXXFoldExpr(CXXFoldExpr *E);
     ExpectedStmt VisitRequiresExpr(RequiresExpr* E);
     ExpectedStmt VisitConceptSpecializationExpr(ConceptSpecializationExpr* E);
+    ExpectedStmt VisitCXXExpansionInitListExpr(CXXExpansionInitListExpr* E);
+    ExpectedStmt VisitCXXExpansionInitListSelectExpr(CXXExpansionInitListSelectExpr* E);
+    ExpectedStmt VisitCXXDestructuringExpansionSelectExpr(CXXDestructuringExpansionSelectExpr* E);
+
 
     // Helper for chaining together multiple imports. If an error is detected,
     // subsequent imports will return default constructed nodes, so that failure
@@ -2828,6 +2838,33 @@ ExpectedDecl ASTNodeImporter::VisitStaticAssertDecl(StaticAssertDecl *D) {
   return ToD;
 }
 
+ExpectedDecl ASTNodeImporter::VisitExpansionStmtDecl(ExpansionStmtDecl *D) {
+  auto DCOrErr = Importer.ImportContext(D->getDeclContext());
+  if (!DCOrErr)
+    return DCOrErr.takeError();
+  DeclContext *DC = *DCOrErr;
+  DeclContext *LexicalDC = DC;
+
+  Error Err = Error::success();
+  auto ToLocation = importChecked(Err, D->getLocation());
+  auto ToExpansion = importChecked(Err, D->getExpansionPattern());
+  auto ToTemplateParams = importChecked(Err, D->getTemplateParameters());
+  auto ToInstantiations = importChecked(Err, D->getInstantiations());
+  if (Err)
+    return std::move(Err);
+
+  ExpansionStmtDecl* ToD;
+  if (GetImportedOrCreateDecl(
+      ToD, D, Importer.getToContext(), DC, ToLocation, ToTemplateParams))
+    return ToD;
+
+  ToD->setExpansionPattern(ToExpansion);
+  ToD->setInstantiations(ToInstantiations);
+  ToD->setLexicalDeclContext(LexicalDC);
+  LexicalDC->addDeclInternal(ToD);
+  return ToD;
+}
+
 ExpectedDecl ASTNodeImporter::VisitNamespaceDecl(NamespaceDecl *D) {
   // Import the major distinguishing characteristics of this namespace.
   DeclContext *DC, *LexicalDC;
@@ -7421,6 +7458,94 @@ ExpectedStmt ASTNodeImporter::VisitCXXForRangeStmt(CXXForRangeStmt *S) {
       ToBody, ToForLoc, ToCoawaitLoc, ToColonLoc, ToRParenLoc);
 }
 
+ExpectedStmt ASTNodeImporter::VisitCXXEnumeratingExpansionStmt(CXXEnumeratingExpansionStmt *S) {
+  Error Err = Error::success();
+  auto ToESD = importChecked(Err, S->getDecl());
+  auto ToInit = importChecked(Err, S->getInit());
+  auto ToExpansionVar = importChecked(Err, S->getExpansionVarStmt());
+  auto ToForLoc = importChecked(Err, S->getForLoc());
+  auto ToLParenLoc = importChecked(Err, S->getLParenLoc());
+  auto ToColonLoc = importChecked(Err, S->getColonLoc());
+  auto ToRParenLoc = importChecked(Err, S->getRParenLoc());
+  if (Err)
+    return std::move(Err);
+
+  return new (Importer.getToContext())
+      CXXEnumeratingExpansionStmt(ToESD, ToInit, ToExpansionVar, ToForLoc,
+                                  ToLParenLoc, ToColonLoc, ToRParenLoc);
+}
+ExpectedStmt ASTNodeImporter::VisitCXXIteratingExpansionStmt(CXXIteratingExpansionStmt *S) {
+    Error Err = Error::success();
+  auto ToESD = importChecked(Err, S->getDecl());
+  auto ToInit = importChecked(Err, S->getInit());
+  auto ToExpansionVar = importChecked(Err, S->getExpansionVarStmt());
+  auto ToRange = importChecked(Err, S->getRangeVarStmt());
+  auto ToBegin = importChecked(Err, S->getBeginVarStmt());
+  auto ToEnd = importChecked(Err, S->getEndVarStmt());
+  auto ToForLoc = importChecked(Err, S->getForLoc());
+  auto ToLParenLoc = importChecked(Err, S->getLParenLoc());
+  auto ToColonLoc = importChecked(Err, S->getColonLoc());
+  auto ToRParenLoc = importChecked(Err, S->getRParenLoc());
+  if (Err)
+    return std::move(Err);
+
+  return new (Importer.getToContext()) CXXIteratingExpansionStmt(
+      ToESD, ToInit, ToExpansionVar, ToRange, ToBegin, ToEnd, ToForLoc,
+      ToLParenLoc, ToColonLoc, ToRParenLoc);
+}
+ExpectedStmt ASTNodeImporter::VisitCXXDestructuringExpansionStmt(CXXDestructuringExpansionStmt *S) {
+    Error Err = Error::success();
+  auto ToESD = importChecked(Err, S->getDecl());
+  auto ToInit = importChecked(Err, S->getInit());
+  auto ToExpansionVar = importChecked(Err, S->getExpansionVarStmt());
+  auto ToDecompositionDeclStmt = importChecked(Err, S->getDecompositionDeclStmt());
+  auto ToForLoc = importChecked(Err, S->getForLoc());
+  auto ToLParenLoc = importChecked(Err, S->getLParenLoc());
+  auto ToColonLoc = importChecked(Err, S->getColonLoc());
+  auto ToRParenLoc = importChecked(Err, S->getRParenLoc());
+  if (Err)
+    return std::move(Err);
+
+  return new (Importer.getToContext()) CXXDestructuringExpansionStmt(
+      ToESD, ToInit, ToExpansionVar, ToDecompositionDeclStmt, ToForLoc,
+      ToLParenLoc, ToColonLoc, ToRParenLoc);
+}
+ExpectedStmt ASTNodeImporter::VisitCXXDependentExpansionStmt(CXXDependentExpansionStmt *S) {
+    Error Err = Error::success();
+  auto ToESD = importChecked(Err, S->getDecl());
+  auto ToInit = importChecked(Err, S->getInit());
+  auto ToExpansionVar = importChecked(Err, S->getExpansionVarStmt());
+  auto ToExpansionInitializer = importChecked(Err, S->getExpansionInitializer());
+  auto ToForLoc = importChecked(Err, S->getForLoc());
+  auto ToLParenLoc = importChecked(Err, S->getLParenLoc());
+  auto ToColonLoc = importChecked(Err, S->getColonLoc());
+  auto ToRParenLoc = importChecked(Err, S->getRParenLoc());
+  if (Err)
+    return std::move(Err);
+
+  return new (Importer.getToContext()) CXXDependentExpansionStmt(
+      ToESD, ToInit, ToExpansionVar, ToExpansionInitializer, ToForLoc,
+      ToLParenLoc, ToColonLoc, ToRParenLoc);
+}
+ExpectedStmt ASTNodeImporter::VisitCXXExpansionInstantiationStmt(CXXExpansionInstantiationStmt *S) {
+  Error Err = Error::success();
+  SmallVector<Stmt*> ToInstantiations;
+  SmallVector<Stmt*> ToSharedStmts;
+  auto ToBeginLoc = importChecked(Err, S->getBeginLoc());
+  auto ToEndLoc = importChecked(Err, S->getEndLoc());
+  for (Stmt* FromInst : S->getInstantiations())
+    ToInstantiations.push_back(importChecked(Err, FromInst));
+  for (Stmt* FromShared : S->getSharedStmts())
+    ToSharedStmts.push_back(importChecked(Err, FromShared));
+
+  if (Err)
+    return std::move(Err);
+
+  return CXXExpansionInstantiationStmt::Create(
+      Importer.getToContext(), ToBeginLoc, ToEndLoc, ToInstantiations,
+      ToSharedStmts, S->shouldApplyLifetimeExtensionToSharedStmts());
+}
+
 ExpectedStmt
 ASTNodeImporter::VisitObjCForCollectionStmt(ObjCForCollectionStmt *S) {
   Error Err = Error::success();
@@ -9273,6 +9398,46 @@ ASTNodeImporter::VisitConceptSpecializationExpr(ConceptSpecializationExpr *E) {
       const_cast<ImplicitConceptSpecializationDecl *>(CSD), &Satisfaction);
 }
 
+ExpectedStmt
+ASTNodeImporter::VisitCXXExpansionInitListExpr(CXXExpansionInitListExpr *E) {
+  Error Err = Error::success();
+  SmallVector<Expr *> ToExprs;
+  auto ToLBraceLoc = importChecked(Err, E->getLBraceLoc());
+  auto ToRBraceLoc = importChecked(Err, E->getRBraceLoc());
+  for (Expr *FromInst : E->getExprs())
+    ToExprs.push_back(importChecked(Err, FromInst));
+
+  if (Err)
+    return std::move(Err);
+
+  return CXXExpansionInitListExpr::Create(Importer.getToContext(), ToExprs,
+                                          ToLBraceLoc, ToRBraceLoc);
+}
+
+ExpectedStmt ASTNodeImporter::VisitCXXExpansionInitListSelectExpr(
+    CXXExpansionInitListSelectExpr *E) {
+  Error Err = Error::success();
+  auto ToRange = importChecked(Err, E->getRangeExpr());
+  auto ToIndex = importChecked(Err, E->getIndexExpr());
+  if (Err)
+    return std::move(Err);
+
+  return new (Importer.getToContext())
+      CXXExpansionInitListSelectExpr(Importer.getToContext(), ToRange, ToIndex);
+}
+
+ExpectedStmt ASTNodeImporter::VisitCXXDestructuringExpansionSelectExpr(
+    CXXDestructuringExpansionSelectExpr *E) {
+  Error Err = Error::success();
+  auto ToDecompositionDecl = importChecked(Err, E->getDecompositionDecl());
+  auto ToIndex = importChecked(Err, E->getIndexExpr());
+  if (Err)
+    return std::move(Err);
+
+  return new (Importer.getToContext()) CXXDestructuringExpansionSelectExpr(
+      Importer.getToContext(), ToDecompositionDecl, ToIndex);
+}
+
 Error ASTNodeImporter::ImportOverriddenMethods(CXXMethodDecl *ToMethod,
                                                CXXMethodDecl *FromMethod) {
   Error ImportErrors = Error::success();

>From 1ae64d3c739ce07cf10f68d7405021b19403b8ee Mon Sep 17 00:00:00 2001
From: Sirraide <aeternalmail at gmail.com>
Date: Mon, 27 Oct 2025 04:37:11 +0100
Subject: [PATCH 27/33] Minor fixes

---
 clang/include/clang/AST/DeclTemplate.h |  8 +-------
 clang/include/clang/AST/ExprCXX.h      |  6 ++++--
 clang/include/clang/Parse/Parser.h     |  7 ++++---
 clang/lib/AST/StmtCXX.cpp              |  2 --
 clang/lib/Parse/ParseStmt.cpp          |  2 +-
 clang/lib/Sema/SemaExpand.cpp          | 14 ++++++--------
 6 files changed, 16 insertions(+), 23 deletions(-)

diff --git a/clang/include/clang/AST/DeclTemplate.h b/clang/include/clang/AST/DeclTemplate.h
index 23aa3eeb0bf87..1ed8d6db74d61 100644
--- a/clang/include/clang/AST/DeclTemplate.h
+++ b/clang/include/clang/AST/DeclTemplate.h
@@ -3352,13 +3352,7 @@ class TemplateParamObjectDecl : public ValueDecl,
 /// compute the "template depth" of entities enclosed therein. In particular,
 /// the "template depth" is used to find instantiations of parameter variables,
 /// and a lambda enclosed within an expansion statement cannot compute its
-/// templat depth without a pointer to the enclosing expansion statement.
-///
-/// Another approach would be to extend 'CXXExpansionStmt' from 'DeclContext'
-/// without also providing a 'Decl' - but it seems as if this would be novel,
-/// and I'm not sure if existing code assumes that a 'DeclContext' is a 'Decl'.
-///
-/// TODO(P2996): This could probably be a 'TemplateDecl'.
+/// template depth without a pointer to the enclosing expansion statement.
 class ExpansionStmtDecl : public Decl, public DeclContext {
   CXXExpansionStmt *Expansion = nullptr;
   TemplateParameterList *TParams;
diff --git a/clang/include/clang/AST/ExprCXX.h b/clang/include/clang/AST/ExprCXX.h
index 33b00f2a760f3..95012388c542c 100644
--- a/clang/include/clang/AST/ExprCXX.h
+++ b/clang/include/clang/AST/ExprCXX.h
@@ -5501,8 +5501,8 @@ class BuiltinBitCastExpr final
   }
 };
 
-// Represents an expansion-init-list to be expanded over by an expansion
-// statement.
+/// Represents an expansion-init-list to be expanded over by an expansion
+/// statement.
 class CXXExpansionInitListExpr final
     : public Expr,
       llvm::TrailingObjects<CXXExpansionInitListExpr, Expr *> {
@@ -5560,6 +5560,8 @@ class CXXExpansionInitListExpr final
   }
 };
 
+/// Helper that selects an expression from an expansion init list depending
+/// on the current expansion index.
 class CXXExpansionInitListSelectExpr : public Expr {
   friend class ASTStmtReader;
 
diff --git a/clang/include/clang/Parse/Parser.h b/clang/include/clang/Parse/Parser.h
index 5d5612da661de..add8e6ae5bdf8 100644
--- a/clang/include/clang/Parse/Parser.h
+++ b/clang/include/clang/Parse/Parser.h
@@ -7449,8 +7449,9 @@ class Parser : public CodeCompletionHandler {
   ///       for-statement: [C99 6.8.5.3]
   ///         'for' '(' expr[opt] ';' expr[opt] ';' expr[opt] ')' statement
   ///         'for' '(' declaration expr[opt] ';' expr[opt] ')' statement
-  /// [C++]   'for' '(' for-init-statement condition[opt] ';' expression[opt]
-  /// ')' [C++]       statement [C++0x] 'for'
+  /// [C++]   'for' '(' for-init-statement condition[opt] ';' expression[opt] ')'
+  /// [C++]       statement
+  /// [C++0x] 'for'
   ///             'co_await'[opt]    [Coroutines]
   ///             '(' for-range-declaration ':' for-range-initializer ')'
   ///             statement
@@ -7709,7 +7710,7 @@ class Parser : public CodeCompletionHandler {
   /// [GNU] asm-clobbers:
   ///         asm-string-literal
   ///         asm-clobbers ',' asm-string-literal
-  /// \endverbatim
+  /// \endverbatim 
   ///
   StmtResult ParseAsmStatement(bool &msAsm);
 
diff --git a/clang/lib/AST/StmtCXX.cpp b/clang/lib/AST/StmtCXX.cpp
index 08a87bfe9cfd3..957e071e419fd 100644
--- a/clang/lib/AST/StmtCXX.cpp
+++ b/clang/lib/AST/StmtCXX.cpp
@@ -157,8 +157,6 @@ SourceLocation CXXExpansionStmt::getBeginLoc() const {
   return ParentDecl->getLocation();
 }
 
-// FIXME: Copy-pasted from CXXForRangeStmt. Can we convert this into a helper
-// function and put it somewhere else maybe?
 VarDecl *CXXExpansionStmt::getExpansionVariable() {
   Decl *LV = cast<DeclStmt>(getExpansionVarStmt())->getSingleDecl();
   assert(LV && "No expansion variable in CXXExpansionStmt");
diff --git a/clang/lib/Parse/ParseStmt.cpp b/clang/lib/Parse/ParseStmt.cpp
index cf977bf3d6fbd..a64c1f59c5563 100644
--- a/clang/lib/Parse/ParseStmt.cpp
+++ b/clang/lib/Parse/ParseStmt.cpp
@@ -2282,7 +2282,7 @@ StmtResult Parser::ParseForStatement(SourceLocation *TrailingElseLoc,
   // combinations, so diagnose that here in OpenACC mode.
   SemaOpenACC::LoopInConstructRAII LCR{getActions().OpenACC()};
   if (ExpansionStmtDeclaration)
-    ; // TODO: Figure out what to do here, if anything.
+    ; // Nothing.
   else if (ForRangeInfo.ParsedForRangeDecl())
     getActions().OpenACC().ActOnRangeForStmtBegin(ForLoc, ForRangeStmt.get());
   else
diff --git a/clang/lib/Sema/SemaExpand.cpp b/clang/lib/Sema/SemaExpand.cpp
index f2a642d70b729..4a2d8fdc4e193 100644
--- a/clang/lib/Sema/SemaExpand.cpp
+++ b/clang/lib/Sema/SemaExpand.cpp
@@ -109,7 +109,7 @@ TryBuildIterableExpansionStmtInitializer(Sema &S, Expr *ExpansionInitializer,
                                       OverloadCandidateSet::CSK_Normal);
 
     S.AddArgumentDependentLookupCandidates(BeginName.getName(), ColonLoc,
-                                           ExpansionInitializer, nullptr,
+                                           ExpansionInitializer, /*ExplicitTemplateArgs=*/nullptr,
                                            Candidates);
 
     if (Candidates.empty())
@@ -117,7 +117,7 @@ TryBuildIterableExpansionStmtInitializer(Sema &S, Expr *ExpansionInitializer,
 
     Candidates.clear(OverloadCandidateSet::CSK_Normal);
     S.AddArgumentDependentLookupCandidates(EndName.getName(), ColonLoc,
-                                           ExpansionInitializer, nullptr,
+                                           ExpansionInitializer, /*ExplicitTemplateArgs=*/nullptr,
                                            Candidates);
 
     if (Candidates.empty())
@@ -237,10 +237,9 @@ static StmtResult BuildDestructuringExpansionStmtDecl(
 ExpansionStmtDecl *Sema::ActOnExpansionStmtDecl(unsigned TemplateDepth,
                                                 SourceLocation TemplateKWLoc) {
   // Create a template parameter '__N'. This will be used to denote the index
-  // of the element that we're instantiating. The wording around iterable
-  // expansion statements (which are the only kind of expansion statements that
-  // actually use this parameter in an expression) implies that its type should
-  // be 'ptrdiff_t', so use that in all cases.
+  // 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 =
@@ -295,7 +294,6 @@ StmtResult Sema::ActOnCXXExpansionStmt(
 
   // This is an enumerating expansion statement.
   if (auto *ILE = dyn_cast<CXXExpansionInitListExpr>(ExpansionInitializer)) {
-
     ExprResult Initializer =
         BuildCXXExpansionInitListSelectExpr(ILE, BuildIndexDRE(*this, ESD));
     if (FinaliseExpansionVar(*this, ExpansionVar, Initializer))
@@ -512,7 +510,7 @@ ExprResult Sema::BuildCXXDestructuringExpansionSelectExpr(DecompositionDecl *DD,
     llvm_unreachable("Failed to evaluate expansion index");
 
   uint64_t I = ER.Val.getInt().getZExtValue();
-  MarkAnyDeclReferenced(Idx->getBeginLoc(), DD, true); // TODO: Do we need this?
+  MarkAnyDeclReferenced(Idx->getBeginLoc(), DD, true);
   if (auto *BD = DD->bindings()[I]; auto *HVD = BD->getHoldingVar())
     return HVD->getInit();
   else

>From 9b09c05f49451a188d93e322b4704e9e4c94bb9f Mon Sep 17 00:00:00 2001
From: Sirraide <aeternalmail at gmail.com>
Date: Mon, 27 Oct 2025 04:39:32 +0100
Subject: [PATCH 28/33] clang-format

---
 clang/include/clang/AST/DeclTemplate.h        |  1 -
 clang/include/clang/AST/ExprCXX.h             | 10 +--
 clang/include/clang/AST/StmtCXX.h             | 46 +++++++-------
 clang/include/clang/AST/TextNodeDumper.h      |  4 +-
 clang/include/clang/Sema/ScopeInfo.h          |  4 +-
 clang/include/clang/Sema/Sema.h               |  5 +-
 .../include/clang/Serialization/ASTBitCodes.h | 24 ++++----
 clang/lib/AST/ASTImporter.cpp                 | 61 +++++++++++--------
 clang/lib/AST/ExprCXX.cpp                     |  5 +-
 clang/lib/AST/ExprConstant.cpp                |  4 +-
 clang/lib/AST/StmtCXX.cpp                     | 14 ++---
 clang/lib/AST/StmtPrinter.cpp                 |  9 ++-
 clang/lib/AST/TextNodeDumper.cpp              |  3 +-
 clang/lib/CodeGen/CGStmt.cpp                  | 10 +--
 clang/lib/Sema/SemaExpand.cpp                 | 28 ++++-----
 clang/lib/Sema/SemaTemplateInstantiate.cpp    |  4 +-
 .../lib/Sema/SemaTemplateInstantiateDecl.cpp  |  2 +-
 clang/lib/Sema/TreeTransform.h                | 30 ++++-----
 clang/lib/Serialization/ASTReaderDecl.cpp     |  3 +-
 19 files changed, 142 insertions(+), 125 deletions(-)

diff --git a/clang/include/clang/AST/DeclTemplate.h b/clang/include/clang/AST/DeclTemplate.h
index 1ed8d6db74d61..4dc7fefb686e9 100644
--- a/clang/include/clang/AST/DeclTemplate.h
+++ b/clang/include/clang/AST/DeclTemplate.h
@@ -3343,7 +3343,6 @@ class TemplateParamObjectDecl : public ValueDecl,
   static bool classofKind(Kind K) { return K == TemplateParamObject; }
 };
 
-
 /// Represents a C++26 expansion statement declaration.
 ///
 /// This is a bit of a hack, since expansion statements shouldn't really be
diff --git a/clang/include/clang/AST/ExprCXX.h b/clang/include/clang/AST/ExprCXX.h
index 95012388c542c..5f50064d512ee 100644
--- a/clang/include/clang/AST/ExprCXX.h
+++ b/clang/include/clang/AST/ExprCXX.h
@@ -5611,8 +5611,8 @@ class CXXExpansionInitListSelectExpr : public Expr {
 class CXXDestructuringExpansionSelectExpr : public Expr {
   friend class ASTStmtReader;
 
-  DecompositionDecl* Decomposition;
-  Expr* Index;
+  DecompositionDecl *Decomposition;
+  Expr *Index;
 
 public:
   CXXDestructuringExpansionSelectExpr(EmptyShell Empty);
@@ -5632,7 +5632,7 @@ class CXXDestructuringExpansionSelectExpr : public Expr {
 
   Expr *getIndexExpr() { return Index; }
   const Expr *getIndexExpr() const { return Index; }
-  void setIndexExpr(Expr* E) { Index = E; }
+  void setIndexExpr(Expr *E) { Index = E; }
 
   SourceLocation getBeginLoc() const { return Decomposition->getBeginLoc(); }
   SourceLocation getEndLoc() const { return Decomposition->getEndLoc(); }
@@ -5644,8 +5644,8 @@ class CXXDestructuringExpansionSelectExpr : public Expr {
 
   const_child_range children() const {
     return const_child_range(
-            reinterpret_cast<Stmt **>(const_cast<Expr **>(&Index)),
-            reinterpret_cast<Stmt **>(const_cast<Expr **>(&Index + 1)));
+        reinterpret_cast<Stmt **>(const_cast<Expr **>(&Index)),
+        reinterpret_cast<Stmt **>(const_cast<Expr **>(&Index + 1)));
   }
 
   static bool classof(const Stmt *T) {
diff --git a/clang/include/clang/AST/StmtCXX.h b/clang/include/clang/AST/StmtCXX.h
index 7e24677608781..570151371e4e9 100644
--- a/clang/include/clang/AST/StmtCXX.h
+++ b/clang/include/clang/AST/StmtCXX.h
@@ -529,7 +529,7 @@ class CoreturnStmt : public Stmt {
 class CXXExpansionStmt : public Stmt {
   friend class ASTStmtReader;
 
-  ExpansionStmtDecl* ParentDecl;
+  ExpansionStmtDecl *ParentDecl;
   SourceLocation ForLoc;
   SourceLocation LParenLoc;
   SourceLocation ColonLoc;
@@ -562,8 +562,7 @@ class CXXExpansionStmt : public Stmt {
   // Managing the memory for this properly would be rather complicated, and
   // expansion statements are fairly uncommon, so just allocate space for the
   // maximum amount of substatements we could possibly have.
-  Stmt* SubStmts[MAX_COUNT];
-
+  Stmt *SubStmts[MAX_COUNT];
 
   CXXExpansionStmt(StmtClass SC, EmptyShell Empty);
   CXXExpansionStmt(StmtClass SC, ExpansionStmtDecl *ESD, Stmt *Init,
@@ -693,11 +692,11 @@ class CXXIteratingExpansionStmt : public CXXExpansionStmt {
   DeclStmt *getRangeVarStmt() { return cast<DeclStmt>(SubStmts[RANGE]); }
   void setRangeVarStmt(DeclStmt *S) { SubStmts[RANGE] = S; }
 
-  const VarDecl* getRangeVar() const {
+  const VarDecl *getRangeVar() const {
     return cast<VarDecl>(getRangeVarStmt()->getSingleDecl());
   }
 
-  VarDecl* getRangeVar() {
+  VarDecl *getRangeVar() {
     return cast<VarDecl>(getRangeVarStmt()->getSingleDecl());
   }
 
@@ -707,11 +706,11 @@ class CXXIteratingExpansionStmt : public CXXExpansionStmt {
   DeclStmt *getBeginVarStmt() { return cast<DeclStmt>(SubStmts[BEGIN]); }
   void setBeginVarStmt(DeclStmt *S) { SubStmts[BEGIN] = S; }
 
-  const VarDecl* getBeginVar() const {
+  const VarDecl *getBeginVar() const {
     return cast<VarDecl>(getBeginVarStmt()->getSingleDecl());
   }
 
-  VarDecl* getBeginVar() {
+  VarDecl *getBeginVar() {
     return cast<VarDecl>(getBeginVarStmt()->getSingleDecl());
   }
 
@@ -721,11 +720,11 @@ class CXXIteratingExpansionStmt : public CXXExpansionStmt {
   DeclStmt *getEndVarStmt() { return cast<DeclStmt>(SubStmts[END]); }
   void setEndVarStmt(DeclStmt *S) { SubStmts[END] = S; }
 
-  const VarDecl* getEndVar() const {
+  const VarDecl *getEndVar() const {
     return cast<VarDecl>(getEndVarStmt()->getSingleDecl());
   }
 
-  VarDecl* getEndVar() {
+  VarDecl *getEndVar() {
     return cast<VarDecl>(getEndVarStmt()->getSingleDecl());
   }
 
@@ -750,21 +749,25 @@ class CXXDestructuringExpansionStmt : public CXXExpansionStmt {
 public:
   CXXDestructuringExpansionStmt(EmptyShell Empty);
   CXXDestructuringExpansionStmt(ExpansionStmtDecl *ESD, Stmt *Init,
-                            DeclStmt *ExpansionVar, Stmt *DecompositionDeclStmt,
-                            SourceLocation ForLoc, SourceLocation LParenLoc,
-                            SourceLocation ColonLoc, SourceLocation RParenLoc);
+                                DeclStmt *ExpansionVar,
+                                Stmt *DecompositionDeclStmt,
+                                SourceLocation ForLoc, SourceLocation LParenLoc,
+                                SourceLocation ColonLoc,
+                                SourceLocation RParenLoc);
 
   Stmt *getDecompositionDeclStmt() { return SubStmts[DECOMP_DECL]; }
   const Stmt *getDecompositionDeclStmt() const { return SubStmts[DECOMP_DECL]; }
-  void setDecompositionDeclStmt(Stmt* S) { SubStmts[DECOMP_DECL] = S; }
+  void setDecompositionDeclStmt(Stmt *S) { SubStmts[DECOMP_DECL] = S; }
 
-  DecompositionDecl* getDecompositionDecl();
-  const DecompositionDecl* getDecompositionDecl() const {
-    return const_cast<CXXDestructuringExpansionStmt *>(this)->getDecompositionDecl();
+  DecompositionDecl *getDecompositionDecl();
+  const DecompositionDecl *getDecompositionDecl() const {
+    return const_cast<CXXDestructuringExpansionStmt *>(this)
+        ->getDecompositionDecl();
   }
 
   child_range children() {
-    return child_range(SubStmts, SubStmts + COUNT_CXXDestructuringExpansionStmt);
+    return child_range(SubStmts,
+                       SubStmts + COUNT_CXXDestructuringExpansionStmt);
   }
 
   const_child_range children() const {
@@ -810,11 +813,10 @@ class CXXExpansionInstantiationStmt final
 
   CXXExpansionInstantiationStmt(EmptyShell Empty, unsigned NumInstantiations,
                                 unsigned NumSharedStmts);
-  CXXExpansionInstantiationStmt(
-      SourceLocation BeginLoc,
-      SourceLocation EndLoc, ArrayRef<Stmt *> Instantiations,
-      ArrayRef<Stmt *> SharedStmts,
-      bool ShouldApplyLifetimeExtensionToSharedStmts);
+  CXXExpansionInstantiationStmt(SourceLocation BeginLoc, SourceLocation EndLoc,
+                                ArrayRef<Stmt *> Instantiations,
+                                ArrayRef<Stmt *> SharedStmts,
+                                bool ShouldApplyLifetimeExtensionToSharedStmts);
 
 public:
   static CXXExpansionInstantiationStmt *
diff --git a/clang/include/clang/AST/TextNodeDumper.h b/clang/include/clang/AST/TextNodeDumper.h
index 584ebddd5c0ac..4355e7fbab9b2 100644
--- a/clang/include/clang/AST/TextNodeDumper.h
+++ b/clang/include/clang/AST/TextNodeDumper.h
@@ -268,8 +268,8 @@ class TextNodeDumper
   void VisitCoawaitExpr(const CoawaitExpr *Node);
   void VisitCoreturnStmt(const CoreturnStmt *Node);
   void VisitCompoundStmt(const CompoundStmt *Node);
-  void VisitCXXExpansionInstantiationStmt(
-      const CXXExpansionInstantiationStmt *Node);
+  void
+  VisitCXXExpansionInstantiationStmt(const CXXExpansionInstantiationStmt *Node);
   void VisitConstantExpr(const ConstantExpr *Node);
   void VisitCallExpr(const CallExpr *Node);
   void VisitCXXOperatorCallExpr(const CXXOperatorCallExpr *Node);
diff --git a/clang/include/clang/Sema/ScopeInfo.h b/clang/include/clang/Sema/ScopeInfo.h
index ad81dee64f8cd..2a410bd2eab91 100644
--- a/clang/include/clang/Sema/ScopeInfo.h
+++ b/clang/include/clang/Sema/ScopeInfo.h
@@ -202,8 +202,8 @@ class FunctionScopeInfo {
 public:
   /// A SwitchStmt, along with a flag indicating if its list of case statements
   /// is incomplete (because we dropped an invalid one while parsing).
-  struct SwitchInfo : llvm::PointerIntPair<SwitchStmt*, 1, bool> {
-    DeclContext* EnclosingDC;
+  struct SwitchInfo : llvm::PointerIntPair<SwitchStmt *, 1, bool> {
+    DeclContext *EnclosingDC;
     SwitchInfo(SwitchStmt *Switch, DeclContext *DC)
         : PointerIntPair(Switch, false), EnclosingDC(DC) {}
   };
diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index a765bf12ef62e..9ef3f9640434f 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -15699,9 +15699,8 @@ class Sema final : public SemaBase {
   BuildCXXExpansionInitListSelectExpr(CXXExpansionInitListExpr *Range,
                                       Expr *Idx);
 
-  ExprResult
-  BuildCXXDestructuringExpansionSelectExpr(DecompositionDecl *DD,
-                                                 Expr *Idx);
+  ExprResult BuildCXXDestructuringExpansionSelectExpr(DecompositionDecl *DD,
+                                                      Expr *Idx);
 
   StmtResult BuildNonEnumeratingCXXExpansionStmt(
       ExpansionStmtDecl *ESD, Stmt *Init, DeclStmt *ExpansionVarStmt,
diff --git a/clang/include/clang/Serialization/ASTBitCodes.h b/clang/include/clang/Serialization/ASTBitCodes.h
index 2dc4116929f3e..faa1fba71311b 100644
--- a/clang/include/clang/Serialization/ASTBitCodes.h
+++ b/clang/include/clang/Serialization/ASTBitCodes.h
@@ -1932,18 +1932,18 @@ enum StmtCode {
   EXPR_TYPE_TRAIT,                  // TypeTraitExpr
   EXPR_ARRAY_TYPE_TRAIT,            // ArrayTypeTraitIntExpr
 
-  EXPR_PACK_EXPANSION,                    // PackExpansionExpr
-  EXPR_PACK_INDEXING,                     // PackIndexingExpr
-  EXPR_SIZEOF_PACK,                       // SizeOfPackExpr
-  EXPR_SUBST_NON_TYPE_TEMPLATE_PARM,      // SubstNonTypeTemplateParmExpr
-  EXPR_SUBST_NON_TYPE_TEMPLATE_PARM_PACK, // SubstNonTypeTemplateParmPackExpr
-  EXPR_FUNCTION_PARM_PACK,                // FunctionParmPackExpr
-  EXPR_MATERIALIZE_TEMPORARY,             // MaterializeTemporaryExpr
-  EXPR_CXX_FOLD,                          // CXXFoldExpr
-  EXPR_CONCEPT_SPECIALIZATION,            // ConceptSpecializationExpr
-  EXPR_REQUIRES,                          // RequiresExpr
-  EXPR_CXX_EXPANSION_INIT_LIST,           // CXXExpansionInitListExpr
-  EXPR_CXX_EXPANSION_INIT_LIST_SELECT,    // CXXExpansionInitListSelectExpr
+  EXPR_PACK_EXPANSION,                     // PackExpansionExpr
+  EXPR_PACK_INDEXING,                      // PackIndexingExpr
+  EXPR_SIZEOF_PACK,                        // SizeOfPackExpr
+  EXPR_SUBST_NON_TYPE_TEMPLATE_PARM,       // SubstNonTypeTemplateParmExpr
+  EXPR_SUBST_NON_TYPE_TEMPLATE_PARM_PACK,  // SubstNonTypeTemplateParmPackExpr
+  EXPR_FUNCTION_PARM_PACK,                 // FunctionParmPackExpr
+  EXPR_MATERIALIZE_TEMPORARY,              // MaterializeTemporaryExpr
+  EXPR_CXX_FOLD,                           // CXXFoldExpr
+  EXPR_CONCEPT_SPECIALIZATION,             // ConceptSpecializationExpr
+  EXPR_REQUIRES,                           // RequiresExpr
+  EXPR_CXX_EXPANSION_INIT_LIST,            // CXXExpansionInitListExpr
+  EXPR_CXX_EXPANSION_INIT_LIST_SELECT,     // CXXExpansionInitListSelectExpr
   EXPR_CXX_DESTRUCTURING_EXPANSION_SELECT, // CXXDestructuringExpansionSelectExpr
 
   // CUDA
diff --git a/clang/lib/AST/ASTImporter.cpp b/clang/lib/AST/ASTImporter.cpp
index e208be7eb90f4..0fd716b904a54 100644
--- a/clang/lib/AST/ASTImporter.cpp
+++ b/clang/lib/AST/ASTImporter.cpp
@@ -516,7 +516,7 @@ namespace clang {
     ExpectedDecl VisitEmptyDecl(EmptyDecl *D);
     ExpectedDecl VisitAccessSpecDecl(AccessSpecDecl *D);
     ExpectedDecl VisitStaticAssertDecl(StaticAssertDecl *D);
-    ExpectedDecl VisitExpansionStmtDecl(ExpansionStmtDecl* D);
+    ExpectedDecl VisitExpansionStmtDecl(ExpansionStmtDecl *D);
     ExpectedDecl VisitTranslationUnitDecl(TranslationUnitDecl *D);
     ExpectedDecl VisitBindingDecl(BindingDecl *D);
     ExpectedDecl VisitNamespaceDecl(NamespaceDecl *D);
@@ -609,11 +609,14 @@ namespace clang {
     ExpectedStmt VisitCXXCatchStmt(CXXCatchStmt *S);
     ExpectedStmt VisitCXXTryStmt(CXXTryStmt *S);
     ExpectedStmt VisitCXXForRangeStmt(CXXForRangeStmt *S);
-    ExpectedStmt VisitCXXEnumeratingExpansionStmt(CXXEnumeratingExpansionStmt *S);
+    ExpectedStmt
+    VisitCXXEnumeratingExpansionStmt(CXXEnumeratingExpansionStmt *S);
     ExpectedStmt VisitCXXIteratingExpansionStmt(CXXIteratingExpansionStmt *S);
-    ExpectedStmt VisitCXXDestructuringExpansionStmt(CXXDestructuringExpansionStmt *S);
+    ExpectedStmt
+    VisitCXXDestructuringExpansionStmt(CXXDestructuringExpansionStmt *S);
     ExpectedStmt VisitCXXDependentExpansionStmt(CXXDependentExpansionStmt *S);
-    ExpectedStmt VisitCXXExpansionInstantiationStmt(CXXExpansionInstantiationStmt *S);
+    ExpectedStmt
+    VisitCXXExpansionInstantiationStmt(CXXExpansionInstantiationStmt *S);
     // FIXME: MSDependentExistsStmt
     ExpectedStmt VisitObjCForCollectionStmt(ObjCForCollectionStmt *S);
     ExpectedStmt VisitObjCAtCatchStmt(ObjCAtCatchStmt *S);
@@ -702,10 +705,11 @@ namespace clang {
     ExpectedStmt VisitCXXFoldExpr(CXXFoldExpr *E);
     ExpectedStmt VisitRequiresExpr(RequiresExpr* E);
     ExpectedStmt VisitConceptSpecializationExpr(ConceptSpecializationExpr* E);
-    ExpectedStmt VisitCXXExpansionInitListExpr(CXXExpansionInitListExpr* E);
-    ExpectedStmt VisitCXXExpansionInitListSelectExpr(CXXExpansionInitListSelectExpr* E);
-    ExpectedStmt VisitCXXDestructuringExpansionSelectExpr(CXXDestructuringExpansionSelectExpr* E);
-
+    ExpectedStmt VisitCXXExpansionInitListExpr(CXXExpansionInitListExpr *E);
+    ExpectedStmt
+    VisitCXXExpansionInitListSelectExpr(CXXExpansionInitListSelectExpr *E);
+    ExpectedStmt VisitCXXDestructuringExpansionSelectExpr(
+        CXXDestructuringExpansionSelectExpr *E);
 
     // Helper for chaining together multiple imports. If an error is detected,
     // subsequent imports will return default constructed nodes, so that failure
@@ -2853,9 +2857,9 @@ ExpectedDecl ASTNodeImporter::VisitExpansionStmtDecl(ExpansionStmtDecl *D) {
   if (Err)
     return std::move(Err);
 
-  ExpansionStmtDecl* ToD;
-  if (GetImportedOrCreateDecl(
-      ToD, D, Importer.getToContext(), DC, ToLocation, ToTemplateParams))
+  ExpansionStmtDecl *ToD;
+  if (GetImportedOrCreateDecl(ToD, D, Importer.getToContext(), DC, ToLocation,
+                              ToTemplateParams))
     return ToD;
 
   ToD->setExpansionPattern(ToExpansion);
@@ -7458,7 +7462,8 @@ ExpectedStmt ASTNodeImporter::VisitCXXForRangeStmt(CXXForRangeStmt *S) {
       ToBody, ToForLoc, ToCoawaitLoc, ToColonLoc, ToRParenLoc);
 }
 
-ExpectedStmt ASTNodeImporter::VisitCXXEnumeratingExpansionStmt(CXXEnumeratingExpansionStmt *S) {
+ExpectedStmt ASTNodeImporter::VisitCXXEnumeratingExpansionStmt(
+    CXXEnumeratingExpansionStmt *S) {
   Error Err = Error::success();
   auto ToESD = importChecked(Err, S->getDecl());
   auto ToInit = importChecked(Err, S->getInit());
@@ -7474,8 +7479,9 @@ ExpectedStmt ASTNodeImporter::VisitCXXEnumeratingExpansionStmt(CXXEnumeratingExp
       CXXEnumeratingExpansionStmt(ToESD, ToInit, ToExpansionVar, ToForLoc,
                                   ToLParenLoc, ToColonLoc, ToRParenLoc);
 }
-ExpectedStmt ASTNodeImporter::VisitCXXIteratingExpansionStmt(CXXIteratingExpansionStmt *S) {
-    Error Err = Error::success();
+ExpectedStmt
+ASTNodeImporter::VisitCXXIteratingExpansionStmt(CXXIteratingExpansionStmt *S) {
+  Error Err = Error::success();
   auto ToESD = importChecked(Err, S->getDecl());
   auto ToInit = importChecked(Err, S->getInit());
   auto ToExpansionVar = importChecked(Err, S->getExpansionVarStmt());
@@ -7493,12 +7499,14 @@ ExpectedStmt ASTNodeImporter::VisitCXXIteratingExpansionStmt(CXXIteratingExpansi
       ToESD, ToInit, ToExpansionVar, ToRange, ToBegin, ToEnd, ToForLoc,
       ToLParenLoc, ToColonLoc, ToRParenLoc);
 }
-ExpectedStmt ASTNodeImporter::VisitCXXDestructuringExpansionStmt(CXXDestructuringExpansionStmt *S) {
-    Error Err = Error::success();
+ExpectedStmt ASTNodeImporter::VisitCXXDestructuringExpansionStmt(
+    CXXDestructuringExpansionStmt *S) {
+  Error Err = Error::success();
   auto ToESD = importChecked(Err, S->getDecl());
   auto ToInit = importChecked(Err, S->getInit());
   auto ToExpansionVar = importChecked(Err, S->getExpansionVarStmt());
-  auto ToDecompositionDeclStmt = importChecked(Err, S->getDecompositionDeclStmt());
+  auto ToDecompositionDeclStmt =
+      importChecked(Err, S->getDecompositionDeclStmt());
   auto ToForLoc = importChecked(Err, S->getForLoc());
   auto ToLParenLoc = importChecked(Err, S->getLParenLoc());
   auto ToColonLoc = importChecked(Err, S->getColonLoc());
@@ -7510,12 +7518,14 @@ ExpectedStmt ASTNodeImporter::VisitCXXDestructuringExpansionStmt(CXXDestructurin
       ToESD, ToInit, ToExpansionVar, ToDecompositionDeclStmt, ToForLoc,
       ToLParenLoc, ToColonLoc, ToRParenLoc);
 }
-ExpectedStmt ASTNodeImporter::VisitCXXDependentExpansionStmt(CXXDependentExpansionStmt *S) {
-    Error Err = Error::success();
+ExpectedStmt
+ASTNodeImporter::VisitCXXDependentExpansionStmt(CXXDependentExpansionStmt *S) {
+  Error Err = Error::success();
   auto ToESD = importChecked(Err, S->getDecl());
   auto ToInit = importChecked(Err, S->getInit());
   auto ToExpansionVar = importChecked(Err, S->getExpansionVarStmt());
-  auto ToExpansionInitializer = importChecked(Err, S->getExpansionInitializer());
+  auto ToExpansionInitializer =
+      importChecked(Err, S->getExpansionInitializer());
   auto ToForLoc = importChecked(Err, S->getForLoc());
   auto ToLParenLoc = importChecked(Err, S->getLParenLoc());
   auto ToColonLoc = importChecked(Err, S->getColonLoc());
@@ -7527,15 +7537,16 @@ ExpectedStmt ASTNodeImporter::VisitCXXDependentExpansionStmt(CXXDependentExpansi
       ToESD, ToInit, ToExpansionVar, ToExpansionInitializer, ToForLoc,
       ToLParenLoc, ToColonLoc, ToRParenLoc);
 }
-ExpectedStmt ASTNodeImporter::VisitCXXExpansionInstantiationStmt(CXXExpansionInstantiationStmt *S) {
+ExpectedStmt ASTNodeImporter::VisitCXXExpansionInstantiationStmt(
+    CXXExpansionInstantiationStmt *S) {
   Error Err = Error::success();
-  SmallVector<Stmt*> ToInstantiations;
-  SmallVector<Stmt*> ToSharedStmts;
+  SmallVector<Stmt *> ToInstantiations;
+  SmallVector<Stmt *> ToSharedStmts;
   auto ToBeginLoc = importChecked(Err, S->getBeginLoc());
   auto ToEndLoc = importChecked(Err, S->getEndLoc());
-  for (Stmt* FromInst : S->getInstantiations())
+  for (Stmt *FromInst : S->getInstantiations())
     ToInstantiations.push_back(importChecked(Err, FromInst));
-  for (Stmt* FromShared : S->getSharedStmts())
+  for (Stmt *FromShared : S->getSharedStmts())
     ToSharedStmts.push_back(importChecked(Err, FromShared));
 
   if (Err)
diff --git a/clang/lib/AST/ExprCXX.cpp b/clang/lib/AST/ExprCXX.cpp
index 824c3ac0a3db5..eb73ab40a3e89 100644
--- a/clang/lib/AST/ExprCXX.cpp
+++ b/clang/lib/AST/ExprCXX.cpp
@@ -2064,9 +2064,8 @@ CXXExpansionInitListSelectExpr::CXXExpansionInitListSelectExpr(
 }
 
 bool CXXExpansionInitListExpr::containsPackExpansion() const {
-  return llvm::any_of(getExprs(), [](const Expr* E) {
-    return isa<PackExpansionExpr>(E);
-  });
+  return llvm::any_of(getExprs(),
+                      [](const Expr *E) { return isa<PackExpansionExpr>(E); });
 }
 
 CXXDestructuringExpansionSelectExpr::CXXDestructuringExpansionSelectExpr(
diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp
index f4479f222840c..6d1461461431b 100644
--- a/clang/lib/AST/ExprConstant.cpp
+++ b/clang/lib/AST/ExprConstant.cpp
@@ -5945,7 +5945,7 @@ static EvalStmtResult EvaluateStmt(StmtResult &Result, EvalInfo &Info,
   case Stmt::CXXExpansionInstantiationStmtClass: {
     BlockScopeRAII Scope(Info);
     const auto *Expansion = cast<CXXExpansionInstantiationStmt>(S);
-    for (const Stmt* Shared : Expansion->getSharedStmts()) {
+    for (const Stmt *Shared : Expansion->getSharedStmts()) {
       EvalStmtResult ESR = EvaluateStmt(Result, Info, Shared);
       if (ESR != ESR_Succeeded) {
         if (ESR != ESR_Failed && !Scope.destroy())
@@ -5956,7 +5956,7 @@ static EvalStmtResult EvaluateStmt(StmtResult &Result, EvalInfo &Info,
 
     // No need to push an extra scope for these since they're already
     // CompoundStmts.
-    for (const Stmt* Instantiation : Expansion->getInstantiations()) {
+    for (const Stmt *Instantiation : Expansion->getInstantiations()) {
       EvalStmtResult ESR = EvaluateStmt(Result, Info, Instantiation);
       if (ESR == ESR_Failed ||
           ShouldPropagateBreakContinue(Info, Expansion, &Scope, ESR))
diff --git a/clang/lib/AST/StmtCXX.cpp b/clang/lib/AST/StmtCXX.cpp
index 957e071e419fd..8bd1acd5ae383 100644
--- a/clang/lib/AST/StmtCXX.cpp
+++ b/clang/lib/AST/StmtCXX.cpp
@@ -171,8 +171,8 @@ bool CXXExpansionStmt::hasDependentSize() const {
         ->containsPackExpansion();
 
   if (auto *Iterating = dyn_cast<CXXIteratingExpansionStmt>(this)) {
-    const Expr* Begin = Iterating->getBeginVar()->getInit();
-    const Expr* End = Iterating->getBeginVar()->getInit();
+    const Expr *Begin = Iterating->getBeginVar()->getInit();
+    const Expr *End = Iterating->getBeginVar()->getInit();
     return Begin->isTypeDependent() || Begin->isValueDependent() ||
            End->isTypeDependent() || End->isValueDependent();
   }
@@ -187,7 +187,7 @@ bool CXXExpansionStmt::hasDependentSize() const {
 }
 
 CXXIteratingExpansionStmt::CXXIteratingExpansionStmt(EmptyShell Empty)
-  : CXXExpansionStmt(CXXIteratingExpansionStmtClass, Empty) {}
+    : CXXExpansionStmt(CXXIteratingExpansionStmtClass, Empty) {}
 
 CXXIteratingExpansionStmt::CXXIteratingExpansionStmt(
     ExpansionStmtDecl *ESD, Stmt *Init, DeclStmt *ExpansionVar, DeclStmt *Range,
@@ -207,18 +207,18 @@ CXXDestructuringExpansionStmt::CXXDestructuringExpansionStmt(
     ExpansionStmtDecl *ESD, Stmt *Init, DeclStmt *ExpansionVar,
     Stmt *DecompositionDeclStmt, SourceLocation ForLoc,
     SourceLocation LParenLoc, SourceLocation ColonLoc, SourceLocation RParenLoc)
-    : CXXExpansionStmt(CXXDestructuringExpansionStmtClass, ESD, Init, ExpansionVar,
-                       ForLoc, LParenLoc, ColonLoc, RParenLoc) {
+    : CXXExpansionStmt(CXXDestructuringExpansionStmtClass, ESD, Init,
+                       ExpansionVar, ForLoc, LParenLoc, ColonLoc, RParenLoc) {
   setDecompositionDeclStmt(DecompositionDeclStmt);
 }
 
-DecompositionDecl* CXXDestructuringExpansionStmt::getDecompositionDecl() {
+DecompositionDecl *CXXDestructuringExpansionStmt::getDecompositionDecl() {
   return cast<DecompositionDecl>(
       cast<DeclStmt>(getDecompositionDeclStmt())->getSingleDecl());
 }
 
 CXXDependentExpansionStmt::CXXDependentExpansionStmt(EmptyShell Empty)
-  : CXXExpansionStmt(CXXDependentExpansionStmtClass, Empty) {}
+    : CXXExpansionStmt(CXXDependentExpansionStmtClass, Empty) {}
 
 CXXDependentExpansionStmt::CXXDependentExpansionStmt(
     ExpansionStmtDecl *ESD, Stmt *Init, DeclStmt *ExpansionVar,
diff --git a/clang/lib/AST/StmtPrinter.cpp b/clang/lib/AST/StmtPrinter.cpp
index e8bc7ca02dce2..4f38f4a6bbccd 100644
--- a/clang/lib/AST/StmtPrinter.cpp
+++ b/clang/lib/AST/StmtPrinter.cpp
@@ -160,7 +160,8 @@ namespace {
     }
 
     void VisitCXXNamedCastExpr(CXXNamedCastExpr *Node);
-    void VisitCXXExpansionStmt(CXXExpansionStmt* Node, Expr* Initializer = nullptr);
+    void VisitCXXExpansionStmt(CXXExpansionStmt *Node,
+                               Expr *Initializer = nullptr);
 
 #define ABSTRACT_STMT(CLASS)
 #define STMT(CLASS, PARENT) \
@@ -264,7 +265,8 @@ void StmtPrinter::VisitDeclStmt(DeclStmt *Node) {
   PrintRawDeclStmt(Node);
   // Certain pragma declarations shouldn't have a semi-colon after them.
   if (!Node->isSingleDecl() ||
-      !isa<ExpansionStmtDecl, OpenACCDeclareDecl, OpenACCRoutineDecl>(Node->getSingleDecl()))
+      !isa<ExpansionStmtDecl, OpenACCDeclareDecl, OpenACCRoutineDecl>(
+          Node->getSingleDecl()))
     OS << ";";
   OS << NL;
 }
@@ -448,7 +450,8 @@ void StmtPrinter::VisitCXXForRangeStmt(CXXForRangeStmt *Node) {
   PrintControlledStmt(Node->getBody());
 }
 
-void StmtPrinter::VisitCXXExpansionStmt(CXXExpansionStmt *Node, Expr* Initializer) {
+void StmtPrinter::VisitCXXExpansionStmt(CXXExpansionStmt *Node,
+                                        Expr *Initializer) {
   OS << "template for (";
   if (Node->getInit())
     PrintInitStmt(Node->getInit(), 14);
diff --git a/clang/lib/AST/TextNodeDumper.cpp b/clang/lib/AST/TextNodeDumper.cpp
index 43037ff688679..b4833e2338893 100644
--- a/clang/lib/AST/TextNodeDumper.cpp
+++ b/clang/lib/AST/TextNodeDumper.cpp
@@ -1836,7 +1836,8 @@ void TextNodeDumper::VisitCXXDependentScopeMemberExpr(
   OS << " " << (Node->isArrow() ? "->" : ".") << Node->getMember();
 }
 
-void TextNodeDumper::VisitCXXExpansionInitListExpr(const CXXExpansionInitListExpr *Node) {
+void TextNodeDumper::VisitCXXExpansionInitListExpr(
+    const CXXExpansionInitListExpr *Node) {
   if (Node->containsPackExpansion())
     OS << " contains_pack";
 }
diff --git a/clang/lib/CodeGen/CGStmt.cpp b/clang/lib/CodeGen/CGStmt.cpp
index a89d65960b364..320893922d7cb 100644
--- a/clang/lib/CodeGen/CGStmt.cpp
+++ b/clang/lib/CodeGen/CGStmt.cpp
@@ -1597,11 +1597,11 @@ void CodeGenFunction::EmitCXXExpansionInstantiationStmt(
      else
       ContinueDest = getJumpDestInCurrentScope("expand.next");
 
-    LexicalScope ExpansionScope(*this, S.getSourceRange());
-    BreakContinueStack.push_back(BreakContinue(S, ExpandExit, ContinueDest));
-    EmitStmt(Inst);
-    BreakContinueStack.pop_back();
-    EmitBlock(ContinueDest.getBlock(), true);
+     LexicalScope ExpansionScope(*this, S.getSourceRange());
+     BreakContinueStack.push_back(BreakContinue(S, ExpandExit, ContinueDest));
+     EmitStmt(Inst);
+     BreakContinueStack.pop_back();
+     EmitBlock(ContinueDest.getBlock(), true);
   }
 }
 
diff --git a/clang/lib/Sema/SemaExpand.cpp b/clang/lib/Sema/SemaExpand.cpp
index 4a2d8fdc4e193..4951c9e52ccba 100644
--- a/clang/lib/Sema/SemaExpand.cpp
+++ b/clang/lib/Sema/SemaExpand.cpp
@@ -105,20 +105,19 @@ TryBuildIterableExpansionStmtInitializer(Sema &S, Expr *ExpansionInitializer,
 
   // Try ADL.
   if (!FoundBeginEnd) {
-    OverloadCandidateSet Candidates(ColonLoc,
-                                      OverloadCandidateSet::CSK_Normal);
+    OverloadCandidateSet Candidates(ColonLoc, OverloadCandidateSet::CSK_Normal);
 
-    S.AddArgumentDependentLookupCandidates(BeginName.getName(), ColonLoc,
-                                           ExpansionInitializer, /*ExplicitTemplateArgs=*/nullptr,
-                                           Candidates);
+    S.AddArgumentDependentLookupCandidates(
+        BeginName.getName(), ColonLoc, ExpansionInitializer,
+        /*ExplicitTemplateArgs=*/nullptr, Candidates);
 
     if (Candidates.empty())
       return Data;
 
     Candidates.clear(OverloadCandidateSet::CSK_Normal);
-    S.AddArgumentDependentLookupCandidates(EndName.getName(), ColonLoc,
-                                           ExpansionInitializer, /*ExplicitTemplateArgs=*/nullptr,
-                                           Candidates);
+    S.AddArgumentDependentLookupCandidates(
+        EndName.getName(), ColonLoc, ExpansionInitializer,
+        /*ExplicitTemplateArgs=*/nullptr, Candidates);
 
     if (Candidates.empty())
       return Data;
@@ -517,15 +516,16 @@ ExprResult Sema::BuildCXXDestructuringExpansionSelectExpr(DecompositionDecl *DD,
     return BD->getBinding();
 }
 
-std::optional<uint64_t>  Sema::ComputeExpansionSize(CXXExpansionStmt *Expansion) {
+std::optional<uint64_t>
+Sema::ComputeExpansionSize(CXXExpansionStmt *Expansion) {
   assert(!Expansion->hasDependentSize());
 
   if (isa<CXXEnumeratingExpansionStmt>(Expansion)) {
     uint64_t Size = cast<CXXExpansionInitListSelectExpr>(
-               Expansion->getExpansionVariable()->getInit())
-        ->getRangeExpr()
-        ->getExprs()
-        .size();
+                        Expansion->getExpansionVariable()->getInit())
+                        ->getRangeExpr()
+                        ->getExprs()
+                        .size();
 
     return Size;
   }
@@ -567,7 +567,7 @@ std::optional<uint64_t>  Sema::ComputeExpansionSize(CXXExpansionStmt *Expansion)
     ER.Diag = &Notes;
     if (!N.get()->EvaluateAsInt(ER, Context)) {
       Diag(Loc, diag::err_expansion_size_expr_not_ice);
-      for (const auto& [Location, PDiag] : Notes)
+      for (const auto &[Location, PDiag] : Notes)
         Diag(Location, PDiag);
       return std::nullopt;
     }
diff --git a/clang/lib/Sema/SemaTemplateInstantiate.cpp b/clang/lib/Sema/SemaTemplateInstantiate.cpp
index 5105697f42a89..0d56520d9733b 100644
--- a/clang/lib/Sema/SemaTemplateInstantiate.cpp
+++ b/clang/lib/Sema/SemaTemplateInstantiate.cpp
@@ -2413,8 +2413,8 @@ ExprResult
 TemplateInstantiator::TransformFunctionParmPackRefExpr(DeclRefExpr *E,
                                                        ValueDecl *PD) {
   typedef LocalInstantiationScope::DeclArgumentPack DeclArgumentPack;
-  llvm::PointerUnion<Decl *, DeclArgumentPack *> *Found
-    = getSema().CurrentInstantiationScope->getInstantiationOfIfExists(PD);
+  llvm::PointerUnion<Decl *, DeclArgumentPack *> *Found =
+      getSema().CurrentInstantiationScope->getInstantiationOfIfExists(PD);
 
   // This can happen when instantiating an expansion statement that contains
   // a pack (e.g. `template for (auto x : {{ts...}})`).
diff --git a/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp b/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp
index d24c404585900..25c9e97b9ad21 100644
--- a/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp
+++ b/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp
@@ -7097,7 +7097,7 @@ NamedDecl *Sema::FindInstantiatedDecl(SourceLocation Loc, NamedDecl *D,
 
   if (CurrentInstantiationScope) {
     if (auto Found = CurrentInstantiationScope->getInstantiationOfIfExists(D))
-      if (auto *FD = dyn_cast<NamedDecl>(cast<Decl*>(*Found)))
+      if (auto *FD = dyn_cast<NamedDecl>(cast<Decl *>(*Found)))
         return FD;
   }
 
diff --git a/clang/lib/Sema/TreeTransform.h b/clang/lib/Sema/TreeTransform.h
index 4d5c05d66306b..3d41ddd943c97 100644
--- a/clang/lib/Sema/TreeTransform.h
+++ b/clang/lib/Sema/TreeTransform.h
@@ -858,15 +858,16 @@ class TreeTransform {
   StmtResult TransformOMPInformationalDirective(OMPExecutableDirective *S);
 
   struct TransformCXXExpansionStmtResult {
-    ExpansionStmtDecl* NewESD{};
-    Stmt* NewInit{};
-    DeclStmt* NewExpansionVarDecl{};
+    ExpansionStmtDecl *NewESD{};
+    Stmt *NewInit{};
+    DeclStmt *NewExpansionVarDecl{};
     bool isValid() const { return NewESD != nullptr; }
   };
 
   TransformCXXExpansionStmtResult
   TransformCXXExpansionStmtCommonParts(CXXExpansionStmt *S) {
-    Decl* ESD = getDerived().TransformDecl(S->getDecl()->getLocation(), S->getDecl());
+    Decl *ESD =
+        getDerived().TransformDecl(S->getDecl()->getLocation(), S->getDecl());
     if (!ESD || ESD->isInvalidDecl())
       return {};
 
@@ -878,14 +879,14 @@ class TreeTransform {
       Init = SR.get();
     }
 
-    StmtResult ExpansionVar = getDerived().TransformStmt(S->getExpansionVarStmt());
+    StmtResult ExpansionVar =
+        getDerived().TransformStmt(S->getExpansionVarStmt());
     if (ExpansionVar.isInvalid())
       return {};
 
     return {cast<ExpansionStmtDecl>(ESD), Init, ExpansionVar.getAs<DeclStmt>()};
   }
 
-
 // FIXME: We use LLVM_ATTRIBUTE_NOINLINE because inlining causes a ridiculous
 // amount of stack usage with clang.
 #define STMT(Node, Parent)                        \
@@ -9316,7 +9317,8 @@ TreeTransform<Derived>::TransformCXXForRangeStmt(CXXForRangeStmt *S) {
 template <typename Derived>
 StmtResult TreeTransform<Derived>::TransformCXXEnumeratingExpansionStmt(
     CXXEnumeratingExpansionStmt *S) {
-  TransformCXXExpansionStmtResult Common = TransformCXXExpansionStmtCommonParts(S);
+  TransformCXXExpansionStmtResult Common =
+      TransformCXXExpansionStmtCommonParts(S);
   if (!Common.isValid())
     return StmtError();
 
@@ -9334,7 +9336,8 @@ StmtResult TreeTransform<Derived>::TransformCXXEnumeratingExpansionStmt(
 template <typename Derived>
 StmtResult TreeTransform<Derived>::TransformCXXIteratingExpansionStmt(
     CXXIteratingExpansionStmt *S) {
-  TransformCXXExpansionStmtResult Common = TransformCXXExpansionStmtCommonParts(S);
+  TransformCXXExpansionStmtResult Common =
+      TransformCXXExpansionStmtCommonParts(S);
   if (!Common.isValid())
     return StmtError();
 
@@ -9364,18 +9367,17 @@ StmtResult TreeTransform<Derived>::TransformCXXDependentExpansionStmt(
     CXXDependentExpansionStmt *S) {
   TransformCXXExpansionStmtResult Common;
   ExprResult ExpansionInitializer;
-  SmallVector<MaterializeTemporaryExpr*, 8> LifetimeExtendTemps;
+  SmallVector<MaterializeTemporaryExpr *, 8> LifetimeExtendTemps;
 
   // Apply lifetime extension in case this ends up begin a destructuring
   // expansion statement.
   {
     EnterExpressionEvaluationContext ExprEvalCtx(
-       SemaRef, SemaRef.currentEvaluationContext().Context);
+        SemaRef, SemaRef.currentEvaluationContext().Context);
     SemaRef.currentEvaluationContext().InLifetimeExtendingContext = true;
     SemaRef.currentEvaluationContext().RebuildDefaultArgOrDefaultInit = true;
 
-    Common =
-        TransformCXXExpansionStmtCommonParts(S);
+    Common = TransformCXXExpansionStmtCommonParts(S);
     if (!Common.isValid())
       return StmtError();
 
@@ -9476,8 +9478,8 @@ StmtResult TreeTransform<Derived>::TransformCXXExpansionInstantiationStmt(
     return S;
 
   return CXXExpansionInstantiationStmt::Create(
-      SemaRef.Context, S->getBeginLoc(), S->getEndLoc(), Instantiations, SharedStmts,
-      S->shouldApplyLifetimeExtensionToSharedStmts());
+      SemaRef.Context, S->getBeginLoc(), S->getEndLoc(), Instantiations,
+      SharedStmts, S->shouldApplyLifetimeExtensionToSharedStmts());
 }
 
 template <typename Derived>
diff --git a/clang/lib/Serialization/ASTReaderDecl.cpp b/clang/lib/Serialization/ASTReaderDecl.cpp
index f1d4f4c1ae659..e5498b0f98901 100644
--- a/clang/lib/Serialization/ASTReaderDecl.cpp
+++ b/clang/lib/Serialization/ASTReaderDecl.cpp
@@ -2773,7 +2773,8 @@ void ASTDeclReader::VisitStaticAssertDecl(StaticAssertDecl *D) {
 void ASTDeclReader::VisitExpansionStmtDecl(ExpansionStmtDecl *D) {
   VisitDecl(D);
   D->Expansion = cast<CXXExpansionStmt>(Record.readStmt());
-  D->Instantiations = cast_or_null<CXXExpansionInstantiationStmt>(Record.readStmt());
+  D->Instantiations =
+      cast_or_null<CXXExpansionInstantiationStmt>(Record.readStmt());
   D->TParams = Record.readTemplateParameterList();
 }
 

>From 8e6a4afa2ca7f30e85d3c975759ecb5bc2fbaad4 Mon Sep 17 00:00:00 2001
From: Sirraide <aeternalmail at gmail.com>
Date: Mon, 27 Oct 2025 05:06:53 +0100
Subject: [PATCH 29/33] clang-format, again

---
 clang/include/clang/AST/ASTNodeTraverser.h    |  2 +-
 clang/include/clang/AST/DeclBase.h            |  4 +---
 clang/include/clang/AST/DeclTemplate.h        |  6 +++--
 clang/include/clang/AST/ExprCXX.h             | 16 ++++++--------
 clang/include/clang/AST/RecursiveASTVisitor.h |  5 +++--
 clang/include/clang/AST/StmtCXX.h             | 22 +++++++++----------
 clang/include/clang/Parse/Parser.h            |  6 ++---
 clang/include/clang/Sema/Sema.h               | 10 +++++----
 clang/lib/AST/ComputeDependence.cpp           |  5 +++--
 clang/lib/AST/DeclPrinter.cpp                 |  4 ++--
 clang/lib/AST/DeclTemplate.cpp                | 11 +++++-----
 clang/lib/AST/ExprCXX.cpp                     |  5 ++---
 clang/lib/AST/StmtPrinter.cpp                 |  2 +-
 clang/lib/CodeGen/CGDecl.cpp                  |  2 +-
 clang/lib/CodeGen/CGStmt.cpp                  | 14 ++++++------
 clang/lib/CodeGen/CodeGenFunction.h           |  3 ++-
 clang/lib/Parse/ParseStmt.cpp                 | 18 ++++++++-------
 clang/lib/Sema/Sema.cpp                       |  3 ++-
 clang/lib/Sema/SemaExpand.cpp                 |  4 ++--
 clang/lib/Sema/SemaLambda.cpp                 | 17 +++++++++-----
 clang/lib/Sema/SemaStmt.cpp                   | 20 ++++++++---------
 .../lib/Sema/SemaTemplateInstantiateDecl.cpp  |  3 ++-
 clang/lib/Sema/TreeTransform.h                | 11 +++++-----
 clang/lib/Serialization/ASTWriterStmt.cpp     |  2 +-
 24 files changed, 101 insertions(+), 94 deletions(-)

diff --git a/clang/include/clang/AST/ASTNodeTraverser.h b/clang/include/clang/AST/ASTNodeTraverser.h
index 69915800397cf..de0aa7f62ad68 100644
--- a/clang/include/clang/AST/ASTNodeTraverser.h
+++ b/clang/include/clang/AST/ASTNodeTraverser.h
@@ -959,7 +959,7 @@ class ASTNodeTraverser
     }
   }
 
-  void VisitExpansionStmtDecl(const ExpansionStmtDecl* Node) {
+  void VisitExpansionStmtDecl(const ExpansionStmtDecl *Node) {
     Visit(Node->getExpansionPattern());
     if (Traversal != TK_IgnoreUnlessSpelledInSource)
       Visit(Node->getInstantiations());
diff --git a/clang/include/clang/AST/DeclBase.h b/clang/include/clang/AST/DeclBase.h
index 00866efa4b164..b26d10c698952 100644
--- a/clang/include/clang/AST/DeclBase.h
+++ b/clang/include/clang/AST/DeclBase.h
@@ -2195,9 +2195,7 @@ class DeclContext {
     return getDeclKind() == Decl::RequiresExprBody;
   }
 
-  bool isExpansionStmt() const {
-    return getDeclKind() == Decl::ExpansionStmt;
-  }
+  bool isExpansionStmt() const { return getDeclKind() == Decl::ExpansionStmt; }
 
   bool isNamespace() const { return getDeclKind() == Decl::Namespace; }
 
diff --git a/clang/include/clang/AST/DeclTemplate.h b/clang/include/clang/AST/DeclTemplate.h
index 4dc7fefb686e9..7edc8e80da825 100644
--- a/clang/include/clang/AST/DeclTemplate.h
+++ b/clang/include/clang/AST/DeclTemplate.h
@@ -3355,7 +3355,7 @@ class TemplateParamObjectDecl : public ValueDecl,
 class ExpansionStmtDecl : public Decl, public DeclContext {
   CXXExpansionStmt *Expansion = nullptr;
   TemplateParameterList *TParams;
-  CXXExpansionInstantiationStmt* Instantiations = nullptr;
+  CXXExpansionInstantiationStmt *Instantiations = nullptr;
 
   ExpansionStmtDecl(DeclContext *DC, SourceLocation Loc,
                     TemplateParameterList *TParams);
@@ -3377,7 +3377,9 @@ class ExpansionStmtDecl : public Decl, public DeclContext {
     return Instantiations;
   }
 
-  void setInstantiations(CXXExpansionInstantiationStmt *S) { Instantiations = S; }
+  void setInstantiations(CXXExpansionInstantiationStmt *S) {
+    Instantiations = S;
+  }
 
   NonTypeTemplateParmDecl *getIndexTemplateParm() const {
     return cast<NonTypeTemplateParmDecl>(TParams->getParam(0));
diff --git a/clang/include/clang/AST/ExprCXX.h b/clang/include/clang/AST/ExprCXX.h
index 5f50064d512ee..580523ed8d74c 100644
--- a/clang/include/clang/AST/ExprCXX.h
+++ b/clang/include/clang/AST/ExprCXX.h
@@ -5546,7 +5546,7 @@ class CXXExpansionInitListExpr final
   }
 
   const_child_range children() const {
-    Stmt** Stmts = getTrailingStmts();
+    Stmt **Stmts = getTrailingStmts();
     return const_child_range(Stmts, Stmts + NumExprs);
   }
 
@@ -5555,8 +5555,8 @@ class CXXExpansionInitListExpr final
   }
 
 private:
-  Stmt** getTrailingStmts() const {
-    return reinterpret_cast<Stmt**>(const_cast<Expr**>(getTrailingObjects()));
+  Stmt **getTrailingStmts() const {
+    return reinterpret_cast<Stmt **>(const_cast<Expr **>(getTrailingObjects()));
   }
 };
 
@@ -5581,13 +5581,11 @@ class CXXExpansionInitListSelectExpr : public Expr {
     return cast<CXXExpansionInitListExpr>(SubExprs[RANGE]);
   }
 
-  void setRangeExpr(CXXExpansionInitListExpr* E) {
-    SubExprs[RANGE] = E;
-  }
+  void setRangeExpr(CXXExpansionInitListExpr *E) { SubExprs[RANGE] = E; }
 
   Expr *getIndexExpr() { return SubExprs[INDEX]; }
   const Expr *getIndexExpr() const { return SubExprs[INDEX]; }
-  void setIndexExpr(Expr* E) { SubExprs[INDEX] = E; }
+  void setIndexExpr(Expr *E) { SubExprs[INDEX] = E; }
 
   SourceLocation getBeginLoc() const { return getRangeExpr()->getBeginLoc(); }
   SourceLocation getEndLoc() const { return getRangeExpr()->getEndLoc(); }
@@ -5599,8 +5597,8 @@ class CXXExpansionInitListSelectExpr : public Expr {
 
   const_child_range children() const {
     return const_child_range(
-            reinterpret_cast<Stmt **>(const_cast<Expr **>(SubExprs)),
-            reinterpret_cast<Stmt **>(const_cast<Expr **>(SubExprs + COUNT)));
+        reinterpret_cast<Stmt **>(const_cast<Expr **>(SubExprs)),
+        reinterpret_cast<Stmt **>(const_cast<Expr **>(SubExprs + COUNT)));
   }
 
   static bool classof(const Stmt *T) {
diff --git a/clang/include/clang/AST/RecursiveASTVisitor.h b/clang/include/clang/AST/RecursiveASTVisitor.h
index 33413f8a742fc..7941b15a79806 100644
--- a/clang/include/clang/AST/RecursiveASTVisitor.h
+++ b/clang/include/clang/AST/RecursiveASTVisitor.h
@@ -1882,8 +1882,9 @@ DEF_TRAVERSE_DECL(UsingShadowDecl, {})
 DEF_TRAVERSE_DECL(ConstructorUsingShadowDecl, {})
 
 DEF_TRAVERSE_DECL(ExpansionStmtDecl, {
-  if (D->getInstantiations() && getDerived().shouldVisitTemplateInstantiations())
-      TRY_TO(TraverseStmt(D->getInstantiations()));
+  if (D->getInstantiations() &&
+      getDerived().shouldVisitTemplateInstantiations())
+    TRY_TO(TraverseStmt(D->getInstantiations()));
 
   TRY_TO(TraverseStmt(D->getExpansionPattern()));
 })
diff --git a/clang/include/clang/AST/StmtCXX.h b/clang/include/clang/AST/StmtCXX.h
index 570151371e4e9..087f7f10c1285 100644
--- a/clang/include/clang/AST/StmtCXX.h
+++ b/clang/include/clang/AST/StmtCXX.h
@@ -583,12 +583,12 @@ class CXXExpansionStmt : public Stmt {
 
   bool hasDependentSize() const;
 
-  ExpansionStmtDecl* getDecl() { return ParentDecl; }
-  const ExpansionStmtDecl* getDecl() const { return ParentDecl; }
+  ExpansionStmtDecl *getDecl() { return ParentDecl; }
+  const ExpansionStmtDecl *getDecl() const { return ParentDecl; }
 
   Stmt *getInit() { return SubStmts[INIT]; }
   const Stmt *getInit() const { return SubStmts[INIT]; }
-  void setInit(Stmt* S) { SubStmts[INIT] = S; }
+  void setInit(Stmt *S) { SubStmts[INIT] = S; }
 
   VarDecl *getExpansionVariable();
   const VarDecl *getExpansionVariable() const {
@@ -600,11 +600,11 @@ class CXXExpansionStmt : public Stmt {
     return cast<DeclStmt>(SubStmts[VAR]);
   }
 
-  void setExpansionVarStmt(Stmt* S) { SubStmts[VAR] = S; }
+  void setExpansionVarStmt(Stmt *S) { SubStmts[VAR] = S; }
 
   Stmt *getBody() { return SubStmts[BODY]; }
   const Stmt *getBody() const { return SubStmts[BODY]; }
-  void setBody(Stmt* S) { SubStmts[BODY] = S; }
+  void setBody(Stmt *S) { SubStmts[BODY] = S; }
 
   static bool classof(const Stmt *T) {
     return T->getStmtClass() >= firstCXXExpansionStmtConstant &&
@@ -829,23 +829,21 @@ class CXXExpansionInstantiationStmt final
                                                     unsigned NumInstantiations,
                                                     unsigned NumSharedStmts);
 
-  ArrayRef<Stmt*> getAllSubStmts() const {
+  ArrayRef<Stmt *> getAllSubStmts() const {
     return getTrailingObjects(getNumSubStmts());
   }
 
-  MutableArrayRef<Stmt*> getAllSubStmts() {
+  MutableArrayRef<Stmt *> getAllSubStmts() {
     return getTrailingObjects(getNumSubStmts());
   }
 
-  unsigned getNumSubStmts() const {
-    return NumInstantiations + NumSharedStmts;
-  }
+  unsigned getNumSubStmts() const { return NumInstantiations + NumSharedStmts; }
 
-  ArrayRef<Stmt*> getInstantiations() const {
+  ArrayRef<Stmt *> getInstantiations() const {
     return getTrailingObjects(NumInstantiations);
   }
 
-  ArrayRef<Stmt*> getSharedStmts() const {
+  ArrayRef<Stmt *> getSharedStmts() const {
     return getAllSubStmts().drop_front(NumInstantiations);
   }
 
diff --git a/clang/include/clang/Parse/Parser.h b/clang/include/clang/Parse/Parser.h
index add8e6ae5bdf8..f5abbfc6f529e 100644
--- a/clang/include/clang/Parse/Parser.h
+++ b/clang/include/clang/Parse/Parser.h
@@ -7469,9 +7469,9 @@ class Parser : public CodeCompletionHandler {
   /// [C++0x]   expression
   /// [C++0x]   braced-init-list            [TODO]
   /// \endverbatim
-  StmtResult ParseForStatement(SourceLocation *TrailingElseLoc,
-                               LabelDecl *PrecedingLabel,
-                               ExpansionStmtDecl *ExpansionStmtDeclaration = nullptr);
+  StmtResult
+  ParseForStatement(SourceLocation *TrailingElseLoc, LabelDecl *PrecedingLabel,
+                    ExpansionStmtDecl *ExpansionStmtDeclaration = nullptr);
 
   void ParseForRangeInitializerAfterColon(ForRangeInit &FRI,
                                           ParsingDeclSpec *VarDeclSpec);
diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index 9ef3f9640434f..a6c63eb5b1287 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -15690,10 +15690,12 @@ class Sema final : public SemaBase {
   ExprResult BuildCXXExpansionInitializer(ExpansionStmtDecl *ESD,
                                           Expr *ExpansionInitializer);
 
-  StmtResult BuildCXXEnumeratingExpansionStmt(
-      Decl *ESD, Stmt *Init, Stmt *ExpansionVar, SourceLocation ForLoc,
-      SourceLocation LParenLoc, SourceLocation ColonLoc,
-      SourceLocation RParenLoc);
+  StmtResult BuildCXXEnumeratingExpansionStmt(Decl *ESD, Stmt *Init,
+                                              Stmt *ExpansionVar,
+                                              SourceLocation ForLoc,
+                                              SourceLocation LParenLoc,
+                                              SourceLocation ColonLoc,
+                                              SourceLocation RParenLoc);
 
   ExprResult
   BuildCXXExpansionInitListSelectExpr(CXXExpansionInitListExpr *Range,
diff --git a/clang/lib/AST/ComputeDependence.cpp b/clang/lib/AST/ComputeDependence.cpp
index c220e10a6e439..b5f2a22bdf54e 100644
--- a/clang/lib/AST/ComputeDependence.cpp
+++ b/clang/lib/AST/ComputeDependence.cpp
@@ -960,8 +960,9 @@ ExprDependence clang::computeDependence(OpenACCAsteriskSizeExpr *E) {
   return ExprDependence::None;
 }
 
-ExprDependence clang::computeDependence(CXXExpansionInitListExpr* ILE) {
+ExprDependence clang::computeDependence(CXXExpansionInitListExpr *ILE) {
   auto D = ExprDependence::None;
-  for (Expr* E : ILE->getExprs()) D |= E->getDependence();
+  for (Expr *E : ILE->getExprs())
+    D |= E->getDependence();
   return D;
 }
diff --git a/clang/lib/AST/DeclPrinter.cpp b/clang/lib/AST/DeclPrinter.cpp
index 45da7ef5a6cc5..6bc06918dbe98 100644
--- a/clang/lib/AST/DeclPrinter.cpp
+++ b/clang/lib/AST/DeclPrinter.cpp
@@ -113,7 +113,7 @@ namespace {
     void VisitNonTypeTemplateParmDecl(const NonTypeTemplateParmDecl *NTTP);
     void VisitTemplateTemplateParmDecl(const TemplateTemplateParmDecl *);
     void VisitHLSLBufferDecl(HLSLBufferDecl *D);
-    void VisitExpansionStmtDecl(const ExpansionStmtDecl* D);
+    void VisitExpansionStmtDecl(const ExpansionStmtDecl *D);
 
     void VisitOpenACCDeclareDecl(OpenACCDeclareDecl *D);
     void VisitOpenACCRoutineDecl(OpenACCRoutineDecl *D);
@@ -1330,7 +1330,7 @@ void DeclPrinter::VisitClassTemplatePartialSpecializationDecl(
   VisitCXXRecordDecl(D);
 }
 
-void DeclPrinter::VisitExpansionStmtDecl(const ExpansionStmtDecl* D) {
+void DeclPrinter::VisitExpansionStmtDecl(const ExpansionStmtDecl *D) {
   D->getExpansionPattern()->printPretty(Out, nullptr, Policy, Indentation, "\n",
                                         &Context);
 }
diff --git a/clang/lib/AST/DeclTemplate.cpp b/clang/lib/AST/DeclTemplate.cpp
index eea5eec0d6dbf..1cd88efb5c2a0 100644
--- a/clang/lib/AST/DeclTemplate.cpp
+++ b/clang/lib/AST/DeclTemplate.cpp
@@ -1795,15 +1795,16 @@ const Decl &clang::adjustDeclToTemplate(const Decl &D) {
 
 ExpansionStmtDecl::ExpansionStmtDecl(DeclContext *DC, SourceLocation Loc,
                                      TemplateParameterList *TParams)
-    : Decl(ExpansionStmt, DC, Loc), DeclContext(ExpansionStmt), TParams(TParams) {}
-
+    : Decl(ExpansionStmt, DC, Loc), DeclContext(ExpansionStmt),
+      TParams(TParams) {}
 
 ExpansionStmtDecl *ExpansionStmtDecl::Create(ASTContext &C, DeclContext *DC,
-                                 SourceLocation Loc,
-                                 TemplateParameterList *TParams) {
+                                             SourceLocation Loc,
+                                             TemplateParameterList *TParams) {
   return new (C, DC) ExpansionStmtDecl(DC, Loc, TParams);
 }
-ExpansionStmtDecl *ExpansionStmtDecl::CreateDeserialized(ASTContext &C, GlobalDeclID ID) {
+ExpansionStmtDecl *ExpansionStmtDecl::CreateDeserialized(ASTContext &C,
+                                                         GlobalDeclID ID) {
   return new (C, ID) ExpansionStmtDecl(nullptr, SourceLocation(), nullptr);
 }
 
diff --git a/clang/lib/AST/ExprCXX.cpp b/clang/lib/AST/ExprCXX.cpp
index eb73ab40a3e89..7ba49f74c1f7d 100644
--- a/clang/lib/AST/ExprCXX.cpp
+++ b/clang/lib/AST/ExprCXX.cpp
@@ -2039,7 +2039,7 @@ CXXExpansionInitListExpr *
 CXXExpansionInitListExpr::Create(const ASTContext &C, ArrayRef<Expr *> Exprs,
                                  SourceLocation LBraceLoc,
                                  SourceLocation RBraceLoc) {
-  void* Mem = C.Allocate(totalSizeToAlloc<Expr *>(Exprs.size()));
+  void *Mem = C.Allocate(totalSizeToAlloc<Expr *>(Exprs.size()));
   return new (Mem) CXXExpansionInitListExpr(Exprs, LBraceLoc, RBraceLoc);
 }
 
@@ -2051,8 +2051,7 @@ CXXExpansionInitListExpr::CreateEmpty(const ASTContext &C, EmptyShell Empty,
 }
 
 CXXExpansionInitListSelectExpr::CXXExpansionInitListSelectExpr(EmptyShell Empty)
-    : Expr(CXXExpansionInitListSelectExprClass, Empty) {
-}
+    : Expr(CXXExpansionInitListSelectExprClass, Empty) {}
 
 CXXExpansionInitListSelectExpr::CXXExpansionInitListSelectExpr(
     const ASTContext &C, CXXExpansionInitListExpr *Range, Expr *Idx)
diff --git a/clang/lib/AST/StmtPrinter.cpp b/clang/lib/AST/StmtPrinter.cpp
index 4f38f4a6bbccd..ce4455fd14479 100644
--- a/clang/lib/AST/StmtPrinter.cpp
+++ b/clang/lib/AST/StmtPrinter.cpp
@@ -493,7 +493,7 @@ void StmtPrinter::VisitCXXExpansionInstantiationStmt(
 void StmtPrinter::VisitCXXExpansionInitListExpr(
     CXXExpansionInitListExpr *Node) {
   OS << "{ ";
-  llvm::interleaveComma(Node->getExprs(), OS, [&](Expr* E) { PrintExpr(E); });
+  llvm::interleaveComma(Node->getExprs(), OS, [&](Expr *E) { PrintExpr(E); });
   OS << " }";
 }
 
diff --git a/clang/lib/CodeGen/CGDecl.cpp b/clang/lib/CodeGen/CGDecl.cpp
index 2e4bfac36d97b..a8f1000640f3a 100644
--- a/clang/lib/CodeGen/CGDecl.cpp
+++ b/clang/lib/CodeGen/CGDecl.cpp
@@ -144,7 +144,7 @@ void CodeGenFunction::EmitDecl(const Decl &D, bool EvaluateConditionDecl) {
     return;
 
   case Decl::ExpansionStmt: {
-    const auto* ESD = cast<ExpansionStmtDecl>(&D);
+    const auto *ESD = cast<ExpansionStmtDecl>(&D);
     assert(ESD->getInstantiations() && "expansion statement not expanded?");
     EmitStmt(ESD->getInstantiations());
     return;
diff --git a/clang/lib/CodeGen/CGStmt.cpp b/clang/lib/CodeGen/CGStmt.cpp
index 320893922d7cb..2892ef8a6e1ee 100644
--- a/clang/lib/CodeGen/CGStmt.cpp
+++ b/clang/lib/CodeGen/CGStmt.cpp
@@ -1578,7 +1578,7 @@ void CodeGenFunction::EmitCXXExpansionInstantiationStmt(
   LexicalScope Scope(*this, S.getSourceRange());
   LexicalScope Scope2(*this, S.getSourceRange());
 
-  for (const Stmt* DS : S.getSharedStmts())
+  for (const Stmt *DS : S.getSharedStmts())
     EmitStmt(DS);
 
   if (S.getInstantiations().empty() || !HaveInsertPoint())
@@ -1594,14 +1594,14 @@ void CodeGenFunction::EmitCXXExpansionInstantiationStmt(
 
     if (N == S.getInstantiations().size() - 1)
       ContinueDest = ExpandExit;
-     else
+    else
       ContinueDest = getJumpDestInCurrentScope("expand.next");
 
-     LexicalScope ExpansionScope(*this, S.getSourceRange());
-     BreakContinueStack.push_back(BreakContinue(S, ExpandExit, ContinueDest));
-     EmitStmt(Inst);
-     BreakContinueStack.pop_back();
-     EmitBlock(ContinueDest.getBlock(), true);
+    LexicalScope ExpansionScope(*this, S.getSourceRange());
+    BreakContinueStack.push_back(BreakContinue(S, ExpandExit, ContinueDest));
+    EmitStmt(Inst);
+    BreakContinueStack.pop_back();
+    EmitBlock(ContinueDest.getBlock(), true);
   }
 }
 
diff --git a/clang/lib/CodeGen/CodeGenFunction.h b/clang/lib/CodeGen/CodeGenFunction.h
index 70fd03d7f6b6e..cf6291166e95d 100644
--- a/clang/lib/CodeGen/CodeGenFunction.h
+++ b/clang/lib/CodeGen/CodeGenFunction.h
@@ -3685,7 +3685,8 @@ class CodeGenFunction : public CodeGenTypeCache {
   void EmitCXXForRangeStmt(const CXXForRangeStmt &S,
                            ArrayRef<const Attr *> Attrs = {});
 
-  void EmitCXXExpansionInstantiationStmt(const CXXExpansionInstantiationStmt& S);
+  void
+  EmitCXXExpansionInstantiationStmt(const CXXExpansionInstantiationStmt &S);
 
   /// Controls insertion of cancellation exit blocks in worksharing constructs.
   class OMPCancelStackRAII {
diff --git a/clang/lib/Parse/ParseStmt.cpp b/clang/lib/Parse/ParseStmt.cpp
index a64c1f59c5563..fcc375da7a332 100644
--- a/clang/lib/Parse/ParseStmt.cpp
+++ b/clang/lib/Parse/ParseStmt.cpp
@@ -1900,7 +1900,8 @@ bool Parser::isForRangeIdentifier() {
   return false;
 }
 
-void Parser::ParseForRangeInitializerAfterColon(ForRangeInit& FRI, ParsingDeclSpec *VarDeclSpec) {
+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;
@@ -1944,9 +1945,10 @@ void Parser::ParseForRangeInitializerAfterColon(ForRangeInit& FRI, ParsingDeclSp
       std::move(Actions.ExprEvalContexts.back().ForRangeLifetimeExtendTemps);
 }
 
-StmtResult Parser::ParseForStatement(SourceLocation *TrailingElseLoc,
-                                     LabelDecl *PrecedingLabel,
-                                     ExpansionStmtDecl *ExpansionStmtDeclaration) {
+StmtResult
+Parser::ParseForStatement(SourceLocation *TrailingElseLoc,
+                          LabelDecl *PrecedingLabel,
+                          ExpansionStmtDecl *ExpansionStmtDeclaration) {
   assert(Tok.is(tok::kw_for) && "Not a for stmt!");
   SourceLocation ForLoc = ConsumeToken();  // eat the 'for'.
 
@@ -2031,10 +2033,10 @@ StmtResult Parser::ParseForStatement(SourceLocation *TrailingElseLoc,
     ParseForRangeInitializerAfterColon(ForRangeInfo, /*VarDeclSpec=*/nullptr);
 
     Diag(Loc, diag::err_for_range_identifier)
-      << ForRangeInfo.ExpansionStmt
-      << ((getLangOpts().CPlusPlus11 && !getLangOpts().CPlusPlus17)
-              ? FixItHint::CreateInsertion(Loc, "auto &&")
-              : FixItHint());
+        << ForRangeInfo.ExpansionStmt
+        << ((getLangOpts().CPlusPlus11 && !getLangOpts().CPlusPlus17)
+                ? FixItHint::CreateInsertion(Loc, "auto &&")
+                : FixItHint());
 
     if (!ForRangeInfo.ExpansionStmt)
       ForRangeInfo.LoopVar =
diff --git a/clang/lib/Sema/Sema.cpp b/clang/lib/Sema/Sema.cpp
index 9a75bfe52bd1b..420c59ccffc00 100644
--- a/clang/lib/Sema/Sema.cpp
+++ b/clang/lib/Sema/Sema.cpp
@@ -1638,7 +1638,8 @@ DeclContext *Sema::getFunctionLevelDeclContext(bool AllowLambda) const {
                cast<CXXMethodDecl>(DC)->getOverloadedOperator() == OO_Call &&
                cast<CXXRecordDecl>(DC->getParent())->isLambda()) {
       DC = DC->getParent()->getParent();
-    } else break;
+    } else
+      break;
   }
 
   return DC;
diff --git a/clang/lib/Sema/SemaExpand.cpp b/clang/lib/Sema/SemaExpand.cpp
index 4951c9e52ccba..210d2f4a98239 100644
--- a/clang/lib/Sema/SemaExpand.cpp
+++ b/clang/lib/Sema/SemaExpand.cpp
@@ -398,7 +398,7 @@ StmtResult Sema::BuildNonEnumeratingCXXExpansionStmt(
       ESD, Init, ExpansionVarStmt, DS, ForLoc, LParenLoc, ColonLoc, RParenLoc);
 }
 
-StmtResult Sema::FinishCXXExpansionStmt(Stmt* Exp, Stmt *Body) {
+StmtResult Sema::FinishCXXExpansionStmt(Stmt *Exp, Stmt *Body) {
   if (!Exp || !Body)
     return StmtError();
 
@@ -419,7 +419,7 @@ StmtResult Sema::FinishCXXExpansionStmt(Stmt* Exp, Stmt *Body) {
     return StmtError();
 
   // Collect shared statements.
-  SmallVector<Stmt*, 1> Shared;
+  SmallVector<Stmt *, 1> Shared;
   if (Expansion->getInit())
     Shared.push_back(Expansion->getInit());
 
diff --git a/clang/lib/Sema/SemaLambda.cpp b/clang/lib/Sema/SemaLambda.cpp
index 16cb865e0b0b7..a37092c163b04 100644
--- a/clang/lib/Sema/SemaLambda.cpp
+++ b/clang/lib/Sema/SemaLambda.cpp
@@ -100,8 +100,9 @@ static inline UnsignedOrNone getStackIndexOfNearestEnclosingCaptureReadyLambda(
     // innermost nested lambda are dependent (otherwise we wouldn't have
     // arrived here) - so we don't yet have a lambda that can capture the
     // variable.
-    if (IsCapturingVariable &&
-        VarToCapture->getDeclContext()->getEnclosingNonExpansionStatementContext()->Equals(EnclosingDC))
+    if (IsCapturingVariable && VarToCapture->getDeclContext()
+                                   ->getEnclosingNonExpansionStatementContext()
+                                   ->Equals(EnclosingDC))
       return NoLambdaIsCaptureReady;
 
     // For an enclosing lambda to be capture ready for an entity, all
@@ -126,7 +127,8 @@ static inline UnsignedOrNone getStackIndexOfNearestEnclosingCaptureReadyLambda(
       if (IsCapturingThis && !LSI->isCXXThisCaptured())
         return NoLambdaIsCaptureReady;
     }
-    EnclosingDC = getLambdaAwareParentOfDeclContext(EnclosingDC)->getEnclosingNonExpansionStatementContext();
+    EnclosingDC = getLambdaAwareParentOfDeclContext(EnclosingDC)
+                      ->getEnclosingNonExpansionStatementContext();
 
     assert(CurScopeIndex);
     --CurScopeIndex;
@@ -2516,9 +2518,12 @@ Sema::LambdaScopeForCallOperatorInstantiationRAII::
   while (FDPattern && FD) {
     InstantiationAndPatterns.emplace_back(FDPattern, FD);
 
-    FDPattern =
-        dyn_cast<FunctionDecl>(getLambdaAwareParentOfDeclContext(FDPattern)->getEnclosingNonExpansionStatementContext());
-    FD = dyn_cast<FunctionDecl>(getLambdaAwareParentOfDeclContext(FD)->getEnclosingNonExpansionStatementContext());
+    FDPattern = dyn_cast<FunctionDecl>(
+        getLambdaAwareParentOfDeclContext(FDPattern)
+            ->getEnclosingNonExpansionStatementContext());
+    FD = dyn_cast<FunctionDecl>(
+        getLambdaAwareParentOfDeclContext(FD)
+            ->getEnclosingNonExpansionStatementContext());
   }
 
   // Add instantiated parameters and local vars to scopes, starting from the
diff --git a/clang/lib/Sema/SemaStmt.cpp b/clang/lib/Sema/SemaStmt.cpp
index 63c4c5e33fa53..14ebf7260ad6f 100644
--- a/clang/lib/Sema/SemaStmt.cpp
+++ b/clang/lib/Sema/SemaStmt.cpp
@@ -2454,7 +2454,6 @@ VarDecl *BuildForRangeVarDecl(Sema &SemaRef, SourceLocation Loc, QualType Type,
     Decl->setConstexpr(true);
   return Decl;
 }
-
 }
 
 static bool ObjCEnumerationCollection(Expr *Collection) {
@@ -2727,20 +2726,19 @@ static StmtResult RebuildForRangeWithDereference(Sema &SemaRef, Scope *S,
 
 void Sema::ApplyForRangeOrExpansionStatementLifetimeExtension(
     VarDecl *RangeVar, ArrayRef<MaterializeTemporaryExpr *> Temporaries) {
-    if (Temporaries.empty())
-      return;
+  if (Temporaries.empty())
+    return;
 
-    InitializedEntity Entity = InitializedEntity::InitializeVariable(RangeVar);
-    for (auto *MTE : Temporaries)
-      MTE->setExtendingDecl(RangeVar, Entity.allocateManglingNumber());
+  InitializedEntity Entity = InitializedEntity::InitializeVariable(RangeVar);
+  for (auto *MTE : Temporaries)
+    MTE->setExtendingDecl(RangeVar, Entity.allocateManglingNumber());
 }
 
 Sema::ForRangeBeginEndInfo Sema::BuildCXXForRangeBeginEndVars(
     Scope *S, VarDecl *RangeVar, SourceLocation ColonLoc,
     SourceLocation CoawaitLoc,
     ArrayRef<MaterializeTemporaryExpr *> LifetimeExtendTemps,
-    BuildForRangeKind Kind, bool ForExpansionStmt,
-    StmtResult *RebuildResult,
+    BuildForRangeKind Kind, bool ForExpansionStmt, StmtResult *RebuildResult,
     llvm::function_ref<StmtResult()> RebuildWithDereference) {
   QualType RangeVarType = RangeVar->getType();
   SourceLocation RangeLoc = RangeVar->getLocation();
@@ -3038,7 +3036,7 @@ StmtResult Sema::BuildCXXForRangeStmt(
           ActOnFinishFullExpr(NotEqExpr.get(), /*DiscardedValue*/ false);
     if (NotEqExpr.isInvalid()) {
       Diag(RangeLoc, diag::note_for_range_invalid_iterator)
-        << RangeLoc << 0 << BeginRangeRefTy;
+          << RangeLoc << 0 << BeginRangeRefTy;
       NoteForRangeBeginEndFunction(*this, BeginExpr, BEF_begin);
       if (!Context.hasSameType(BeginType, EndType))
         NoteForRangeBeginEndFunction(*this, EndExpr, BEF_end);
@@ -3061,7 +3059,7 @@ StmtResult Sema::BuildCXXForRangeStmt(
       IncrExpr = ActOnFinishFullExpr(IncrExpr.get(), /*DiscardedValue*/ false);
     if (IncrExpr.isInvalid()) {
       Diag(RangeLoc, diag::note_for_range_invalid_iterator)
-        << RangeLoc << 2 << BeginRangeRefTy ;
+          << RangeLoc << 2 << BeginRangeRefTy;
       NoteForRangeBeginEndFunction(*this, BeginExpr, BEF_begin);
       return StmtError();
     }
@@ -3075,7 +3073,7 @@ StmtResult Sema::BuildCXXForRangeStmt(
     ExprResult DerefExpr = ActOnUnaryOp(S, ColonLoc, tok::star, BeginRef.get());
     if (DerefExpr.isInvalid()) {
       Diag(RangeLoc, diag::note_for_range_invalid_iterator)
-        << RangeLoc << 1 << BeginRangeRefTy;
+          << RangeLoc << 1 << BeginRangeRefTy;
       NoteForRangeBeginEndFunction(*this, BeginExpr, BEF_begin);
       return StmtError();
     }
diff --git a/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp b/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp
index 25c9e97b9ad21..2cebdcff01811 100644
--- a/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp
+++ b/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp
@@ -2070,7 +2070,8 @@ Decl *TemplateDeclInstantiator::VisitStaticAssertDecl(StaticAssertDecl *D) {
       InstantiatedMessageExpr.get(), D->getRParenLoc(), D->isFailed());
 }
 
-Decl *TemplateDeclInstantiator::VisitExpansionStmtDecl(ExpansionStmtDecl *OldESD) {
+Decl *
+TemplateDeclInstantiator::VisitExpansionStmtDecl(ExpansionStmtDecl *OldESD) {
   Decl *Index = VisitNonTypeTemplateParmDecl(OldESD->getIndexTemplateParm());
   ExpansionStmtDecl *NewESD = SemaRef.BuildExpansionStmtDecl(
       Owner, OldESD->getBeginLoc(), cast<NonTypeTemplateParmDecl>(Index));
diff --git a/clang/lib/Sema/TreeTransform.h b/clang/lib/Sema/TreeTransform.h
index 3d41ddd943c97..6c94ef0afd01c 100644
--- a/clang/lib/Sema/TreeTransform.h
+++ b/clang/lib/Sema/TreeTransform.h
@@ -9419,9 +9419,8 @@ ExprResult TreeTransform<Derived>::TransformCXXExpansionInitListExpr(
     CXXExpansionInitListExpr *E) {
   bool ArgChanged = false;
   SmallVector<Expr *> SubExprs;
-  if (getDerived().TransformExprs(E->getExprs().data(),
-                                  E->getExprs().size(), false, SubExprs,
-                                  &ArgChanged))
+  if (getDerived().TransformExprs(E->getExprs().data(), E->getExprs().size(),
+                                  false, SubExprs, &ArgChanged))
     return ExprError();
 
   if (!getDerived().AlwaysRebuild() && !ArgChanged)
@@ -9449,8 +9448,8 @@ StmtResult TreeTransform<Derived>::TransformCXXExpansionInstantiationStmt(
     return false;
   };
 
-  SmallVector<Stmt*> SharedStmts;
-  SmallVector<Stmt*> Instantiations;
+  SmallVector<Stmt *> SharedStmts;
+  SmallVector<Stmt *> Instantiations;
 
   // Apply lifetime extension to the shared statements in case this is a
   // destructuring expansion statement (for other kinds of expansion
@@ -9495,7 +9494,7 @@ ExprResult TreeTransform<Derived>::TransformCXXExpansionInitListSelectExpr(
     return E;
 
   return SemaRef.BuildCXXExpansionInitListSelectExpr(
-          Range.getAs<CXXExpansionInitListExpr>(), Idx.get());
+      Range.getAs<CXXExpansionInitListExpr>(), Idx.get());
 }
 
 template <typename Derived>
diff --git a/clang/lib/Serialization/ASTWriterStmt.cpp b/clang/lib/Serialization/ASTWriterStmt.cpp
index a2d4daaeedcd8..d3f57716692f8 100644
--- a/clang/lib/Serialization/ASTWriterStmt.cpp
+++ b/clang/lib/Serialization/ASTWriterStmt.cpp
@@ -1763,7 +1763,7 @@ void ASTStmtWriter::VisitCXXExpansionInitListExpr(CXXExpansionInitListExpr *E) {
   Record.push_back(E->getNumExprs());
   Record.AddSourceLocation(E->getLBraceLoc());
   Record.AddSourceLocation(E->getRBraceLoc());
-  for (Expr* SubExpr : E->getExprs())
+  for (Expr *SubExpr : E->getExprs())
     Record.AddStmt(SubExpr);
   Code = serialization::EXPR_CXX_EXPANSION_INIT_LIST;
 }

>From 84a3d71c86b9f71eb71f400eddc2e10dc8cc2c8e Mon Sep 17 00:00:00 2001
From: Sirraide <aeternalmail at gmail.com>
Date: Mon, 27 Oct 2025 05:03:40 +0100
Subject: [PATCH 30/33] Update LanguageExtensions.rst

---
 clang/docs/LanguageExtensions.rst | 1 +
 1 file changed, 1 insertion(+)

diff --git a/clang/docs/LanguageExtensions.rst b/clang/docs/LanguageExtensions.rst
index bef6e9c14b182..7d6f65b16c12b 100644
--- a/clang/docs/LanguageExtensions.rst
+++ b/clang/docs/LanguageExtensions.rst
@@ -1784,6 +1784,7 @@ Pack Indexing                                 __cpp_pack_indexing              C
 ``= delete ("should have a reason");``        __cpp_deleted_function           C++26         C++03
 Variadic Friends                              __cpp_variadic_friend            C++26         C++03
 Trivial Relocatability                        __cpp_trivial_relocatability     C++26         C++03
+Expansion Statements                                                           C++26         C++03
 --------------------------------------------- -------------------------------- ------------- -------------
 Designated initializers (N494)                                                 C99           C89
 ``_Complex`` (N693)                                                            C99           C89, C++

>From ddede951812f92bd7544ce547b3755497301b5ff Mon Sep 17 00:00:00 2001
From: Sirraide <aeternalmail at gmail.com>
Date: Mon, 27 Oct 2025 05:32:05 +0100
Subject: [PATCH 31/33] Add extension warning test

---
 clang/test/SemaCXX/cxx2c-expansion-stmt-ext-warn.cpp | 8 ++++++++
 1 file changed, 8 insertions(+)
 create mode 100644 clang/test/SemaCXX/cxx2c-expansion-stmt-ext-warn.cpp

diff --git a/clang/test/SemaCXX/cxx2c-expansion-stmt-ext-warn.cpp b/clang/test/SemaCXX/cxx2c-expansion-stmt-ext-warn.cpp
new file mode 100644
index 0000000000000..13bee4371793a
--- /dev/null
+++ b/clang/test/SemaCXX/cxx2c-expansion-stmt-ext-warn.cpp
@@ -0,0 +1,8 @@
+// RUN: %clang_cc1 %s -std=c++2c -fsyntax-only -verify=cxx26 -Wpre-c++26-compat
+// RUN: %clang_cc1 %s -std=c++23 -fsyntax-only -verify=cxx23
+
+void f() {
+  template for (auto _ : {1}) { // cxx23-warning {{expansion statements are a C++2c extension}} \
+                                // cxx26-warning {{expansion statements are incompatible with C++ standards before C++2c}}
+  }
+}

>From b188e7ca29be97ba2398961a3e6df235b6171bc6 Mon Sep 17 00:00:00 2001
From: Sirraide <aeternalmail at gmail.com>
Date: Mon, 27 Oct 2025 07:01:08 +0100
Subject: [PATCH 32/33] Apply code review suggestions

---
 clang/include/clang/Sema/Sema.h |  8 ++++----
 clang/lib/Sema/SemaExpand.cpp   | 29 ++++++++++++-----------------
 2 files changed, 16 insertions(+), 21 deletions(-)

diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index a6c63eb5b1287..9084ad9e204b5 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -11065,10 +11065,10 @@ class Sema final : public SemaBase {
   /// expansion statement; begin-expr and end-expr are also provided; the
   /// latter are used in some diagnostics.
   struct ForRangeBeginEndInfo {
-    VarDecl *BeginVar{};
-    VarDecl *EndVar{};
-    Expr *BeginExpr{};
-    Expr *EndExpr{};
+    VarDecl *BeginVar = nullptr;
+    VarDecl *EndVar = nullptr;
+    Expr *BeginExpr = nullptr;
+    Expr *EndExpr = nullptr;
     bool isValid() const { return BeginVar != nullptr && EndVar != nullptr; }
   };
 
diff --git a/clang/lib/Sema/SemaExpand.cpp b/clang/lib/Sema/SemaExpand.cpp
index 210d2f4a98239..4ce60e0818b0f 100644
--- a/clang/lib/Sema/SemaExpand.cpp
+++ b/clang/lib/Sema/SemaExpand.cpp
@@ -12,16 +12,14 @@
 //===----------------------------------------------------------------------===//
 
 #include "clang/AST/DeclCXX.h"
-#include "clang/AST/DynamicRecursiveASTVisitor.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/EnterExpressionEvaluationContext.h>
-#include <clang/Sema/Template.h>
+#include "clang/Sema/Template.h"
 
 using namespace clang;
 using namespace sema;
@@ -34,10 +32,10 @@ struct IterableExpansionStmtData {
     Ok,
   };
 
-  DeclStmt *RangeDecl{};
-  DeclStmt *BeginDecl{};
-  DeclStmt *EndDecl{};
-  Expr *Initializer{};
+  DeclStmt *RangeDecl = nullptr;
+  DeclStmt *BeginDecl = nullptr;
+  DeclStmt *EndDecl = nullptr;
+  Expr *Initializer = nullptr;
   State TheState = State::NotIterable;
 
   bool isIterable() const { return TheState == State::Ok; }
@@ -520,15 +518,12 @@ std::optional<uint64_t>
 Sema::ComputeExpansionSize(CXXExpansionStmt *Expansion) {
   assert(!Expansion->hasDependentSize());
 
-  if (isa<CXXEnumeratingExpansionStmt>(Expansion)) {
-    uint64_t Size = cast<CXXExpansionInitListSelectExpr>(
-                        Expansion->getExpansionVariable()->getInit())
-                        ->getRangeExpr()
-                        ->getExprs()
-                        .size();
-
-    return Size;
-  }
+  if (isa<CXXEnumeratingExpansionStmt>(Expansion))
+    return cast<CXXExpansionInitListSelectExpr>(
+               Expansion->getExpansionVariable()->getInit())
+        ->getRangeExpr()
+        ->getExprs()
+        .size();
 
   if (auto *Destructuring = dyn_cast<CXXDestructuringExpansionStmt>(Expansion))
     return Destructuring->getDecompositionDecl()->bindings().size();

>From 5a0c30f8eb0a2a2b2013cbb97f03e849827436ae Mon Sep 17 00:00:00 2001
From: Sirraide <aeternalmail at gmail.com>
Date: Mon, 27 Oct 2025 22:52:13 +0100
Subject: [PATCH 33/33] Handle returning from an expansion properly in constant
 evaluation

---
 clang/lib/AST/ExprConstant.cpp                | 11 +++++++---
 .../SemaCXX/cxx2c-expansion-statements.cpp    | 21 +++++++++++++++++++
 2 files changed, 29 insertions(+), 3 deletions(-)

diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp
index 6d1461461431b..691d2c456b2e5 100644
--- a/clang/lib/AST/ExprConstant.cpp
+++ b/clang/lib/AST/ExprConstant.cpp
@@ -5956,19 +5956,24 @@ static EvalStmtResult EvaluateStmt(StmtResult &Result, EvalInfo &Info,
 
     // No need to push an extra scope for these since they're already
     // CompoundStmts.
+    EvalStmtResult ESR = ESR_Succeeded;
     for (const Stmt *Instantiation : Expansion->getInstantiations()) {
-      EvalStmtResult ESR = EvaluateStmt(Result, Info, Instantiation);
+      ESR = EvaluateStmt(Result, Info, Instantiation);
       if (ESR == ESR_Failed ||
           ShouldPropagateBreakContinue(Info, Expansion, &Scope, ESR))
         return ESR;
       if (ESR != ESR_Continue) {
         // Succeeded here actually means we encountered a 'break'.
-        assert(ESR == ESR_Succeeded);
+        assert(ESR == ESR_Succeeded || ESR == ESR_Returned);
         break;
       }
     }
 
-    return Scope.destroy() ? ESR_Succeeded : ESR_Failed;
+    // Map Continue back to Succeeded if we fell off the end of the loop.
+    if (ESR == ESR_Continue)
+      ESR = ESR_Succeeded;
+
+    return Scope.destroy() ? ESR : ESR_Failed;
   }
 
   case Stmt::SwitchStmtClass:
diff --git a/clang/test/SemaCXX/cxx2c-expansion-statements.cpp b/clang/test/SemaCXX/cxx2c-expansion-statements.cpp
index 592b22c92de86..bbb8de7b134f9 100644
--- a/clang/test/SemaCXX/cxx2c-expansion-statements.cpp
+++ b/clang/test/SemaCXX/cxx2c-expansion-statements.cpp
@@ -918,3 +918,24 @@ 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);
 }
+
+template <typename... Ts>
+constexpr int return_from_expansion(Ts... ts) {
+  template for (int i : {1, 2, 3}) {
+    return (ts + ...);
+  }
+  __builtin_unreachable();
+}
+
+static_assert(return_from_expansion(4, 5, 6) == 15);
+
+void not_constexpr();
+
+constexpr int empty_expansion_consteval() {
+  template for (auto _ : {}) {
+    not_constexpr();
+  }
+  return 3;
+}
+
+static_assert(empty_expansion_consteval() == 3);



More information about the cfe-commits mailing list