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