[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