[llvm-branch-commits] [clang] [Clang] [C++26] Expansion Statements (Part 6: Destructuring Expansion Statements) (PR #169685)
via llvm-branch-commits
llvm-branch-commits at lists.llvm.org
Wed Jul 1 08:08:30 PDT 2026
https://github.com/Sirraide updated https://github.com/llvm/llvm-project/pull/169685
>From cd4132ca8671a497a405ab4c9234686ca0581aa0 Mon Sep 17 00:00:00 2001
From: Sirraide <aeternalmail at gmail.com>
Date: Wed, 26 Nov 2025 17:00:57 +0100
Subject: [PATCH] [Clang] [C++26] Expansion Statements (Part 6)
---
.../clang/Basic/DiagnosticSemaKinds.td | 2 +
clang/lib/Sema/SemaExpand.cpp | 112 +++++++++++++++++-
clang/lib/Sema/TreeTransform.h | 26 +++-
3 files changed, 131 insertions(+), 9 deletions(-)
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index 6da6d3cf56b35..dcb56a6a0be07 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -3738,6 +3738,8 @@ def err_conflicting_codeseg_attribute : Error<
def warn_duplicate_codeseg_attribute : Warning<
"duplicate code segment specifiers">, InGroup<Section>;
+def err_expansion_stmt_invalid_init : Error<
+ "cannot expand expression of type %0">;
def err_expansion_stmt_vla : Error<
"cannot expand variable length array type %0">;
def err_expansion_stmt_incomplete : Error<
diff --git a/clang/lib/Sema/SemaExpand.cpp b/clang/lib/Sema/SemaExpand.cpp
index 38436e1d47495..2a15691c9a0a6 100644
--- a/clang/lib/Sema/SemaExpand.cpp
+++ b/clang/lib/Sema/SemaExpand.cpp
@@ -86,7 +86,7 @@ static bool HasDependentSize(const DeclContext *CurContext,
return true;
case CXXExpansionStmtPattern::ExpansionStmtKind::Destructuring:
- llvm_unreachable("TODO");
+ return false;
}
llvm_unreachable("invalid pattern kind");
@@ -224,6 +224,51 @@ static IterableExpansionStmtData TryBuildIterableExpansionStmtInitializer(
#endif
}
+static StmtResult BuildDestructuringDecompositionDecl(
+ Sema &S, Expr *ExpansionInitializer, SourceLocation ColonLoc,
+ bool VarIsConstexpr,
+ ArrayRef<MaterializeTemporaryExpr *> LifetimeExtendTemps) {
+ 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->getParent(),
+ /*NewThis=*/false);
+
+ UnsignedOrNone Arity =
+ S.GetDecompositionElementCount(ExpansionInitializer->getType(), ColonLoc);
+
+ if (!Arity) {
+ S.Diag(ExpansionInitializer->getBeginLoc(),
+ diag::err_expansion_stmt_invalid_init)
+ << ExpansionInitializer->getType()
+ << ExpansionInitializer->getSourceRange();
+ return StmtError();
+ }
+
+ QualType AutoRRef = S.Context.getAutoRRefDeductType();
+ SmallVector<BindingDecl *> Bindings;
+ for (unsigned I = 0; I < *Arity; ++I)
+ Bindings.push_back(BindingDecl::Create(
+ S.Context, S.CurContext, ColonLoc,
+ S.getPreprocessor().getIdentifierInfo("__u" + std::to_string(I)),
+ AutoRRef));
+
+ TypeSourceInfo *TSI = S.Context.getTrivialTypeSourceInfo(AutoRRef);
+ auto *DD =
+ DecompositionDecl::Create(S.Context, S.CurContext, ColonLoc, ColonLoc,
+ AutoRRef, TSI, SC_Auto, Bindings);
+
+ if (VarIsConstexpr)
+ DD->setConstexpr(true);
+
+ S.ApplyForRangeOrExpansionStatementLifetimeExtension(DD, LifetimeExtendTemps);
+ S.AddInitializerToDecl(DD, ExpansionInitializer, false);
+ return S.ActOnDeclStmt(S.ConvertDeclToDeclGroup(DD), ColonLoc, ColonLoc);
+}
+
CXXExpansionStmtDecl *
Sema::ActOnCXXExpansionStmtDecl(unsigned TemplateDepth,
SourceLocation TemplateKWLoc) {
@@ -384,8 +429,60 @@ StmtResult Sema::BuildNonEnumeratingCXXExpansionStmtPattern(
Data.IterDecl, LParenLoc, ColonLoc, RParenLoc);
}
- Diag(ESD->getLocation(), diag::err_expansion_statements_todo);
- return StmtError();
+ // If not, try destructuring.
+ StmtResult DecompDeclStmt = BuildDestructuringDecompositionDecl(
+ *this, ExpansionInitializer, ColonLoc, ExpansionVar->isConstexpr(),
+ LifetimeExtendTemps);
+ if (DecompDeclStmt.isInvalid()) {
+ ActOnInitializerError(ExpansionVar);
+ return StmtError();
+ }
+
+ auto *DS = DecompDeclStmt.getAs<DeclStmt>();
+ auto *DD = cast<DecompositionDecl>(DS->getSingleDecl());
+ if (DD->isInvalidDecl())
+ return StmtError();
+
+ // Synthesise an InitListExpr to store the bindings; this essentially lets us
+ // desugar the expansion of a destructuring expansion statement to that of an
+ // enumerating expansion statement.
+ SmallVector<Expr *> Bindings;
+ Bindings.reserve(DD->bindings().size());
+ for (BindingDecl *BD : DD->bindings()) {
+ Expr *Element = BuildDeclRefExpr(BD, BD->getType().getNonReferenceType(),
+ VK_LValue, ColonLoc);
+
+ // CWG 3149: If the expansion-initializer is an lvalue, then vi is ui;
+ // otherwise, vi is static_cast<decltype(ui)&&>(ui).
+ if (!ExpansionInitializer->isLValue()) {
+ QualType Ty =
+ BuildReferenceType(getDecltypeForExpr(Element), /*LValueRef=*/false,
+ ColonLoc, /*Entity=*/DeclarationName());
+ TypeSourceInfo *TSI = Context.getTrivialTypeSourceInfo(Ty);
+ ExprResult Cast = BuildCXXNamedCast(
+ ColonLoc, tok::kw_static_cast, TSI, Element,
+ SourceRange(ColonLoc, ColonLoc), SourceRange(ColonLoc, ColonLoc));
+ assert(!Cast.isInvalid() && "cast to rvalue reference type failed?");
+ Element = Cast.get();
+ }
+
+ Bindings.push_back(Element);
+ }
+
+ ExprResult Select = BuildCXXExpansionSelectExpr(
+ new (Context) InitListExpr(Context, ColonLoc, Bindings, ColonLoc),
+ Index);
+
+ if (Select.isInvalid()) {
+ ActOnInitializerError(ExpansionVar);
+ return StmtError();
+ }
+
+ if (FinalizeExpansionVar(*this, ExpansionVar, Select))
+ return StmtError();
+
+ return CXXExpansionStmtPattern::CreateDestructuring(
+ Context, ESD, Init, ExpansionVarStmt, DS, LParenLoc, ColonLoc, RParenLoc);
}
StmtResult Sema::FinishCXXExpansionStmt(Stmt *Exp, Stmt *Body) {
@@ -424,8 +521,10 @@ StmtResult Sema::FinishCXXExpansionStmt(Stmt *Exp, Stmt *Body) {
if (Expansion->isIterating()) {
Preamble.push_back(Expansion->getRangeVarStmt());
Preamble.push_back(Expansion->getBeginVarStmt());
- } else {
- assert(Expansion->isEnumerating() && "TODO");
+ } else if (Expansion->isDestructuring()) {
+ Preamble.push_back(Expansion->getDecompositionDeclStmt());
+ MarkAnyDeclReferenced(Exp->getBeginLoc(), Expansion->getDecompositionDecl(),
+ true);
}
// Return an empty statement if the range is empty.
@@ -531,5 +630,6 @@ Sema::ComputeExpansionSize(CXXExpansionStmtPattern *Expansion) {
#endif
}
- llvm_unreachable("TODO");
+ assert(Expansion->isDestructuring());
+ return Expansion->getDecompositionDecl()->bindings().size();
}
diff --git a/clang/lib/Sema/TreeTransform.h b/clang/lib/Sema/TreeTransform.h
index 0257e246e4d82..4db6f8bd64782 100644
--- a/clang/lib/Sema/TreeTransform.h
+++ b/clang/lib/Sema/TreeTransform.h
@@ -9479,7 +9479,12 @@ StmtResult TreeTransform<Derived>::TransformCXXExpansionStmtPattern(
NewPattern = cast<CXXExpansionStmtPattern>(Res.get());
} else {
- llvm_unreachable("TODO");
+ // The only time we instantiate an expansion statement is if its expansion
+ // size is dependent (otherwise, we only instantiate the expansions and
+ // leave the underlying CXXExpansionStmtPattern as-is). Since destructuring
+ // expansion statements never have a dependent size, we should never get
+ // here.
+ llvm_unreachable("destructuring pattern should never be instantiated");
}
StmtResult Body = getDerived().TransformStmt(S->getBody());
@@ -9516,8 +9521,23 @@ StmtResult TreeTransform<Derived>::TransformCXXExpansionStmtInstantiation(
SmallVector<Stmt *> PreambleStmts;
SmallVector<Stmt *> Instantiations;
- if (TransformStmts(PreambleStmts, S->getPreambleStmts()))
- return StmtError();
+ // Apply lifetime extension to the preamble statements if this was a
+ // destructuring expansion statement.
+ {
+ EnterExpressionEvaluationContext ExprEvalCtx(
+ SemaRef, SemaRef.currentEvaluationContext().Context);
+ SemaRef.currentEvaluationContext().InLifetimeExtendingContext = true;
+ SemaRef.currentEvaluationContext().RebuildDefaultArgOrDefaultInit = true;
+ if (TransformStmts(PreambleStmts, S->getPreambleStmts()))
+ return StmtError();
+
+ if (S->shouldApplyLifetimeExtensionToPreamble()) {
+ auto *VD =
+ cast<VarDecl>(cast<DeclStmt>(PreambleStmts.front())->getSingleDecl());
+ SemaRef.ApplyForRangeOrExpansionStatementLifetimeExtension(
+ VD, SemaRef.currentEvaluationContext().ForRangeLifetimeExtendTemps);
+ }
+ }
if (TransformStmts(Instantiations, S->getInstantiations()))
return StmtError();
More information about the llvm-branch-commits
mailing list