[clang] [clang] Support DecompositionDecl in -ast-print (PR #221711)
via cfe-commits
cfe-commits at lists.llvm.org
Mon Sep 7 04:42:16 PDT 2026
llvmorg-github-actions[bot] wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-clang
Author: Ran Regev (regevran)
<details>
<summary>Changes</summary>
Found while working on an out-of-tree [prototype](https://github.com/regevran/llvm-project-fork/tree/p3817) of [P3817](https://wg21.link/p3817) ("Structured
Binding Assignments"), an experimental extension to structured
bindings.
The bug itself is unrelated to that extension: it's a
pre-existing gap in plain C++17 structured-binding printing that the
prototype's own testing happened to expose.
DeclPrinter had no VisitDecompositionDecl:
```cpp
auto [a, b] = get();
```
printed as:
```cpp
auto = get();
```
silently dropping the whole binding list.
At namespace scope this was worse:
```cpp
struct Pair { int a, b; };
Pair get();
auto [gx, gy] = get();
```
printed as:
```cpp
struct Pair {
int a;
int b;
};
Pair get();
;
;
auto = get();
```
The Fix:
Added `VisitDecompositionDecl`, sharing `VisitVarDecl`'s specifier and
initializer logic via two extracted helpers: `printVarDeclSpecifiers`,
`printVarInitializer`, to keep the two in sync.
Skipped 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.
```cpp
auto [first, ...rest, last] = arr;
```
currently prints as
```cpp
auto [first, rest, last] = arr;
```
---
Full diff: https://github.com/llvm/llvm-project/pull/221711.diff
2 Files Affected:
- (modified) clang/lib/AST/DeclPrinter.cpp (+40-1)
- (added) clang/test/AST/ast-print-decomposition.cpp (+66)
``````````diff
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
``````````
</details>
https://github.com/llvm/llvm-project/pull/221711
More information about the cfe-commits
mailing list