[llvm-branch-commits] [clang] [Clang] [C++26] Expansion Statements (Part 5: Iterating Expansion Statements) (PR #169684)
via llvm-branch-commits
llvm-branch-commits at lists.llvm.org
Fri Dec 5 09:55:10 PST 2025
https://github.com/Sirraide updated https://github.com/llvm/llvm-project/pull/169684
>From 778c6a7efd7a4f330ab81d8aae7cddebe516c8ec Mon Sep 17 00:00:00 2001
From: Sirraide <aeternalmail at gmail.com>
Date: Wed, 26 Nov 2025 16:18:02 +0100
Subject: [PATCH 1/9] [Clang] [C++26] Expansion Statements (Part 5)
---
.../clang/Basic/DiagnosticSemaKinds.td | 7 +
clang/include/clang/Sema/Sema.h | 6 +
clang/lib/Sema/SemaExpand.cpp | 257 +++++++++++++++++-
clang/lib/Sema/SemaStmt.cpp | 13 +-
clang/lib/Sema/TreeTransform.h | 14 +
5 files changed, 291 insertions(+), 6 deletions(-)
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index fea25ef6a0734..0ce3d8164d955 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -165,6 +165,10 @@ def err_ice_too_large : Error<
def err_expr_not_string_literal : Error<"expression is not a string literal">;
def note_constexpr_assert_failed : Note<
"assertion failed during evaluation of constant expression">;
+def err_expansion_size_expr_not_ice : Error<
+ "expansion size is not a constant expression">;
+def err_expansion_size_negative : Error<
+ "expansion size must not be negative (was %0)">;
// Semantic analysis of constant literals.
def ext_predef_outside_function : Warning<
@@ -3710,6 +3714,9 @@ def err_conflicting_codeseg_attribute : Error<
def warn_duplicate_codeseg_attribute : Warning<
"duplicate code segment specifiers">, InGroup<Section>;
+def err_expansion_stmt_lambda : Error<
+ "cannot expand lambda closure type">;
+
def err_attribute_patchable_function_entry_invalid_section
: Error<"section argument to 'patchable_function_entry' attribute is not "
"valid for this target: %0">;
diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index 5dee71b23422e..0adc570c73011 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -15705,6 +15705,12 @@ class Sema final : public SemaBase {
SourceLocation ColonLoc,
SourceLocation RParenLoc);
+ StmtResult BuildNonEnumeratingCXXExpansionStmtPattern(
+ CXXExpansionStmtDecl *ESD, Stmt *Init, DeclStmt *ExpansionVarStmt,
+ Expr *ExpansionInitializer, SourceLocation LParenLoc,
+ SourceLocation ColonLoc, SourceLocation RParenLoc,
+ ArrayRef<MaterializeTemporaryExpr *> LifetimeExtendTemps = {});
+
ExprResult BuildCXXExpansionSelectExpr(InitListExpr *Range, Expr *Idx);
std::optional<uint64_t>
diff --git a/clang/lib/Sema/SemaExpand.cpp b/clang/lib/Sema/SemaExpand.cpp
index a0f5e852ebdb1..4e392e33578e2 100644
--- a/clang/lib/Sema/SemaExpand.cpp
+++ b/clang/lib/Sema/SemaExpand.cpp
@@ -24,6 +24,25 @@
using namespace clang;
using namespace sema;
+namespace {
+struct IterableExpansionStmtData {
+ enum class State {
+ NotIterable,
+ Error,
+ Ok,
+ };
+
+ DeclStmt *RangeDecl = nullptr;
+ DeclStmt *BeginDecl = nullptr;
+ DeclStmt *EndDecl = nullptr;
+ Expr *Initializer = nullptr;
+ State TheState = State::NotIterable;
+
+ bool isIterable() const { return TheState == State::Ok; }
+ bool hasError() { return TheState == State::Error; }
+};
+} // namespace
+
// Build a 'DeclRefExpr' designating the template parameter '__N'.
static DeclRefExpr *BuildIndexDRE(Sema &S, CXXExpansionStmtDecl *ESD) {
return S.BuildDeclRefExpr(ESD->getIndexTemplateParm(),
@@ -55,15 +74,134 @@ static bool HasDependentSize(const CXXExpansionStmtPattern *Pattern) {
return InitListContainsPack(SelectExpr->getRangeExpr());
}
- case CXXExpansionStmtPattern::ExpansionStmtKind::Iterating:
- case CXXExpansionStmtPattern::ExpansionStmtKind::Destructuring:
+ case CXXExpansionStmtPattern::ExpansionStmtKind::Iterating: {
+ const Expr *Begin = Pattern->getBeginVar()->getInit();
+ const Expr *End = Pattern->getEndVar()->getInit();
+ return Begin->isInstantiationDependent() || End->isInstantiationDependent();
+ }
+
case CXXExpansionStmtPattern::ExpansionStmtKind::Dependent:
+ return true;
+
+ case CXXExpansionStmtPattern::ExpansionStmtKind::Destructuring:
llvm_unreachable("TODO");
}
llvm_unreachable("invalid pattern kind");
}
+static IterableExpansionStmtData
+TryBuildIterableExpansionStmtInitializer(Sema &S, Expr *ExpansionInitializer,
+ Expr *Index, SourceLocation ColonLoc,
+ bool VarIsConstexpr) {
+ IterableExpansionStmtData Data;
+
+ // C++26 [stmt.expand]p3: An expression is expansion-iterable if it does not
+ // have array type [...]
+ QualType Ty = ExpansionInitializer->getType().getNonReferenceType();
+ if (Ty->isArrayType())
+ return Data;
+
+ // Lookup member and ADL 'begin()'/'end()'. Only check if they exist; even if
+ // they're deleted, inaccessible, etc., this is still an iterating expansion
+ // statement, albeit an ill-formed one.
+ DeclarationNameInfo BeginName(&S.PP.getIdentifierTable().get("begin"),
+ ColonLoc);
+ DeclarationNameInfo EndName(&S.PP.getIdentifierTable().get("end"), ColonLoc);
+
+ // Try member lookup first.
+ bool FoundBeginEnd = false;
+ if (auto *Record = Ty->getAsCXXRecordDecl()) {
+ LookupResult BeginLR(S, BeginName, Sema::LookupMemberName);
+ LookupResult EndLR(S, EndName, Sema::LookupMemberName);
+ FoundBeginEnd = S.LookupQualifiedName(BeginLR, Record) &&
+ S.LookupQualifiedName(EndLR, Record);
+ }
+
+ // Try ADL.
+ //
+ // If overload resolution for 'begin()' *and* 'end()' succeeds (irrespective
+ // of whether it results in a usable candidate), then assume this is an
+ // iterating expansion statement.
+ auto HasADLCandidate = [&](DeclarationName Name) {
+ OverloadCandidateSet Candidates(ColonLoc, OverloadCandidateSet::CSK_Normal);
+ OverloadCandidateSet::iterator Best;
+
+ S.AddArgumentDependentLookupCandidates(Name, ColonLoc, ExpansionInitializer,
+ /*ExplicitTemplateArgs=*/nullptr,
+ Candidates);
+
+ return Candidates.BestViableFunction(S, ColonLoc, Best) !=
+ OR_No_Viable_Function;
+ };
+
+ if (!FoundBeginEnd && (!HasADLCandidate(BeginName.getName()) ||
+ !HasADLCandidate(EndName.getName())))
+ return Data;
+
+ auto Ctx = Sema::ExpressionEvaluationContext::PotentiallyEvaluated;
+ if (VarIsConstexpr)
+ Ctx = Sema::ExpressionEvaluationContext::ImmediateFunctionContext;
+ EnterExpressionEvaluationContext ExprEvalCtx(S, Ctx);
+
+ // The declarations should be attached to the parent decl context.
+ Sema::ContextRAII CtxGuard(
+ S, S.CurContext->getEnclosingNonExpansionStatementContext(),
+ /*NewThis=*/false);
+
+ // Ok, we know that this is supposed to be an iterable expansion statement;
+ // delegate to the for-range code to build the range/begin/end variables.
+ //
+ // Any failure at this point is a hard error.
+ Data.TheState = IterableExpansionStmtData::State::Error;
+ Scope *Scope = S.getCurScope();
+
+ // TODO: CWG 3131 changes how this range is declared.
+ StmtResult Var = S.BuildCXXForRangeRangeVar(Scope, ExpansionInitializer,
+ /*ForExpansionStmt=*/true);
+ if (Var.isInvalid())
+ return Data;
+
+ auto *RangeVar = cast<DeclStmt>(Var.get());
+ Sema::ForRangeBeginEndInfo Info = S.BuildCXXForRangeBeginEndVars(
+ Scope, cast<VarDecl>(RangeVar->getSingleDecl()), ColonLoc,
+ /*CoawaitLoc=*/{},
+ /*LifetimeExtendTemps=*/{}, Sema::BFRK_Build, /*ForExpansionStmt=*/true);
+
+ if (!Info.isValid())
+ return Data;
+
+ StmtResult BeginStmt = S.ActOnDeclStmt(
+ S.ConvertDeclToDeclGroup(Info.BeginVar), ColonLoc, ColonLoc);
+ StmtResult EndStmt = S.ActOnDeclStmt(S.ConvertDeclToDeclGroup(Info.EndVar),
+ ColonLoc, ColonLoc);
+ if (BeginStmt.isInvalid() || EndStmt.isInvalid())
+ return Data;
+
+ // Build '*(begin + i)'.
+ DeclRefExpr *Begin = S.BuildDeclRefExpr(
+ Info.BeginVar, Info.BeginVar->getType().getNonReferenceType(), VK_LValue,
+ ColonLoc);
+
+ ExprResult BeginPlusI =
+ S.ActOnBinOp(Scope, ColonLoc, tok::plus, Begin, Index);
+ if (BeginPlusI.isInvalid())
+ return Data;
+
+ ExprResult Deref =
+ S.ActOnUnaryOp(Scope, ColonLoc, tok::star, BeginPlusI.get());
+ if (Deref.isInvalid())
+ return Data;
+
+ Deref = S.MaybeCreateExprWithCleanups(Deref.get());
+ Data.BeginDecl = BeginStmt.getAs<DeclStmt>();
+ Data.EndDecl = EndStmt.getAs<DeclStmt>();
+ Data.RangeDecl = RangeVar;
+ Data.Initializer = Deref.get();
+ Data.TheState = IterableExpansionStmtData::State::Ok;
+ return Data;
+}
+
CXXExpansionStmtDecl *
Sema::ActOnCXXExpansionStmtDecl(unsigned TemplateDepth,
SourceLocation TemplateKWLoc) {
@@ -134,8 +272,26 @@ StmtResult Sema::ActOnCXXExpansionStmtPattern(
ColonLoc, RParenLoc);
}
- Diag(ESD->getLocation(), diag::err_expansion_statements_todo);
- return StmtError();
+ if (ExpansionInitializer->hasPlaceholderType()) {
+ ExprResult R = CheckPlaceholderExpr(ExpansionInitializer);
+ if (R.isInvalid())
+ return StmtError();
+ ExpansionInitializer = R.get();
+ }
+
+ if (DiagnoseUnexpandedParameterPack(ExpansionInitializer))
+ return StmtError();
+
+ // Reject lambdas early.
+ if (auto *RD = ExpansionInitializer->getType()->getAsCXXRecordDecl();
+ RD && RD->isLambda()) {
+ Diag(ExpansionInitializer->getBeginLoc(), diag::err_expansion_stmt_lambda);
+ return StmtError();
+ }
+
+ return BuildNonEnumeratingCXXExpansionStmtPattern(
+ ESD, Init, DS, ExpansionInitializer, LParenLoc, ColonLoc, RParenLoc,
+ LifetimeExtendTemps);
}
StmtResult Sema::BuildCXXEnumeratingExpansionStmtPattern(
@@ -146,6 +302,43 @@ StmtResult Sema::BuildCXXEnumeratingExpansionStmtPattern(
cast<DeclStmt>(ExpansionVar), LParenLoc, ColonLoc, RParenLoc);
}
+StmtResult Sema::BuildNonEnumeratingCXXExpansionStmtPattern(
+ CXXExpansionStmtDecl *ESD, Stmt *Init, DeclStmt *ExpansionVarStmt,
+ Expr *ExpansionInitializer, SourceLocation LParenLoc,
+ SourceLocation ColonLoc, SourceLocation RParenLoc,
+ ArrayRef<MaterializeTemporaryExpr *> LifetimeExtendTemps) {
+ VarDecl *ExpansionVar = cast<VarDecl>(ExpansionVarStmt->getSingleDecl());
+
+ if (ExpansionInitializer->isTypeDependent()) {
+ ActOnDependentForRangeInitializer(ExpansionVar, BFRK_Build);
+ return new (Context) CXXDependentExpansionStmtPattern(
+ ESD, Init, ExpansionVarStmt, ExpansionInitializer, LParenLoc, ColonLoc,
+ RParenLoc);
+ }
+
+ // Otherwise, if it can be an iterating expansion statement, it is one.
+ DeclRefExpr *Index = BuildIndexDRE(*this, ESD);
+ IterableExpansionStmtData Data = TryBuildIterableExpansionStmtInitializer(
+ *this, ExpansionInitializer, Index, ColonLoc,
+ ExpansionVar->isConstexpr());
+ if (Data.hasError()) {
+ ActOnInitializerError(ExpansionVar);
+ return StmtError();
+ }
+
+ if (Data.isIterable()) {
+ if (FinaliseExpansionVar(*this, ExpansionVar, Data.Initializer))
+ return StmtError();
+
+ return new (Context) CXXIteratingExpansionStmtPattern(
+ ESD, Init, ExpansionVarStmt, Data.RangeDecl, Data.BeginDecl,
+ Data.EndDecl, LParenLoc, ColonLoc, RParenLoc);
+ }
+
+ Diag(ESD->getLocation(), diag::err_expansion_statements_todo);
+ return StmtError();
+}
+
StmtResult Sema::FinishCXXExpansionStmt(Stmt *Exp, Stmt *Body) {
if (!Exp || !Body)
return StmtError();
@@ -168,7 +361,13 @@ StmtResult Sema::FinishCXXExpansionStmt(Stmt *Exp, Stmt *Body) {
if (Expansion->getInit())
Shared.push_back(Expansion->getInit());
- assert(Expansion->isEnumerating() && "TODO");
+ if (Expansion->isIterating()) {
+ Shared.push_back(Expansion->getRangeVarStmt());
+ Shared.push_back(Expansion->getBeginVarStmt());
+ Shared.push_back(Expansion->getEndVarStmt());
+ } else {
+ assert(Expansion->isEnumerating() && "TODO");
+ }
// Return an empty statement if the range is empty.
if (*NumInstantiations == 0) {
@@ -243,5 +442,53 @@ Sema::ComputeExpansionSize(CXXExpansionStmtPattern *Expansion) {
->getRangeExpr()
->getNumInits();
+ // By [stmt.expand]5.2, N is the result of evaluating the expression
+ //
+ // [] consteval {
+ // std::ptrdiff_t result = 0;
+ // for (auto i = begin; i != end; ++i) ++result;
+ // return result;
+ // }()
+ // TODO: CWG 3131 changes this lambda a bit.
+ if (auto *Iterating = dyn_cast<CXXIteratingExpansionStmtPattern>(Expansion)) {
+ EnterExpressionEvaluationContext ExprEvalCtx(
+ *this, ExpressionEvaluationContext::ConstantEvaluated);
+
+ // FIXME: Actually do that; unfortunately, conjuring a lambda out of thin
+ // air in Sema is a massive pain, so for now just cheat by computing
+ // 'end - begin'.
+ SourceLocation Loc = Iterating->getColonLoc();
+ DeclRefExpr *Begin = BuildDeclRefExpr(
+ Iterating->getBeginVar(),
+ Iterating->getBeginVar()->getType().getNonReferenceType(), VK_LValue,
+ Loc);
+
+ DeclRefExpr *End = BuildDeclRefExpr(
+ Iterating->getEndVar(),
+ Iterating->getEndVar()->getType().getNonReferenceType(), VK_LValue,
+ Loc);
+
+ ExprResult N = ActOnBinOp(getCurScope(), Loc, tok::minus, End, Begin);
+ if (N.isInvalid())
+ return std::nullopt;
+
+ Expr::EvalResult ER;
+ SmallVector<PartialDiagnosticAt, 4> Notes;
+ ER.Diag = &Notes;
+ if (!N.get()->EvaluateAsInt(ER, Context)) {
+ Diag(Loc, diag::err_expansion_size_expr_not_ice);
+ for (const auto &[Location, PDiag] : Notes)
+ Diag(Location, PDiag);
+ return std::nullopt;
+ }
+
+ if (ER.Val.getInt().isNegative()) {
+ Diag(Loc, diag::err_expansion_size_negative) << ER.Val.getInt();
+ return std::nullopt;
+ }
+
+ return ER.Val.getInt().getZExtValue();
+ }
+
llvm_unreachable("TODO");
}
diff --git a/clang/lib/Sema/SemaStmt.cpp b/clang/lib/Sema/SemaStmt.cpp
index a9fa0c3910c86..5a4c2ee9dd06f 100644
--- a/clang/lib/Sema/SemaStmt.cpp
+++ b/clang/lib/Sema/SemaStmt.cpp
@@ -2411,6 +2411,11 @@ void NoteForRangeBeginEndFunction(Sema &SemaRef, Expr *E,
/// Build a variable declaration for a for-range statement.
VarDecl *BuildForRangeVarDecl(Sema &SemaRef, SourceLocation Loc, QualType Type,
StringRef Name, bool ForExpansionStmt) {
+ // Making the variable constexpr doesn't automatically add 'const' to the
+ // type, so do that now.
+ if (ForExpansionStmt && !Type->isReferenceType())
+ Type = Type.withConst();
+
DeclContext *DC = SemaRef.CurContext;
IdentifierInfo *II = &SemaRef.PP.getIdentifierTable().get(Name);
TypeSourceInfo *TInfo = SemaRef.Context.getTrivialTypeSourceInfo(Type, Loc);
@@ -2418,6 +2423,9 @@ VarDecl *BuildForRangeVarDecl(Sema &SemaRef, SourceLocation Loc, QualType Type,
TInfo, SC_None);
Decl->setImplicit();
Decl->setCXXForRangeImplicitVar(true);
+ if (ForExpansionStmt)
+ // CWG 3044: Do not make the variable 'static'.
+ Decl->setConstexpr(true);
return Decl;
}
}
@@ -2731,7 +2739,10 @@ Sema::ForRangeBeginEndInfo Sema::BuildCXXForRangeBeginEndVars(
return {};
// P2718R0 - Lifetime extension in range-based for loops.
- if (getLangOpts().CPlusPlus23)
+ //
+ // CWG 3043 – Do not apply lifetime extension to iterating
+ // expansion statements.
+ if (getLangOpts().CPlusPlus23 && !ForExpansionStmt)
ApplyForRangeOrExpansionStatementLifetimeExtension(RangeVar,
LifetimeExtendTemps);
diff --git a/clang/lib/Sema/TreeTransform.h b/clang/lib/Sema/TreeTransform.h
index 7ed1b91ee733a..4d85bc4f76c72 100644
--- a/clang/lib/Sema/TreeTransform.h
+++ b/clang/lib/Sema/TreeTransform.h
@@ -9325,6 +9325,20 @@ StmtResult TreeTransform<Derived>::TransformCXXExpansionStmtPattern(
NewPattern = CXXExpansionStmtPattern::CreateEnumerating(
SemaRef.Context, NewESD, Init, ExpansionVarStmt, S->getLParenLoc(),
S->getColonLoc(), S->getRParenLoc());
+ } else if (S->isIterating()) {
+ StmtResult Range = getDerived().TransformStmt(S->getRangeVarStmt());
+ if (Range.isInvalid())
+ return StmtError();
+
+ StmtResult Begin = getDerived().TransformStmt(S->getBeginVarStmt());
+ StmtResult End = getDerived().TransformStmt(S->getEndVarStmt());
+ if (Begin.isInvalid() || End.isInvalid())
+ return StmtError();
+
+ NewPattern = CXXExpansionStmtPattern::CreateIterating(
+ SemaRef.Context, NewESD, Init, ExpansionVarStmt,
+ Range.getAs<DeclStmt>(), Begin.getAs<DeclStmt>(), End.getAs<DeclStmt>(),
+ S->getLParenLoc(), S->getColonLoc(), S->getRParenLoc());
} else {
llvm_unreachable("TODO");
}
>From b57428b28d4fbc0637dc5900be164bd819565836 Mon Sep 17 00:00:00 2001
From: Sirraide <aeternalmail at gmail.com>
Date: Mon, 1 Dec 2025 19:12:52 +0100
Subject: [PATCH 2/9] Reject incomplete types and vlas
---
clang/include/clang/Basic/DiagnosticSemaKinds.td | 4 ++++
clang/lib/Sema/SemaExpand.cpp | 11 +++++++++++
2 files changed, 15 insertions(+)
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index 0ce3d8164d955..ef7321ed0dfa0 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -3714,6 +3714,10 @@ def err_conflicting_codeseg_attribute : Error<
def warn_duplicate_codeseg_attribute : Warning<
"duplicate code segment specifiers">, InGroup<Section>;
+def err_expansion_stmt_vla : Error<
+ "cannot expand variable length array type %0">;
+def err_expansion_stmt_incomplete : Error<
+ "cannot expand expression of incomplete type %0">;
def err_expansion_stmt_lambda : Error<
"cannot expand lambda closure type">;
diff --git a/clang/lib/Sema/SemaExpand.cpp b/clang/lib/Sema/SemaExpand.cpp
index 4e392e33578e2..a8496720ad39e 100644
--- a/clang/lib/Sema/SemaExpand.cpp
+++ b/clang/lib/Sema/SemaExpand.cpp
@@ -316,6 +316,17 @@ StmtResult Sema::BuildNonEnumeratingCXXExpansionStmtPattern(
RParenLoc);
}
+ if (RequireCompleteType(ExpansionInitializer->getExprLoc(),
+ ExpansionInitializer->getType(),
+ diag::err_expansion_stmt_incomplete))
+ return StmtError();
+
+ if (ExpansionInitializer->getType()->isVariableArrayType()) {
+ Diag(ExpansionInitializer->getExprLoc(), diag::err_expansion_stmt_vla)
+ << ExpansionInitializer->getType();
+ return StmtError();
+ }
+
// Otherwise, if it can be an iterating expansion statement, it is one.
DeclRefExpr *Index = BuildIndexDRE(*this, ESD);
IterableExpansionStmtData Data = TryBuildIterableExpansionStmtInitializer(
>From def1353c1f8a5ef86d7b8843fa0f53dab173dc1f Mon Sep 17 00:00:00 2001
From: Sirraide <aeternalmail at gmail.com>
Date: Tue, 2 Dec 2025 21:52:48 +0100
Subject: [PATCH 3/9] Reject lambdas in Build instead of ActOn
---
clang/lib/Sema/SemaExpand.cpp | 14 +++++++-------
1 file changed, 7 insertions(+), 7 deletions(-)
diff --git a/clang/lib/Sema/SemaExpand.cpp b/clang/lib/Sema/SemaExpand.cpp
index a8496720ad39e..bea0374b53f41 100644
--- a/clang/lib/Sema/SemaExpand.cpp
+++ b/clang/lib/Sema/SemaExpand.cpp
@@ -282,13 +282,6 @@ StmtResult Sema::ActOnCXXExpansionStmtPattern(
if (DiagnoseUnexpandedParameterPack(ExpansionInitializer))
return StmtError();
- // Reject lambdas early.
- if (auto *RD = ExpansionInitializer->getType()->getAsCXXRecordDecl();
- RD && RD->isLambda()) {
- Diag(ExpansionInitializer->getBeginLoc(), diag::err_expansion_stmt_lambda);
- return StmtError();
- }
-
return BuildNonEnumeratingCXXExpansionStmtPattern(
ESD, Init, DS, ExpansionInitializer, LParenLoc, ColonLoc, RParenLoc,
LifetimeExtendTemps);
@@ -327,6 +320,13 @@ StmtResult Sema::BuildNonEnumeratingCXXExpansionStmtPattern(
return StmtError();
}
+ // Reject lambdas early.
+ if (auto *RD = ExpansionInitializer->getType()->getAsCXXRecordDecl();
+ RD && RD->isLambda()) {
+ Diag(ExpansionInitializer->getBeginLoc(), diag::err_expansion_stmt_lambda);
+ return StmtError();
+ }
+
// Otherwise, if it can be an iterating expansion statement, it is one.
DeclRefExpr *Index = BuildIndexDRE(*this, ESD);
IterableExpansionStmtData Data = TryBuildIterableExpansionStmtInitializer(
>From 41889bc78926d404a8f619f8d75c31a9a9500ea2 Mon Sep 17 00:00:00 2001
From: Sirraide <aeternalmail at gmail.com>
Date: Wed, 3 Dec 2025 01:50:44 +0100
Subject: [PATCH 4/9] Properly compute iterating expansion stmt size
---
clang/lib/Sema/SemaExpand.cpp | 191 +++++++++++++++++++++++++++++-----
1 file changed, 167 insertions(+), 24 deletions(-)
diff --git a/clang/lib/Sema/SemaExpand.cpp b/clang/lib/Sema/SemaExpand.cpp
index bea0374b53f41..be772a9ce7c6b 100644
--- a/clang/lib/Sema/SemaExpand.cpp
+++ b/clang/lib/Sema/SemaExpand.cpp
@@ -21,6 +21,8 @@
#include "clang/Sema/Sema.h"
#include "clang/Sema/Template.h"
+#include <llvm/ADT/ScopeExit.h>
+
using namespace clang;
using namespace sema;
@@ -302,6 +304,13 @@ StmtResult Sema::BuildNonEnumeratingCXXExpansionStmtPattern(
ArrayRef<MaterializeTemporaryExpr *> LifetimeExtendTemps) {
VarDecl *ExpansionVar = cast<VarDecl>(ExpansionVarStmt->getSingleDecl());
+ // Reject lambdas early.
+ if (auto *RD = ExpansionInitializer->getType()->getAsCXXRecordDecl();
+ RD && RD->isLambda()) {
+ Diag(ExpansionInitializer->getBeginLoc(), diag::err_expansion_stmt_lambda);
+ return StmtError();
+ }
+
if (ExpansionInitializer->isTypeDependent()) {
ActOnDependentForRangeInitializer(ExpansionVar, BFRK_Build);
return new (Context) CXXDependentExpansionStmtPattern(
@@ -320,13 +329,6 @@ StmtResult Sema::BuildNonEnumeratingCXXExpansionStmtPattern(
return StmtError();
}
- // Reject lambdas early.
- if (auto *RD = ExpansionInitializer->getType()->getAsCXXRecordDecl();
- RD && RD->isLambda()) {
- Diag(ExpansionInitializer->getBeginLoc(), diag::err_expansion_stmt_lambda);
- return StmtError();
- }
-
// Otherwise, if it can be an iterating expansion statement, it is one.
DeclRefExpr *Index = BuildIndexDRE(*this, ESD);
IterableExpansionStmtData Data = TryBuildIterableExpansionStmtInitializer(
@@ -362,6 +364,18 @@ StmtResult Sema::FinishCXXExpansionStmt(Stmt *Exp, Stmt *Body) {
if (HasDependentSize(Expansion))
return Expansion;
+ // 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);
+
+ // Even if the size isn't technically dependent, delay expansion until
+ // we're no longer in a template if this is an iterating expansion statement
+ // since evaluating a lambda declared in a template doesn't work too well.
+ if (CurContext->isDependentContext() &&
+ isa<CXXIteratingExpansionStmtPattern>(Expansion))
+ return Expansion;
+
// This can fail if this is an iterating expansion statement.
std::optional<uint64_t> NumInstantiations = ComputeExpansionSize(Expansion);
if (!NumInstantiations)
@@ -399,11 +413,6 @@ StmtResult Sema::FinishCXXExpansionStmt(Stmt *Exp, Stmt *Body) {
SmallVector<Stmt *, 4> 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);
@@ -462,42 +471,176 @@ Sema::ComputeExpansionSize(CXXExpansionStmtPattern *Expansion) {
// }()
// TODO: CWG 3131 changes this lambda a bit.
if (auto *Iterating = dyn_cast<CXXIteratingExpansionStmtPattern>(Expansion)) {
+ SourceLocation Loc = Expansion->getColonLoc();
EnterExpressionEvaluationContext ExprEvalCtx(
*this, ExpressionEvaluationContext::ConstantEvaluated);
- // FIXME: Actually do that; unfortunately, conjuring a lambda out of thin
- // air in Sema is a massive pain, so for now just cheat by computing
- // 'end - begin'.
- SourceLocation Loc = Iterating->getColonLoc();
+ // This is mostly copied from ParseLambdaExpressionAfterIntroducer().
+ ParseScope LambdaScope(*this, Scope::LambdaScope | Scope::DeclScope |
+ Scope::FunctionDeclarationScope |
+ Scope::FunctionPrototypeScope);
+ AttributeFactory AttrFactory;
+ LambdaIntroducer Intro;
+ Intro.Range = SourceRange(Loc, Loc);
+ Intro.Default = LCD_ByRef; // CWG 3131
+ Intro.DefaultLoc = Loc;
+ DeclSpec DS(AttrFactory);
+ Declarator D(DS, ParsedAttributesView::none(),
+ DeclaratorContext::LambdaExpr);
+ PushLambdaScope();
+ ActOnLambdaExpressionAfterIntroducer(Intro, getCurScope());
+
+ // Make the lambda 'consteval'.
+ {
+ ParseScope Prototype(*this, Scope::FunctionPrototypeScope |
+ Scope::FunctionDeclarationScope |
+ Scope::DeclScope);
+ const char* PrevSpec = nullptr;
+ unsigned DiagId = 0;
+ DS.SetConstexprSpec(ConstexprSpecKind::Consteval, Loc, PrevSpec, DiagId);
+ assert(DiagId == 0 && PrevSpec == nullptr);
+ ActOnLambdaClosureParameters(getCurScope(), /*ParamInfo=*/{});
+ ActOnLambdaClosureQualifiers(Intro, /*MutableLoc=*/SourceLocation());
+ }
+
+ ParseScope BodyScope(*this, Scope::BlockScope | Scope::FnScope |
+ Scope::DeclScope |
+ Scope::CompoundStmtScope);
+
+ ActOnStartOfLambdaDefinition(Intro, D, DS);
+
+ // Enter the compound statement that is the lambda body.
+ ActOnStartOfCompoundStmt(/*IsStmtExpr=*/false);
+ ActOnAfterCompoundStatementLeadingPragmas();
+ auto PopScopesOnReturn = llvm::make_scope_exit([&] {
+ ActOnFinishOfCompoundStmt();
+ ActOnLambdaError(Loc, getCurScope());
+ });
+
+ // std::ptrdiff_t result = 0;
+ QualType PtrDiffT = Context.getPointerDiffType();
+ VarDecl *ResultVar = VarDecl::Create(
+ Context, CurContext, Loc, Loc, &PP.getIdentifierTable().get("__result"),
+ PtrDiffT, Context.getTrivialTypeSourceInfo(PtrDiffT, Loc), SC_None);
+ Expr *Zero = ActOnIntegerConstant(Loc, 0).get();
+ AddInitializerToDecl(ResultVar, Zero, false);
+ StmtResult ResultVarStmt =
+ ActOnDeclStmt(ConvertDeclToDeclGroup(ResultVar), Loc, Loc);
+ if (ResultVarStmt.isInvalid() || ResultVar->isInvalidDecl())
+ return std::nullopt;
+
+ // Start the for loop.
+ ParseScope ForScope(*this, Scope::DeclScope | Scope::ControlScope);
+
+ // auto i = begin;
+ VarDecl *IterationVar = VarDecl::Create(
+ Context, CurContext, Loc, Loc, &PP.getIdentifierTable().get("__i"),
+ Context.getAutoDeductType(),
+ Context.getTrivialTypeSourceInfo(Context.getAutoDeductType(), Loc),
+ SC_None);
DeclRefExpr *Begin = BuildDeclRefExpr(
Iterating->getBeginVar(),
Iterating->getBeginVar()->getType().getNonReferenceType(), VK_LValue,
Loc);
+ AddInitializerToDecl(IterationVar, Begin, false);
+ StmtResult IterationVarStmt =
+ ActOnDeclStmt(ConvertDeclToDeclGroup(IterationVar), Loc, Loc);
+ if (IterationVarStmt.isInvalid() || IterationVar->isInvalidDecl())
+ return std::nullopt;
+ // i != end
+ DeclRefExpr *IterationVarDeclRef = BuildDeclRefExpr(
+ IterationVar, IterationVar->getType().getNonReferenceType(), VK_LValue,
+ Loc);
DeclRefExpr *End = BuildDeclRefExpr(
Iterating->getEndVar(),
Iterating->getEndVar()->getType().getNonReferenceType(), VK_LValue,
Loc);
+ ExprResult NotEqual = ActOnBinOp(getCurScope(), Loc, tok::exclaimequal,
+ IterationVarDeclRef, End);
+ if (NotEqual.isInvalid())
+ return std::nullopt;
+ ConditionResult Condition = ActOnCondition(
+ getCurScope(), Loc, NotEqual.get(), ConditionKind::Boolean,
+ /*MissingOk=*/false);
+ if (Condition.isInvalid())
+ return std::nullopt;
+
+ // ++i
+ IterationVarDeclRef = BuildDeclRefExpr(
+ IterationVar, IterationVar->getType().getNonReferenceType(), VK_LValue,
+ Loc);
+ ExprResult Increment =
+ ActOnUnaryOp(getCurScope(), Loc, tok::plusplus, IterationVarDeclRef);
+ if (Increment.isInvalid())
+ return std::nullopt;
+ FullExprArg ThirdPart = MakeFullDiscardedValueExpr(Increment.get());
+
+ // Enter the body of the for loop.
+ ParseScope InnerScope(*this, Scope::DeclScope);
+ getCurScope()->decrementMSManglingNumber();
+
+ // ++result;
+ DeclRefExpr *ResultDeclRef = BuildDeclRefExpr(
+ ResultVar, ResultVar->getType().getNonReferenceType(), VK_LValue, Loc);
+ ExprResult IncrementResult =
+ ActOnUnaryOp(getCurScope(), Loc, tok::plusplus, ResultDeclRef);
+ if (IncrementResult.isInvalid())
+ return std::nullopt;
+ StmtResult IncrementStmt = ActOnExprStmt(IncrementResult.get());
+ if (IncrementStmt.isInvalid())
+ return std::nullopt;
- ExprResult N = ActOnBinOp(getCurScope(), Loc, tok::minus, End, Begin);
- if (N.isInvalid())
+ // Exit the for loop.
+ InnerScope.Exit();
+ ForScope.Exit();
+ StmtResult ForLoop =
+ ActOnForStmt(Loc, Loc, IterationVarStmt.get(), Condition, ThirdPart,
+ Loc, IncrementStmt.get());
+ if (ForLoop.isInvalid())
+ return std::nullopt;
+
+ // return result;
+ ResultDeclRef = BuildDeclRefExpr(
+ ResultVar, ResultVar->getType().getNonReferenceType(), VK_LValue, Loc);
+ StmtResult Return = ActOnReturnStmt(Loc, ResultDeclRef, getCurScope());
+ if (Return.isInvalid())
+ return std::nullopt;
+
+ // Finally, we can build the compound statement that is the lambda body.
+ StmtResult LambdaBody = ActOnCompoundStmt(
+ Loc, Loc, {ResultVarStmt.get(), ForLoop.get(), Return.get()},
+ /*isStmtExpr=*/false);
+ if (LambdaBody.isInvalid())
+ return std::nullopt;
+
+ ActOnFinishOfCompoundStmt();
+ BodyScope.Exit();
+ LambdaScope.Exit();
+ PopScopesOnReturn.release();
+ ExprResult Lambda = ActOnLambdaExpr(Loc, LambdaBody.get());
+ if (Lambda.isInvalid())
+ return std::nullopt;
+
+ // Invoke the lambda.
+ ExprResult Call =
+ ActOnCallExpr(getCurScope(), Lambda.get(), Loc, /*ArgExprs=*/{}, Loc);
+ if (Call.isInvalid())
return std::nullopt;
Expr::EvalResult ER;
SmallVector<PartialDiagnosticAt, 4> Notes;
ER.Diag = &Notes;
- if (!N.get()->EvaluateAsInt(ER, Context)) {
+ if (!Call.get()->EvaluateAsInt(ER, Context)) {
Diag(Loc, diag::err_expansion_size_expr_not_ice);
for (const auto &[Location, PDiag] : Notes)
Diag(Location, PDiag);
return std::nullopt;
}
- if (ER.Val.getInt().isNegative()) {
- Diag(Loc, diag::err_expansion_size_negative) << ER.Val.getInt();
- return std::nullopt;
- }
-
+ // It shouldn't be possible for this to be negative since we compute this
+ // via the built-in '++' on a ptrdiff_t.
+ assert(ER.Val.getInt().isNonNegative());
return ER.Val.getInt().getZExtValue();
}
>From bcfbabc1709405490bc15a8fc1aa97b447d23d7b Mon Sep 17 00:00:00 2001
From: Sirraide <aeternalmail at gmail.com>
Date: Wed, 3 Dec 2025 01:52:54 +0100
Subject: [PATCH 5/9] Remove unused diagnostic
---
clang/include/clang/Basic/DiagnosticSemaKinds.td | 2 --
1 file changed, 2 deletions(-)
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index ef7321ed0dfa0..a92a3a6a7c331 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -167,8 +167,6 @@ def note_constexpr_assert_failed : Note<
"assertion failed during evaluation of constant expression">;
def err_expansion_size_expr_not_ice : Error<
"expansion size is not a constant expression">;
-def err_expansion_size_negative : Error<
- "expansion size must not be negative (was %0)">;
// Semantic analysis of constant literals.
def ext_predef_outside_function : Warning<
>From 352e26fa25f6663bf548fea70c26626da5beb4fe Mon Sep 17 00:00:00 2001
From: Sirraide <aeternalmail at gmail.com>
Date: Wed, 3 Dec 2025 01:53:24 +0100
Subject: [PATCH 6/9] Formatting
---
clang/lib/Sema/SemaExpand.cpp | 9 ++++-----
1 file changed, 4 insertions(+), 5 deletions(-)
diff --git a/clang/lib/Sema/SemaExpand.cpp b/clang/lib/Sema/SemaExpand.cpp
index be772a9ce7c6b..76c562f538400 100644
--- a/clang/lib/Sema/SemaExpand.cpp
+++ b/clang/lib/Sema/SemaExpand.cpp
@@ -20,8 +20,7 @@
#include "clang/Sema/Overload.h"
#include "clang/Sema/Sema.h"
#include "clang/Sema/Template.h"
-
-#include <llvm/ADT/ScopeExit.h>
+#include "llvm/ADT/ScopeExit.h"
using namespace clang;
using namespace sema;
@@ -493,9 +492,9 @@ Sema::ComputeExpansionSize(CXXExpansionStmtPattern *Expansion) {
// Make the lambda 'consteval'.
{
ParseScope Prototype(*this, Scope::FunctionPrototypeScope |
- Scope::FunctionDeclarationScope |
- Scope::DeclScope);
- const char* PrevSpec = nullptr;
+ Scope::FunctionDeclarationScope |
+ Scope::DeclScope);
+ const char *PrevSpec = nullptr;
unsigned DiagId = 0;
DS.SetConstexprSpec(ConstexprSpecKind::Consteval, Loc, PrevSpec, DiagId);
assert(DiagId == 0 && PrevSpec == nullptr);
>From 4c1b3796efde5aee0b3398975be06d9691cea6e1 Mon Sep 17 00:00:00 2001
From: Sirraide <aeternalmail at gmail.com>
Date: Wed, 3 Dec 2025 03:19:19 +0100
Subject: [PATCH 7/9] CWG 3131
---
clang/lib/Sema/SemaExpand.cpp | 107 ++++++++++++++++++++--------------
clang/lib/Sema/SemaStmt.cpp | 18 ++++--
2 files changed, 78 insertions(+), 47 deletions(-)
diff --git a/clang/lib/Sema/SemaExpand.cpp b/clang/lib/Sema/SemaExpand.cpp
index 76c562f538400..13d981d7b426a 100644
--- a/clang/lib/Sema/SemaExpand.cpp
+++ b/clang/lib/Sema/SemaExpand.cpp
@@ -157,17 +157,34 @@ TryBuildIterableExpansionStmtInitializer(Sema &S, Expr *ExpansionInitializer,
Data.TheState = IterableExpansionStmtData::State::Error;
Scope *Scope = S.getCurScope();
- // TODO: CWG 3131 changes how this range is declared.
- StmtResult Var = S.BuildCXXForRangeRangeVar(Scope, ExpansionInitializer,
- /*ForExpansionStmt=*/true);
+ // CWG 3131: The declaration of 'range' is of the form
+ //
+ // constexpr[opt] decltype(auto) range = (expansion-initializer);
+ //
+ // where 'constexpr' is present iff the for-range-declaration is 'constexpr'.
+ StmtResult Var = S.BuildCXXForRangeRangeVar(
+ Scope, S.ActOnParenExpr(ColonLoc, ColonLoc, ExpansionInitializer).get(),
+ S.Context.getAutoType(QualType(), AutoTypeKeyword::DecltypeAuto,
+ /*IsDependent*/ false),
+ VarIsConstexpr);
if (Var.isInvalid())
return Data;
+ // CWG 3131: Discussion around this core issue (though as of the time of
+ // writing not the resolution itself) suggests that the other variables we
+ // create here should likewise be 'constexpr' iff the range variable is
+ // declared 'constexpr'.
+ //
+ // FIXME: As of CWG 3131, 'end' is no longer used outside the lambda that
+ // performs the size calculation (despite that, CWG 3131 currently still
+ // lists it in the generated code, but this is likely an oversight). Ideally,
+ // we should only create 'begin' here instead, but that requires another
+ // substantial refactor of the for-range code.
auto *RangeVar = cast<DeclStmt>(Var.get());
Sema::ForRangeBeginEndInfo Info = S.BuildCXXForRangeBeginEndVars(
Scope, cast<VarDecl>(RangeVar->getSingleDecl()), ColonLoc,
/*CoawaitLoc=*/{},
- /*LifetimeExtendTemps=*/{}, Sema::BFRK_Build, /*ForExpansionStmt=*/true);
+ /*LifetimeExtendTemps=*/{}, Sema::BFRK_Build, VarIsConstexpr);
if (!Info.isValid())
return Data;
@@ -269,6 +286,10 @@ StmtResult Sema::ActOnCXXExpansionStmtPattern(
// 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).
+ //
+ // TODO: CWG 3131 makes it so the 'range' variable of an iterating
+ // expansion statement need no longer be 'constexpr'... so do we want
+ // lifetime extension for iterating expansion statements after all?
return BuildCXXEnumeratingExpansionStmtPattern(ESD, Init, DS, LParenLoc,
ColonLoc, RParenLoc);
}
@@ -461,14 +482,15 @@ Sema::ComputeExpansionSize(CXXExpansionStmtPattern *Expansion) {
->getRangeExpr()
->getNumInits();
- // By [stmt.expand]5.2, N is the result of evaluating the expression
+ // CWG 3131: N is the result of evaluating the expression
//
- // [] consteval {
+ // [&] consteval {
// std::ptrdiff_t result = 0;
- // for (auto i = begin; i != end; ++i) ++result;
+ // auto b = begin-expr;
+ // auto e = end-expr;
+ // for (; b != e; ++b) ++result;
// return result;
// }()
- // TODO: CWG 3131 changes this lambda a bit.
if (auto *Iterating = dyn_cast<CXXIteratingExpansionStmtPattern>(Expansion)) {
SourceLocation Loc = Expansion->getColonLoc();
EnterExpressionEvaluationContext ExprEvalCtx(
@@ -531,32 +553,32 @@ Sema::ComputeExpansionSize(CXXExpansionStmtPattern *Expansion) {
// Start the for loop.
ParseScope ForScope(*this, Scope::DeclScope | Scope::ControlScope);
- // auto i = begin;
- VarDecl *IterationVar = VarDecl::Create(
- Context, CurContext, Loc, Loc, &PP.getIdentifierTable().get("__i"),
- Context.getAutoDeductType(),
- Context.getTrivialTypeSourceInfo(Context.getAutoDeductType(), Loc),
- SC_None);
- DeclRefExpr *Begin = BuildDeclRefExpr(
- Iterating->getBeginVar(),
- Iterating->getBeginVar()->getType().getNonReferenceType(), VK_LValue,
- Loc);
- AddInitializerToDecl(IterationVar, Begin, false);
- StmtResult IterationVarStmt =
- ActOnDeclStmt(ConvertDeclToDeclGroup(IterationVar), Loc, Loc);
- if (IterationVarStmt.isInvalid() || IterationVar->isInvalidDecl())
+ // auto b = begin-expr;
+ // auto e = end-expr;
+ ForRangeBeginEndInfo Info = BuildCXXForRangeBeginEndVars(
+ getCurScope(), Iterating->getRangeVar(), Loc,
+ /*CoawaitLoc=*/{},
+ /*LifetimeExtendTemps=*/{}, BFRK_Build, /*Constexpr=*/false);
+ if (!Info.isValid())
return std::nullopt;
- // i != end
- DeclRefExpr *IterationVarDeclRef = BuildDeclRefExpr(
- IterationVar, IterationVar->getType().getNonReferenceType(), VK_LValue,
- Loc);
- DeclRefExpr *End = BuildDeclRefExpr(
- Iterating->getEndVar(),
- Iterating->getEndVar()->getType().getNonReferenceType(), VK_LValue,
- Loc);
- ExprResult NotEqual = ActOnBinOp(getCurScope(), Loc, tok::exclaimequal,
- IterationVarDeclRef, End);
+ StmtResult BeginStmt =
+ ActOnDeclStmt(ConvertDeclToDeclGroup(Info.BeginVar), Loc, Loc);
+ StmtResult EndStmt =
+ ActOnDeclStmt(ConvertDeclToDeclGroup(Info.EndVar), Loc, Loc);
+ if (BeginStmt.isInvalid() || EndStmt.isInvalid())
+ return std::nullopt;
+
+ // b != e
+ auto GetDeclRef = [&](VarDecl *VD) -> DeclRefExpr * {
+ return BuildDeclRefExpr(VD, VD->getType().getNonReferenceType(),
+ VK_LValue, Loc);
+ };
+
+ DeclRefExpr *Begin = GetDeclRef(Info.BeginVar);
+ DeclRefExpr *End = GetDeclRef(Info.EndVar);
+ ExprResult NotEqual =
+ ActOnBinOp(getCurScope(), Loc, tok::exclaimequal, Begin, End);
if (NotEqual.isInvalid())
return std::nullopt;
ConditionResult Condition = ActOnCondition(
@@ -565,12 +587,10 @@ Sema::ComputeExpansionSize(CXXExpansionStmtPattern *Expansion) {
if (Condition.isInvalid())
return std::nullopt;
- // ++i
- IterationVarDeclRef = BuildDeclRefExpr(
- IterationVar, IterationVar->getType().getNonReferenceType(), VK_LValue,
- Loc);
+ // ++b
+ Begin = GetDeclRef(Info.BeginVar);
ExprResult Increment =
- ActOnUnaryOp(getCurScope(), Loc, tok::plusplus, IterationVarDeclRef);
+ ActOnUnaryOp(getCurScope(), Loc, tok::plusplus, Begin);
if (Increment.isInvalid())
return std::nullopt;
FullExprArg ThirdPart = MakeFullDiscardedValueExpr(Increment.get());
@@ -593,9 +613,8 @@ Sema::ComputeExpansionSize(CXXExpansionStmtPattern *Expansion) {
// Exit the for loop.
InnerScope.Exit();
ForScope.Exit();
- StmtResult ForLoop =
- ActOnForStmt(Loc, Loc, IterationVarStmt.get(), Condition, ThirdPart,
- Loc, IncrementStmt.get());
+ StmtResult ForLoop = ActOnForStmt(Loc, Loc, /*First=*/nullptr, Condition,
+ ThirdPart, Loc, IncrementStmt.get());
if (ForLoop.isInvalid())
return std::nullopt;
@@ -607,9 +626,11 @@ Sema::ComputeExpansionSize(CXXExpansionStmtPattern *Expansion) {
return std::nullopt;
// Finally, we can build the compound statement that is the lambda body.
- StmtResult LambdaBody = ActOnCompoundStmt(
- Loc, Loc, {ResultVarStmt.get(), ForLoop.get(), Return.get()},
- /*isStmtExpr=*/false);
+ StmtResult LambdaBody =
+ ActOnCompoundStmt(Loc, Loc,
+ {ResultVarStmt.get(), BeginStmt.get(), EndStmt.get(),
+ ForLoop.get(), Return.get()},
+ /*isStmtExpr=*/false);
if (LambdaBody.isInvalid())
return std::nullopt;
diff --git a/clang/lib/Sema/SemaStmt.cpp b/clang/lib/Sema/SemaStmt.cpp
index 5a4c2ee9dd06f..f398fa59d88d7 100644
--- a/clang/lib/Sema/SemaStmt.cpp
+++ b/clang/lib/Sema/SemaStmt.cpp
@@ -2410,10 +2410,10 @@ void NoteForRangeBeginEndFunction(Sema &SemaRef, Expr *E,
/// Build a variable declaration for a for-range statement.
VarDecl *BuildForRangeVarDecl(Sema &SemaRef, SourceLocation Loc, QualType Type,
- StringRef Name, bool ForExpansionStmt) {
+ StringRef Name, bool Constexpr) {
// Making the variable constexpr doesn't automatically add 'const' to the
// type, so do that now.
- if (ForExpansionStmt && !Type->isReferenceType())
+ if (Constexpr && !Type->isReferenceType())
Type = Type.withConst();
DeclContext *DC = SemaRef.CurContext;
@@ -2423,7 +2423,7 @@ VarDecl *BuildForRangeVarDecl(Sema &SemaRef, SourceLocation Loc, QualType Type,
TInfo, SC_None);
Decl->setImplicit();
Decl->setCXXForRangeImplicitVar(true);
- if (ForExpansionStmt)
+ if (Constexpr)
// CWG 3044: Do not make the variable 'static'.
Decl->setConstexpr(true);
return Decl;
@@ -2742,7 +2742,17 @@ Sema::ForRangeBeginEndInfo Sema::BuildCXXForRangeBeginEndVars(
//
// CWG 3043 – Do not apply lifetime extension to iterating
// expansion statements.
- if (getLangOpts().CPlusPlus23 && !ForExpansionStmt)
+ //
+ // Note: CWG 3131 makes it so the 'range' variable need not be
+ // constexpr anymore, which means that we probably *do* want
+ // lifetime extension in that case after all, contrary to what
+ // CWG 3043 currently states. This just works out naturally with
+ // this implementation at the moment, but wg21 insist on no lifetime
+ // extension for iterating expansion statements, then this instead
+ // needs to check whether we're building this for an expansion statement
+ // instead of just applying lifetime extension if the variable isn't
+ // constexpr (or we could pass in an empty range for 'LifetimeExtendTemps').
+ if (getLangOpts().CPlusPlus23 && !Constexpr)
ApplyForRangeOrExpansionStatementLifetimeExtension(RangeVar,
LifetimeExtendTemps);
>From a1973c89dfbc35acfb8b4e6197b840123e0a4827 Mon Sep 17 00:00:00 2001
From: Sirraide <aeternalmail at gmail.com>
Date: Wed, 3 Dec 2025 20:32:53 +0100
Subject: [PATCH 8/9] Add code that was accidentally deleted in rebase
---
clang/lib/Sema/TreeTransform.h | 15 +++++++++++++++
1 file changed, 15 insertions(+)
diff --git a/clang/lib/Sema/TreeTransform.h b/clang/lib/Sema/TreeTransform.h
index 4d85bc4f76c72..58ba5fadd6f08 100644
--- a/clang/lib/Sema/TreeTransform.h
+++ b/clang/lib/Sema/TreeTransform.h
@@ -9339,6 +9339,21 @@ StmtResult TreeTransform<Derived>::TransformCXXExpansionStmtPattern(
SemaRef.Context, NewESD, Init, ExpansionVarStmt,
Range.getAs<DeclStmt>(), Begin.getAs<DeclStmt>(), End.getAs<DeclStmt>(),
S->getLParenLoc(), S->getColonLoc(), S->getRParenLoc());
+ } else if (S->isDependent()) {
+ ExprResult ExpansionInitializer =
+ getDerived().TransformExpr(S->getExpansionInitializer());
+ if (ExpansionInitializer.isInvalid())
+ return StmtError();
+
+ StmtResult Res = SemaRef.BuildNonEnumeratingCXXExpansionStmtPattern(
+ NewESD, Init, ExpansionVarStmt, ExpansionInitializer.get(),
+ S->getLParenLoc(), S->getColonLoc(), S->getRParenLoc(),
+ /*LifetimeExtendTemps=*/{});
+
+ if (Res.isInvalid())
+ return StmtError();
+
+ NewPattern = cast<CXXExpansionStmtPattern>(Res.get());
} else {
llvm_unreachable("TODO");
}
>From 2731a8154441150cd72392331701622493640e3b Mon Sep 17 00:00:00 2001
From: Sirraide <aeternalmail at gmail.com>
Date: Wed, 3 Dec 2025 20:43:08 +0100
Subject: [PATCH 9/9] Merge all pattern kinds into a single AST node
---
clang/lib/Sema/SemaExpand.cpp | 17 ++++++++---------
1 file changed, 8 insertions(+), 9 deletions(-)
diff --git a/clang/lib/Sema/SemaExpand.cpp b/clang/lib/Sema/SemaExpand.cpp
index 13d981d7b426a..40891e96e97de 100644
--- a/clang/lib/Sema/SemaExpand.cpp
+++ b/clang/lib/Sema/SemaExpand.cpp
@@ -333,9 +333,9 @@ StmtResult Sema::BuildNonEnumeratingCXXExpansionStmtPattern(
if (ExpansionInitializer->isTypeDependent()) {
ActOnDependentForRangeInitializer(ExpansionVar, BFRK_Build);
- return new (Context) CXXDependentExpansionStmtPattern(
- ESD, Init, ExpansionVarStmt, ExpansionInitializer, LParenLoc, ColonLoc,
- RParenLoc);
+ return CXXExpansionStmtPattern::CreateDependent(
+ Context, ESD, Init, ExpansionVarStmt, ExpansionInitializer, LParenLoc,
+ ColonLoc, RParenLoc);
}
if (RequireCompleteType(ExpansionInitializer->getExprLoc(),
@@ -363,8 +363,8 @@ StmtResult Sema::BuildNonEnumeratingCXXExpansionStmtPattern(
if (FinaliseExpansionVar(*this, ExpansionVar, Data.Initializer))
return StmtError();
- return new (Context) CXXIteratingExpansionStmtPattern(
- ESD, Init, ExpansionVarStmt, Data.RangeDecl, Data.BeginDecl,
+ return CXXExpansionStmtPattern::CreateIterating(
+ Context, ESD, Init, ExpansionVarStmt, Data.RangeDecl, Data.BeginDecl,
Data.EndDecl, LParenLoc, ColonLoc, RParenLoc);
}
@@ -392,8 +392,7 @@ StmtResult Sema::FinishCXXExpansionStmt(Stmt *Exp, Stmt *Body) {
// Even if the size isn't technically dependent, delay expansion until
// we're no longer in a template if this is an iterating expansion statement
// since evaluating a lambda declared in a template doesn't work too well.
- if (CurContext->isDependentContext() &&
- isa<CXXIteratingExpansionStmtPattern>(Expansion))
+ if (CurContext->isDependentContext() && Expansion->isIterating())
return Expansion;
// This can fail if this is an iterating expansion statement.
@@ -491,7 +490,7 @@ Sema::ComputeExpansionSize(CXXExpansionStmtPattern *Expansion) {
// for (; b != e; ++b) ++result;
// return result;
// }()
- if (auto *Iterating = dyn_cast<CXXIteratingExpansionStmtPattern>(Expansion)) {
+ if (Expansion->isIterating()) {
SourceLocation Loc = Expansion->getColonLoc();
EnterExpressionEvaluationContext ExprEvalCtx(
*this, ExpressionEvaluationContext::ConstantEvaluated);
@@ -556,7 +555,7 @@ Sema::ComputeExpansionSize(CXXExpansionStmtPattern *Expansion) {
// auto b = begin-expr;
// auto e = end-expr;
ForRangeBeginEndInfo Info = BuildCXXForRangeBeginEndVars(
- getCurScope(), Iterating->getRangeVar(), Loc,
+ getCurScope(), Expansion->getRangeVar(), Loc,
/*CoawaitLoc=*/{},
/*LifetimeExtendTemps=*/{}, BFRK_Build, /*Constexpr=*/false);
if (!Info.isValid())
More information about the llvm-branch-commits
mailing list