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 01/14] [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 a6772d4da6fc0a404c3af1cc4da80ba5f97ec100 Mon Sep 17 00:00:00 2001 From: Ran Regev <[email protected]> Date: Tue, 8 Sep 2026 12:02:32 +0300 Subject: [PATCH 02/14] [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 "" -- 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. Assisted-by: AI --- 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; >From 85ee278033b37ab3205df3b559931c6f922b0d2c Mon Sep 17 00:00:00 2001 From: Ran Regev <[email protected]> Date: Tue, 8 Sep 2026 12:38:37 +0300 Subject: [PATCH 03/14] [clang] Use llvm::ListSeparator instead of a hand-rolled bool flag Using existing practice instead of reimplementing it: DeclPrinter.cpp already uses this idiom elsewhere for the same purpose. Assisted-by: AI --- clang/lib/AST/DeclPrinter.cpp | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/clang/lib/AST/DeclPrinter.cpp b/clang/lib/AST/DeclPrinter.cpp index 3d79b5eb241fe..4f36862e58d72 100644 --- a/clang/lib/AST/DeclPrinter.cpp +++ b/clang/lib/AST/DeclPrinter.cpp @@ -1067,11 +1067,9 @@ void DeclPrinter::VisitDecompositionDecl(DecompositionDecl *D) { printVarDeclSpecifiers(D); Out << " ["; - bool First = true; + llvm::ListSeparator LS; for (BindingDecl *B : D->bindings()) { - if (!First) - Out << ", "; - First = false; + Out << LS; // FIXME: this drops the leading "..." for a pack binding. Out << B->getName(); } >From 57f1f22caab4d00ed19acf2d4c584258f335559d Mon Sep 17 00:00:00 2001 From: Ran Regev <[email protected]> Date: Tue, 8 Sep 2026 12:58:30 +0300 Subject: [PATCH 04/14] [clang] Fix ast-print-decomposition.cpp's namespace-scope regression check The check was non-functional in both directions: - CHECK-NOT only ran after the CHECK: auto [gx, gy]... match, so a regression reintroducing the bogus ';' statements before that line would still pass (CHECK doesn't require adjacency). - Independently, {{^;$}} could never match at all: the printed line is indented (' ;', inside namespace NamespaceScope), and ^ anchors to true column 0. Add a CHECK-NOT before the CHECK too, and use {{^[[:space:]]*;[[:space:]]*$}} so it actually matches an indented-only-a-semicolon line. Verified against a reverted VisitDeclContext fix: fails as expected with the bug reintroduced, passes with it fixed. Assisted-by: AI --- clang/test/AST/ast-print-decomposition.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/clang/test/AST/ast-print-decomposition.cpp b/clang/test/AST/ast-print-decomposition.cpp index aabfcad111bc5..b65140061b7ad 100644 --- a/clang/test/AST/ast-print-decomposition.cpp +++ b/clang/test/AST/ast-print-decomposition.cpp @@ -60,7 +60,8 @@ namespace NamespaceScope { using Aggregate::Pair; using Aggregate::get; +// CHECK-NOT: {{^[[:space:]]*;[[:space:]]*$}} // CHECK: auto [gx, gy] = get(); auto [gx, gy] = get(); -// CHECK-NOT: {{^;$}} +// CHECK-NOT: {{^[[:space:]]*;[[:space:]]*$}} } // namespace NamespaceScope >From 92424a507fe1eae896700f48ac24aeb8e25065c3 Mon Sep 17 00:00:00 2001 From: Ran Regev <[email protected]> Date: Tue, 8 Sep 2026 13:08:21 +0300 Subject: [PATCH 05/14] [clang] Print the leading "..." for a structured binding pack D->bindings() already exposes the pack as a single BindingDecl with isParameterPack() set, for both the primary template and each instantiation, so this is a one-line addition, not a design change. Assisted-by: AI --- clang/lib/AST/DeclPrinter.cpp | 3 ++- clang/test/AST/ast-print-decomposition.cpp | 13 ++++++++++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/clang/lib/AST/DeclPrinter.cpp b/clang/lib/AST/DeclPrinter.cpp index 4f36862e58d72..d7a62f490fd7d 100644 --- a/clang/lib/AST/DeclPrinter.cpp +++ b/clang/lib/AST/DeclPrinter.cpp @@ -1070,7 +1070,8 @@ void DeclPrinter::VisitDecompositionDecl(DecompositionDecl *D) { llvm::ListSeparator LS; for (BindingDecl *B : D->bindings()) { Out << LS; - // FIXME: this drops the leading "..." for a pack binding. + if (B->isParameterPack()) + Out << "..."; Out << B->getName(); } Out << "]"; diff --git a/clang/test/AST/ast-print-decomposition.cpp b/clang/test/AST/ast-print-decomposition.cpp index b65140061b7ad..efbfa8a215263 100644 --- a/clang/test/AST/ast-print-decomposition.cpp +++ b/clang/test/AST/ast-print-decomposition.cpp @@ -1,4 +1,4 @@ -// RUN: %clang_cc1 -std=c++20 -ast-print %s | FileCheck %s +// RUN: %clang_cc1 -std=c++26 -ast-print %s | FileCheck %s // The `[a, b]` binding list must survive -ast-print, not just the type. @@ -65,3 +65,14 @@ using Aggregate::get; auto [gx, gy] = get(); // CHECK-NOT: {{^[[:space:]]*;[[:space:]]*$}} } // namespace NamespaceScope + +namespace Packs { +// CHECK-LABEL: void local() { +template <unsigned N> void local() { + // CHECK-NEXT: int arr[4] = {1, 2, 3, 4}; + int arr[4] = {1, 2, 3, 4}; + // CHECK-NEXT: auto [first, ...rest, last] + auto [first, ...rest, last] = arr; +} +void (*p)() = local<0>; +} // namespace Packs >From a6ff2a82565faa5416df4f15c8fe3492308889df Mon Sep 17 00:00:00 2001 From: Ran Regev <[email protected]> Date: Tue, 8 Sep 2026 13:13:11 +0300 Subject: [PATCH 06/14] [clang] Print attributes on individual structured bindings prettyPrintAttributes already takes a generic const Decl*, and BindingDecl is-a Decl, so this is the same one-liner VisitVarDecl already uses for its own right-position attributes, just called on each binding instead of on D. Assisted-by: AI --- clang/lib/AST/DeclPrinter.cpp | 3 +++ clang/test/AST/ast-print-decomposition.cpp | 11 +++++++++++ 2 files changed, 14 insertions(+) diff --git a/clang/lib/AST/DeclPrinter.cpp b/clang/lib/AST/DeclPrinter.cpp index d7a62f490fd7d..812bfa3d84eee 100644 --- a/clang/lib/AST/DeclPrinter.cpp +++ b/clang/lib/AST/DeclPrinter.cpp @@ -1073,6 +1073,9 @@ void DeclPrinter::VisitDecompositionDecl(DecompositionDecl *D) { if (B->isParameterPack()) Out << "..."; Out << B->getName(); + if (std::optional<std::string> Attrs = + prettyPrintAttributes(B, AttrPosAsWritten::Right)) + Out << ' ' << *Attrs; } Out << "]"; diff --git a/clang/test/AST/ast-print-decomposition.cpp b/clang/test/AST/ast-print-decomposition.cpp index efbfa8a215263..37e53745115c4 100644 --- a/clang/test/AST/ast-print-decomposition.cpp +++ b/clang/test/AST/ast-print-decomposition.cpp @@ -76,3 +76,14 @@ template <unsigned N> void local() { } void (*p)() = local<0>; } // namespace Packs + +namespace Attributes { +using Aggregate::Pair; +using Aggregate::get; + +// CHECK-LABEL: void local() { +void local() { + // CHECK-NEXT: auto [x {{\[\[}}maybe_unused{{\]\]}}, y] = get(); + auto [x [[maybe_unused]], y] = get(); +} +} // namespace Attributes >From d39b4c49cb42da0cc90077d2a1fd2a32ee93ae41 Mon Sep 17 00:00:00 2001 From: Ran Regev <[email protected]> Date: Tue, 8 Sep 2026 17:59:15 +0300 Subject: [PATCH 07/14] [clang][test] Loosen two Analysis CHECK lines pinned to DecompositionDecl's spelling cfg.cpp and anonymous-decls.cpp each dump a decomposition's DeclStmt as one element of a larger CFG-shape / DeclRefExpr-printing test, not as a test of the declaration's own printed form. Both CHECK lines hard-coded that text exactly. Adding the binding list -- the actual target of this PR -- broke both, even though neither test cares about that text: they were asserting against wrong output, just because something had to be printed there. This commit loosens both lines to a wildcard over the binding list. That's safe because the wildcard only spans text these tests never examine -- it doesn't hide the DeclStmt element itself disappearing, or anything about the surrounding CFG elements the tests do check; a real regression there would still fail the match. So future changes to declaration printing don't require touching unrelated CFG tests again. Assisted-by: AI --- clang/test/Analysis/anonymous-decls.cpp | 2 +- clang/test/Analysis/cfg.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/clang/test/Analysis/anonymous-decls.cpp b/clang/test/Analysis/anonymous-decls.cpp index 705273328e6cc..88aa569a1212b 100644 --- a/clang/test/Analysis/anonymous-decls.cpp +++ b/clang/test/Analysis/anonymous-decls.cpp @@ -72,7 +72,7 @@ int main() { // CHECK-NEXT: 2: [B3.1] (ImplicitCastExpr, FunctionToPointerDecay, iterator_traits<pair<int, int> *>::reference (*)(void)) // CHECK-NEXT: 3: __begin1 // CHECK-NEXT: 4: * [B3.3] (OperatorCall) -// CHECK-NEXT: 5: auto &; +// CHECK-NEXT: 5: auto &{{.*}}; // CHECK-NEXT: 6: get<0UL> // CHECK-NEXT: 7: [B3.6] (ImplicitCastExpr, FunctionToPointerDecay, tuple_element<0L, pair<int, int> >::type (*)(pair<int, int> &)) // CHECK-NEXT: 8: decomposition-a-b diff --git a/clang/test/Analysis/cfg.cpp b/clang/test/Analysis/cfg.cpp index 2a88b73d27756..478e457d5dcd4 100644 --- a/clang/test/Analysis/cfg.cpp +++ b/clang/test/Analysis/cfg.cpp @@ -659,7 +659,7 @@ int crash_with_thread_local(char *p, int *q) { // CHECK-NEXT: 5: [B1.3]{{\[\[}}B1.4]] // CHECK-NEXT: 6: [B1.5] (ImplicitCastExpr, LValueToRValue, int) // CHECK-NEXT: 7: {{\{}}[B1.6]{{(\})}} -// CHECK-NEXT: 8: auto = {{\{}}arr[*]{{(\})}}; +// CHECK-NEXT: 8: auto{{.*}} = {{\{}}arr[*]{{(\})}}; void DecompositionDecl() { int arr[2]; >From 4aa3278fb291a5e8f2cbdb9959530c670fdc35c2 Mon Sep 17 00:00:00 2001 From: Ran Regev <[email protected]> Date: Tue, 8 Sep 2026 18:19:19 +0300 Subject: [PATCH 08/14] [clang] Combine the ObjCIvarDecl/BindingDecl skip checks in VisitDeclContext Both checks exist for the same reason -- each is printed later as part of its containing decl instead -- and isa<> already supports multiple types in one call, so there's no need for two separate checks and two separate comments. Assisted-by: AI --- clang/lib/AST/DeclPrinter.cpp | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/clang/lib/AST/DeclPrinter.cpp b/clang/lib/AST/DeclPrinter.cpp index 812bfa3d84eee..22c7c295011c1 100644 --- a/clang/lib/AST/DeclPrinter.cpp +++ b/clang/lib/AST/DeclPrinter.cpp @@ -460,14 +460,9 @@ void DeclPrinter::VisitDeclContext(DeclContext *DC, bool Indent) { for (DeclContext::decl_iterator D = DC->decls_begin(), DEnd = DC->decls_end(); D != DEnd; ++D) { - // Don't print ObjCIvarDecls, as they are printed when visiting the - // containing ObjCInterfaceDecl. - if (isa<ObjCIvarDecl>(*D)) - continue; - - // Don't print BindingDecls, as they are printed when visiting the - // containing DecompositionDecl. - if (isa<BindingDecl>(*D)) + // Don't print ObjCIvarDecls or BindingDecls, as they are printed when + // visiting the containing ObjCInterfaceDecl or DecompositionDecl. + if (isa<ObjCIvarDecl, BindingDecl>(*D)) continue; // Skip over implicit declarations in pretty-printing mode. >From 92372303f6190693577b2433617efbd3316d3c76 Mon Sep 17 00:00:00 2001 From: Ran Regev <[email protected]> Date: Tue, 8 Sep 2026 18:19:52 +0300 Subject: [PATCH 09/14] [clang][test] Verify the initializer prints in ast-print-decomposition.cpp's Array case The Array namespace's CHECK-NEXT only checked the binding list, not that '= ...' after it was still there -- so the initializer could have silently gone missing without failing the test. Assisted-by: AI --- clang/test/AST/ast-print-decomposition.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clang/test/AST/ast-print-decomposition.cpp b/clang/test/AST/ast-print-decomposition.cpp index 37e53745115c4..429b3feb22076 100644 --- a/clang/test/AST/ast-print-decomposition.cpp +++ b/clang/test/AST/ast-print-decomposition.cpp @@ -31,7 +31,7 @@ namespace Array { void local() { // CHECK-NEXT: int arr[3] = {1, 2, 3}; int arr[3] = {1, 2, 3}; - // CHECK-NEXT: auto [a, b, c] + // CHECK-NEXT: auto [a, b, c] = {{\{}}arr[*]{{(\})}}; auto [a, b, c] = arr; } } // namespace Array >From cb58ff331ca6eaa77d319c5ad1dae69f76a8b8ed Mon Sep 17 00:00:00 2001 From: Ran Regev <[email protected]> Date: Tue, 8 Sep 2026 18:20:06 +0300 Subject: [PATCH 10/14] [clang][test] Verify the initializer prints in ast-print-decomposition.cpp's Packs case Same gap as the Array case: the CHECK-NEXT only checked the binding list, not that '= ...' after it was still there. Assisted-by: AI --- clang/test/AST/ast-print-decomposition.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clang/test/AST/ast-print-decomposition.cpp b/clang/test/AST/ast-print-decomposition.cpp index 429b3feb22076..504e4c23416f5 100644 --- a/clang/test/AST/ast-print-decomposition.cpp +++ b/clang/test/AST/ast-print-decomposition.cpp @@ -71,7 +71,7 @@ namespace Packs { template <unsigned N> void local() { // CHECK-NEXT: int arr[4] = {1, 2, 3, 4}; int arr[4] = {1, 2, 3, 4}; - // CHECK-NEXT: auto [first, ...rest, last] + // CHECK-NEXT: auto [first, ...rest, last] = {{\{}}arr[*]{{(\})}}; auto [first, ...rest, last] = arr; } void (*p)() = local<0>; >From 2620457bc83ad6276c8414e07b1bf58cae487f1f Mon Sep 17 00:00:00 2001 From: Ran Regev <[email protected]> Date: Tue, 8 Sep 2026 18:36:23 +0300 Subject: [PATCH 11/14] [clang][test] Don't escape the closing ]] in the attribute CHECK line Only [[ is FileCheck's own special syntax (a variable reference); a closing ]] on its own has no special meaning and needs no escaping. Existing tests already rely on exactly this (e.g. clang/test/Sema/format-attr-missing.c), so match that instead of over-escaping both brackets symmetrically. Assisted-by: AI --- clang/test/AST/ast-print-decomposition.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clang/test/AST/ast-print-decomposition.cpp b/clang/test/AST/ast-print-decomposition.cpp index 504e4c23416f5..a849fcbca2e10 100644 --- a/clang/test/AST/ast-print-decomposition.cpp +++ b/clang/test/AST/ast-print-decomposition.cpp @@ -83,7 +83,7 @@ using Aggregate::get; // CHECK-LABEL: void local() { void local() { - // CHECK-NEXT: auto [x {{\[\[}}maybe_unused{{\]\]}}, y] = get(); + // CHECK-NEXT: auto [x {{\[\[}}maybe_unused]], y] = get(); auto [x [[maybe_unused]], y] = get(); } } // namespace Attributes >From 3a98dd1aa0868c9e861ab8f6548634172fa9ead8 Mon Sep 17 00:00:00 2001 From: Ran Regev <[email protected]> Date: Tue, 8 Sep 2026 20:37:25 +0300 Subject: [PATCH 12/14] [clang][test] Drop the unnecessary regex around the Array case's initializer { and } are not special to FileCheck (only the doubled {{/}} regex delimiter is), so the literal braces match without escaping. Assisted-by: AI --- clang/test/AST/ast-print-decomposition.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clang/test/AST/ast-print-decomposition.cpp b/clang/test/AST/ast-print-decomposition.cpp index a849fcbca2e10..235759a1e7493 100644 --- a/clang/test/AST/ast-print-decomposition.cpp +++ b/clang/test/AST/ast-print-decomposition.cpp @@ -31,7 +31,7 @@ namespace Array { void local() { // CHECK-NEXT: int arr[3] = {1, 2, 3}; int arr[3] = {1, 2, 3}; - // CHECK-NEXT: auto [a, b, c] = {{\{}}arr[*]{{(\})}}; + // CHECK-NEXT: auto [a, b, c] = {arr[*]}; auto [a, b, c] = arr; } } // namespace Array >From f7741812f4d69a9920a7ab198b06bf0626a525aa Mon Sep 17 00:00:00 2001 From: Ran Regev <[email protected]> Date: Tue, 8 Sep 2026 20:37:33 +0300 Subject: [PATCH 13/14] [clang][test] Drop the unnecessary regex around the Packs case's initializer Same as the Array case: { and } are literal to FileCheck outside a {{/}} regex block, so no escaping is needed. Assisted-by: AI --- clang/test/AST/ast-print-decomposition.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clang/test/AST/ast-print-decomposition.cpp b/clang/test/AST/ast-print-decomposition.cpp index 235759a1e7493..47267c732a445 100644 --- a/clang/test/AST/ast-print-decomposition.cpp +++ b/clang/test/AST/ast-print-decomposition.cpp @@ -71,7 +71,7 @@ namespace Packs { template <unsigned N> void local() { // CHECK-NEXT: int arr[4] = {1, 2, 3, 4}; int arr[4] = {1, 2, 3, 4}; - // CHECK-NEXT: auto [first, ...rest, last] = {{\{}}arr[*]{{(\})}}; + // CHECK-NEXT: auto [first, ...rest, last] = {arr[*]}; auto [first, ...rest, last] = arr; } void (*p)() = local<0>; >From 993407e60762f81c57dc1ce684ba98e7bcdf0ef1 Mon Sep 17 00:00:00 2001 From: Ran Regev <[email protected]> Date: Thu, 10 Sep 2026 15:11:46 +0300 Subject: [PATCH 14/14] [clang] Don't merge a decomposition declaration into a preceding tag decl VisitDeclContext groups a non-free-standing tag declaration with the declarators that follow it, matching on each declaration's type being the owned TagType of that tag. A decomposition declaration's deduced type can be exactly that owned tag type, so struct S { int a, b; } obj; auto [x, y] = obj; printed as: struct S { int a; int b; } obj, auto [x, y] = obj; A decomposition declaration is always a declaration of its own; it can never be one declarator among several, so keep it out of the group. Assisted-by: AI --- clang/lib/AST/DeclPrinter.cpp | 8 ++++++-- clang/test/AST/ast-print-decomposition.cpp | 18 ++++++++++++++++++ 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/clang/lib/AST/DeclPrinter.cpp b/clang/lib/AST/DeclPrinter.cpp index 22c7c295011c1..77b0c017f6ceb 100644 --- a/clang/lib/AST/DeclPrinter.cpp +++ b/clang/lib/AST/DeclPrinter.cpp @@ -487,9 +487,13 @@ void DeclPrinter::VisitDeclContext(DeclContext *DC, bool Indent) { // only merges declarations directly referring to the tag, not typedefs. // // Check whether the current declaration should be grouped with a previous - // non-free-standing tag declaration. + // non-free-standing tag declaration. A decomposition declaration is always + // a declaration of its own -- it can never be one declarator among several + // -- but its deduced type can be the tag type owned by the preceding + // declaration, so exclude it explicitly. QualType CurDeclType = getDeclType(*D); - if (!Decls.empty() && !CurDeclType.isNull()) { + if (!Decls.empty() && !CurDeclType.isNull() && + !isa<DecompositionDecl>(*D)) { QualType BaseType = GetBaseType(CurDeclType); if (const auto *TT = dyn_cast_or_null<TagType>(BaseType); TT && TT->isTagOwned()) { diff --git a/clang/test/AST/ast-print-decomposition.cpp b/clang/test/AST/ast-print-decomposition.cpp index 47267c732a445..294b2a606ca4b 100644 --- a/clang/test/AST/ast-print-decomposition.cpp +++ b/clang/test/AST/ast-print-decomposition.cpp @@ -87,3 +87,21 @@ void local() { auto [x [[maybe_unused]], y] = get(); } } // namespace Attributes + +namespace OwnedTag { +// At declaration-context scope the printer groups a tag declaration with the +// declarators that follow it, so an owned tag type keeps `struct Owned { ... } +// obj;` on one line. A decomposition declaration is a declaration of its own +// and must never be pulled into that group, even though its deduced type is +// precisely the tag type owned by `obj`'s declaration. +// CHECK-LABEL: struct Owned { +// CHECK: } obj; +// CHECK-NEXT: auto [ox, oy] = obj; +struct Owned { int a, b; } obj; +auto [ox, oy] = obj; + +// A second declarator of an owned tag type still merges. +// CHECK-NEXT: struct Merged { +// CHECK: } m1, m2; +struct Merged { int v; } m1, m2; +} // namespace OwnedTag _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
