Skip to content

Commit 1c80c09

Browse files
committed
[Clang] [C++26] Expansion Statements (Part 5)
1 parent bf106a6 commit 1c80c09

File tree

5 files changed

+316
-6
lines changed

5 files changed

+316
-6
lines changed

clang/include/clang/Basic/DiagnosticSemaKinds.td

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,10 @@ def err_ice_too_large : Error<
165165
def err_expr_not_string_literal : Error<"expression is not a string literal">;
166166
def note_constexpr_assert_failed : Note<
167167
"assertion failed during evaluation of constant expression">;
168+
def err_expansion_size_expr_not_ice : Error<
169+
"expansion size is not a constant expression">;
170+
def err_expansion_size_negative : Error<
171+
"expansion size must not be negative (was %0)">;
168172

169173
// Semantic analysis of constant literals.
170174
def ext_predef_outside_function : Warning<
@@ -3698,6 +3702,9 @@ def err_conflicting_codeseg_attribute : Error<
36983702
def warn_duplicate_codeseg_attribute : Warning<
36993703
"duplicate code segment specifiers">, InGroup<Section>;
37003704

3705+
def err_expansion_stmt_lambda : Error<
3706+
"cannot expand lambda closure type">;
3707+
37013708
def err_attribute_patchable_function_entry_invalid_section
37023709
: Error<"section argument to 'patchable_function_entry' attribute is not "
37033710
"valid for this target: %0">;

clang/include/clang/Sema/Sema.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15693,6 +15693,12 @@ class Sema final : public SemaBase {
1569315693
SourceLocation ColonLoc,
1569415694
SourceLocation RParenLoc);
1569515695

15696+
StmtResult BuildNonEnumeratingCXXExpansionStmtPattern(
15697+
CXXExpansionStmtDecl *ESD, Stmt *Init, DeclStmt *ExpansionVarStmt,
15698+
Expr *ExpansionInitializer, SourceLocation LParenLoc,
15699+
SourceLocation ColonLoc, SourceLocation RParenLoc,
15700+
ArrayRef<MaterializeTemporaryExpr *> LifetimeExtendTemps = {});
15701+
1569615702
ExprResult
1569715703
BuildCXXExpansionInitListSelectExpr(CXXExpansionInitListExpr *Range,
1569815704
Expr *Idx);

clang/lib/Sema/SemaExpand.cpp

Lines changed: 243 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,25 @@
2424
using namespace clang;
2525
using namespace sema;
2626

27+
namespace {
28+
struct IterableExpansionStmtData {
29+
enum class State {
30+
NotIterable,
31+
Error,
32+
Ok,
33+
};
34+
35+
DeclStmt *RangeDecl = nullptr;
36+
DeclStmt *BeginDecl = nullptr;
37+
DeclStmt *EndDecl = nullptr;
38+
Expr *Initializer = nullptr;
39+
State TheState = State::NotIterable;
40+
41+
bool isIterable() const { return TheState == State::Ok; }
42+
bool hasError() { return TheState == State::Error; }
43+
};
44+
} // namespace
45+
2746
// Build a 'DeclRefExpr' designating the template parameter '__N'.
2847
static DeclRefExpr *BuildIndexDRE(Sema &S, CXXExpansionStmtDecl *ESD) {
2948
return S.BuildDeclRefExpr(ESD->getIndexTemplateParm(),
@@ -42,6 +61,118 @@ static bool FinaliseExpansionVar(Sema &S, VarDecl *ExpansionVar,
4261
return ExpansionVar->isInvalidDecl();
4362
}
4463

64+
static IterableExpansionStmtData
65+
TryBuildIterableExpansionStmtInitializer(Sema &S, Expr *ExpansionInitializer,
66+
Expr *Index, SourceLocation ColonLoc,
67+
bool VarIsConstexpr) {
68+
IterableExpansionStmtData Data;
69+
70+
// C++26 [stmt.expand]p3: An expression is expansion-iterable if it does not
71+
// have array type [...]
72+
QualType Ty = ExpansionInitializer->getType().getNonReferenceType();
73+
if (Ty->isArrayType())
74+
return Data;
75+
76+
// Lookup member and ADL 'begin()'/'end()'. Only check if they exist; even if
77+
// they're deleted, inaccessible, etc., this is still an iterating expansion
78+
// statement, albeit an ill-formed one.
79+
DeclarationNameInfo BeginName(&S.PP.getIdentifierTable().get("begin"),
80+
ColonLoc);
81+
DeclarationNameInfo EndName(&S.PP.getIdentifierTable().get("end"), ColonLoc);
82+
83+
// Try member lookup first.
84+
bool FoundBeginEnd = false;
85+
if (auto *Record = Ty->getAsCXXRecordDecl()) {
86+
LookupResult BeginLR(S, BeginName, Sema::LookupMemberName);
87+
LookupResult EndLR(S, EndName, Sema::LookupMemberName);
88+
FoundBeginEnd = S.LookupQualifiedName(BeginLR, Record) &&
89+
S.LookupQualifiedName(EndLR, Record);
90+
}
91+
92+
// Try ADL.
93+
//
94+
// If overload resolution for 'begin()' *and* 'end()' succeeds (irrespective
95+
// of whether it results in a usable candidate), then assume this is an
96+
// iterating expansion statement.
97+
auto HasADLCandidate = [&](DeclarationName Name) {
98+
OverloadCandidateSet Candidates(ColonLoc, OverloadCandidateSet::CSK_Normal);
99+
OverloadCandidateSet::iterator Best;
100+
101+
S.AddArgumentDependentLookupCandidates(Name, ColonLoc, ExpansionInitializer,
102+
/*ExplicitTemplateArgs=*/nullptr,
103+
Candidates);
104+
105+
return Candidates.BestViableFunction(S, ColonLoc, Best) !=
106+
OR_No_Viable_Function;
107+
};
108+
109+
if (!FoundBeginEnd && (!HasADLCandidate(BeginName.getName()) ||
110+
!HasADLCandidate(EndName.getName())))
111+
return Data;
112+
113+
auto Ctx = Sema::ExpressionEvaluationContext::PotentiallyEvaluated;
114+
if (VarIsConstexpr)
115+
Ctx = Sema::ExpressionEvaluationContext::ImmediateFunctionContext;
116+
EnterExpressionEvaluationContext ExprEvalCtx(S, Ctx);
117+
118+
// The declarations should be attached to the parent decl context.
119+
Sema::ContextRAII CtxGuard(
120+
S, S.CurContext->getEnclosingNonExpansionStatementContext(),
121+
/*NewThis=*/false);
122+
123+
// Ok, we know that this is supposed to be an iterable expansion statement;
124+
// delegate to the for-range code to build the range/begin/end variables.
125+
//
126+
// Any failure at this point is a hard error.
127+
Data.TheState = IterableExpansionStmtData::State::Error;
128+
Scope *Scope = S.getCurScope();
129+
130+
// TODO: CWG 3131 changes how this range is declared.
131+
StmtResult Var = S.BuildCXXForRangeRangeVar(Scope, ExpansionInitializer,
132+
/*ForExpansionStmt=*/true);
133+
if (Var.isInvalid())
134+
return Data;
135+
136+
auto *RangeVar = cast<DeclStmt>(Var.get());
137+
Sema::ForRangeBeginEndInfo Info = S.BuildCXXForRangeBeginEndVars(
138+
Scope, cast<VarDecl>(RangeVar->getSingleDecl()), ColonLoc,
139+
/*CoawaitLoc=*/{},
140+
/*LifetimeExtendTemps=*/{}, Sema::BFRK_Build, /*ForExpansionStmt=*/true);
141+
142+
if (!Info.isValid())
143+
return Data;
144+
145+
StmtResult BeginStmt = S.ActOnDeclStmt(
146+
S.ConvertDeclToDeclGroup(Info.BeginVar), ColonLoc, ColonLoc);
147+
StmtResult EndStmt = S.ActOnDeclStmt(S.ConvertDeclToDeclGroup(Info.EndVar),
148+
ColonLoc, ColonLoc);
149+
if (BeginStmt.isInvalid() || EndStmt.isInvalid())
150+
return Data;
151+
152+
// Build '*(begin + i)'.
153+
DeclRefExpr *Begin = S.BuildDeclRefExpr(
154+
Info.BeginVar, Info.BeginVar->getType().getNonReferenceType(), VK_LValue,
155+
ColonLoc);
156+
157+
ExprResult BeginPlusI =
158+
S.ActOnBinOp(Scope, ColonLoc, tok::plus, Begin, Index);
159+
if (BeginPlusI.isInvalid())
160+
return Data;
161+
162+
ExprResult Deref =
163+
S.ActOnUnaryOp(Scope, ColonLoc, tok::star, BeginPlusI.get());
164+
if (Deref.isInvalid())
165+
return Data;
166+
167+
Deref = S.MaybeCreateExprWithCleanups(Deref.get());
168+
Data.BeginDecl = BeginStmt.getAs<DeclStmt>();
169+
Data.EndDecl = EndStmt.getAs<DeclStmt>();
170+
Data.RangeDecl = RangeVar;
171+
Data.Initializer = Deref.get();
172+
Data.TheState = IterableExpansionStmtData::State::Ok;
173+
return Data;
174+
}
175+
45176
CXXExpansionStmtDecl *
46177
Sema::ActOnCXXExpansionStmtDecl(unsigned TemplateDepth,
47178
SourceLocation TemplateKWLoc) {
@@ -115,8 +246,26 @@ StmtResult Sema::ActOnCXXExpansionStmtPattern(
115246
ColonLoc, RParenLoc);
116247
}
117248

118-
Diag(ESD->getLocation(), diag::err_expansion_statements_todo);
119-
return StmtError();
249+
if (ExpansionInitializer->hasPlaceholderType()) {
250+
ExprResult R = CheckPlaceholderExpr(ExpansionInitializer);
251+
if (R.isInvalid())
252+
return StmtError();
253+
ExpansionInitializer = R.get();
254+
}
255+
256+
if (DiagnoseUnexpandedParameterPack(ExpansionInitializer))
257+
return StmtError();
258+
259+
// Reject lambdas early.
260+
if (auto *RD = ExpansionInitializer->getType()->getAsCXXRecordDecl();
261+
RD && RD->isLambda()) {
262+
Diag(ExpansionInitializer->getBeginLoc(), diag::err_expansion_stmt_lambda);
263+
return StmtError();
264+
}
265+
266+
return BuildNonEnumeratingCXXExpansionStmtPattern(
267+
ESD, Init, DS, ExpansionInitializer, LParenLoc, ColonLoc, RParenLoc,
268+
LifetimeExtendTemps);
120269
}
121270

122271
StmtResult Sema::BuildCXXEnumeratingExpansionStmtPattern(
@@ -127,6 +276,43 @@ StmtResult Sema::BuildCXXEnumeratingExpansionStmtPattern(
127276
LParenLoc, ColonLoc, RParenLoc);
128277
}
129278

279+
StmtResult Sema::BuildNonEnumeratingCXXExpansionStmtPattern(
280+
CXXExpansionStmtDecl *ESD, Stmt *Init, DeclStmt *ExpansionVarStmt,
281+
Expr *ExpansionInitializer, SourceLocation LParenLoc,
282+
SourceLocation ColonLoc, SourceLocation RParenLoc,
283+
ArrayRef<MaterializeTemporaryExpr *> LifetimeExtendTemps) {
284+
VarDecl *ExpansionVar = cast<VarDecl>(ExpansionVarStmt->getSingleDecl());
285+
286+
if (ExpansionInitializer->isTypeDependent()) {
287+
ActOnDependentForRangeInitializer(ExpansionVar, BFRK_Build);
288+
return new (Context) CXXDependentExpansionStmtPattern(
289+
ESD, Init, ExpansionVarStmt, ExpansionInitializer, LParenLoc, ColonLoc,
290+
RParenLoc);
291+
}
292+
293+
// Otherwise, if it can be an iterating expansion statement, it is one.
294+
DeclRefExpr *Index = BuildIndexDRE(*this, ESD);
295+
IterableExpansionStmtData Data = TryBuildIterableExpansionStmtInitializer(
296+
*this, ExpansionInitializer, Index, ColonLoc,
297+
ExpansionVar->isConstexpr());
298+
if (Data.hasError()) {
299+
ActOnInitializerError(ExpansionVar);
300+
return StmtError();
301+
}
302+
303+
if (Data.isIterable()) {
304+
if (FinaliseExpansionVar(*this, ExpansionVar, Data.Initializer))
305+
return StmtError();
306+
307+
return new (Context) CXXIteratingExpansionStmtPattern(
308+
ESD, Init, ExpansionVarStmt, Data.RangeDecl, Data.BeginDecl,
309+
Data.EndDecl, LParenLoc, ColonLoc, RParenLoc);
310+
}
311+
312+
Diag(ESD->getLocation(), diag::err_expansion_statements_todo);
313+
return StmtError();
314+
}
315+
130316
StmtResult Sema::FinishCXXExpansionStmt(Stmt *Exp, Stmt *Body) {
131317
if (!Exp || !Body)
132318
return StmtError();
@@ -149,7 +335,13 @@ StmtResult Sema::FinishCXXExpansionStmt(Stmt *Exp, Stmt *Body) {
149335
if (Expansion->getInit())
150336
Shared.push_back(Expansion->getInit());
151337

152-
assert(isa<CXXEnumeratingExpansionStmtPattern>(Expansion) && "TODO");
338+
if (auto *Iter = dyn_cast<CXXIteratingExpansionStmtPattern>(Expansion)) {
339+
Shared.push_back(Iter->getRangeVarStmt());
340+
Shared.push_back(Iter->getBeginVarStmt());
341+
Shared.push_back(Iter->getEndVarStmt());
342+
} else {
343+
assert(isa<CXXEnumeratingExpansionStmtPattern>(Expansion) && "TODO");
344+
}
153345

154346
// Return an empty statement if the range is empty.
155347
if (*NumInstantiations == 0) {
@@ -228,5 +420,53 @@ Sema::ComputeExpansionSize(CXXExpansionStmtPattern *Expansion) {
228420
->getExprs()
229421
.size();
230422

423+
// By [stmt.expand]5.2, N is the result of evaluating the expression
424+
//
425+
// [] consteval {
426+
// std::ptrdiff_t result = 0;
427+
// for (auto i = begin; i != end; ++i) ++result;
428+
// return result;
429+
// }()
430+
// TODO: CWG 3131 changes this lambda a bit.
431+
if (auto *Iterating = dyn_cast<CXXIteratingExpansionStmtPattern>(Expansion)) {
432+
EnterExpressionEvaluationContext ExprEvalCtx(
433+
*this, ExpressionEvaluationContext::ConstantEvaluated);
434+
435+
// FIXME: Actually do that; unfortunately, conjuring a lambda out of thin
436+
// air in Sema is a massive pain, so for now just cheat by computing
437+
// 'end - begin'.
438+
SourceLocation Loc = Iterating->getColonLoc();
439+
DeclRefExpr *Begin = BuildDeclRefExpr(
440+
Iterating->getBeginVar(),
441+
Iterating->getBeginVar()->getType().getNonReferenceType(), VK_LValue,
442+
Loc);
443+
444+
DeclRefExpr *End = BuildDeclRefExpr(
445+
Iterating->getEndVar(),
446+
Iterating->getEndVar()->getType().getNonReferenceType(), VK_LValue,
447+
Loc);
448+
449+
ExprResult N = ActOnBinOp(getCurScope(), Loc, tok::minus, End, Begin);
450+
if (N.isInvalid())
451+
return std::nullopt;
452+
453+
Expr::EvalResult ER;
454+
SmallVector<PartialDiagnosticAt, 4> Notes;
455+
ER.Diag = &Notes;
456+
if (!N.get()->EvaluateAsInt(ER, Context)) {
457+
Diag(Loc, diag::err_expansion_size_expr_not_ice);
458+
for (const auto &[Location, PDiag] : Notes)
459+
Diag(Location, PDiag);
460+
return std::nullopt;
461+
}
462+
463+
if (ER.Val.getInt().isNegative()) {
464+
Diag(Loc, diag::err_expansion_size_negative) << ER.Val.getInt();
465+
return std::nullopt;
466+
}
467+
468+
return ER.Val.getInt().getZExtValue();
469+
}
470+
231471
llvm_unreachable("TODO");
232472
}

clang/lib/Sema/SemaStmt.cpp

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2411,13 +2411,21 @@ void NoteForRangeBeginEndFunction(Sema &SemaRef, Expr *E,
24112411
/// Build a variable declaration for a for-range statement.
24122412
VarDecl *BuildForRangeVarDecl(Sema &SemaRef, SourceLocation Loc, QualType Type,
24132413
StringRef Name, bool ForExpansionStmt) {
2414+
// Making the variable constexpr doesn't automatically add 'const' to the
2415+
// type, so do that now.
2416+
if (ForExpansionStmt && !Type->isReferenceType())
2417+
Type = Type.withConst();
2418+
24142419
DeclContext *DC = SemaRef.CurContext;
24152420
IdentifierInfo *II = &SemaRef.PP.getIdentifierTable().get(Name);
24162421
TypeSourceInfo *TInfo = SemaRef.Context.getTrivialTypeSourceInfo(Type, Loc);
24172422
VarDecl *Decl = VarDecl::Create(SemaRef.Context, DC, Loc, Loc, II, Type,
24182423
TInfo, SC_None);
24192424
Decl->setImplicit();
24202425
Decl->setCXXForRangeImplicitVar(true);
2426+
if (ForExpansionStmt)
2427+
// CWG 3044: Do not make the variable 'static'.
2428+
Decl->setConstexpr(true);
24212429
return Decl;
24222430
}
24232431
}
@@ -2731,7 +2739,10 @@ Sema::ForRangeBeginEndInfo Sema::BuildCXXForRangeBeginEndVars(
27312739
return {};
27322740

27332741
// P2718R0 - Lifetime extension in range-based for loops.
2734-
if (getLangOpts().CPlusPlus23)
2742+
//
2743+
// CWG 3043 – Do not apply lifetime extension to iterating
2744+
// expansion statements.
2745+
if (getLangOpts().CPlusPlus23 && !ForExpansionStmt)
27352746
ApplyForRangeOrExpansionStatementLifetimeExtension(RangeVar,
27362747
LifetimeExtendTemps);
27372748

0 commit comments

Comments
 (0)