From ce432df0abadee6cec9eedcfa44f8f9f70663c69 Mon Sep 17 00:00:00 2001 From: Sirraide Date: Tue, 25 Nov 2025 20:47:23 +0100 Subject: [PATCH 1/4] [Clang] [C++26] Expansion Statements (Part 3) --- .../clang/Basic/DiagnosticSemaKinds.td | 2 + clang/include/clang/Sema/Sema.h | 22 +++ clang/lib/Frontend/FrontendActions.cpp | 2 + clang/lib/Sema/SemaExpand.cpp | 151 ++++++++++++++++++ clang/lib/Sema/SemaTemplateInstantiate.cpp | 29 +++- .../lib/Sema/SemaTemplateInstantiateDecl.cpp | 38 ++++- clang/lib/Sema/SemaTemplateVariadic.cpp | 8 +- clang/lib/Sema/TreeTransform.h | 83 +++++++++- .../Parser/cxx2c-expansion-statements.cpp | 80 +++++----- 9 files changed, 365 insertions(+), 50 deletions(-) diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td index ca862316a2f27..d4783c1c9677d 100644 --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -5827,6 +5827,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/Sema/Sema.h b/clang/include/clang/Sema/Sema.h index 786e53a2e179a..4d25143cffaf4 100644 --- a/clang/include/clang/Sema/Sema.h +++ b/clang/include/clang/Sema/Sema.h @@ -13176,6 +13176,9 @@ class Sema final : public SemaBase { /// We are performing partial ordering for template template parameters. PartialOrderingTTP, + + /// We are instantiating an expansion statement. + ExpansionStmtInstantiation, } Kind; /// Whether we're substituting into constraints. @@ -13371,6 +13374,12 @@ class Sema final : public SemaBase { concepts::Requirement *Req, SourceRange InstantiationRange = SourceRange()); + /// \brief Note that we are substituting the body of an expansion statement. + InstantiatingTemplate(Sema &SemaRef, SourceLocation PointOfInstantiation, + CXXExpansionStmtPattern *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, @@ -15643,6 +15652,19 @@ class Sema final : public SemaBase { ArrayRef LifetimeExtendTemps); StmtResult FinishCXXExpansionStmt(Stmt *Expansion, Stmt *Body); + + StmtResult BuildCXXEnumeratingExpansionStmtPattern(Decl *ESD, Stmt *Init, + Stmt *ExpansionVar, + SourceLocation LParenLoc, + SourceLocation ColonLoc, + SourceLocation RParenLoc); + + ExprResult + BuildCXXExpansionInitListSelectExpr(CXXExpansionInitListExpr *Range, + Expr *Idx); + + std::optional + ComputeExpansionSize(CXXExpansionStmtPattern *Expansion); ///@} }; diff --git a/clang/lib/Frontend/FrontendActions.cpp b/clang/lib/Frontend/FrontendActions.cpp index e0c1d304e8290..75d5a76c04a32 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/Sema/SemaExpand.cpp b/clang/lib/Sema/SemaExpand.cpp index 96c0d9be9451c..f1cd2baa9ae39 100644 --- a/clang/lib/Sema/SemaExpand.cpp +++ b/clang/lib/Sema/SemaExpand.cpp @@ -24,6 +24,23 @@ using namespace clang; using namespace sema; +// Build a 'DeclRefExpr' designating the template parameter '__N'. +static DeclRefExpr *BuildIndexDRE(Sema &S, CXXExpansionStmtDecl *ESD) { + return S.BuildDeclRefExpr(ESD->getIndexTemplateParm(), + S.Context.getPointerDiffType(), VK_PRValue, + ESD->getBeginLoc()); +} + +static bool FinaliseExpansionVar(Sema &S, VarDecl *ExpansionVar, + ExprResult Initializer) { + if (Initializer.isInvalid()) { + S.ActOnInitializerError(ExpansionVar); + return true; + } + + S.AddInitializerToDecl(ExpansionVar, Initializer.get(), /*DirectInit=*/false); + return ExpansionVar->isInvalidDecl(); +} CXXExpansionStmtDecl * Sema::ActOnCXXExpansionStmtDecl(unsigned TemplateDepth, @@ -66,13 +83,147 @@ StmtResult Sema::ActOnCXXExpansionStmtPattern( Expr *ExpansionInitializer, SourceLocation LParenLoc, SourceLocation ColonLoc, SourceLocation RParenLoc, ArrayRef LifetimeExtendTemps) { + if (!ExpansionInitializer || !ExpansionVarStmt) + return StmtError(); + + assert(CurContext->isExpansionStmt()); + auto *DS = cast(ExpansionVarStmt); + if (!DS->isSingleDecl()) { + Diag(DS->getBeginLoc(), diag::err_type_defined_in_for_range); + return StmtError(); + } + + VarDecl *ExpansionVar = dyn_cast(DS->getSingleDecl()); + if (!ExpansionVar || ExpansionVar->isInvalidDecl() || + ExpansionInitializer->containsErrors()) + return StmtError(); + + // This is an enumerating expansion statement. + if (auto *ILE = dyn_cast(ExpansionInitializer)) { + ExprResult Initializer = + BuildCXXExpansionInitListSelectExpr(ILE, BuildIndexDRE(*this, ESD)); + if (FinaliseExpansionVar(*this, ExpansionVar, Initializer)) + return StmtError(); + + // Note that lifetime extension only applies to destructuring expansion + // statements, so we just ignore 'LifetimeExtendedTemps' entirely for other + // types of expansion statements (this is CWG 3043). + return BuildCXXEnumeratingExpansionStmtPattern(ESD, Init, DS, LParenLoc, + ColonLoc, RParenLoc); + } + Diag(ESD->getLocation(), diag::err_expansion_statements_todo); return StmtError(); } +StmtResult Sema::BuildCXXEnumeratingExpansionStmtPattern( + Decl *ESD, Stmt *Init, Stmt *ExpansionVar, SourceLocation LParenLoc, + SourceLocation ColonLoc, SourceLocation RParenLoc) { + return new (Context) CXXEnumeratingExpansionStmtPattern( + cast(ESD), Init, cast(ExpansionVar), + LParenLoc, ColonLoc, RParenLoc); +} + 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"); + + Expansion->setBody(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(); + + // Collect shared statements. + SmallVector Shared; + if (Expansion->getInit()) + Shared.push_back(Expansion->getInit()); + + assert(isa(Expansion) && "TODO"); + + // Return an empty statement if the range is empty. + if (*NumInstantiations == 0) { + Expansion->getDecl()->setInstantiations( + CXXExpansionStmtInstantiation::Create( + Context, Expansion->getBeginLoc(), Expansion->getEndLoc(), + /*Instantiations=*/{}, Shared, + isa(Expansion))); + return Expansion; + } + + // Create a compound statement binding the expansion variable and body. + Stmt *VarAndBody[] = {Expansion->getExpansionVarStmt(), Body}; + Stmt *CombinedBody = + CompoundStmt::Create(Context, VarAndBody, FPOptionsOverride(), + Body->getBeginLoc(), Body->getEndLoc()); + + // Expand the body for each instantiation. + SmallVector Instantiations; + CXXExpansionStmtDecl *ESD = Expansion->getDecl(); + for (uint64_t I = 0; I < *NumInstantiations; ++I) { + // Now that we're expanding this, exit the context of the expansion stmt + // so that we no longer treat this as dependent. + ContextRAII CtxGuard(*this, CurContext->getParent(), + /*NewThis=*/false); + + TemplateArgument Arg{Context, llvm::APSInt::get(I), + Context.getPointerDiffType()}; + MultiLevelTemplateArgumentList MTArgList(ESD, Arg, true); + MTArgList.addOuterRetainedLevels( + Expansion->getDecl()->getIndexTemplateParm()->getDepth()); + + LocalInstantiationScope LIScope(*this, /*CombineWithOuterScope=*/true); + NonSFINAEContext _(*this); + 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 = CXXExpansionStmtInstantiation::Create( + Context, Expansion->getBeginLoc(), Expansion->getEndLoc(), Instantiations, + Shared, isa(Expansion)); + + Expansion->getDecl()->setInstantiations(InstantiationsStmt); + return Expansion; +} + +ExprResult +Sema::BuildCXXExpansionInitListSelectExpr(CXXExpansionInitListExpr *Range, + Expr *Idx) { + if (Range->containsPackExpansion() || 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 index"); + + uint64_t I = ER.Val.getInt().getZExtValue(); + return Range->getExprs()[I]; +} + +std::optional +Sema::ComputeExpansionSize(CXXExpansionStmtPattern *Expansion) { + assert(!Expansion->hasDependentSize()); + + if (isa(Expansion)) + return cast( + Expansion->getExpansionVariable()->getInit()) + ->getRangeExpr() + ->getExprs() + .size(); + llvm_unreachable("TODO"); } diff --git a/clang/lib/Sema/SemaTemplateInstantiate.cpp b/clang/lib/Sema/SemaTemplateInstantiate.cpp index 35205f40cbcef..5957af710d38e 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: @@ -759,6 +760,15 @@ Sema::InstantiatingTemplate::InstantiatingTemplate( PointOfInstantiation, InstantiationRange, /*Entity=*/nullptr, /*Template=*/nullptr, /*TemplateArgs=*/{}) {} +Sema::InstantiatingTemplate::InstantiatingTemplate( + Sema &SemaRef, SourceLocation PointOfInstantiation, + CXXExpansionStmtPattern *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, @@ -1260,6 +1270,9 @@ void Sema::PrintInstantiationStack(InstantiationContextDiagFuncRef DiagFunc) { << /*isTemplateTemplateParam=*/true << Active->InstantiationRange); break; + case CodeSynthesisContext::ExpansionStmtInstantiation: + Diags.Report(Active->PointOfInstantiation, + diag::note_expansion_stmt_instantiation_here); } } } @@ -1894,6 +1907,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); } @@ -2288,9 +2307,13 @@ ExprResult TemplateInstantiator::TransformFunctionParmPackRefExpr(DeclRefExpr *E, ValueDecl *PD) { typedef LocalInstantiationScope::DeclArgumentPack DeclArgumentPack; - llvm::PointerUnion *Found - = getSema().CurrentInstantiationScope->findInstantiationOf(PD); - assert(Found && "no instantiation for parameter pack"); + 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...}})`). + if (!Found) + return E; Decl *TransformedDecl; if (DeclArgumentPack *Pack = dyn_cast(*Found)) { diff --git a/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp b/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp index f8136c3c24a52..f88ddcb40adf7 100644 --- a/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp +++ b/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp @@ -2089,7 +2089,37 @@ Decl *TemplateDeclInstantiator::VisitStaticAssertDecl(StaticAssertDecl *D) { Decl *TemplateDeclInstantiator::VisitCXXExpansionStmtDecl( CXXExpansionStmtDecl *OldESD) { - llvm_unreachable("TODO"); + Decl *Index = VisitNonTypeTemplateParmDecl(OldESD->getIndexTemplateParm()); + CXXExpansionStmtDecl *NewESD = SemaRef.BuildCXXExpansionStmtDecl( + Owner, OldESD->getBeginLoc(), cast(Index)); + SemaRef.CurrentInstantiationScope->InstantiatedLocal(OldESD, NewESD); + + // If this was already expanded, only instantiate the expansion and + // don't touch the unexpanded expansion statement. + if (CXXExpansionStmtInstantiation *OldInst = OldESD->getInstantiations()) { + StmtResult NewInst = SemaRef.SubstStmt(OldInst, TemplateArgs); + if (NewInst.isInvalid()) + return nullptr; + + NewESD->setInstantiations(NewInst.getAs()); + NewESD->setExpansionPattern(OldESD->getExpansionPattern()); + 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()) + return nullptr; + + // The code that handles CXXExpansionStmtPattern 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) { @@ -7093,6 +7123,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; diff --git a/clang/lib/Sema/SemaTemplateVariadic.cpp b/clang/lib/Sema/SemaTemplateVariadic.cpp index 5b1aad3fa8470..b61148bf95738 100644 --- a/clang/lib/Sema/SemaTemplateVariadic.cpp +++ b/clang/lib/Sema/SemaTemplateVariadic.cpp @@ -912,10 +912,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/lib/Sema/TreeTransform.h b/clang/lib/Sema/TreeTransform.h index df25b1701207f..514a404c16ce5 100644 --- a/clang/lib/Sema/TreeTransform.h +++ b/clang/lib/Sema/TreeTransform.h @@ -9295,19 +9295,94 @@ TreeTransform::TransformCXXForRangeStmt(CXXForRangeStmt *S) { template StmtResult TreeTransform::TransformCXXExpansionStmtPattern( CXXExpansionStmtPattern *S) { - llvm_unreachable("TOOD"); + CXXExpansionStmtDecl *NewESD = nullptr; + Stmt *Init = nullptr; + DeclStmt *ExpansionVarStmt = nullptr; + Decl *ESD = + getDerived().TransformDecl(S->getDecl()->getLocation(), S->getDecl()); + if (!ESD || ESD->isInvalidDecl()) + return StmtError(); + NewESD = cast(ESD); + + 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(); + ExpansionVarStmt = cast(ExpansionVar.get()); + + CXXExpansionStmtPattern *NewPattern = nullptr; + if (S->isEnumerating()) { + NewPattern = CXXExpansionStmtPattern::CreateEnumerating( + SemaRef.Context, NewESD, Init, ExpansionVarStmt, S->getLParenLoc(), + S->getColonLoc(), S->getRParenLoc()); + } else { + llvm_unreachable("TODO"); + } + + StmtResult Body = getDerived().TransformStmt(S->getBody()); + if (Body.isInvalid()) + return StmtError(); + + return SemaRef.FinishCXXExpansionStmt(NewPattern, Body.get()); } template StmtResult TreeTransform::TransformCXXExpansionStmtInstantiation( CXXExpansionStmtInstantiation *S) { - llvm_unreachable("TOOD"); + 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())) + return StmtError(); + + if (TransformStmts(Instantiations, S->getInstantiations())) + return StmtError(); + + if (!getDerived().AlwaysRebuild() && !SubStmtChanged) + return S; + + return CXXExpansionStmtInstantiation::Create( + SemaRef.Context, S->getBeginLoc(), S->getEndLoc(), Instantiations, + SharedStmts, S->shouldApplyLifetimeExtensionToSharedStmts()); } template ExprResult TreeTransform::TransformCXXExpansionSelectExpr( CXXExpansionSelectExpr *E) { - llvm_unreachable("TOOD"); + 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.BuildCXXExpansionSelectExpr( + Range.getAs(), Idx.get()); } template @@ -15642,7 +15717,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/test/Parser/cxx2c-expansion-statements.cpp b/clang/test/Parser/cxx2c-expansion-statements.cpp index 658257922b734..e58b9a189041c 100644 --- a/clang/test/Parser/cxx2c-expansion-statements.cpp +++ b/clang/test/Parser/cxx2c-expansion-statements.cpp @@ -10,56 +10,56 @@ 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 a range-based for loop}} expected-error {{TODO (expansion statements)}} - template for (;); // expected-error {{expected ';' in 'for' statement specifier}} expected-error {{expansion statement must be a range-based for loop}} expected-error {{TODO (expansion statements)}} - template for (;;); // expected-error {{expansion statement must be a range-based for loop}} expected-error {{TODO (expansion statements)}} - template for (int x;;); // expected-error {{expansion statement must be a range-based for loop}} expected-error {{TODO (expansion statements)}} - template for (x : {1}); // expected-error {{expansion statement requires type for expansion variable}} expected-error {{TODO (expansion statements)}} - template for (: {1}); // expected-error {{expected expression}} expected-error {{expected ';' in 'for' statement specifier}} expected-error {{expansion statement must be a range-based for loop}} expected-error {{TODO (expansion statements)}} - template for (auto y : {1})]; // expected-error {{expected expression}} expected-error {{TODO (expansion statements)}} - template for (auto y : {1}; // expected-error {{expected ')'}} expected-note {{to match this '('}} expected-error {{TODO (expansion statements)}} - template for (extern auto y : {1, 2}); // expected-error {{expansion variable 'y' may not be declared 'extern'}} expected-error {{TODO (expansion statements)}} - template for (extern static auto y : {1, 2}); // expected-error {{cannot combine with previous 'extern' declaration specifier}} expected-error {{expansion variable 'y' may not be declared 'extern'}} expected-error {{TODO (expansion statements)}} - template for (static auto y : {1, 2}); // expected-error {{expansion variable 'y' may not be declared 'static'}} expected-error {{TODO (expansion statements)}} - template for (thread_local auto y : {1, 2}); // expected-error {{'thread_local' variables must have global storage}} expected-error {{TODO (expansion statements)}} - template for (static thread_local auto y : {1, 2}); // expected-error {{expansion variable 'y' may not be declared 'thread_local'}} expected-error {{TODO (expansion statements)}} - template for (__thread auto y : {1, 2}); // expected-error {{'__thread' variables must have global storage}} expected-error {{TODO (expansion statements)}} - template for (static __thread auto y : {1, 2}); // expected-error {{expansion variable 'y' may not be declared 'static'}} expected-error {{TODO (expansion statements)}} - template for (constinit auto y : {1, 2}); // expected-error {{local variable cannot be declared 'constinit'}} expected-error {{TODO (expansion statements)}} - template for (consteval auto y : {1, 2}); // expected-error {{consteval can only be used in function declarations}} expected-error {{TODO (expansion statements)}} - template for (int x; extern auto y : {1, 2}); // expected-error {{expansion variable 'y' may not be declared 'extern'}} expected-error {{TODO (expansion statements)}} - template for (int x; extern static auto y : {1, 2}); // expected-error {{cannot combine with previous 'extern' declaration specifier}} expected-error {{expansion variable 'y' may not be declared 'extern'}} expected-error {{TODO (expansion statements)}} - template for (int x; static auto y : {1, 2}); // expected-error {{expansion variable 'y' may not be declared 'static'}} expected-error {{TODO (expansion statements)}} - template for (int x; thread_local auto y : {1, 2}); // expected-error {{'thread_local' variables must have global storage}} expected-error {{TODO (expansion statements)}} - template for (int x; static thread_local auto y : {1, 2}); // expected-error {{expansion variable 'y' may not be declared 'thread_local'}} expected-error {{TODO (expansion statements)}} - template for (int x; __thread auto y : {1, 2}); // expected-error {{'__thread' variables must have global storage}} expected-error {{TODO (expansion statements)}} - template for (int x; static __thread auto y : {1, 2}); // expected-error {{expansion variable 'y' may not be declared 'static'}} expected-error {{TODO (expansion statements)}} - template for (int x; constinit auto y : {1, 2}); // expected-error {{local variable cannot be declared 'constinit'}} expected-error {{TODO (expansion statements)}} - template for (int x; consteval auto y : {1, 2}); // expected-error {{consteval can only be used in function declarations}} expected-error {{TODO (expansion statements)}} - template for (auto y : {abc, -+, }); // expected-error {{use of undeclared identifier 'abc'}} expected-error {{expected expression}} expected-error {{TODO (expansion statements)}} + template for (); // 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 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 '('}} + 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 {{expected expression}} template for (3 : "error") // expected-error {{expansion statement declaration must declare a variable}} \ - expected-error {{expansion statement must be a range-based for loop}} expected-error {{TODO (expansion statements)}} + expected-error {{expansion statement must be a range-based for loop}} ; template while (true) {} // expected-error {{'template while' is invalid}} template do {} while (true); // expected-error {{'template do' is invalid}} - for template (int x : {}) {} // expected-error {{'for template' is invalid; use 'template for' instead}} expected-error {{TODO (expansion statements)}} + for template (int x : {}) {} // expected-error {{'for template' is invalid; use 'template for' instead}} } void good() { - template for (auto y : {}); // expected-error {{TODO (expansion statements)}} - template for (auto y : {1, 2}); // expected-error {{TODO (expansion statements)}} - template for (int x; auto y : {1, 2}); // expected-error {{TODO (expansion statements)}} - template for (int x; int y : {1, 2}); // expected-error {{TODO (expansion statements)}} - template for (int x; constexpr auto y : {1, 2}); // expected-error {{TODO (expansion statements)}} - template for (int x; constexpr int y : {1, 2}); // expected-error {{TODO (expansion statements)}} - template for (constexpr int a : {1, 2}) { // expected-error {{TODO (expansion statements)}} - template for (constexpr int b : {1, 2}) { // expected-error {{TODO (expansion statements)}} - template for (constexpr int c : {1, 2}); // expected-error {{TODO (expansion statements)}} + 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}); } } } void trailing_comma() { - template for (int x : {1, 2,}) {} // expected-error {{TODO (expansion statements)}} - template for (int x : {,}) {} // expected-error {{expected expression}} expected-error {{TODO (expansion statements)}} + template for (int x : {1, 2,}) {} + template for (int x : {,}) {} // expected-error {{expected expression}} } From fc78491092ba0ee3de1370be7fd1b56b844e57d2 Mon Sep 17 00:00:00 2001 From: Sirraide Date: Wed, 3 Dec 2025 18:20:19 +0100 Subject: [PATCH 2/4] Eliminate CXXExpansionInitListExpr --- clang/include/clang/Sema/Sema.h | 5 ++--- clang/lib/Sema/SemaExpand.cpp | 34 +++++++++++++++++++++++---------- 2 files changed, 26 insertions(+), 13 deletions(-) diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h index 4d25143cffaf4..e99f42b5e8f9f 100644 --- a/clang/include/clang/Sema/Sema.h +++ b/clang/include/clang/Sema/Sema.h @@ -15659,9 +15659,8 @@ class Sema final : public SemaBase { SourceLocation ColonLoc, SourceLocation RParenLoc); - ExprResult - BuildCXXExpansionInitListSelectExpr(CXXExpansionInitListExpr *Range, - Expr *Idx); + ExprResult BuildCXXExpansionInitListSelectExpr(InitListExpr *Range, + Expr *Idx); std::optional ComputeExpansionSize(CXXExpansionStmtPattern *Expansion); diff --git a/clang/lib/Sema/SemaExpand.cpp b/clang/lib/Sema/SemaExpand.cpp index f1cd2baa9ae39..7dd57ca86c36d 100644 --- a/clang/lib/Sema/SemaExpand.cpp +++ b/clang/lib/Sema/SemaExpand.cpp @@ -42,6 +42,21 @@ static bool FinaliseExpansionVar(Sema &S, VarDecl *ExpansionVar, return ExpansionVar->isInvalidDecl(); } +static auto InitListContainsPack(const InitListExpr *ILE) { + return llvm::any_of(ArrayRef(ILE->getInits(), ILE->getNumInits()), + [](const Expr *E) { return isa(E); }); +} + +static bool HasDependentSize(const CXXExpansionStmtPattern* pattern) { + if (isa(pattern)) { + auto *SelectExpr = cast( + pattern->getExpansionVariable()->getInit()); + return InitListContainsPack(SelectExpr->getRangeExpr()); + } + + llvm_unreachable("Invalid expansion statement class"); +} + CXXExpansionStmtDecl * Sema::ActOnCXXExpansionStmtDecl(unsigned TemplateDepth, SourceLocation TemplateKWLoc) { @@ -74,8 +89,7 @@ Sema::BuildCXXExpansionStmtDecl(DeclContext *Ctx, SourceLocation TemplateKWLoc, ExprResult Sema::ActOnCXXExpansionInitList(MultiExprArg SubExprs, SourceLocation LBraceLoc, SourceLocation RBraceLoc) { - return CXXExpansionInitListExpr::Create(Context, SubExprs, LBraceLoc, - RBraceLoc); + return new (Context) InitListExpr(Context, LBraceLoc, SubExprs, RBraceLoc); } StmtResult Sema::ActOnCXXExpansionStmtPattern( @@ -99,7 +113,8 @@ StmtResult Sema::ActOnCXXExpansionStmtPattern( return StmtError(); // This is an enumerating expansion statement. - if (auto *ILE = dyn_cast(ExpansionInitializer)) { + if (auto *ILE = dyn_cast(ExpansionInitializer)) { + assert(ILE->isSyntacticForm()); ExprResult Initializer = BuildCXXExpansionInitListSelectExpr(ILE, BuildIndexDRE(*this, ESD)); if (FinaliseExpansionVar(*this, ExpansionVar, Initializer)) @@ -133,7 +148,7 @@ StmtResult Sema::FinishCXXExpansionStmt(Stmt *Exp, Stmt *Body) { "should not rebuild expansion statement after instantiation"); Expansion->setBody(Body); - if (Expansion->hasDependentSize()) + if (HasDependentSize(Expansion)) return Expansion; // This can fail if this is an iterating expansion statement. @@ -199,9 +214,9 @@ StmtResult Sema::FinishCXXExpansionStmt(Stmt *Exp, Stmt *Body) { } ExprResult -Sema::BuildCXXExpansionInitListSelectExpr(CXXExpansionInitListExpr *Range, +Sema::BuildCXXExpansionInitListSelectExpr(InitListExpr *Range, Expr *Idx) { - if (Range->containsPackExpansion() || Idx->isValueDependent()) + if (Idx->isValueDependent() || InitListContainsPack(Range)) return new (Context) CXXExpansionInitListSelectExpr(Context, Range, Idx); // The index is a DRE to a template parameter; we should never @@ -211,19 +226,18 @@ Sema::BuildCXXExpansionInitListSelectExpr(CXXExpansionInitListExpr *Range, llvm_unreachable("Failed to evaluate expansion index"); uint64_t I = ER.Val.getInt().getZExtValue(); - return Range->getExprs()[I]; + return Range->getInit(I); } std::optional Sema::ComputeExpansionSize(CXXExpansionStmtPattern *Expansion) { - assert(!Expansion->hasDependentSize()); + assert(!HasDependentSize(Expansion)); if (isa(Expansion)) return cast( Expansion->getExpansionVariable()->getInit()) ->getRangeExpr() - ->getExprs() - .size(); + ->getNumInits(); llvm_unreachable("TODO"); } From 48718a4319bff8a75a3a1a384d3a4c2185468c04 Mon Sep 17 00:00:00 2001 From: Sirraide Date: Wed, 3 Dec 2025 20:38:22 +0100 Subject: [PATCH 3/4] Merge all pattern kinds into a single AST node --- clang/lib/Sema/SemaExpand.cpp | 30 ++++++++++++++++++------------ 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/clang/lib/Sema/SemaExpand.cpp b/clang/lib/Sema/SemaExpand.cpp index 7dd57ca86c36d..83231ecaa835a 100644 --- a/clang/lib/Sema/SemaExpand.cpp +++ b/clang/lib/Sema/SemaExpand.cpp @@ -47,14 +47,21 @@ static auto InitListContainsPack(const InitListExpr *ILE) { [](const Expr *E) { return isa(E); }); } -static bool HasDependentSize(const CXXExpansionStmtPattern* pattern) { - if (isa(pattern)) { +static bool HasDependentSize(const CXXExpansionStmtPattern *Pattern) { + switch (Pattern->getKind()) { + case CXXExpansionStmtPattern::ExpansionStmtKind::Enumerating: { auto *SelectExpr = cast( - pattern->getExpansionVariable()->getInit()); + Pattern->getExpansionVariable()->getInit()); return InitListContainsPack(SelectExpr->getRangeExpr()); } - llvm_unreachable("Invalid expansion statement class"); + case CXXExpansionStmtPattern::ExpansionStmtKind::Iterating: + case CXXExpansionStmtPattern::ExpansionStmtKind::Destructuring: + case CXXExpansionStmtPattern::ExpansionStmtKind::Dependent: + llvm_unreachable("TODO"); + } + + llvm_unreachable("invalid pattern kind"); } CXXExpansionStmtDecl * @@ -134,9 +141,9 @@ StmtResult Sema::ActOnCXXExpansionStmtPattern( StmtResult Sema::BuildCXXEnumeratingExpansionStmtPattern( Decl *ESD, Stmt *Init, Stmt *ExpansionVar, SourceLocation LParenLoc, SourceLocation ColonLoc, SourceLocation RParenLoc) { - return new (Context) CXXEnumeratingExpansionStmtPattern( - cast(ESD), Init, cast(ExpansionVar), - LParenLoc, ColonLoc, RParenLoc); + return CXXExpansionStmtPattern::CreateEnumerating( + Context, cast(ESD), Init, + cast(ExpansionVar), LParenLoc, ColonLoc, RParenLoc); } StmtResult Sema::FinishCXXExpansionStmt(Stmt *Exp, Stmt *Body) { @@ -161,15 +168,14 @@ StmtResult Sema::FinishCXXExpansionStmt(Stmt *Exp, Stmt *Body) { if (Expansion->getInit()) Shared.push_back(Expansion->getInit()); - assert(isa(Expansion) && "TODO"); + assert(Expansion->isEnumerating() && "TODO"); // Return an empty statement if the range is empty. if (*NumInstantiations == 0) { Expansion->getDecl()->setInstantiations( CXXExpansionStmtInstantiation::Create( Context, Expansion->getBeginLoc(), Expansion->getEndLoc(), - /*Instantiations=*/{}, Shared, - isa(Expansion))); + /*Instantiations=*/{}, Shared, Expansion->isDestructuring())); return Expansion; } @@ -207,7 +213,7 @@ StmtResult Sema::FinishCXXExpansionStmt(Stmt *Exp, Stmt *Body) { auto *InstantiationsStmt = CXXExpansionStmtInstantiation::Create( Context, Expansion->getBeginLoc(), Expansion->getEndLoc(), Instantiations, - Shared, isa(Expansion)); + Shared, Expansion->isDestructuring()); Expansion->getDecl()->setInstantiations(InstantiationsStmt); return Expansion; @@ -233,7 +239,7 @@ std::optional Sema::ComputeExpansionSize(CXXExpansionStmtPattern *Expansion) { assert(!HasDependentSize(Expansion)); - if (isa(Expansion)) + if (Expansion->isEnumerating()) return cast( Expansion->getExpansionVariable()->getInit()) ->getRangeExpr() From 2a9bd94f9305ba1c00071a9a2c784a04de61491a Mon Sep 17 00:00:00 2001 From: Sirraide Date: Wed, 3 Dec 2025 22:07:58 +0100 Subject: [PATCH 4/4] Only use a single CXXExpansionSelectExpr --- clang/include/clang/Sema/Sema.h | 3 +-- clang/lib/Sema/SemaExpand.cpp | 12 +++++------- clang/lib/Sema/TreeTransform.h | 4 ++-- 3 files changed, 8 insertions(+), 11 deletions(-) diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h index e99f42b5e8f9f..c5711ab7ea751 100644 --- a/clang/include/clang/Sema/Sema.h +++ b/clang/include/clang/Sema/Sema.h @@ -15659,8 +15659,7 @@ class Sema final : public SemaBase { SourceLocation ColonLoc, SourceLocation RParenLoc); - ExprResult BuildCXXExpansionInitListSelectExpr(InitListExpr *Range, - Expr *Idx); + ExprResult BuildCXXExpansionSelectExpr(InitListExpr *Range, Expr *Idx); std::optional ComputeExpansionSize(CXXExpansionStmtPattern *Expansion); diff --git a/clang/lib/Sema/SemaExpand.cpp b/clang/lib/Sema/SemaExpand.cpp index 83231ecaa835a..a0f5e852ebdb1 100644 --- a/clang/lib/Sema/SemaExpand.cpp +++ b/clang/lib/Sema/SemaExpand.cpp @@ -50,7 +50,7 @@ static auto InitListContainsPack(const InitListExpr *ILE) { static bool HasDependentSize(const CXXExpansionStmtPattern *Pattern) { switch (Pattern->getKind()) { case CXXExpansionStmtPattern::ExpansionStmtKind::Enumerating: { - auto *SelectExpr = cast( + auto *SelectExpr = cast( Pattern->getExpansionVariable()->getInit()); return InitListContainsPack(SelectExpr->getRangeExpr()); } @@ -123,7 +123,7 @@ StmtResult Sema::ActOnCXXExpansionStmtPattern( if (auto *ILE = dyn_cast(ExpansionInitializer)) { assert(ILE->isSyntacticForm()); ExprResult Initializer = - BuildCXXExpansionInitListSelectExpr(ILE, BuildIndexDRE(*this, ESD)); + BuildCXXExpansionSelectExpr(ILE, BuildIndexDRE(*this, ESD)); if (FinaliseExpansionVar(*this, ExpansionVar, Initializer)) return StmtError(); @@ -219,11 +219,9 @@ StmtResult Sema::FinishCXXExpansionStmt(Stmt *Exp, Stmt *Body) { return Expansion; } -ExprResult -Sema::BuildCXXExpansionInitListSelectExpr(InitListExpr *Range, - Expr *Idx) { +ExprResult Sema::BuildCXXExpansionSelectExpr(InitListExpr *Range, Expr *Idx) { if (Idx->isValueDependent() || InitListContainsPack(Range)) - return new (Context) CXXExpansionInitListSelectExpr(Context, Range, Idx); + return new (Context) CXXExpansionSelectExpr(Context, Range, Idx); // The index is a DRE to a template parameter; we should never // fail to evaluate it. @@ -240,7 +238,7 @@ Sema::ComputeExpansionSize(CXXExpansionStmtPattern *Expansion) { assert(!HasDependentSize(Expansion)); if (Expansion->isEnumerating()) - return cast( + return cast( Expansion->getExpansionVariable()->getInit()) ->getRangeExpr() ->getNumInits(); diff --git a/clang/lib/Sema/TreeTransform.h b/clang/lib/Sema/TreeTransform.h index 514a404c16ce5..fc646f5051905 100644 --- a/clang/lib/Sema/TreeTransform.h +++ b/clang/lib/Sema/TreeTransform.h @@ -9381,8 +9381,8 @@ ExprResult TreeTransform::TransformCXXExpansionSelectExpr( Idx.get() == E->getIndexExpr()) return E; - return SemaRef.BuildCXXExpansionSelectExpr( - Range.getAs(), Idx.get()); + return SemaRef.BuildCXXExpansionSelectExpr(Range.getAs(), + Idx.get()); } template