https://github.com/regevran updated 
https://github.com/llvm/llvm-project/pull/221711

>From 7fb058545a2c275cc5bee88b3b41b35eb56c4fb6 Mon Sep 17 00:00:00 2001
From: Ran Regev <[email protected]>
Date: Mon, 7 Sep 2026 12:57:15 +0300
Subject: [PATCH 1/2] [clang] Support DecompositionDecl in -ast-print

DeclPrinter had no VisitDecompositionDecl:
auto [a, b] = get();
printed as:
auto = get();
silently dropping the whole binding list.

At namespace scope this was worse:
    struct Pair { int a, b; };
    Pair get();
    auto [gx, gy] = get();
printed as:
    struct Pair {
        int a;
        int b;
    };
    Pair get();
    ;
    ;
    auto = get();

Added VisitDecompositionDecl, sharing VisitVarDecl's specifier and
initializer logic via two extracted helpers: printVarDeclSpecifiers,
printVarInitializer, to keep the two in sync Skip BindingDecl in
VisitDeclContext.

Structured binding packs (`auto [...rest] = arr;`) are a separate,
pre-existing gap this doesn't address: a pack binding still prints its
name, just without the leading `...`, e.g.

    auto [first, ...rest, last] = arr;

currently prints as

    auto [first, rest, last] = arr;
---
 clang/lib/AST/DeclPrinter.cpp              | 41 +++++++++++++-
 clang/test/AST/ast-print-decomposition.cpp | 66 ++++++++++++++++++++++
 2 files changed, 106 insertions(+), 1 deletion(-)
 create mode 100644 clang/test/AST/ast-print-decomposition.cpp

diff --git a/clang/lib/AST/DeclPrinter.cpp b/clang/lib/AST/DeclPrinter.cpp
index cdd6bb0a90a2b..6fa7c341ef9d5 100644
--- a/clang/lib/AST/DeclPrinter.cpp
+++ b/clang/lib/AST/DeclPrinter.cpp
@@ -52,6 +52,9 @@ namespace {
     void PrintObjCTypeParams(ObjCTypeParamList *Params);
     void PrintOpenACCRoutineOnLambda(Decl *D);
 
+    QualType printVarDeclSpecifiers(VarDecl *D);
+    void printVarInitializer(VarDecl *D);
+
   public:
     DeclPrinter(raw_ostream &Out, const PrintingPolicy &Policy,
                 const ASTContext &Context, unsigned Indentation = 0,
@@ -73,6 +76,7 @@ namespace {
     void VisitFriendTemplateDecl(FriendTemplateDecl *D);
     void VisitFieldDecl(FieldDecl *D);
     void VisitVarDecl(VarDecl *D);
+    void VisitDecompositionDecl(DecompositionDecl *D);
     void VisitLabelDecl(LabelDecl *D);
     void VisitParmVarDecl(ParmVarDecl *D);
     void VisitFileScopeAsmDecl(FileScopeAsmDecl *D);
@@ -461,6 +465,11 @@ void DeclPrinter::VisitDeclContext(DeclContext *DC, bool 
Indent) {
     if (isa<ObjCIvarDecl>(*D))
       continue;
 
+    // Don't print BindingDecls, as they are printed when visiting the
+    // containing DecompositionDecl.
+    if (isa<BindingDecl>(*D))
+      continue;
+
     // Skip over implicit declarations in pretty-printing mode.
     if (D->isImplicit())
       continue;
@@ -963,7 +972,7 @@ void DeclPrinter::VisitLabelDecl(LabelDecl *D) {
   Out << *D << ":";
 }
 
-void DeclPrinter::VisitVarDecl(VarDecl *D) {
+QualType DeclPrinter::printVarDeclSpecifiers(VarDecl *D) {
   prettyPrintPragmas(D);
 
   if (std::optional<std::string> Attrs =
@@ -1006,6 +1015,12 @@ void DeclPrinter::VisitVarDecl(VarDecl *D) {
     }
   }
 
+  return T;
+}
+
+void DeclPrinter::VisitVarDecl(VarDecl *D) {
+  QualType T = printVarDeclSpecifiers(D);
+
   printDeclType(T, (isa<ParmVarDecl>(D) && Policy.CleanUglifiedParameters &&
                     D->getIdentifier())
                        ? D->getIdentifier()->deuglifiedName()
@@ -1015,6 +1030,10 @@ void DeclPrinter::VisitVarDecl(VarDecl *D) {
           prettyPrintAttributes(D, AttrPosAsWritten::Right))
     Out << ' ' << *Attrs;
 
+  printVarInitializer(D);
+}
+
+void DeclPrinter::printVarInitializer(VarDecl *D) {
   Expr *Init = D->getInit();
   if (!Policy.SuppressInitializers && Init) {
     bool ImplicitInit = false;
@@ -1044,6 +1063,26 @@ void DeclPrinter::VisitVarDecl(VarDecl *D) {
   }
 }
 
+void DeclPrinter::VisitDecompositionDecl(DecompositionDecl *D) {
+  QualType T = printVarDeclSpecifiers(D);
+
+  // DecompositionDecl has no name of its own.
+  printDeclType(T, "");
+
+  Out << " [";
+  bool First = true;
+  for (BindingDecl *B : D->bindings()) {
+    if (!First)
+      Out << ", ";
+    First = false;
+    // FIXME: this drops the leading "..." for a pack binding.
+    Out << B->getName();
+  }
+  Out << "]";
+
+  printVarInitializer(D);
+}
+
 void DeclPrinter::VisitParmVarDecl(ParmVarDecl *D) {
   VisitVarDecl(D);
 }
diff --git a/clang/test/AST/ast-print-decomposition.cpp 
b/clang/test/AST/ast-print-decomposition.cpp
new file mode 100644
index 0000000000000..aabfcad111bc5
--- /dev/null
+++ b/clang/test/AST/ast-print-decomposition.cpp
@@ -0,0 +1,66 @@
+// RUN: %clang_cc1 -std=c++20 -ast-print %s | FileCheck %s
+
+// The `[a, b]` binding list must survive -ast-print, not just the type.
+
+namespace std {
+using size_t = decltype(sizeof(0));
+template <typename> struct tuple_size;
+template <size_t, typename> struct tuple_element;
+} // namespace std
+
+namespace Aggregate {
+struct Pair { int a, b; };
+Pair get();
+Pair &getref();
+
+// CHECK-LABEL: void local() {
+void local() {
+  // CHECK-NEXT: auto [x, y] = get();
+  auto [x, y] = get();
+  // CHECK-NEXT: auto & [rx, ry] = getref();
+  auto &[rx, ry] = getref();
+  // CHECK-NEXT: const auto [cx, cy] = get();
+  const auto [cx, cy] = get();
+  // CHECK-NEXT: static auto [sx, sy] = get();
+  static auto [sx, sy] = get();
+}
+} // namespace Aggregate
+
+namespace Array {
+// CHECK-LABEL: void local() {
+void local() {
+  // CHECK-NEXT: int arr[3] = {1, 2, 3};
+  int arr[3] = {1, 2, 3};
+  // CHECK-NEXT: auto [a, b, c]
+  auto [a, b, c] = arr;
+}
+} // namespace Array
+
+namespace TupleLike {
+struct Two {};
+Two getTwo();
+} // namespace TupleLike
+
+template <> struct std::tuple_size<TupleLike::Two> { enum { value = 2 }; };
+template <> struct std::tuple_element<0, TupleLike::Two> { using type = int; };
+template <> struct std::tuple_element<1, TupleLike::Two> { using type = int; };
+
+namespace TupleLike {
+// get() must be found by ADL, so it needs to live here, not at global scope.
+template <std::size_t N> int get(Two);
+
+// CHECK-LABEL: void local() {
+void local() {
+  // CHECK-NEXT: auto [p, q] = getTwo();
+  auto [p, q] = getTwo();
+}
+} // namespace TupleLike
+
+namespace NamespaceScope {
+using Aggregate::Pair;
+using Aggregate::get;
+
+// CHECK: auto [gx, gy] = get();
+auto [gx, gy] = get();
+// CHECK-NOT: {{^;$}}
+} // namespace NamespaceScope

>From 5c1b8b829b81695155a5f982a56792cc8ae12951 Mon Sep 17 00:00:00 2001
From: Ran Regev <[email protected]>
Date: Tue, 8 Sep 2026 12:02:32 +0300
Subject: [PATCH 2/2] [clang] Fold printDeclType into printVarDeclSpecifiers

Both VisitVarDecl and VisitDecompositionDecl called printDeclType(T,
...) immediately after printVarDeclSpecifiers(D) returned T, and the
name argument in both cases is computable from D alone:
DecompositionDecl has no identifier (Id is null), and
NamedDecl::getName() already returns "" safely for that case (the
DeclarationName constructed from a null IdentifierInfo* still reports
StoredIdentifier, so the assert in getName() holds, and it falls back
to the "" branch) -- exactly the declarator VisitDecompositionDecl
wants. So the existing ternary, unmodified, already produces the right
name for all three cases (VarDecl, ParmVarDecl, DecompositionDecl)
without a separate parameter.

Move the printDeclType call into printVarDeclSpecifiers so it no
longer needs to return QualType across the function boundary; both
callers now just call printVarDeclSpecifiers(D) with no local T
needed.
---
 clang/lib/AST/DeclPrinter.cpp | 21 +++++++++------------
 1 file changed, 9 insertions(+), 12 deletions(-)

diff --git a/clang/lib/AST/DeclPrinter.cpp b/clang/lib/AST/DeclPrinter.cpp
index 6fa7c341ef9d5..3d79b5eb241fe 100644
--- a/clang/lib/AST/DeclPrinter.cpp
+++ b/clang/lib/AST/DeclPrinter.cpp
@@ -52,7 +52,7 @@ namespace {
     void PrintObjCTypeParams(ObjCTypeParamList *Params);
     void PrintOpenACCRoutineOnLambda(Decl *D);
 
-    QualType printVarDeclSpecifiers(VarDecl *D);
+    void printVarDeclSpecifiers(VarDecl *D);
     void printVarInitializer(VarDecl *D);
 
   public:
@@ -972,7 +972,7 @@ void DeclPrinter::VisitLabelDecl(LabelDecl *D) {
   Out << *D << ":";
 }
 
-QualType DeclPrinter::printVarDeclSpecifiers(VarDecl *D) {
+void DeclPrinter::printVarDeclSpecifiers(VarDecl *D) {
   prettyPrintPragmas(D);
 
   if (std::optional<std::string> Attrs =
@@ -1015,16 +1015,16 @@ QualType DeclPrinter::printVarDeclSpecifiers(VarDecl 
*D) {
     }
   }
 
-  return T;
-}
-
-void DeclPrinter::VisitVarDecl(VarDecl *D) {
-  QualType T = printVarDeclSpecifiers(D);
-
+  // D->getName() is "" for a DecompositionDecl (it has no name of its own),
+  // which is exactly the declarator we want for one: just the type.
   printDeclType(T, (isa<ParmVarDecl>(D) && Policy.CleanUglifiedParameters &&
                     D->getIdentifier())
                        ? D->getIdentifier()->deuglifiedName()
                        : D->getName());
+}
+
+void DeclPrinter::VisitVarDecl(VarDecl *D) {
+  printVarDeclSpecifiers(D);
 
   if (std::optional<std::string> Attrs =
           prettyPrintAttributes(D, AttrPosAsWritten::Right))
@@ -1064,10 +1064,7 @@ void DeclPrinter::printVarInitializer(VarDecl *D) {
 }
 
 void DeclPrinter::VisitDecompositionDecl(DecompositionDecl *D) {
-  QualType T = printVarDeclSpecifiers(D);
-
-  // DecompositionDecl has no name of its own.
-  printDeclType(T, "");
+  printVarDeclSpecifiers(D);
 
   Out << " [";
   bool First = true;

_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to