[clang] [clang][constexpr] Fix assertion failure in C++26 constexpr structured binding pack evaluation (#170991) (PR #213534)

via cfe-commits cfe-commits at lists.llvm.org
Tue Aug 25 04:49:54 PDT 2026


https://github.com/babadany2999 updated https://github.com/llvm/llvm-project/pull/213534

>From 150e34eeb30b520fe35b0a0148773cc07974086a Mon Sep 17 00:00:00 2001
From: Baba Dan Constantin <babadany2999 at gmail.com>
Date: Sun, 2 Aug 2026 14:35:25 +0300
Subject: [PATCH 1/2] [clang][constexpr] Fix assertion failure in constexpr
 structured binding pack evaluation (#GH170991)

Signed-off-by: Baba Dan Constantin <babadany2999 at gmail.com>
---
 clang/docs/ReleaseNotes.md                 |   3 +
 clang/lib/Sema/SemaDeclCXX.cpp             |   5 +
 clang/test/SemaCXX/cxx2c-decomposition.cpp | 125 +++++++++++++++++++++
 3 files changed, 133 insertions(+)

diff --git a/clang/docs/ReleaseNotes.md b/clang/docs/ReleaseNotes.md
index 7108392abbaa1..3d73e420fe5b4 100644
--- a/clang/docs/ReleaseNotes.md
+++ b/clang/docs/ReleaseNotes.md
@@ -424,6 +424,9 @@ features cannot lower the translation-unit ABI level;
   copy so the union's object representation is copied, matching the defaulted
   union copy constructor.
 
+- Fixed an assertion failure when evaluating C++26 `constexpr` structured binding packs during template
+  instantiation. (#GH170991)
+
 #### Bug Fixes to AST Handling
 
 - Fixed a non-deterministic ordering of unused local typedefs that made
diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp
index 47b01b913b428..0a035f4cd1c7c 100644
--- a/clang/lib/Sema/SemaDeclCXX.cpp
+++ b/clang/lib/Sema/SemaDeclCXX.cpp
@@ -1655,16 +1655,19 @@ void Sema::CheckCompleteDecompositionDeclaration(DecompositionDecl *DD) {
   if (auto *CAT = Context.getAsConstantArrayType(DecompType)) {
     if (checkArrayDecomposition(*this, Bindings, DD, DecompType, CAT))
       DD->setInvalidDecl();
+    CleanupVarDeclMarking();
     return;
   }
   if (auto *VT = DecompType->getAs<VectorType>()) {
     if (checkVectorDecomposition(*this, Bindings, DD, DecompType, VT))
       DD->setInvalidDecl();
+    CleanupVarDeclMarking();
     return;
   }
   if (auto *CT = DecompType->getAs<ComplexType>()) {
     if (checkComplexDecomposition(*this, Bindings, DD, DecompType, CT))
       DD->setInvalidDecl();
+    CleanupVarDeclMarking();
     return;
   }
 
@@ -1680,6 +1683,7 @@ void Sema::CheckCompleteDecompositionDeclaration(DecompositionDecl *DD) {
   case IsTupleLike::TupleLike:
     if (checkTupleLikeDecomposition(*this, Bindings, DD, DecompType, TupleSize))
       DD->setInvalidDecl();
+    CleanupVarDeclMarking();
     return;
 
   case IsTupleLike::NotTupleLike:
@@ -1701,6 +1705,7 @@ void Sema::CheckCompleteDecompositionDeclaration(DecompositionDecl *DD) {
   //   E or of the same unambiguous public base class of E, ...
   if (checkMemberDecomposition(*this, Bindings, DD, DecompType, RD))
     DD->setInvalidDecl();
+  CleanupVarDeclMarking();
 }
 
 UnsignedOrNone Sema::GetDecompositionElementCount(QualType T,
diff --git a/clang/test/SemaCXX/cxx2c-decomposition.cpp b/clang/test/SemaCXX/cxx2c-decomposition.cpp
index 2ab26b1313518..c60ab6fa68d03 100644
--- a/clang/test/SemaCXX/cxx2c-decomposition.cpp
+++ b/clang/test/SemaCXX/cxx2c-decomposition.cpp
@@ -155,3 +155,128 @@ constexpr auto [e1] = E(true);
 //   expected-note at -1 {{in implicit initialization of binding declaration 'e1'}} \
 //   expected-note at -1 {{reference to temporary is not a constant expression}} \
 //   expected-note@#E-get {{temporary created here}}
+
+namespace GH170991 {
+// Test case: struct
+struct S { int x{}; };
+
+template <typename = void>
+void f() {
+  constexpr S s;
+  constexpr auto [x] = s;
+  constexpr auto [...xs] = s;
+}
+
+template void f<void>();
+
+// Test case: array
+template <typename = void>
+void g() {
+  constexpr int a[2]{};
+  constexpr auto [x, y] = a;
+  constexpr auto [...xs] = a;
+}
+
+template void g<void>();
+} // namespace GH170991
+
+// Test case: tuple-like
+namespace GH170991 {
+struct TupleLikeFnTemplate {
+  int x = 100;
+  char y = 'D';
+
+  // If a search for the name get in the scope of E [...] the initializer is e.get<i>()
+  template <unsigned I>
+  constexpr decltype(auto) get() const {
+    if constexpr (I == 0) return 500;
+    else return y;
+  }
+};
+
+struct TupleLikeADL {
+  int x = 100;
+  char y = 'D';
+};
+
+// Otherwise, the initializer is get<i>(e)[...]
+template <unsigned I>
+constexpr decltype(auto) get(const TupleLikeADL &t) {
+  if constexpr (I == 0) return 500;
+  else return t.y;
+}
+} // namespace GH170991
+
+namespace std {
+template <>
+struct tuple_size<const GH170991::TupleLikeFnTemplate> {
+  static constexpr unsigned value = 2;
+};
+template <>
+struct tuple_size<const GH170991::TupleLikeADL> {
+  static constexpr unsigned value = 2;
+};
+
+template <>
+struct tuple_element<0, const GH170991::TupleLikeFnTemplate> {
+  using type = int;
+};
+template <>
+struct tuple_element<0, const GH170991::TupleLikeADL> {
+  using type = int;
+};
+
+template <>
+struct tuple_element<1, const GH170991::TupleLikeFnTemplate> {
+  using type = char;
+};
+template <>
+struct tuple_element<1, const GH170991::TupleLikeADL> {
+  using type = char;
+};
+} // namespace std
+
+namespace GH170991 {
+template <typename = void>
+void h() {
+  constexpr TupleLikeFnTemplate fn_template{};
+  constexpr auto [x, y] = fn_template;
+  static_assert(x == 500); // Proves tuple_like.get<0>(tuple-like decomposition) was called
+  static_assert(y == 'D');
+  constexpr auto [...xs] = fn_template;
+  static_assert(xs...[0] == 500);
+  static_assert(xs...[1] == 'D');
+
+  constexpr TupleLikeADL adl{};
+  constexpr auto [x2, y2] = adl;
+  static_assert(x2 == 500); // Proves get<0>(tuple-like decomposition) was called
+  static_assert(y2 == 'D');
+  constexpr auto [...xs2] = adl;
+  static_assert(xs2...[0] == 500);
+  static_assert(xs2...[1] == 'D');
+}
+
+template void h<void>();
+
+// Test case: vector type(GCC generic vector type)
+typedef int v4si __attribute__ ((vector_size (8)));
+
+template <typename = void>
+void i() {
+  constexpr v4si vector_type{};
+  constexpr auto [x, y] = vector_type;
+  constexpr auto [...xs] = vector_type;
+}
+
+template void i<void>();
+
+// Test case: complex type
+template <typename = void>
+void j() {
+  constexpr _Complex float complex_type{};
+  constexpr auto [x, y] = complex_type;
+  constexpr auto [...xs] = complex_type;
+}
+
+template void j<void>();
+} // namespace GH170991

>From 829358ed6bdd80dbfed459413125ae9c34b9c746 Mon Sep 17 00:00:00 2001
From: Baba Dan Constantin <babadany2999 at gmail.com>
Date: Tue, 25 Aug 2026 14:49:39 +0300
Subject: [PATCH 2/2] Removed duplicate calls to CleanupVarDeclMarking()

---
 clang/lib/Sema/SemaDeclCXX.cpp | 72 +++++++++++++++-------------------
 1 file changed, 32 insertions(+), 40 deletions(-)

diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp
index ffe365396d3db..4dbdf8b60f48a 100644
--- a/clang/lib/Sema/SemaDeclCXX.cpp
+++ b/clang/lib/Sema/SemaDeclCXX.cpp
@@ -1665,56 +1665,48 @@ void Sema::CheckCompleteDecompositionDeclaration(DecompositionDecl *DD) {
   if (auto *CAT = Context.getAsConstantArrayType(DecompType)) {
     if (checkArrayDecomposition(*this, Bindings, DD, DecompType, CAT))
       DD->setInvalidDecl();
-    CleanupVarDeclMarking();
-    return;
-  }
-  if (auto *VT = DecompType->getAs<VectorType>()) {
+  } else if (auto *VT = DecompType->getAs<VectorType>()) {
     if (checkVectorDecomposition(*this, Bindings, DD, DecompType, VT))
       DD->setInvalidDecl();
-    CleanupVarDeclMarking();
-    return;
-  }
-  if (auto *CT = DecompType->getAs<ComplexType>()) {
+  } else if (auto *CT = DecompType->getAs<ComplexType>()) {
     if (checkComplexDecomposition(*this, Bindings, DD, DecompType, CT))
       DD->setInvalidDecl();
-    CleanupVarDeclMarking();
-    return;
-  }
+  } else {
+    // C++1z [dcl.decomp]/3:
+    //   if the expression std::tuple_size<E>::value is a well-formed integral
+    //   constant expression, [...]
+    unsigned TupleSize;
+    switch (isTupleLike(*this, DD->getLocation(), DecompType, TupleSize)) {
+    case IsTupleLike::Error:
+      DD->setInvalidDecl();
+      return;
 
-  // C++1z [dcl.decomp]/3:
-  //   if the expression std::tuple_size<E>::value is a well-formed integral
-  //   constant expression, [...]
-  unsigned TupleSize;
-  switch (isTupleLike(*this, DD->getLocation(), DecompType, TupleSize)) {
-  case IsTupleLike::Error:
-    DD->setInvalidDecl();
-    return;
+    case IsTupleLike::TupleLike:
+      if (checkTupleLikeDecomposition(*this, Bindings, DD, DecompType, TupleSize))
+        DD->setInvalidDecl();
+      break;
 
-  case IsTupleLike::TupleLike:
-    if (checkTupleLikeDecomposition(*this, Bindings, DD, DecompType, TupleSize))
-      DD->setInvalidDecl();
-    CleanupVarDeclMarking();
-    return;
+    case IsTupleLike::NotTupleLike: {
+      // C++1z [dcl.dcl]/8:
+      //   [E shall be of array or non-union class type]
+      CXXRecordDecl *RD = DecompType->getAsCXXRecordDecl();
+      if (!RD || RD->isUnion()) {
+        Diag(DD->getLocation(), diag::err_decomp_decl_unbindable_type)
+            << DD << !RD << DecompType;
+        DD->setInvalidDecl();
+        return;
+      }
 
-  case IsTupleLike::NotTupleLike:
-    break;
+      // C++1z [dcl.decomp]/4:
+      //   all of E's non-static data members shall be [...] direct members of
+      //   E or of the same unambiguous public base class of E, ...
+      if (checkMemberDecomposition(*this, Bindings, DD, DecompType, RD))
+        DD->setInvalidDecl();
+      break;
+    }
   }
-
-  // C++1z [dcl.dcl]/8:
-  //   [E shall be of array or non-union class type]
-  CXXRecordDecl *RD = DecompType->getAsCXXRecordDecl();
-  if (!RD || RD->isUnion()) {
-    Diag(DD->getLocation(), diag::err_decomp_decl_unbindable_type)
-        << DD << !RD << DecompType;
-    DD->setInvalidDecl();
-    return;
   }
 
-  // C++1z [dcl.decomp]/4:
-  //   all of E's non-static data members shall be [...] direct members of
-  //   E or of the same unambiguous public base class of E, ...
-  if (checkMemberDecomposition(*this, Bindings, DD, DecompType, RD))
-    DD->setInvalidDecl();
   CleanupVarDeclMarking();
 }
 



More information about the cfe-commits mailing list