From f8bd65dddb6b20c6ce491a49923633a640aa1f19 Mon Sep 17 00:00:00 2001 From: Sirraide Date: Fri, 24 Oct 2025 19:11:46 +0200 Subject: [PATCH 01/37] 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 { 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(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(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(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()) 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 { + friend class ASTStmtReader; + friend TrailingObjects; + + const unsigned NumExprs; + SourceLocation LBraceLoc; + SourceLocation RBraceLoc; + + CXXExpansionInitListExpr(EmptyShell ES, unsigned NumExprs); + CXXExpansionInitListExpr(ArrayRef Exprs, SourceLocation LBraceLoc, + SourceLocation RBraceLoc); + +public: + static CXXExpansionInitListExpr *Create(const ASTContext &C, + ArrayRef Exprs, + SourceLocation LBraceLoc, + SourceLocation RBraceLoc); + + static CXXExpansionInitListExpr * + CreateEmpty(const ASTContext &C, EmptyShell Empty, unsigned NumExprs); + + ArrayRef getExprs() const { return getTrailingObjects(NumExprs); } + MutableArrayRef 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(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(const_cast(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(SubExprs[RANGE]); + } + + const CXXExpansionInitListExpr *getRangeExpr() const { + return cast(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(SubExprs), + reinterpret_cast(SubExprs + COUNT)); + } + + const_child_range children() const { + return const_child_range( + reinterpret_cast(const_cast(SubExprs)), + reinterpret_cast(const_cast(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 + 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(this)->getExpansionVariable(); + } + + DeclStmt *getExpansionVarStmt() { return cast(SubStmts[VAR]); } + const DeclStmt *getExpansionVarStmt() const { + return cast(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 +/// { +/// +/// { +/// <1st instantiation> +/// } +/// ... +/// { +/// +/// } +/// } +/// \endverbatim +class CXXExpansionInstantiationStmt final + : public Stmt, + llvm::TrailingObjects { + 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 Instantiations, + ArrayRef SharedStmts); + +public: + static CXXExpansionInstantiationStmt * + Create(ASTContext &C, SourceLocation Loc, ArrayRef Instantiations, + ArrayRef SharedStmts); + + static CXXExpansionInstantiationStmt *CreateEmpty(ASTContext &C, + EmptyShell Empty, + unsigned NumInstantiations, + unsigned NumSharedStmts); + + ArrayRef getAllSubStmts() const { + return getTrailingObjects(getNumSubStmts()); + } + + MutableArrayRef getAllSubStmts() { + return getTrailingObjects(getNumSubStmts()); + } + + unsigned getNumSubStmts() const { + return NumInstantiations + NumSharedStmts; + } + + ArrayRef getInstantiations() const { + return getTrailingObjects(NumInstantiations); + } + + ArrayRef 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; def Friend : DeclNode; def FriendTemplate : DeclNode; def StaticAssert : DeclNode; +def ExpansionStmt : DeclNode, DeclContext; def Block : DeclNode, DeclContext; def OutlinedFunction : DeclNode, DeclContext; def Captured : DeclNode, 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, 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
; +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; def CoroutineBodyStmt : StmtNode; def CoreturnStmt : StmtNode; +// C++ expansion statements (P1306) +def CXXExpansionStmt : StmtNode; +def CXXEnumeratingExpansionStmt : StmtNode; +def CXXExpansionInstantiationStmt : StmtNode; // *Not* derived from CXXExpansionStmt! + // Expressions def Expr : StmtNode; def PredefinedExpr : StmtNode; @@ -177,6 +182,12 @@ def CoyieldExpr : StmtNode; def ConceptSpecializationExpr : StmtNode; def RequiresExpr : StmtNode; +// C++26 Expansion statement support expressions +def CXXExpansionInitListExpr : StmtNode; +def CXXExpansionInitListSelectExpr : StmtNode; +//def CXXDestructurableExpansionSelectExpr : StmtNode; +//def CXXIterableExpansionSelectExpr : StmtNode; + // Obj-C Expressions. def ObjCStringLiteral : StmtNode; def ObjCBoxedExpr : StmtNode; 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 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 &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 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 LifetimeExtendTemps, + BuildForRangeKind Kind, bool ForExpansionStmt, + StmtResult *RebuildResult = nullptr, + llvm::function_ref 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 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 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 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(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(this)) + if (isa(this)) return true; if (const auto *Record = dyn_cast(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(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(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 Exprs, + SourceLocation LBraceLoc, + SourceLocation RBraceLoc) + : Expr(CXXExpansionInitListExprClass, QualType(), VK_PRValue, OK_Ordinary), + NumExprs(static_cast(Exprs.size())), LBraceLoc(LBraceLoc), + RBraceLoc(RBraceLoc) { + llvm::uninitialized_copy(Exprs, getTrailingObjects()); + setDependence(computeDependence(this)); +} + +CXXExpansionInitListExpr * +CXXExpansionInitListExpr::Create(const ASTContext &C, ArrayRef Exprs, + SourceLocation LBraceLoc, + SourceLocation RBraceLoc) { + void* Mem = C.Allocate(totalSizeToAlloc(Exprs.size())); + return new (Mem) CXXExpansionInitListExpr(Exprs, LBraceLoc, RBraceLoc); +} + +CXXExpansionInitListExpr * +CXXExpansionInitListExpr::CreateEmpty(const ASTContext &C, EmptyShell Empty, + unsigned NumExprs) { + void *Mem = C.Allocate(totalSizeToAlloc(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(DC) || isa(DC) || isa(DC); + return isa(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(DC)) GD = GlobalDecl(DD, Dtor_Complete); + else if (DC->isExpansionStmt()) + GD = getParentOfLocalEntity(DC->getEnclosingNonExpansionStatementContext()); else GD = GlobalDecl(cast(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(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(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(getExpansionVarStmt())->getSingleDecl(); + assert(LV && "No expansion variable in CXXExpansionStmt"); + return cast(LV); +} + +bool CXXExpansionStmt::hasDependentSize() const { + if (isa(this)) + return getExpansionVariable()->getInit()->containsUnexpandedParameterPack(); + + llvm_unreachable("Invalid expansion statement class"); +} + +size_t CXXExpansionStmt::getNumInstantiations() const { + if (isa(this)) + return cast( + 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 Instantiations, + ArrayRef 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 Instantiations, + ArrayRef SharedStmts) { + void *Mem = C.Allocate( + totalSizeToAlloc(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(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(&D); + assert(ESD->getInstantiations() && "expansion statement not expanded?"); + EmitStmt(ESD->getInstantiations()); + return; + } + case Decl::NamespaceAlias: if (CGDebugInfo *DI = getDebugInfo()) DI->EmitNamespaceAlias(cast(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 Attrs) { case Stmt::CXXForRangeStmtClass: EmitCXXForRangeStmt(cast(*S), Attrs); break; + case Stmt::CXXEnumeratingExpansionStmtClass: + llvm_unreachable("unexpanded expansion statements should not be emitted"); + case Stmt::CXXExpansionInstantiationStmtClass: + EmitCXXExpansionInstantiationStmt(cast(*S)); + break; case Stmt::SEHTryStmtClass: EmitSEHTryStmt(cast(*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 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(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(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(DC) || isa(DC) || isa(DC) || - isa(DC)) { + if (isa(DC)) { DC = DC->getParent(); } else if (!AllowLambda && isa(DC) && cast(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(DC)) + if (isa(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 + +using namespace clang; +using namespace sema; + +static unsigned ExtractParmVarDeclDepth(Expr *E) { + if (auto *DRE = dyn_cast(E)) { + if (auto *PVD = cast(DRE->getDecl())) + return PVD->getDepth(); + } else if (auto *SNTTPE = cast(E)) { + if (auto *PVD = cast(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 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(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(Var.get()); + return S.BuildCXXForRangeBeginEndVars( + Scope, cast(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 LifetimeExtendTemps) { + // TODO: Do we actually need a BuildForRangeKind here at all? + if (!ExpansionInitializer || !ExpansionVarStmt || Kind == BFRK_Check) + return StmtError(); + + auto *DS = cast(ExpansionVarStmt); + if (!DS->isSingleDecl()) { + Diag(DS->getBeginLoc(), diag::err_type_defined_in_for_range); + return StmtError(); + } + + VarDecl *ExpansionVar = cast(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(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(ESD), Init, cast(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 LifetimeExtendTemps) { + auto *ExpansionVar = cast(ExpansionVarStmt); + Expr *Initializer = cast(ExpansionVar->getSingleDecl())->getInit(); + assert(Initializer); + + if (auto *WithCleanups = dyn_cast(Initializer)) + Initializer = WithCleanups->getSubExpr(); + + if (Initializer->isTypeDependent()) + llvm_unreachable("TODO"); + + if (isa(Initializer)) + return CXXExpansionStmt::Create(Context, Init, ExpansionVar, + ESD->getLocation(), ForLoc, LParenLoc, + ColonLoc, RParenLoc); + + llvm_unreachable("TODO"); + /*else if (isa(Initializer)) { + return BuildCXXDestructurableExpansionStmt(TemplateKWLoc, ForLoc, LParenLoc, + Init, ExpansionVarStmt, ColonLoc, + RParenLoc, Index); + } else if (auto *IESE = dyn_cast(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(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 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 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(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(getLambdaAwareParentOfDeclContext(FDPattern)); - FD = dyn_cast(getLambdaAwareParentOfDeclContext(FD)); + dyn_cast(getLambdaAwareParentOfDeclContext(FDPattern)->getEnclosingNonExpansionStatementContext()); + FD = dyn_cast(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() != 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 **)&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 **)&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 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 LifetimeExtendTemps, + BuildForRangeKind Kind, bool ForExpansionStmt, + StmtResult *RebuildResult, + llvm::function_ref 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(UnqAT)) + BoundExpr = IntegerLiteral::Create( + Context, CAT->getSize(), Context.getPointerDiffType(), RangeLoc); + else if (const VariableArrayType *VAT = + dyn_cast(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(Range)) { + if (ParmVarDecl *PVD = dyn_cast(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(UnqAT)) - BoundExpr = IntegerLiteral::Create( - Context, CAT->getSize(), Context.getPointerDiffType(), RangeLoc); - else if (const VariableArrayType *VAT = - dyn_cast(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(Range)) { - if (ParmVarDecl *PVD = dyn_cast(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 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 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(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()); + 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(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(D)); + return cast( + cast(*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::TransformCXXForRangeStmt(CXXForRangeStmt *S) { return FinishCXXForRangeStmt(NewStmt.get(), Body.get()); } +template +StmtResult TreeTransform::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(ESD), Init, ExpansionVar.getAs(), + 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 +ExprResult TreeTransform::TransformCXXExpansionInitListExpr( + CXXExpansionInitListExpr *E) { + bool ArgChanged = false; + SmallVector 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 +StmtResult TreeTransform::TransformCXXExpansionInstantiationStmt( + CXXExpansionInstantiationStmt *S) { + bool SubStmtChanged = false; + auto TransformStmts = [&](SmallVectorImpl &NewStmts, + ArrayRef 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 SharedStmts; + SmallVector 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 +ExprResult TreeTransform::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(), Idx.get()); +} + + template StmtResult TreeTransform::TransformMSDependentExistsStmt( @@ -15596,7 +15692,7 @@ TreeTransform::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(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 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 { 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(Record.readStmt()); + D->Instantiations = cast(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(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(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 +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 +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 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(); + template for (constexpr auto x : {1, 2, 3}) + static_assert(tg()); + + 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(); + + 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() == 4); // expected-error 3 {{static assertion failed due to requirement 'tg() == 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 +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()); + template for (constexpr auto x : {T(1), T(2)}) static_assert(tg()); +} + +template +struct s1 { + template + 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()); + template for (constexpr T x : {U(1), U(2)}) static_assert(tg()); + template for (constexpr auto x : {T(1), U(2)}) static_assert(tg()); + } +}; + +template +void t2() { + template for (T x : {}) g(x); +} + +void f2() { + t1(); + t1(); + s1().tf(); + s1().tf(); + s1().tf(); + s1().tf(); + t2(); + t2(); + t2(); +} 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 Date: Fri, 24 Oct 2025 21:46:08 +0200 Subject: [PATCH 02/37] 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 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(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(cast(*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(D)); - return cast( - cast(*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 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(); + template for (constexpr auto x : {10, 11, 12}) + static_assert(tg()); + + 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(); +} + +template +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()); + template for (constexpr auto x : {T(9), T(10)}) static_assert(tg()); +} + +template +struct s1 { + template + 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()); + template for (constexpr T x : {U(15), U(16)}) static_assert(tg()); + template for (constexpr auto x : {T(17), U(18)}) static_assert(tg()); + } +}; + +template +void t2() { + template for (T x : {}) g(x); +} + +void f6() { + t1(); + t1(); + s1().tf(); + s1().tf(); + s1().tf(); + s1().tf(); + t2(); + t2(); + t2(); +} + +// 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 Date: Fri, 24 Oct 2025 23:21:01 +0200 Subject: [PATCH 03/37] 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 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(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(this)) - return getExpansionVariable()->getInit()->containsUnexpandedParameterPack(); + return cast( + getExpansionVariable()->getInit()) + ->getRangeExpr() + ->containsPackExpansion(); llvm_unreachable("Invalid expansion statement class"); } size_t CXXExpansionStmt::getNumInstantiations() const { - if (isa(this)) + assert(!hasDependentSize()); + + if (isa(this)) { return cast( 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(D)) { + assert(SemaRef.CurrentInstantiationScope); + return cast( + *SemaRef.CurrentInstantiationScope->findInstantiationOf(D)); + } + return SemaRef.FindInstantiatedDecl(Loc, cast(D), TemplateArgs); } @@ -2408,8 +2414,12 @@ TemplateInstantiator::TransformFunctionParmPackRefExpr(DeclRefExpr *E, ValueDecl *PD) { typedef LocalInstantiationScope::DeclArgumentPack DeclArgumentPack; llvm::PointerUnion *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(*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 *Instantiation = - CurrentInstantiationScope->findInstantiationOf( + CurrentInstantiationScope->getInstantiationOfIfExists( cast(ParmPack.first)); - if (isa(*Instantiation)) { + if (Instantiation && isa(*Instantiation)) { // We could expand this function parameter pack. NewPackSize = cast(*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(); } +struct X { + int a, b, c; +}; + +template +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 +void t4() { + template for (constexpr auto x : {is...}) { + g(x); + tg(); + } + + template for (constexpr auto x : {1, is..., 2, is..., 3}) { + g(x); + tg(); + } + + template for (constexpr auto x : {4, is..., is..., 5}) { + g(x); + tg(); + } + + template for (constexpr X x : {{is...}, {is...}, {6, 7, 8}}) { + g(x.a); + tg(); + } + + template for (constexpr X x : {X{is...}}) { + g(x.a); + tg(); + } +} + +template +struct s2 { + template + 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(); + template for (constexpr X x : {{is...}, {js...}}) tg(); + } +}; + +void f7() { + t3(42, 43, 44); + t4<42, 43, 44>(); + s2<1, 2, 3>().tf<4, 5, 6>(); +} + +template +void t5() { + ([] { + template for (constexpr auto x : {is}) { + g(x); + tg(); + } + }(), ...); +} + +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 Date: Sat, 25 Oct 2025 20:29:19 +0200 Subject: [PATCH 04/37] 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(getRangeVarStmt()->getSingleDecl()); + } + + VarDecl* getRangeVar() { + return cast(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(getBeginVarStmt()->getSingleDecl()); + } + + VarDecl* getBeginVar() { + return cast(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(getEndVarStmt()->getSingleDecl()); + } + + VarDecl* getEndVar() { + return cast(getEndVarStmt()->getSingleDecl()); + } + + child_range children() { + const_child_range CCR = + const_cast(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(CXXExpansionStmt::COUNT) + + static_cast(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; // C++ expansion statements (P1306) def CXXExpansionStmt : StmtNode; def CXXEnumeratingExpansionStmt : StmtNode; +def CXXIteratingExpansionStmt : StmtNode; def CXXExpansionInstantiationStmt : StmtNode; // *Not* derived from CXXExpansionStmt! // Expressions @@ -186,7 +187,6 @@ def RequiresExpr : StmtNode; def CXXExpansionInitListExpr : StmtNode; def CXXExpansionInitListSelectExpr : StmtNode; //def CXXDestructurableExpansionSelectExpr : StmtNode; -//def CXXIterableExpansionSelectExpr : StmtNode; // Obj-C Expressions. def ObjCStringLiteral : StmtNode; 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 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(D)->getTemplateParameters()->getParam(Index), {}}; case Decl::Kind::ClassTemplateSpecialization: { @@ -1718,6 +1717,10 @@ clang::getReplacedTemplateParameter(Decl *D, unsigned Index) { return getReplacedTemplateParameter( cast(D)->getTemplateSpecializationInfo()->getTemplate(), Index); + case Decl::Kind::ExpansionStmt: + return { + cast(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(D); if (VD && !CheckLocalVariableDeclaration(Info, VD)) return ESR_Failed; + + if (const auto *ESD = dyn_cast(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(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(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(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(this)) { - return cast( - 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 Attrs) { EmitCXXForRangeStmt(cast(*S), Attrs); break; case Stmt::CXXEnumeratingExpansionStmtClass: + case Stmt::CXXIteratingExpansionStmtClass: llvm_unreachable("unexpanded expansion statements should not be emitted"); case Stmt::CXXExpansionInstantiationStmtClass: EmitCXXExpansionInstantiationStmt(cast(*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 #include using namespace clang; using namespace sema; -static unsigned ExtractParmVarDeclDepth(Expr *E) { - if (auto *DRE = dyn_cast(E)) { - if (auto *PVD = cast(DRE->getDecl())) - return PVD->getDepth(); - } else if (auto *SNTTPE = cast(E)) { - if (auto *PVD = cast(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 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(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(Var.get()); - return S.BuildCXXForRangeBeginEndVars( - Scope, cast(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(Var.get()); + Sema::ForRangeBeginEndInfo Info = S.BuildCXXForRangeBeginEndVars( + Scope, cast(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(); + Data.EndDecl = EndStmt.getAs(); + 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(ExpansionVarStmt); if (!DS->isSingleDecl()) { Diag(DS->getBeginLoc(), diag::err_type_defined_in_for_range); @@ -267,26 +239,61 @@ StmtResult Sema::ActOnCXXExpansionStmt( } VarDecl *ExpansionVar = cast(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(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(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 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 Shared; if (Expansion->getInit()) Shared.push_back(Expansion->getInit()); + if (auto *Iter = dyn_cast(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 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(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 Sema::ComputeExpansionSize(CXXExpansionStmt *Expansion) { + assert(!Expansion->hasDependentSize()); + + if (isa(Expansion)) { + uint64_t Size = cast( + 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(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 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(ESD), Init, ExpansionVar.getAs()}; + } + + // 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::TransformCXXForRangeStmt(CXXForRangeStmt *S) { template StmtResult TreeTransform::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(ESD), Init, ExpansionVar.getAs(), + return SemaRef.FinishCXXExpansionStmt(Expansion, Body.get()); +} + +template +StmtResult TreeTransform::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(), Begin.getAs(), End.getAs(), 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(Record.readSubStmt())); + S->setBeginVarStmt(cast(Record.readSubStmt())); + S->setEndVarStmt(cast(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 +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 integers{1, 2, 3}; + int sum = 0; + template for (auto x : integers) sum += x; + return sum; +} + +int f2() { + static constexpr Array integers{1, 2, 3}; + int sum = 0; + template for (constexpr auto x : integers) sum += x; + return sum; +} + +int f3() { + static constexpr Array integers{}; + int sum = 0; + template for (constexpr auto x : integers) { + static_assert(false, "not expanded"); + sum += x; + } + return sum; +} + +int f4() { + static constexpr Array a{1, 2}; + static constexpr Array 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 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(); t2(); } + +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; + +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 +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 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 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 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 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 Date: Sat, 25 Oct 2025 22:29:21 +0200 Subject: [PATCH 05/37] 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(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(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(CXXExpansionStmt::COUNT) + static_cast(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; def CXXExpansionStmt : StmtNode; def CXXEnumeratingExpansionStmt : StmtNode; def CXXIteratingExpansionStmt : StmtNode; +def CXXDependentExpansionStmt : StmtNode; def CXXExpansionInstantiationStmt : StmtNode; // *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 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 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 LifetimeExtendTemps = {}); + std::optional 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(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 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(*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 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(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(ESD), Init, cast(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 LifetimeExtendTemps) { + VarDecl *ExpansionVar = cast(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(ESD), Init, cast(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(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(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::TransformCXXIteratingExpansionStmt( return SemaRef.FinishCXXExpansionStmt(Expansion, Body.get()); } +template +StmtResult TreeTransform::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 ExprResult TreeTransform::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(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 +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 Date: Sun, 26 Oct 2025 01:34:30 +0200 Subject: [PATCH 06/37] 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(Decomposition); + } + + const DecompositionDecl *getDecompositionDecl() const { + return cast(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(&Index), + reinterpret_cast(&Index + 1)); + } + + const_child_range children() const { + return const_child_range( + reinterpret_cast(const_cast(&Index)), + reinterpret_cast(const_cast(&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(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(this)->getDecompositionDecl(); + } + + child_range children() { + const_child_range CCR = + const_cast(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(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
; +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; def CXXExpansionStmt : StmtNode; def CXXEnumeratingExpansionStmt : StmtNode; def CXXIteratingExpansionStmt : StmtNode; +def CXXDestructuringExpansionStmt : StmtNode; def CXXDependentExpansionStmt : StmtNode; def CXXExpansionInstantiationStmt : StmtNode; // *Not* derived from CXXExpansionStmt! @@ -187,7 +188,7 @@ def RequiresExpr : StmtNode; // C++26 Expansion statement support expressions def CXXExpansionInitListExpr : StmtNode; def CXXExpansionInitListSelectExpr : StmtNode; -//def CXXDestructurableExpansionSelectExpr : StmtNode; +def CXXDestructuringExpansionSelectExpr : StmtNode; // Obj-C Expressions. def ObjCStringLiteral : StmtNode; 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(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(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(this)) + return false; + if (isa(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( + cast(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(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 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 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 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 LifetimeExtendTemps) { - auto *ExpansionVar = cast(ExpansionVarStmt); - Expr *Initializer = cast(ExpansionVar->getSingleDecl())->getInit(); - assert(Initializer); - - if (auto *WithCleanups = dyn_cast(Initializer)) - Initializer = WithCleanups->getSubExpr(); - - if (Initializer->isTypeDependent()) - llvm_unreachable("TODO"); - - if (isa(Initializer)) - return CXXExpansionStmt::Create(Context, Init, ExpansionVar, - ESD->getLocation(), ForLoc, LParenLoc, - ColonLoc, RParenLoc); - - llvm_unreachable("TODO"); - /*else if (isa(Initializer)) { - return BuildCXXDestructurableExpansionStmt(TemplateKWLoc, ForLoc, LParenLoc, - Init, ExpansionVarStmt, ColonLoc, - RParenLoc, Index); - } else if (auto *IESE = dyn_cast(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(); + auto *DD = cast(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(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 Sema::ComputeExpansionSize(CXXExpansionStmt *Expansion) { assert(!Expansion->hasDependentSize()); @@ -468,6 +523,9 @@ std::optional Sema::ComputeExpansionSize(CXXExpansionStmt *Expansion) return Size; } + if (auto *Destructuring = dyn_cast(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::TransformCXXDependentExpansionStmt( return SemaRef.FinishCXXExpansionStmt(Expansion.get(), Body.get()); } +template +StmtResult TreeTransform::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 ExprResult TreeTransform::TransformCXXExpansionInitListExpr( CXXExpansionInitListExpr *E) { @@ -9450,6 +9475,22 @@ ExprResult TreeTransform::TransformCXXExpansionInitListSelectExpr( Range.getAs(), Idx.get()); } +template +ExprResult TreeTransform::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(DD), Idx.get()); +} template 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(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(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 struct initializer_list { @@ -13,7 +13,7 @@ struct S { constexpr S(int x) : x{x} {} }; -void g(int); +void g(int); // #g template 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 +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() == 20); +static_assert(destructure() == 12); +static_assert(destructure() == 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 Date: Sun, 26 Oct 2025 02:38:48 +0100 Subject: [PATCH 07/37] 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 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(); auto *DD = cast(DS->getSingleDecl()); + if (DD->isInvalidDecl()) + return StmtError(); + ExprResult Select = BuildCXXDestructuringExpansionSelectExpr(DD, Index); if (Select.isInvalid()) { ActOnInitializerError(ExpansionVar); @@ -530,7 +540,7 @@ std::optional 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(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 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 Date: Sun, 26 Oct 2025 02:51:31 +0100 Subject: [PATCH 08/37] 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 +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(); + destructure(); + destructure(); +} + +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 Date: Sun, 26 Oct 2025 03:43:40 +0100 Subject: [PATCH 09/37] 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 Date: Sun, 26 Oct 2025 04:04:09 +0100 Subject: [PATCH 10/37] 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 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 Date: Sun, 26 Oct 2025 04:33:30 +0100 Subject: [PATCH 11/37] 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; + struct SwitchInfo : llvm::PointerIntPair { + 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 Date: Sun, 26 Oct 2025 04:35:02 +0100 Subject: [PATCH 12/37] 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 Date: Sun, 26 Oct 2025 04:37:47 +0100 Subject: [PATCH 13/37] 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 Date: Sun, 26 Oct 2025 05:42:56 +0100 Subject: [PATCH 14/37] 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(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(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 Date: Sun, 26 Oct 2025 06:40:23 +0100 Subject: [PATCH 15/37] 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 - 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(SubStmts[EXPANSION_INITIALIZER]); + } + const Expr *getExpansionInitializer() const { + return cast(SubStmts[EXPANSION_INITIALIZER]); + } + void setExpansionInitializer(Expr *S) { SubStmts[EXPANSION_INITIALIZER] = S; } child_range children() { - const_child_range CCR = - const_cast(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(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(SubStmts[RANGE]); + } + DeclStmt *getRangeVarStmt() { return cast(SubStmts[RANGE]); } + void setRangeVarStmt(DeclStmt *S) { SubStmts[RANGE] = S; } const VarDecl* getRangeVar() const { return cast(getRangeVarStmt()->getSingleDecl()); @@ -695,9 +701,11 @@ class CXXIteratingExpansionStmt : public CXXExpansionStmt { return cast(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(SubStmts[BEGIN]); + } + DeclStmt *getBeginVarStmt() { return cast(SubStmts[BEGIN]); } + void setBeginVarStmt(DeclStmt *S) { SubStmts[BEGIN] = S; } const VarDecl* getBeginVar() const { return cast(getBeginVarStmt()->getSingleDecl()); @@ -707,9 +715,11 @@ class CXXIteratingExpansionStmt : public CXXExpansionStmt { return cast(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(SubStmts[END]); + } + DeclStmt *getEndVarStmt() { return cast(SubStmts[END]); } + void setEndVarStmt(DeclStmt *S) { SubStmts[END] = S; } const VarDecl* getEndVar() const { return cast(getEndVarStmt()->getSingleDecl()); @@ -720,26 +730,12 @@ class CXXIteratingExpansionStmt : public CXXExpansionStmt { } child_range children() { - const_child_range CCR = - const_cast(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(CXXExpansionStmt::COUNT) + - static_cast(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(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(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( - cast(DecompositionDeclStmt)->getSingleDecl()); + cast(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 Date: Sun, 26 Oct 2025 06:55:19 +0100 Subject: [PATCH 16/37] 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 Date: Sun, 26 Oct 2025 20:52:40 +0100 Subject: [PATCH 17/37] 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(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(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 +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 +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 +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 Date: Sun, 26 Oct 2025 21:17:44 +0100 Subject: [PATCH 18/37] 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, Visibility<[ClangOption, CC1Option]>, HelpText<"Set the maximum number of steps in constexpr function evaluation (0 = no limit)">, MarshallingInfoInt, "1048576">; +def fexpansion_limit_EQ : Joined<["-"], "fexpansion-limit=">, Group, + Visibility<[ClangOption, CC1Option]>, + HelpText<"Set the maximum number of times a single expansion statement may be expanded (0 = no limit)">, + MarshallingInfoInt, "256">; def fexperimental_new_constant_interpreter : Flag<["-"], "fexperimental-new-constant-interpreter">, 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 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 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 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 Date: Sun, 26 Oct 2025 21:57:16 +0100 Subject: [PATCH 19/37] 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 `_ 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 @@

C++2c implementation status

Expansion Statements P1306R5 - No + Clang 22 constexpr virtual inheritance From a2b72d1bb65f1612c3a409ad03c16d8e4821cdb2 Mon Sep 17 00:00:00 2001 From: Sirraide Date: Sun, 26 Oct 2025 21:57:43 +0100 Subject: [PATCH 20/37] 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(Node->getSingleDecl())) + !isa(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(Record.readStmt()); - D->Instantiations = cast(Record.readStmt()); + D->Instantiations = cast_or_null(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/ //" -e "s/ imported//" + +template +struct Array { + T data[size]{}; + constexpr const T* begin() const { return data; } + constexpr const T* end() const { return data + size; } +}; + +void foo(int); + +template +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 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 +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 void test(T t) { +template +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 a; + // CHECK-NEXT: template for (auto x : a) { + // CHECK-NEXT: foo(x); + // CHECK-NEXT: } + static constexpr Array 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 void test2(T t) { +template +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 a; + // CHECK-NEXT: template for (int x : a) { + // CHECK-NEXT: foo(x); + // CHECK-NEXT: } + static constexpr Array 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 Date: Sun, 26 Oct 2025 22:24:01 +0100 Subject: [PATCH 21/37] 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 &Exprs, llvm::function_ref ExpressionStarts = llvm::function_ref(), - 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 &Exprs, llvm::function_ref ExpressionStarts, - bool FailImmediatelyOnInvalidExpr) { + bool FailImmediatelyOnInvalidExpr, + bool StopAtRBraceAfterComma) { bool SawError = false; while (true) { if (ExpressionStarts) @@ -3195,7 +3196,11 @@ bool Parser::ParseExpressionList(SmallVectorImpl &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 &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 Date: Sun, 26 Oct 2025 22:37:10 +0100 Subject: [PATCH 22/37] 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 Date: Sun, 26 Oct 2025 23:32:45 +0100 Subject: [PATCH 23/37] 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 Date: Mon, 27 Oct 2025 03:48:37 +0100 Subject: [PATCH 24/37] 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 Instantiations, - ArrayRef SharedStmts); + CXXExpansionInstantiationStmt( + SourceLocation BeginLoc, + SourceLocation EndLoc, ArrayRef Instantiations, + ArrayRef SharedStmts, + bool ShouldApplyLifetimeExtensionToSharedStmts); public: static CXXExpansionInstantiationStmt * - Create(ASTContext &C, SourceLocation Loc, ArrayRef Instantiations, - ArrayRef SharedStmts); + Create(ASTContext &C, SourceLocation BeginLoc, SourceLocation EndLoc, + ArrayRef Instantiations, ArrayRef 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 Instantiations, - ArrayRef SharedStmts) - : Stmt(CXXExpansionInstantiationStmtClass), Loc(Loc), - NumInstantiations(unsigned(Instantiations.size())), - NumSharedStmts(unsigned(SharedStmts.size())) { + SourceLocation BeginLoc, SourceLocation EndLoc, + ArrayRef Instantiations, ArrayRef 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 Instantiations, - ArrayRef SharedStmts) { +CXXExpansionInstantiationStmt *CXXExpansionInstantiationStmt::Create( + ASTContext &C, SourceLocation BeginLoc, SourceLocation EndLoc, + ArrayRef Instantiations, ArrayRef SharedStmts, + bool ShouldApplyLifetimeExtensionToSharedStmts) { void *Mem = C.Allocate( totalSizeToAlloc(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(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 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(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::TransformCXXIteratingExpansionStmt( template StmtResult TreeTransform::TransformCXXDependentExpansionStmt( CXXDependentExpansionStmt *S) { - TransformCXXExpansionStmtResult Common = - TransformCXXExpansionStmtCommonParts(S); - if (!Common.isValid()) - return StmtError(); + TransformCXXExpansionStmtResult Common; + ExprResult ExpansionInitializer; + SmallVector 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::TransformCXXDependentExpansionStmt( template StmtResult TreeTransform::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::TransformCXXDestructuringExpansionStmt( if (Body.isInvalid()) return StmtError(); - return SemaRef.FinishCXXExpansionStmt(Expansion, Body.get()); + return SemaRef.FinishCXXExpansionStmt(Expansion, Body.get());*/ } template @@ -9448,15 +9469,35 @@ StmtResult TreeTransform::TransformCXXExpansionInstantiationStmt( SmallVector SharedStmts; SmallVector 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(cast(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 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 +int lifetime_extension_instantiate_expansions() { + int x = 5; + int sum = 0; + template for (T e : f(g(x))) { + sum += x; + } + return sum + x; +} + +template +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 +struct foo { + template + 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(); + lifetime_extension_dependent_expansion_stmt(); + foo().lifetime_extension_multiple_instantiations(); +} } // 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 +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 +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 +struct foo { + template + 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() == 47); +static_assert(lifetime_extension_dependent_expansion_stmt() == 47); +static_assert(foo().lifetime_extension_multiple_instantiations() == 47); } From ea2a347c1d2b5627b876e97346ef630a319414df Mon Sep 17 00:00:00 2001 From: Sirraide Date: Mon, 27 Oct 2025 03:50:01 +0100 Subject: [PATCH 25/37] 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::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 From d10d82aabc2cfb3e85f13459ce388e37d09b19b4 Mon Sep 17 00:00:00 2001 From: Sirraide Date: Mon, 27 Oct 2025 04:20:15 +0100 Subject: [PATCH 26/37] 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 ToInstantiations; + SmallVector 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(CSD), &Satisfaction); } +ExpectedStmt +ASTNodeImporter::VisitCXXExpansionInitListExpr(CXXExpansionInitListExpr *E) { + Error Err = Error::success(); + SmallVector 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 Date: Mon, 27 Oct 2025 04:37:11 +0100 Subject: [PATCH 27/37] 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 { @@ -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(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(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 Date: Mon, 27 Oct 2025 04:39:32 +0100 Subject: [PATCH 28/37] 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(const_cast(&Index)), - reinterpret_cast(const_cast(&Index + 1))); + reinterpret_cast(const_cast(&Index)), + reinterpret_cast(const_cast(&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(SubStmts[RANGE]); } void setRangeVarStmt(DeclStmt *S) { SubStmts[RANGE] = S; } - const VarDecl* getRangeVar() const { + const VarDecl *getRangeVar() const { return cast(getRangeVarStmt()->getSingleDecl()); } - VarDecl* getRangeVar() { + VarDecl *getRangeVar() { return cast(getRangeVarStmt()->getSingleDecl()); } @@ -707,11 +706,11 @@ class CXXIteratingExpansionStmt : public CXXExpansionStmt { DeclStmt *getBeginVarStmt() { return cast(SubStmts[BEGIN]); } void setBeginVarStmt(DeclStmt *S) { SubStmts[BEGIN] = S; } - const VarDecl* getBeginVar() const { + const VarDecl *getBeginVar() const { return cast(getBeginVarStmt()->getSingleDecl()); } - VarDecl* getBeginVar() { + VarDecl *getBeginVar() { return cast(getBeginVarStmt()->getSingleDecl()); } @@ -721,11 +720,11 @@ class CXXIteratingExpansionStmt : public CXXExpansionStmt { DeclStmt *getEndVarStmt() { return cast(SubStmts[END]); } void setEndVarStmt(DeclStmt *S) { SubStmts[END] = S; } - const VarDecl* getEndVar() const { + const VarDecl *getEndVar() const { return cast(getEndVarStmt()->getSingleDecl()); } - VarDecl* getEndVar() { + VarDecl *getEndVar() { return cast(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(this)->getDecompositionDecl(); + DecompositionDecl *getDecompositionDecl(); + const DecompositionDecl *getDecompositionDecl() const { + return const_cast(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 Instantiations, - ArrayRef SharedStmts, - bool ShouldApplyLifetimeExtensionToSharedStmts); + CXXExpansionInstantiationStmt(SourceLocation BeginLoc, SourceLocation EndLoc, + ArrayRef Instantiations, + ArrayRef 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 { - DeclContext* EnclosingDC; + struct SwitchInfo : llvm::PointerIntPair { + 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 ToInstantiations; - SmallVector ToSharedStmts; + SmallVector ToInstantiations; + SmallVector 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(E); - }); + return llvm::any_of(getExprs(), + [](const Expr *E) { return isa(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(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(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( cast(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(Node->getSingleDecl())) + !isa( + 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 Sema::ComputeExpansionSize(CXXExpansionStmt *Expansion) { +std::optional +Sema::ComputeExpansionSize(CXXExpansionStmt *Expansion) { assert(!Expansion->hasDependentSize()); if (isa(Expansion)) { uint64_t Size = cast( - Expansion->getExpansionVariable()->getInit()) - ->getRangeExpr() - ->getExprs() - .size(); + Expansion->getExpansionVariable()->getInit()) + ->getRangeExpr() + ->getExprs() + .size(); return Size; } @@ -567,7 +567,7 @@ std::optional 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 *Found - = getSema().CurrentInstantiationScope->getInstantiationOfIfExists(PD); + llvm::PointerUnion *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(cast(*Found))) + if (auto *FD = dyn_cast(cast(*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(ESD), Init, ExpansionVar.getAs()}; } - // 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::TransformCXXForRangeStmt(CXXForRangeStmt *S) { template StmtResult TreeTransform::TransformCXXEnumeratingExpansionStmt( CXXEnumeratingExpansionStmt *S) { - TransformCXXExpansionStmtResult Common = TransformCXXExpansionStmtCommonParts(S); + TransformCXXExpansionStmtResult Common = + TransformCXXExpansionStmtCommonParts(S); if (!Common.isValid()) return StmtError(); @@ -9334,7 +9336,8 @@ StmtResult TreeTransform::TransformCXXEnumeratingExpansionStmt( template StmtResult TreeTransform::TransformCXXIteratingExpansionStmt( CXXIteratingExpansionStmt *S) { - TransformCXXExpansionStmtResult Common = TransformCXXExpansionStmtCommonParts(S); + TransformCXXExpansionStmtResult Common = + TransformCXXExpansionStmtCommonParts(S); if (!Common.isValid()) return StmtError(); @@ -9364,18 +9367,17 @@ StmtResult TreeTransform::TransformCXXDependentExpansionStmt( CXXDependentExpansionStmt *S) { TransformCXXExpansionStmtResult Common; ExprResult ExpansionInitializer; - SmallVector LifetimeExtendTemps; + SmallVector 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::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 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(Record.readStmt()); - D->Instantiations = cast_or_null(Record.readStmt()); + D->Instantiations = + cast_or_null(Record.readStmt()); D->TParams = Record.readTemplateParameterList(); } From 8e6a4afa2ca7f30e85d3c975759ecb5bc2fbaad4 Mon Sep 17 00:00:00 2001 From: Sirraide Date: Mon, 27 Oct 2025 05:06:53 +0100 Subject: [PATCH 29/37] 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(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(const_cast(getTrailingObjects())); + Stmt **getTrailingStmts() const { + return reinterpret_cast(const_cast(getTrailingObjects())); } }; @@ -5581,13 +5581,11 @@ class CXXExpansionInitListSelectExpr : public Expr { return cast(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(const_cast(SubExprs)), - reinterpret_cast(const_cast(SubExprs + COUNT))); + reinterpret_cast(const_cast(SubExprs)), + reinterpret_cast(const_cast(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(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 getAllSubStmts() const { + ArrayRef getAllSubStmts() const { return getTrailingObjects(getNumSubStmts()); } - MutableArrayRef getAllSubStmts() { + MutableArrayRef getAllSubStmts() { return getTrailingObjects(getNumSubStmts()); } - unsigned getNumSubStmts() const { - return NumInstantiations + NumSharedStmts; - } + unsigned getNumSubStmts() const { return NumInstantiations + NumSharedStmts; } - ArrayRef getInstantiations() const { + ArrayRef getInstantiations() const { return getTrailingObjects(NumInstantiations); } - ArrayRef getSharedStmts() const { + ArrayRef 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 Exprs, SourceLocation LBraceLoc, SourceLocation RBraceLoc) { - void* Mem = C.Allocate(totalSizeToAlloc(Exprs.size())); + void *Mem = C.Allocate(totalSizeToAlloc(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(&D); + const auto *ESD = cast(&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 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(DC)->getOverloadedOperator() == OO_Call && cast(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 Shared; + SmallVector 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(getLambdaAwareParentOfDeclContext(FDPattern)->getEnclosingNonExpansionStatementContext()); - FD = dyn_cast(getLambdaAwareParentOfDeclContext(FD)->getEnclosingNonExpansionStatementContext()); + FDPattern = dyn_cast( + getLambdaAwareParentOfDeclContext(FDPattern) + ->getEnclosingNonExpansionStatementContext()); + FD = dyn_cast( + 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 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 LifetimeExtendTemps, - BuildForRangeKind Kind, bool ForExpansionStmt, - StmtResult *RebuildResult, + BuildForRangeKind Kind, bool ForExpansionStmt, StmtResult *RebuildResult, llvm::function_ref 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(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::TransformCXXExpansionInitListExpr( CXXExpansionInitListExpr *E) { bool ArgChanged = false; SmallVector 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::TransformCXXExpansionInstantiationStmt( return false; }; - SmallVector SharedStmts; - SmallVector Instantiations; + SmallVector SharedStmts; + SmallVector 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::TransformCXXExpansionInitListSelectExpr( return E; return SemaRef.BuildCXXExpansionInitListSelectExpr( - Range.getAs(), Idx.get()); + Range.getAs(), Idx.get()); } template 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 Date: Mon, 27 Oct 2025 05:03:40 +0100 Subject: [PATCH 30/37] 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 Date: Mon, 27 Oct 2025 05:32:05 +0100 Subject: [PATCH 31/37] 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 Date: Mon, 27 Oct 2025 07:01:08 +0100 Subject: [PATCH 32/37] 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 -#include +#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 Sema::ComputeExpansionSize(CXXExpansionStmt *Expansion) { assert(!Expansion->hasDependentSize()); - if (isa(Expansion)) { - uint64_t Size = cast( - Expansion->getExpansionVariable()->getInit()) - ->getRangeExpr() - ->getExprs() - .size(); - - return Size; - } + if (isa(Expansion)) + return cast( + Expansion->getExpansionVariable()->getInit()) + ->getRangeExpr() + ->getExprs() + .size(); if (auto *Destructuring = dyn_cast(Expansion)) return Destructuring->getDecompositionDecl()->bindings().size(); From 5a0c30f8eb0a2a2b2013cbb97f03e849827436ae Mon Sep 17 00:00:00 2001 From: Sirraide Date: Mon, 27 Oct 2025 22:52:13 +0100 Subject: [PATCH 33/37] 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() == 47); static_assert(lifetime_extension_dependent_expansion_stmt() == 47); static_assert(foo().lifetime_extension_multiple_instantiations() == 47); } + +template +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); From 17d05ae2a76b3b9bf11b5346446ce4d852c810ee Mon Sep 17 00:00:00 2001 From: Sirraide Date: Wed, 29 Oct 2025 05:34:26 +0100 Subject: [PATCH 34/37] Actually make this test expand to nothing --- clang/test/SemaCXX/cxx2c-expansion-statements.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/clang/test/SemaCXX/cxx2c-expansion-statements.cpp b/clang/test/SemaCXX/cxx2c-expansion-statements.cpp index bbb8de7b134f9..de33131c25bc9 100644 --- a/clang/test/SemaCXX/cxx2c-expansion-statements.cpp +++ b/clang/test/SemaCXX/cxx2c-expansion-statements.cpp @@ -843,14 +843,13 @@ static_assert(unexpanded_pack_good(E{1, 2}, E{3, 4}) == 62); // a DecompositionDecl w/ zero bindings. constexpr bool empty_side_effect() { struct A { - bool& b; - constexpr A(bool& b) : b{b} { + constexpr A(bool& b) { b = true; } }; bool constructed = false; - template for (auto x : A(constructed)) {} + template for (auto x : A(constructed)) static_assert(false); return constructed; } From 4965e7d67a14b1304a6b966e67877e05d8170409 Mon Sep 17 00:00:00 2001 From: Sirraide Date: Mon, 3 Nov 2025 14:55:54 +0100 Subject: [PATCH 35/37] Amend diagnostic wording --- clang/include/clang/Basic/DiagnosticParseKinds.td | 2 +- clang/test/Parser/cxx2c-expansion-statements.cpp | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/clang/include/clang/Basic/DiagnosticParseKinds.td b/clang/include/clang/Basic/DiagnosticParseKinds.td index e194c22bdb614..b4dee76722893 100644 --- a/clang/include/clang/Basic/DiagnosticParseKinds.td +++ b/clang/include/clang/Basic/DiagnosticParseKinds.td @@ -451,7 +451,7 @@ def err_unspecified_size_with_static : Error< 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">; + "expansion statement must be a range-based for loop">; def err_expected_case_before_expression: Error< "expected 'case' keyword before expression">; diff --git a/clang/test/Parser/cxx2c-expansion-statements.cpp b/clang/test/Parser/cxx2c-expansion-statements.cpp index f2eee4d89d817..4c594a111dee6 100644 --- a/clang/test/Parser/cxx2c-expansion-statements.cpp +++ b/clang/test/Parser/cxx2c-expansion-statements.cpp @@ -10,12 +10,12 @@ struct initializer_list { 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 (); // expected-error {{expected expression}} expected-error {{expected ';' in 'for' statement specifier}} expected-error {{expansion statement must be a range-based for loop}} + template for (;); // expected-error {{expected ';' in 'for' statement specifier}} expected-error {{expansion statement must be a range-based for loop}} + template for (;;); // expected-error {{expansion statement must be a range-based for loop}} + template for (int x;;); // expected-error {{expansion statement must be a range-based for loop}} 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 (: {1}); // expected-error {{expected expression}} expected-error {{expected ';' in 'for' statement specifier}} expected-error {{expansion statement must be a range-based for loop}} template for (auto y : {1})]; // expected-error {{expected expression}} template for (auto y : {1}; // expected-error {{expected ')'}} expected-note {{to match this '('}} From 79af6ec0d14012627a83a17bd4bfe3221d6aa1fb Mon Sep 17 00:00:00 2001 From: Sirraide Date: Mon, 3 Nov 2025 15:53:41 +0100 Subject: [PATCH 36/37] Only move up to the parent context during expansion --- clang/lib/Sema/SemaExpand.cpp | 3 +- .../SemaCXX/cxx2c-expansion-statements.cpp | 63 +++++++++++++++++++ 2 files changed, 64 insertions(+), 2 deletions(-) diff --git a/clang/lib/Sema/SemaExpand.cpp b/clang/lib/Sema/SemaExpand.cpp index 4ce60e0818b0f..a9022c4b31ee1 100644 --- a/clang/lib/Sema/SemaExpand.cpp +++ b/clang/lib/Sema/SemaExpand.cpp @@ -452,8 +452,7 @@ StmtResult Sema::FinishCXXExpansionStmt(Stmt *Exp, Stmt *Body) { for (uint64_t I = 0; I < *NumInstantiations; ++I) { // Now that we're expanding this, exit the context of the expansion stmt // so that we no longer treat this as dependent. - ContextRAII CtxGuard(*this, - CurContext->getEnclosingNonExpansionStatementContext(), + ContextRAII CtxGuard(*this, CurContext->getParent(), /*NewThis=*/false); TemplateArgument Arg{Context, llvm::APSInt::get(I), diff --git a/clang/test/SemaCXX/cxx2c-expansion-statements.cpp b/clang/test/SemaCXX/cxx2c-expansion-statements.cpp index de33131c25bc9..50a3bdfcd3061 100644 --- a/clang/test/SemaCXX/cxx2c-expansion-statements.cpp +++ b/clang/test/SemaCXX/cxx2c-expansion-statements.cpp @@ -938,3 +938,66 @@ constexpr int empty_expansion_consteval() { } static_assert(empty_expansion_consteval() == 3); + +void nested_empty_expansion() { + template for (auto x1 : {}) + template for (auto x2 : {1}) + static_assert(false); + + template for (auto x1 : {1}) + template for (auto x2 : {}) + template for (auto x3 : {1}) + static_assert(false); + + template for (auto x1 : {}) + template for (auto x2 : {}) + template for (auto x3 : {}) + template for (auto x4 : {1}) + static_assert(false); + + template for (auto x1 : {}) + template for (auto x2 : {1}) + template for (auto x3 : {}) + template for (auto x4 : {1}) + static_assert(false); + + template for (auto x1 : {}) + template for (auto x2 : {1}) + template for (auto x4 : {1}) + static_assert(false); +} + +struct Empty {}; + +template +void nested_empty_expansion_dependent() { + template for (auto x1 : T()) + template for (auto x2 : {1}) + static_assert(false); + + template for (auto x1 : {1}) + template for (auto x2 : T()) + template for (auto x3 : {1}) + static_assert(false); + + template for (auto x1 : T()) + template for (auto x2 : T()) + template for (auto x3 : T()) + template for (auto x4 : {1}) + static_assert(false); + + template for (auto x1 : T()) + template for (auto x2 : {1}) + template for (auto x3 : T()) + template for (auto x4 : {1}) + static_assert(false); + + template for (auto x1 : T()) + template for (auto x2 : {1}) + template for (auto x4 : {1}) + static_assert(false); +} + +void nested_empty_expansion_dependent_instantiate() { + nested_empty_expansion_dependent(); +} From 3642092788b16d06c21174e926af6642cbfd3e90 Mon Sep 17 00:00:00 2001 From: Sirraide Date: Mon, 3 Nov 2025 16:14:23 +0100 Subject: [PATCH 37/37] Add tests for destructuring using tuple_size --- .../SemaCXX/cxx2c-expansion-statements.cpp | 93 +++++++++++++++++++ 1 file changed, 93 insertions(+) diff --git a/clang/test/SemaCXX/cxx2c-expansion-statements.cpp b/clang/test/SemaCXX/cxx2c-expansion-statements.cpp index 50a3bdfcd3061..61825914f90dd 100644 --- a/clang/test/SemaCXX/cxx2c-expansion-statements.cpp +++ b/clang/test/SemaCXX/cxx2c-expansion-statements.cpp @@ -1001,3 +1001,96 @@ void nested_empty_expansion_dependent() { void nested_empty_expansion_dependent_instantiate() { nested_empty_expansion_dependent(); } + +// Destructuring expansion statements using tuple_size/tuple_element/get. +namespace std { +template +struct tuple_size; + +template <__SIZE_TYPE__, typename> +struct tuple_element; // expected-note {{template is declared here}} + +namespace get_decomposition { +struct MemberGet { + int x[6]{}; + + template <__SIZE_TYPE__ I> + constexpr int& get() { return x[I * 2]; } +}; + +struct ADLGet { + long x[8]{}; +}; + +template <__SIZE_TYPE__ I> +constexpr long& get(ADLGet& a) { return a.x[I * 2]; } +} // namespace get_decomposition + +template <> +struct tuple_size { + static constexpr __SIZE_TYPE__ value = 3; +}; + +template <__SIZE_TYPE__ I> +struct tuple_element { + using type = int; +}; + +template <> +struct tuple_size { + static constexpr __SIZE_TYPE__ value = 4; +}; + +template <__SIZE_TYPE__ I> +struct tuple_element { + using type = long; +}; + +constexpr int member() { + get_decomposition::MemberGet m; + int v = 1; + template for (int& i : m) { + i = v; + v++; + } + return m.x[0] + m.x[2] + m.x[4]; +} + +constexpr long adl() { + get_decomposition::ADLGet m; + long v = 1; + template for (long& i : m) { + i = v; + v++; + } + return m.x[0] + m.x[2] + m.x[4] + m.x[6]; +} + +static_assert(member() == 6); +static_assert(adl() == 10); + +struct TupleSizeOnly {}; + +template <> +struct tuple_size { + static constexpr __SIZE_TYPE__ value = 3; +}; + +struct TupleSizeAndGet { + template <__SIZE_TYPE__> + constexpr int get() { return 1; } +}; + +template <> +struct tuple_size { + static constexpr __SIZE_TYPE__ value = 3; +}; + +void invalid() { + template for (auto x : TupleSizeOnly()) {} // expected-error {{use of undeclared identifier 'get'}} \ + expected-note {{in implicit initialization of binding declaration}} + + template for (auto x : TupleSizeAndGet()) {} // expected-error {{implicit instantiation of undefined template 'std::tuple_element<0, std::TupleSizeAndGet>'}} \ + expected-note {{in implicit initialization of binding declaration}} +} +} // namespace std