[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
Tue Mar 24 08:29:46 PDT 2026


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

>From e4abcd3e0fa975ea764e57a39f89bdf43f18bf67 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                 | 111 +++++++++++++++++-
 clang/lib/Sema/TreeTransform.h                |  26 +++-
 3 files changed, 130 insertions(+), 9 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 f821f73cb1e55..27878377f934f 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");
@@ -274,6 +274,51 @@ static IterableExpansionStmtData TryBuildIterableExpansionStmtInitializer(
   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->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) {
@@ -435,8 +480,59 @@ 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;
+  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) {
@@ -469,8 +565,10 @@ StmtResult Sema::FinishCXXExpansionStmt(Stmt *Exp, Stmt *Body) {
   if (Expansion->isIterating()) {
     Shared.push_back(Expansion->getRangeVarStmt());
     Shared.push_back(Expansion->getBeginVarStmt());
-  } 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.
@@ -722,5 +820,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 864667d784e6d..b3984a47eac98 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());
@@ -9510,8 +9515,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