[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 Mar 18 18:00:34 PDT 2026


https://github.com/Sirraide updated https://github.com/llvm/llvm-project/pull/169685

>From ae93fdd0b4999283f4002dddaade64d8bfadb160 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                 | 110 +++++++++++++++++-
 clang/lib/Sema/TreeTransform.h                |  55 +++++++--
 3 files changed, 151 insertions(+), 16 deletions(-)

diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index 9142afb95b89d..7f6fd47b3bb71 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -3730,6 +3730,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 ca286854b4002..231f6e657e55e 100644
--- a/clang/lib/Sema/SemaExpand.cpp
+++ b/clang/lib/Sema/SemaExpand.cpp
@@ -85,7 +85,7 @@ static bool HasDependentSize(const CXXExpansionStmtPattern *Pattern) {
     return true;
 
   case CXXExpansionStmtPattern::ExpansionStmtKind::Destructuring:
-    llvm_unreachable("TODO");
+    return false;
   }
 
   llvm_unreachable("invalid pattern kind");
@@ -281,6 +281,52 @@ TryBuildIterableExpansionStmtInitializer(Sema &S, Expr *ExpansionInitializer,
   return Data;
 }
 
+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->getEnclosingNonExpansionStatementContext(),
+      /*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) {
@@ -442,8 +488,57 @@ StmtResult Sema::BuildNonEnumeratingCXXExpansionStmtPattern(
         Data.EndDecl, 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;
+  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()) {
+      TypeSourceInfo *TSI = Context.getTrivialTypeSourceInfo(
+          Context.getRValueReferenceType(BD->getType().getNonReferenceType()));
+      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 (FinaliseExpansionVar(*this, ExpansionVar, Select))
+    return StmtError();
+
+  return CXXExpansionStmtPattern::CreateDestructuring(
+      Context, ESD, Init, ExpansionVarStmt, DS, LParenLoc, ColonLoc, RParenLoc);
 }
 
 StmtResult Sema::FinishCXXExpansionStmt(Stmt *Exp, Stmt *Body) {
@@ -483,8 +578,10 @@ StmtResult Sema::FinishCXXExpansionStmt(Stmt *Exp, Stmt *Body) {
     Shared.push_back(Expansion->getRangeVarStmt());
     Shared.push_back(Expansion->getBeginVarStmt());
     Shared.push_back(Expansion->getEndVarStmt());
-  } else {
-    assert(Expansion->isEnumerating() && "TODO");
+  } else if (Expansion->isDestructuring()) {
+    Shared.push_back(Expansion->getDecompositionDeclStmt());
+    MarkAnyDeclReferenced(Exp->getBeginLoc(), Expansion->getDecompositionDecl(),
+                          true);
   }
 
   // Return an empty statement if the range is empty.
@@ -742,5 +839,6 @@ Sema::ComputeExpansionSize(CXXExpansionStmtPattern *Expansion) {
     return ER.Val.getInt().getZExtValue();
   }
 
-  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 1eb4cdfe7df65..cbfcce517580d 100644
--- a/clang/lib/Sema/TreeTransform.h
+++ b/clang/lib/Sema/TreeTransform.h
@@ -9386,6 +9386,27 @@ StmtResult TreeTransform<Derived>::TransformCXXExpansionStmtPattern(
     Init = SR.get();
   }
 
+  // Collect lifetime-extended temporaries in case this ends up being a
+  // destructuring expansion statement (for other kinds of expansion statements,
+  // this should make no difference since we ignore 'LifetimeExtendTemps' for
+  // those).
+  ExprResult ExpansionInitializer;
+  SmallVector<MaterializeTemporaryExpr *, 8> LifetimeExtendTemps;
+  if (S->isDependent()) {
+    EnterExpressionEvaluationContext ExprEvalCtx(
+        SemaRef, SemaRef.currentEvaluationContext().Context);
+    SemaRef.currentEvaluationContext().InLifetimeExtendingContext = true;
+    SemaRef.currentEvaluationContext().RebuildDefaultArgOrDefaultInit = true;
+
+    ExpansionInitializer =
+        getDerived().TransformExpr(S->getExpansionInitializer());
+    if (ExpansionInitializer.isInvalid())
+      return StmtError();
+
+    LifetimeExtendTemps =
+        SemaRef.currentEvaluationContext().ForRangeLifetimeExtendTemps;
+  }
+
   CXXExpansionStmtPattern *NewPattern = nullptr;
   if (S->isEnumerating()) {
     StmtResult ExpansionVar =
@@ -9418,11 +9439,6 @@ StmtResult TreeTransform<Derived>::TransformCXXExpansionStmtPattern(
         Iter.getAs<DeclStmt>(), S->getLParenLoc(), S->getColonLoc(),
         S->getRParenLoc());
   } else if (S->isDependent()) {
-    ExprResult ExpansionInitializer =
-        getDerived().TransformExpr(S->getExpansionInitializer());
-    if (ExpansionInitializer.isInvalid())
-      return StmtError();
-
     StmtResult ExpansionVar =
         getDerived().TransformStmt(S->getExpansionVarStmt());
     if (ExpansionVar.isInvalid())
@@ -9431,15 +9447,19 @@ StmtResult TreeTransform<Derived>::TransformCXXExpansionStmtPattern(
     StmtResult Res = SemaRef.BuildNonEnumeratingCXXExpansionStmtPattern(
         NewESD, Init, ExpansionVar.getAs<DeclStmt>(),
         ExpansionInitializer.get(), S->getLParenLoc(), S->getColonLoc(),
-        S->getRParenLoc(),
-        /*LifetimeExtendTemps=*/{});
+        S->getRParenLoc(), LifetimeExtendTemps);
 
     if (Res.isInvalid())
       return StmtError();
 
     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());
@@ -9470,8 +9490,23 @@ StmtResult TreeTransform<Derived>::TransformCXXExpansionStmtInstantiation(
   SmallVector<Stmt *> SharedStmts;
   SmallVector<Stmt *> Instantiations;
 
-  if (TransformStmts(SharedStmts, S->getSharedStmts()))
-    return StmtError();
+  // Apply lifetime extension to the shared statements if this was a
+  // destructuring expansion statement.
+  {
+    EnterExpressionEvaluationContext ExprEvalCtx(
+        SemaRef, SemaRef.currentEvaluationContext().Context);
+    SemaRef.currentEvaluationContext().InLifetimeExtendingContext = true;
+    SemaRef.currentEvaluationContext().RebuildDefaultArgOrDefaultInit = true;
+    if (TransformStmts(SharedStmts, S->getSharedStmts()))
+      return StmtError();
+
+    if (S->shouldApplyLifetimeExtensionToSharedStmts()) {
+      auto *VD =
+          cast<VarDecl>(cast<DeclStmt>(SharedStmts.front())->getSingleDecl());
+      SemaRef.ApplyForRangeOrExpansionStatementLifetimeExtension(
+          VD, SemaRef.currentEvaluationContext().ForRangeLifetimeExtendTemps);
+    }
+  }
 
   if (TransformStmts(Instantiations, S->getInstantiations()))
     return StmtError();



More information about the llvm-branch-commits mailing list