[llvm-branch-commits] [clang] [Clang] [C++26] Expansion Statements (Part 2: Parsing and Parser Tests) (PR #169681)
via llvm-branch-commits
llvm-branch-commits at lists.llvm.org
Wed Nov 26 10:05:15 PST 2025
https://github.com/Sirraide updated https://github.com/llvm/llvm-project/pull/169681
>From e99d65e03373265e571069e99761ed5de627e725 Mon Sep 17 00:00:00 2001
From: Sirraide <aeternalmail at gmail.com>
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 <aeternalmail at gmail.com>
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 <aeternalmail at gmail.com>
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)}}
+}
More information about the llvm-branch-commits
mailing list