Skip to content

Commit b85d1e4

Browse files
committed
[Clang] [C++26] Expansion Statements (Part 3)
1 parent 2d43de2 commit b85d1e4

File tree

9 files changed

+390
-50
lines changed

9 files changed

+390
-50
lines changed

clang/include/clang/Basic/DiagnosticSemaKinds.td

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5827,6 +5827,8 @@ def note_template_nsdmi_here : Note<
58275827
"in instantiation of default member initializer %q0 requested here">;
58285828
def note_template_type_alias_instantiation_here : Note<
58295829
"in instantiation of template type alias %0 requested here">;
5830+
def note_expansion_stmt_instantiation_here : Note<
5831+
"in instantiation of expansion statement requested here">;
58305832
def note_template_exception_spec_instantiation_here : Note<
58315833
"in instantiation of exception specification for %0 requested here">;
58325834
def note_template_requirement_instantiation_here : Note<

clang/include/clang/Sema/Sema.h

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13176,6 +13176,9 @@ class Sema final : public SemaBase {
1317613176

1317713177
/// We are performing partial ordering for template template parameters.
1317813178
PartialOrderingTTP,
13179+
13180+
/// We are instantiating an expansion statement.
13181+
ExpansionStmtInstantiation,
1317913182
} Kind;
1318013183

1318113184
/// Whether we're substituting into constraints.
@@ -13371,6 +13374,12 @@ class Sema final : public SemaBase {
1337113374
concepts::Requirement *Req,
1337213375
SourceRange InstantiationRange = SourceRange());
1337313376

13377+
/// \brief Note that we are substituting the body of an expansion statement.
13378+
InstantiatingTemplate(Sema &SemaRef, SourceLocation PointOfInstantiation,
13379+
CXXExpansionStmtPattern *ExpansionStmt,
13380+
ArrayRef<TemplateArgument> TArgs,
13381+
SourceRange InstantiationRange);
13382+
1337413383
/// \brief Note that we are checking the satisfaction of the constraint
1337513384
/// expression inside of a nested requirement.
1337613385
InstantiatingTemplate(Sema &SemaRef, SourceLocation PointOfInstantiation,
@@ -15643,6 +15652,19 @@ class Sema final : public SemaBase {
1564315652
ArrayRef<MaterializeTemporaryExpr *> LifetimeExtendTemps);
1564415653

1564515654
StmtResult FinishCXXExpansionStmt(Stmt *Expansion, Stmt *Body);
15655+
15656+
StmtResult BuildCXXEnumeratingExpansionStmtPattern(Decl *ESD, Stmt *Init,
15657+
Stmt *ExpansionVar,
15658+
SourceLocation LParenLoc,
15659+
SourceLocation ColonLoc,
15660+
SourceLocation RParenLoc);
15661+
15662+
ExprResult
15663+
BuildCXXExpansionInitListSelectExpr(CXXExpansionInitListExpr *Range,
15664+
Expr *Idx);
15665+
15666+
std::optional<uint64_t>
15667+
ComputeExpansionSize(CXXExpansionStmtPattern *Expansion);
1564615668
///@}
1564715669
};
1564815670

clang/lib/Frontend/FrontendActions.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -476,6 +476,8 @@ class DefaultTemplateInstCallback : public TemplateInstantiationCallback {
476476
return "TypeAliasTemplateInstantiation";
477477
case CodeSynthesisContext::PartialOrderingTTP:
478478
return "PartialOrderingTTP";
479+
case CodeSynthesisContext::ExpansionStmtInstantiation:
480+
return "ExpansionStmtInstantiation";
479481
}
480482
return "";
481483
}

clang/lib/Sema/SemaExpand.cpp

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

27+
// Build a 'DeclRefExpr' designating the template parameter '__N'.
28+
static DeclRefExpr *BuildIndexDRE(Sema &S, CXXExpansionStmtDecl *ESD) {
29+
return S.BuildDeclRefExpr(ESD->getIndexTemplateParm(),
30+
S.Context.getPointerDiffType(), VK_PRValue,
31+
ESD->getBeginLoc());
32+
}
33+
34+
static bool FinaliseExpansionVar(Sema &S, VarDecl *ExpansionVar,
35+
ExprResult Initializer) {
36+
if (Initializer.isInvalid()) {
37+
S.ActOnInitializerError(ExpansionVar);
38+
return true;
39+
}
40+
41+
S.AddInitializerToDecl(ExpansionVar, Initializer.get(), /*DirectInit=*/false);
42+
return ExpansionVar->isInvalidDecl();
43+
}
2744

2845
CXXExpansionStmtDecl *
2946
Sema::ActOnCXXExpansionStmtDecl(unsigned TemplateDepth,
@@ -69,13 +86,147 @@ StmtResult Sema::ActOnCXXExpansionStmtPattern(
6986
Expr *ExpansionInitializer, SourceLocation LParenLoc,
7087
SourceLocation ColonLoc, SourceLocation RParenLoc,
7188
ArrayRef<MaterializeTemporaryExpr *> LifetimeExtendTemps) {
89+
if (!ExpansionInitializer || !ExpansionVarStmt)
90+
return StmtError();
91+
92+
assert(CurContext->isExpansionStmt());
93+
auto *DS = cast<DeclStmt>(ExpansionVarStmt);
94+
if (!DS->isSingleDecl()) {
95+
Diag(DS->getBeginLoc(), diag::err_type_defined_in_for_range);
96+
return StmtError();
97+
}
98+
99+
VarDecl *ExpansionVar = dyn_cast<VarDecl>(DS->getSingleDecl());
100+
if (!ExpansionVar || ExpansionVar->isInvalidDecl() ||
101+
ExpansionInitializer->containsErrors())
102+
return StmtError();
103+
104+
// This is an enumerating expansion statement.
105+
if (auto *ILE = dyn_cast<CXXExpansionInitListExpr>(ExpansionInitializer)) {
106+
ExprResult Initializer =
107+
BuildCXXExpansionInitListSelectExpr(ILE, BuildIndexDRE(*this, ESD));
108+
if (FinaliseExpansionVar(*this, ExpansionVar, Initializer))
109+
return StmtError();
110+
111+
// Note that lifetime extension only applies to destructuring expansion
112+
// statements, so we just ignore 'LifetimeExtendedTemps' entirely for other
113+
// types of expansion statements (this is CWG 3043).
114+
return BuildCXXEnumeratingExpansionStmtPattern(ESD, Init, DS, LParenLoc,
115+
ColonLoc, RParenLoc);
116+
}
117+
72118
Diag(ESD->getLocation(), diag::err_expansion_statements_todo);
73119
return StmtError();
74120
}
75121

122+
StmtResult Sema::BuildCXXEnumeratingExpansionStmtPattern(
123+
Decl *ESD, Stmt *Init, Stmt *ExpansionVar, SourceLocation LParenLoc,
124+
SourceLocation ColonLoc, SourceLocation RParenLoc) {
125+
return new (Context) CXXEnumeratingExpansionStmtPattern(
126+
cast<CXXExpansionStmtDecl>(ESD), Init, cast<DeclStmt>(ExpansionVar),
127+
LParenLoc, ColonLoc, RParenLoc);
128+
}
129+
76130
StmtResult Sema::FinishCXXExpansionStmt(Stmt *Exp, Stmt *Body) {
77131
if (!Exp || !Body)
78132
return StmtError();
79133

134+
auto *Expansion = cast<CXXExpansionStmtPattern>(Exp);
135+
assert(!Expansion->getDecl()->getInstantiations() &&
136+
"should not rebuild expansion statement after instantiation");
137+
138+
Expansion->setBody(Body);
139+
if (Expansion->hasDependentSize())
140+
return Expansion;
141+
142+
// This can fail if this is an iterating expansion statement.
143+
std::optional<uint64_t> NumInstantiations = ComputeExpansionSize(Expansion);
144+
if (!NumInstantiations)
145+
return StmtError();
146+
147+
// Collect shared statements.
148+
SmallVector<Stmt *, 1> Shared;
149+
if (Expansion->getInit())
150+
Shared.push_back(Expansion->getInit());
151+
152+
assert(isa<CXXEnumeratingExpansionStmtPattern>(Expansion) && "TODO");
153+
154+
// Return an empty statement if the range is empty.
155+
if (*NumInstantiations == 0) {
156+
Expansion->getDecl()->setInstantiations(
157+
CXXExpansionStmtInstantiation::Create(
158+
Context, Expansion->getBeginLoc(), Expansion->getEndLoc(),
159+
/*Instantiations=*/{}, Shared,
160+
isa<CXXDestructuringExpansionStmtPattern>(Expansion)));
161+
return Expansion;
162+
}
163+
164+
// Create a compound statement binding the expansion variable and body.
165+
Stmt *VarAndBody[] = {Expansion->getExpansionVarStmt(), Body};
166+
Stmt *CombinedBody =
167+
CompoundStmt::Create(Context, VarAndBody, FPOptionsOverride(),
168+
Body->getBeginLoc(), Body->getEndLoc());
169+
170+
// Expand the body for each instantiation.
171+
SmallVector<Stmt *, 4> Instantiations;
172+
CXXExpansionStmtDecl *ESD = Expansion->getDecl();
173+
for (uint64_t I = 0; I < *NumInstantiations; ++I) {
174+
// Now that we're expanding this, exit the context of the expansion stmt
175+
// so that we no longer treat this as dependent.
176+
ContextRAII CtxGuard(*this, CurContext->getParent(),
177+
/*NewThis=*/false);
178+
179+
TemplateArgument Arg{Context, llvm::APSInt::get(I),
180+
Context.getPointerDiffType()};
181+
MultiLevelTemplateArgumentList MTArgList(ESD, Arg, true);
182+
MTArgList.addOuterRetainedLevels(
183+
Expansion->getDecl()->getIndexTemplateParm()->getDepth());
184+
185+
LocalInstantiationScope LIScope(*this, /*CombineWithOuterScope=*/true);
186+
NonSFINAEContext _(*this);
187+
InstantiatingTemplate Inst(*this, Body->getBeginLoc(), Expansion, Arg,
188+
Body->getSourceRange());
189+
190+
StmtResult Instantiation = SubstStmt(CombinedBody, MTArgList);
191+
if (Instantiation.isInvalid())
192+
return StmtError();
193+
Instantiations.push_back(Instantiation.get());
194+
}
195+
196+
auto *InstantiationsStmt = CXXExpansionStmtInstantiation::Create(
197+
Context, Expansion->getBeginLoc(), Expansion->getEndLoc(), Instantiations,
198+
Shared, isa<CXXDestructuringExpansionStmtPattern>(Expansion));
199+
200+
Expansion->getDecl()->setInstantiations(InstantiationsStmt);
201+
return Expansion;
202+
}
203+
204+
ExprResult
205+
Sema::BuildCXXExpansionInitListSelectExpr(CXXExpansionInitListExpr *Range,
206+
Expr *Idx) {
207+
if (Range->containsPackExpansion() || Idx->isValueDependent())
208+
return new (Context) CXXExpansionInitListSelectExpr(Context, Range, Idx);
209+
210+
// The index is a DRE to a template parameter; we should never
211+
// fail to evaluate it.
212+
Expr::EvalResult ER;
213+
if (!Idx->EvaluateAsInt(ER, Context))
214+
llvm_unreachable("Failed to evaluate expansion index");
215+
216+
uint64_t I = ER.Val.getInt().getZExtValue();
217+
return Range->getExprs()[I];
218+
}
219+
220+
std::optional<uint64_t>
221+
Sema::ComputeExpansionSize(CXXExpansionStmtPattern *Expansion) {
222+
assert(!Expansion->hasDependentSize());
223+
224+
if (isa<CXXEnumeratingExpansionStmtPattern>(Expansion))
225+
return cast<CXXExpansionInitListSelectExpr>(
226+
Expansion->getExpansionVariable()->getInit())
227+
->getRangeExpr()
228+
->getExprs()
229+
.size();
230+
80231
llvm_unreachable("TODO");
81232
}

clang/lib/Sema/SemaTemplateInstantiate.cpp

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -573,6 +573,7 @@ bool Sema::CodeSynthesisContext::isInstantiationRecord() const {
573573
case PriorTemplateArgumentSubstitution:
574574
case ConstraintsCheck:
575575
case NestedRequirementConstraintsCheck:
576+
case ExpansionStmtInstantiation:
576577
return true;
577578

578579
case RequirementInstantiation:
@@ -759,6 +760,15 @@ Sema::InstantiatingTemplate::InstantiatingTemplate(
759760
PointOfInstantiation, InstantiationRange, /*Entity=*/nullptr,
760761
/*Template=*/nullptr, /*TemplateArgs=*/{}) {}
761762

763+
Sema::InstantiatingTemplate::InstantiatingTemplate(
764+
Sema &SemaRef, SourceLocation PointOfInstantiation,
765+
CXXExpansionStmtPattern *ExpansionStmt, ArrayRef<TemplateArgument> TArgs,
766+
SourceRange InstantiationRange)
767+
: InstantiatingTemplate(
768+
SemaRef, CodeSynthesisContext::ExpansionStmtInstantiation,
769+
PointOfInstantiation, InstantiationRange, /*Entity=*/nullptr,
770+
/*Template=*/nullptr, /*TemplateArgs=*/TArgs) {}
771+
762772
Sema::InstantiatingTemplate::InstantiatingTemplate(
763773
Sema &SemaRef, SourceLocation PointOfInstantiation,
764774
concepts::NestedRequirement *Req, ConstraintsCheck,
@@ -1260,6 +1270,9 @@ void Sema::PrintInstantiationStack(InstantiationContextDiagFuncRef DiagFunc) {
12601270
<< /*isTemplateTemplateParam=*/true
12611271
<< Active->InstantiationRange);
12621272
break;
1273+
case CodeSynthesisContext::ExpansionStmtInstantiation:
1274+
Diags.Report(Active->PointOfInstantiation,
1275+
diag::note_expansion_stmt_instantiation_here);
12631276
}
12641277
}
12651278
}
@@ -1894,6 +1907,12 @@ Decl *TemplateInstantiator::TransformDecl(SourceLocation Loc, Decl *D) {
18941907
maybeInstantiateFunctionParameterToScope(PVD))
18951908
return nullptr;
18961909

1910+
if (isa<CXXExpansionStmtDecl>(D)) {
1911+
assert(SemaRef.CurrentInstantiationScope);
1912+
return cast<Decl *>(
1913+
*SemaRef.CurrentInstantiationScope->findInstantiationOf(D));
1914+
}
1915+
18971916
return SemaRef.FindInstantiatedDecl(Loc, cast<NamedDecl>(D), TemplateArgs);
18981917
}
18991918

@@ -2288,9 +2307,13 @@ ExprResult
22882307
TemplateInstantiator::TransformFunctionParmPackRefExpr(DeclRefExpr *E,
22892308
ValueDecl *PD) {
22902309
typedef LocalInstantiationScope::DeclArgumentPack DeclArgumentPack;
2291-
llvm::PointerUnion<Decl *, DeclArgumentPack *> *Found
2292-
= getSema().CurrentInstantiationScope->findInstantiationOf(PD);
2293-
assert(Found && "no instantiation for parameter pack");
2310+
llvm::PointerUnion<Decl *, DeclArgumentPack *> *Found =
2311+
getSema().CurrentInstantiationScope->getInstantiationOfIfExists(PD);
2312+
2313+
// This can happen when instantiating an expansion statement that contains
2314+
// a pack (e.g. `template for (auto x : {{ts...}})`).
2315+
if (!Found)
2316+
return E;
22942317

22952318
Decl *TransformedDecl;
22962319
if (DeclArgumentPack *Pack = dyn_cast<DeclArgumentPack *>(*Found)) {

clang/lib/Sema/SemaTemplateInstantiateDecl.cpp

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2089,7 +2089,37 @@ Decl *TemplateDeclInstantiator::VisitStaticAssertDecl(StaticAssertDecl *D) {
20892089

20902090
Decl *TemplateDeclInstantiator::VisitCXXExpansionStmtDecl(
20912091
CXXExpansionStmtDecl *OldESD) {
2092-
llvm_unreachable("TODO");
2092+
Decl *Index = VisitNonTypeTemplateParmDecl(OldESD->getIndexTemplateParm());
2093+
CXXExpansionStmtDecl *NewESD = SemaRef.BuildCXXExpansionStmtDecl(
2094+
Owner, OldESD->getBeginLoc(), cast<NonTypeTemplateParmDecl>(Index));
2095+
SemaRef.CurrentInstantiationScope->InstantiatedLocal(OldESD, NewESD);
2096+
2097+
// If this was already expanded, only instantiate the expansion and
2098+
// don't touch the unexpanded expansion statement.
2099+
if (CXXExpansionStmtInstantiation *OldInst = OldESD->getInstantiations()) {
2100+
StmtResult NewInst = SemaRef.SubstStmt(OldInst, TemplateArgs);
2101+
if (NewInst.isInvalid())
2102+
return nullptr;
2103+
2104+
NewESD->setInstantiations(NewInst.getAs<CXXExpansionStmtInstantiation>());
2105+
NewESD->setExpansionPattern(OldESD->getExpansionPattern());
2106+
return NewESD;
2107+
}
2108+
2109+
// Enter the scope of this expansion statement; don't do this if we've
2110+
// already expanded it, as in that case we no longer want to treat its
2111+
// content as dependent.
2112+
Sema::ContextRAII Context(SemaRef, NewESD, /*NewThis=*/false);
2113+
2114+
StmtResult Expansion =
2115+
SemaRef.SubstStmt(OldESD->getExpansionPattern(), TemplateArgs);
2116+
if (Expansion.isInvalid())
2117+
return nullptr;
2118+
2119+
// The code that handles CXXExpansionStmtPattern takes care of calling
2120+
// setInstantiation() on the ESD if there was an expansion.
2121+
NewESD->setExpansionPattern(cast<CXXExpansionStmtPattern>(Expansion.get()));
2122+
return NewESD;
20932123
}
20942124

20952125
Decl *TemplateDeclInstantiator::VisitEnumDecl(EnumDecl *D) {
@@ -7093,6 +7123,12 @@ NamedDecl *Sema::FindInstantiatedDecl(SourceLocation Loc, NamedDecl *D,
70937123
// anonymous unions in class templates).
70947124
}
70957125

7126+
if (CurrentInstantiationScope) {
7127+
if (auto Found = CurrentInstantiationScope->getInstantiationOfIfExists(D))
7128+
if (auto *FD = dyn_cast<NamedDecl>(cast<Decl *>(*Found)))
7129+
return FD;
7130+
}
7131+
70967132
if (!ParentDependsOnArgs)
70977133
return D;
70987134

clang/lib/Sema/SemaTemplateVariadic.cpp

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -912,10 +912,14 @@ bool Sema::CheckParameterPacksForExpansion(
912912
unsigned NewPackSize, PendingPackExpansionSize = 0;
913913
if (IsVarDeclPack) {
914914
// Figure out whether we're instantiating to an argument pack or not.
915+
//
916+
// The instantiation may not exist; this can happen when instantiating an
917+
// expansion statement that contains a pack (e.g.
918+
// `template for (auto x : {{ts...}})`).
915919
llvm::PointerUnion<Decl *, DeclArgumentPack *> *Instantiation =
916-
CurrentInstantiationScope->findInstantiationOf(
920+
CurrentInstantiationScope->getInstantiationOfIfExists(
917921
cast<NamedDecl *>(ParmPack.first));
918-
if (isa<DeclArgumentPack *>(*Instantiation)) {
922+
if (Instantiation && isa<DeclArgumentPack *>(*Instantiation)) {
919923
// We could expand this function parameter pack.
920924
NewPackSize = cast<DeclArgumentPack *>(*Instantiation)->size();
921925
} else {

0 commit comments

Comments
 (0)