Skip to content

Commit b2c2312

Browse files
committed
[Clang] [C++26] Expansion Statements (Part 5)
1 parent 46e2331 commit b2c2312

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
@@ -15704,6 +15704,12 @@ class Sema final : public SemaBase {
1570415704
SourceLocation ColonLoc,
1570515705
SourceLocation RParenLoc);
1570615706

15707+
StmtResult BuildNonEnumeratingCXXExpansionStmtPattern(
15708+
CXXExpansionStmtDecl *ESD, Stmt *Init, DeclStmt *ExpansionVarStmt,
15709+
Expr *ExpansionInitializer, SourceLocation LParenLoc,
15710+
SourceLocation ColonLoc, SourceLocation RParenLoc,
15711+
ArrayRef<MaterializeTemporaryExpr *> LifetimeExtendTemps = {});
15712+
1570715713
ExprResult
1570815714
BuildCXXExpansionInitListSelectExpr(CXXExpansionInitListExpr *Range,
1570915715
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) {
@@ -112,8 +243,26 @@ StmtResult Sema::ActOnCXXExpansionStmtPattern(
112243
ColonLoc, RParenLoc);
113244
}
114245

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

119268
StmtResult Sema::BuildCXXEnumeratingExpansionStmtPattern(
@@ -124,6 +273,43 @@ StmtResult Sema::BuildCXXEnumeratingExpansionStmtPattern(
124273
LParenLoc, ColonLoc, RParenLoc);
125274
}
126275

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

149-
assert(isa<CXXEnumeratingExpansionStmtPattern>(Expansion) && "TODO");
335+
if (auto *Iter = dyn_cast<CXXIteratingExpansionStmtPattern>(Expansion)) {
336+
Shared.push_back(Iter->getRangeVarStmt());
337+
Shared.push_back(Iter->getBeginVarStmt());
338+
Shared.push_back(Iter->getEndVarStmt());
339+
} else {
340+
assert(isa<CXXEnumeratingExpansionStmtPattern>(Expansion) && "TODO");
341+
}
150342

151343
// Return an empty statement if the range is empty.
152344
if (*NumInstantiations == 0) {
@@ -225,5 +417,53 @@ Sema::ComputeExpansionSize(CXXExpansionStmtPattern *Expansion) {
225417
->getExprs()
226418
.size();
227419

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

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)