[clang] [clang-tools-extra] [clang] more useful error message for decomposition declaration missing initializer (PR #127924)
Kevin Bravo via cfe-commits
cfe-commits at lists.llvm.org
Mon Jul 6 22:11:26 PDT 2026
https://github.com/kbrav updated https://github.com/llvm/llvm-project/pull/127924
>From b51bf0d01b3e84f88fde845ff0532da882ab52c4 Mon Sep 17 00:00:00 2001
From: kbrav <kevin at halys.dev>
Date: Wed, 1 Jul 2026 00:26:02 -0400
Subject: [PATCH 1/2] [clang] more useful error message for decomposition
declaration missing initializer (#90107)
---
.../checkers/misc/misplaced-const-cxx17.cpp | 2 +-
clang/docs/ReleaseNotes.md | 2 ++
clang/include/clang/AST/DeclCXX.h | 15 +++++++++-----
.../clang/Basic/DiagnosticSemaKinds.td | 3 ++-
clang/lib/AST/ASTImporter.cpp | 3 ++-
clang/lib/AST/DeclCXX.cpp | 20 +++++++++----------
clang/lib/Sema/SemaDecl.cpp | 11 +++++++---
.../lib/Sema/SemaTemplateInstantiateDecl.cpp | 6 +++---
clang/test/PCH/cxx1z-decomposition.cpp | 2 +-
clang/test/Parser/cxx1z-decomposition.cpp | 13 ++++++++----
10 files changed, 47 insertions(+), 30 deletions(-)
diff --git a/clang-tools-extra/test/clang-tidy/checkers/misc/misplaced-const-cxx17.cpp b/clang-tools-extra/test/clang-tidy/checkers/misc/misplaced-const-cxx17.cpp
index 6fa32d1e7f452..67c55e25f4fee 100644
--- a/clang-tools-extra/test/clang-tidy/checkers/misc/misplaced-const-cxx17.cpp
+++ b/clang-tools-extra/test/clang-tidy/checkers/misc/misplaced-const-cxx17.cpp
@@ -3,7 +3,7 @@
// This test previously would cause a failed assertion because the structured
// binding declaration had no valid type associated with it. This ensures the
// expected clang diagnostic is generated instead.
-// CHECK-MESSAGES: :[[@LINE+1]]:6: error: structured binding declaration '[x]' requires an initializer [clang-diagnostic-error]
+// CHECK-MESSAGES: :[[@LINE+1]]:9: error: structured binding declaration '[x]' requires an initializer; expected '=' or braced initializer list [clang-diagnostic-error]
auto [x];
struct S { int a; };
diff --git a/clang/docs/ReleaseNotes.md b/clang/docs/ReleaseNotes.md
index cde904ebaac1e..27a3f5cadf756 100644
--- a/clang/docs/ReleaseNotes.md
+++ b/clang/docs/ReleaseNotes.md
@@ -679,6 +679,8 @@ latest release, please see the [Clang Web Site](https://clang.llvm.org) or the
by the kernel, so setting them is almost always a typo (matching the
bionic libc `diagnose_if` check).
+- Added a clearer diagnostic for uninitialized decomposition declarations. (#GH90107)
+
### Improvements to Clang's time-trace
### Improvements to Coverage Mapping
diff --git a/clang/include/clang/AST/DeclCXX.h b/clang/include/clang/AST/DeclCXX.h
index 28d171253dc03..02cd94870db1c 100644
--- a/clang/include/clang/AST/DeclCXX.h
+++ b/clang/include/clang/AST/DeclCXX.h
@@ -4265,16 +4265,18 @@ class BindingDecl : public ValueDecl {
class DecompositionDecl final
: public VarDecl,
private llvm::TrailingObjects<DecompositionDecl, BindingDecl *> {
+ /// The closing bracket (before the initializer is expected).
+ SourceLocation RSquareLoc;
/// The number of BindingDecl*s following this object.
unsigned NumBindings;
DecompositionDecl(ASTContext &C, DeclContext *DC, SourceLocation StartLoc,
- SourceLocation LSquareLoc, QualType T,
- TypeSourceInfo *TInfo, StorageClass SC,
+ SourceLocation LSquareLoc, SourceLocation RSquareLoc,
+ QualType T, TypeSourceInfo *TInfo, StorageClass SC,
ArrayRef<BindingDecl *> Bindings)
: VarDecl(Decomposition, C, DC, StartLoc, LSquareLoc, nullptr, T, TInfo,
SC),
- NumBindings(Bindings.size()) {
+ RSquareLoc(RSquareLoc), NumBindings(Bindings.size()) {
llvm::uninitialized_copy(Bindings, getTrailingObjects());
for (auto *B : Bindings) {
B->setDecomposedDecl(this);
@@ -4295,8 +4297,8 @@ class DecompositionDecl final
static DecompositionDecl *Create(ASTContext &C, DeclContext *DC,
SourceLocation StartLoc,
SourceLocation LSquareLoc,
- QualType T, TypeSourceInfo *TInfo,
- StorageClass S,
+ SourceLocation RSquareLoc, QualType T,
+ TypeSourceInfo *TInfo, StorageClass S,
ArrayRef<BindingDecl *> Bindings);
static DecompositionDecl *CreateDeserialized(ASTContext &C, GlobalDeclID ID,
unsigned NumBindings);
@@ -4326,6 +4328,9 @@ class DecompositionDecl final
std::move(Bindings));
}
+ /// The closing bracket (before the initializer is expected).
+ SourceLocation getRSquareLoc() const { return RSquareLoc; }
+
void printName(raw_ostream &OS, const PrintingPolicy &Policy) const override;
static bool classof(const Decl *D) { return classofKind(D->getKind()); }
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index 7e4a9338d5b1f..85f41ae8c7548 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -619,8 +619,9 @@ def err_decomp_decl_template : Error<
def err_decomp_decl_not_alone : Error<
"structured binding declaration must be the only declaration in its group">;
def err_decomp_decl_requires_init : Error<
- "structured binding declaration %0 requires an initializer">;
+ "structured binding declaration %0 requires an initializer; expected '=' or braced initializer list">;
def err_decomp_decl_wrong_number_bindings : Error<
+
"type %0 binds to %3 %plural{1:element|:elements}2, but "
"%select{%plural{0:no|:only %1}1|%1}4 "
"%plural{1:name was|:names were}1 provided">;
diff --git a/clang/lib/AST/ASTImporter.cpp b/clang/lib/AST/ASTImporter.cpp
index 567d2d07298a3..ebca88ae51d23 100644
--- a/clang/lib/AST/ASTImporter.cpp
+++ b/clang/lib/AST/ASTImporter.cpp
@@ -4838,7 +4838,8 @@ ExpectedDecl ASTNodeImporter::VisitVarDecl(VarDecl *D) {
DecompositionDecl *ToDecomp;
if (GetImportedOrCreateDecl(
ToDecomp, FromDecomp, Importer.getToContext(), DC, ToInnerLocStart,
- Loc, ToType, ToTypeSourceInfo, D->getStorageClass(), Bindings))
+ Loc, FromDecomp->getRSquareLoc(), ToType, ToTypeSourceInfo,
+ D->getStorageClass(), Bindings))
return ToDecomp;
ToVar = ToDecomp;
} else {
diff --git a/clang/lib/AST/DeclCXX.cpp b/clang/lib/AST/DeclCXX.cpp
index ce4ba971a4631..9052a31be57af 100644
--- a/clang/lib/AST/DeclCXX.cpp
+++ b/clang/lib/AST/DeclCXX.cpp
@@ -3727,24 +3727,22 @@ ArrayRef<BindingDecl *> BindingDecl::getBindingPackDecls() const {
void DecompositionDecl::anchor() {}
-DecompositionDecl *DecompositionDecl::Create(ASTContext &C, DeclContext *DC,
- SourceLocation StartLoc,
- SourceLocation LSquareLoc,
- QualType T, TypeSourceInfo *TInfo,
- StorageClass SC,
- ArrayRef<BindingDecl *> Bindings) {
+DecompositionDecl *DecompositionDecl::Create(
+ ASTContext &C, DeclContext *DC, SourceLocation StartLoc,
+ SourceLocation LSquareLoc, SourceLocation RSquareLoc, QualType T,
+ TypeSourceInfo *TInfo, StorageClass SC, ArrayRef<BindingDecl *> Bindings) {
size_t Extra = additionalSizeToAlloc<BindingDecl *>(Bindings.size());
- return new (C, DC, Extra)
- DecompositionDecl(C, DC, StartLoc, LSquareLoc, T, TInfo, SC, Bindings);
+ return new (C, DC, Extra) DecompositionDecl(
+ C, DC, StartLoc, LSquareLoc, RSquareLoc, T, TInfo, SC, Bindings);
}
DecompositionDecl *DecompositionDecl::CreateDeserialized(ASTContext &C,
GlobalDeclID ID,
unsigned NumBindings) {
size_t Extra = additionalSizeToAlloc<BindingDecl *>(NumBindings);
- auto *Result = new (C, ID, Extra)
- DecompositionDecl(C, nullptr, SourceLocation(), SourceLocation(),
- QualType(), nullptr, StorageClass(), {});
+ auto *Result = new (C, ID, Extra) DecompositionDecl(
+ C, nullptr, SourceLocation(), SourceLocation(), SourceLocation(),
+ QualType(), nullptr, StorageClass(), {});
// Set up and clean out the bindings array.
Result->NumBindings = NumBindings;
auto *Trail = Result->getTrailingObjects();
diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp
index f6d1d7a7877c7..526b8517c58c5 100644
--- a/clang/lib/Sema/SemaDecl.cpp
+++ b/clang/lib/Sema/SemaDecl.cpp
@@ -8036,8 +8036,8 @@ NamedDecl *Sema::ActOnVariableDeclarator(
AddToScope = false;
} else if (D.isDecompositionDeclarator()) {
NewVD = DecompositionDecl::Create(Context, DC, D.getBeginLoc(),
- D.getIdentifierLoc(), R, TInfo, SC,
- Bindings);
+ D.getIdentifierLoc(), D.getEndLoc(), R,
+ TInfo, SC, Bindings);
} else
NewVD = VarDecl::Create(Context, DC, D.getBeginLoc(),
D.getIdentifierLoc(), II, R, TInfo, SC);
@@ -14480,7 +14480,12 @@ void Sema::ActOnUninitializedDecl(Decl *RealDecl) {
}
// C++1z [dcl.dcl]p1 grammar implies that an initializer is mandatory.
if (isa<DecompositionDecl>(RealDecl)) {
- Diag(Var->getLocation(), diag::err_decomp_decl_requires_init) << Var;
+ // point carat to the token immediately after the closing bracket
+ auto NextLoc = dyn_cast<DecompositionDecl>(RealDecl)->getRSquareLoc();
+ NextLoc =
+ Lexer::findNextToken(NextLoc, PP.getSourceManager(), PP.getLangOpts())
+ ->getLocation();
+ Diag(NextLoc, diag::err_decomp_decl_requires_init) << Var;
Var->setInvalidDecl();
return;
}
diff --git a/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp b/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp
index 324d6bf3857c7..be87c7b131126 100644
--- a/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp
+++ b/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp
@@ -1776,9 +1776,9 @@ Decl *TemplateDeclInstantiator::VisitVarDecl(VarDecl *D,
// Build the instantiated declaration.
VarDecl *Var;
if (Bindings)
- Var = DecompositionDecl::Create(SemaRef.Context, DC, D->getInnerLocStart(),
- D->getLocation(), TSI->getType(), TSI,
- D->getStorageClass(), *Bindings);
+ Var = DecompositionDecl::Create(
+ SemaRef.Context, DC, D->getInnerLocStart(), D->getLocation(),
+ D->getEndLoc(), TSI->getType(), TSI, D->getStorageClass(), *Bindings);
else
Var = VarDecl::Create(SemaRef.Context, DC, D->getInnerLocStart(),
D->getLocation(), D->getIdentifier(), TSI->getType(),
diff --git a/clang/test/PCH/cxx1z-decomposition.cpp b/clang/test/PCH/cxx1z-decomposition.cpp
index 340d5eae0b8c3..635ba7c26e09b 100644
--- a/clang/test/PCH/cxx1z-decomposition.cpp
+++ b/clang/test/PCH/cxx1z-decomposition.cpp
@@ -22,7 +22,7 @@ constexpr int foo(Q &&q) {
return a * 10 + b;
}
-auto [noinit]; // expected-error{{structured binding declaration '[noinit]' requires an initializer}}
+auto [noinit]; // expected-error{{structured binding declaration '[noinit]' requires an initializer; expected '=' or braced initializer list}}
#else
diff --git a/clang/test/Parser/cxx1z-decomposition.cpp b/clang/test/Parser/cxx1z-decomposition.cpp
index 10f0c07766ccf..76f1b97085814 100644
--- a/clang/test/Parser/cxx1z-decomposition.cpp
+++ b/clang/test/Parser/cxx1z-decomposition.cpp
@@ -111,10 +111,10 @@ namespace BadSpecifiers {
// FIXME: This error is not very good.
auto [d]() = s; // expected-error {{expected ';'}} expected-error {{expected expression}}
- auto [e][1] = s; // expected-error {{expected ';'}} expected-error {{requires an initializer}}
+ auto [e][1] = s; // expected-error {{expected ';'}} expected-error {{structured binding declaration '[e]' requires an initializer; expected '=' or braced initializer list}}
// FIXME: This should fire the 'misplaced array declarator' diagnostic.
- int [K] arr = {0}; // expected-error {{expected ';'}} expected-error {{cannot be declared with type 'int'}} expected-error {{structured binding declaration '[K]' requires an initializer}}
+ int [K] arr = {0}; // expected-error {{expected ';'}} expected-error {{cannot be declared with type 'int'}} expected-error {{structured binding declaration '[K]' requires an initializer; expected '=' or braced initializer list}}
int [5] arr = {0}; // expected-error {{place the brackets after the name}}
auto *[f] = s; // expected-error {{cannot be declared with type 'auto *'}} expected-error {{incompatible initializer}}
@@ -148,11 +148,13 @@ namespace Template {
template<typename T> auto [a, b, c] = n; // expected-error {{structured binding declaration cannot be a template}}
}
+#define MYC C
+
namespace Init {
- void f() {
+ template<typename T> T f(T t) {
int arr[1];
struct S { int n; };
- auto &[bad1]; // expected-error {{structured binding declaration '[bad1]' requires an initializer}}
+ auto &[bad1]; // expected-error {{structured binding declaration '[bad1]' requires an initializer; expected '=' or braced initializer list}}
const auto &[bad2](S{}, S{}); // expected-error {{initializer for variable '[bad2]' with type 'const auto &' contains multiple expressions}}
const auto &[bad3](); // expected-error {{expected expression}}
auto &[good1] = arr;
@@ -160,6 +162,9 @@ namespace Init {
const auto &[good3](S{});
S [goodish3] = { 4 }; // expected-error {{cannot be declared with type 'S'}}
S [goodish4] { 4 }; // expected-error {{cannot be declared with type 'S'}}
+ auto [A, B] C = {1, 2}; // expected-error{{structured binding declaration '[A, B]' requires an initializer; expected '=' or braced initializer list}} expected-error{{expected ';' at end of declaration}}
+ T t1 = t; // check that uninitialized structured binding declaration error works with templates and macros
+ auto [t0, t2] MYC = {t, t1}; // expected-error{{structured binding declaration '[t0, t2]' requires an initializer; expected '=' or braced initializer list}} expected-error{{expected ';' at end of declaration}}
}
}
>From 24db551d6abb65f4d6c7ce18a33bd4b7bed8319f Mon Sep 17 00:00:00 2001
From: kbrav <kevin at halys.dev>
Date: Tue, 7 Jul 2026 01:07:24 -0400
Subject: [PATCH 2/2] test column of decomposition declaration error, and test
for full diagnostic string
---
clang/test/PCH/cxx1z-decomposition.cpp | 4 +++-
clang/test/Parser/cxx1z-decomposition.cpp | 8 +++++++-
2 files changed, 10 insertions(+), 2 deletions(-)
diff --git a/clang/test/PCH/cxx1z-decomposition.cpp b/clang/test/PCH/cxx1z-decomposition.cpp
index 635ba7c26e09b..40acac71d00bb 100644
--- a/clang/test/PCH/cxx1z-decomposition.cpp
+++ b/clang/test/PCH/cxx1z-decomposition.cpp
@@ -1,5 +1,6 @@
// No PCH:
// RUN: %clang_cc1 -pedantic -std=c++1z -include %s -verify %s
+// RUN: not %clang_cc1 -pedantic -std=c++1z -fsyntax-only %s 2>&1 | FileCheck %s
//
// With PCH:
// RUN: %clang_cc1 -pedantic -std=c++1z -emit-pch -fallow-pch-with-compiler-errors %s -o %t
@@ -23,6 +24,7 @@ constexpr int foo(Q &&q) {
}
auto [noinit]; // expected-error{{structured binding declaration '[noinit]' requires an initializer; expected '=' or braced initializer list}}
+ // CHECK: clang/test/PCH/cxx1z-decomposition.cpp:[[@LINE-1]]:14: error: structured binding declaration '[noinit]' requires an initializer; expected '=' or braced initializer list
#else
@@ -31,7 +33,7 @@ int k = decomp(arr);
static_assert(foo({1, 2}) == 12);
-// expected-error at 15 {{cannot bind non-class, non-array type 'const int'}}
+// expected-error at 16 {{cannot bind non-class, non-array type 'const int'}}
int z = decomp(10); // expected-note {{instantiation of}}
#endif
diff --git a/clang/test/Parser/cxx1z-decomposition.cpp b/clang/test/Parser/cxx1z-decomposition.cpp
index 76f1b97085814..f5953a980738c 100644
--- a/clang/test/Parser/cxx1z-decomposition.cpp
+++ b/clang/test/Parser/cxx1z-decomposition.cpp
@@ -2,6 +2,7 @@
// RUN: %clang_cc1 -std=c++2b %s -triple x86_64-unknown-linux-gnu -verify=expected,cxx2b,pre2c,post2b -fcxx-exceptions
// RUN: %clang_cc1 -std=c++2c %s -triple x86_64-unknown-linux-gnu -verify=expected,cxx2c,post2b -fcxx-exceptions
// RUN: not %clang_cc1 -std=c++17 %s -triple x86_64-unknown-linux-gnu -emit-llvm-only -fcxx-exceptions
+// RUN: not %clang_cc1 -std=c++2b %s -triple x86_64-unknown-linux-gnu -fsyntax-only -fcxx-exceptions 2>&1 | FileCheck %s
struct S { int a, b, c; }; // expected-note 2 {{'S::a' declared here}}
@@ -112,9 +113,11 @@ namespace BadSpecifiers {
// FIXME: This error is not very good.
auto [d]() = s; // expected-error {{expected ';'}} expected-error {{expected expression}}
auto [e][1] = s; // expected-error {{expected ';'}} expected-error {{structured binding declaration '[e]' requires an initializer; expected '=' or braced initializer list}}
+ // CHECK: clang/test/Parser/cxx1z-decomposition.cpp:[[@LINE-1]]:13: error: structured binding declaration '[e]' requires an initializer; expected '=' or braced initializer list
// FIXME: This should fire the 'misplaced array declarator' diagnostic.
- int [K] arr = {0}; // expected-error {{expected ';'}} expected-error {{cannot be declared with type 'int'}} expected-error {{structured binding declaration '[K]' requires an initializer; expected '=' or braced initializer list}}
+ int [K] arr = {0}; // expected-error {{structured binding declaration '[K]' requires an initializer; expected '=' or braced initializer list}} expected-error {{expected ';'}} expected-error {{cannot be declared with type 'int'}}
+ // CHECK: clang/test/Parser/cxx1z-decomposition.cpp:[[@LINE-1]]:13: error: structured binding declaration '[K]' requires an initializer; expected '=' or braced initializer list
int [5] arr = {0}; // expected-error {{place the brackets after the name}}
auto *[f] = s; // expected-error {{cannot be declared with type 'auto *'}} expected-error {{incompatible initializer}}
@@ -155,6 +158,7 @@ namespace Init {
int arr[1];
struct S { int n; };
auto &[bad1]; // expected-error {{structured binding declaration '[bad1]' requires an initializer; expected '=' or braced initializer list}}
+ // CHECK: clang/test/Parser/cxx1z-decomposition.cpp:[[@LINE-1]]:17: error: structured binding declaration '[bad1]' requires an initializer; expected '=' or braced initializer list
const auto &[bad2](S{}, S{}); // expected-error {{initializer for variable '[bad2]' with type 'const auto &' contains multiple expressions}}
const auto &[bad3](); // expected-error {{expected expression}}
auto &[good1] = arr;
@@ -163,8 +167,10 @@ namespace Init {
S [goodish3] = { 4 }; // expected-error {{cannot be declared with type 'S'}}
S [goodish4] { 4 }; // expected-error {{cannot be declared with type 'S'}}
auto [A, B] C = {1, 2}; // expected-error{{structured binding declaration '[A, B]' requires an initializer; expected '=' or braced initializer list}} expected-error{{expected ';' at end of declaration}}
+ // CHECK: clang/test/Parser/cxx1z-decomposition.cpp:[[@LINE-1]]:17: error: structured binding declaration '[A, B]' requires an initializer; expected '=' or braced initializer list
T t1 = t; // check that uninitialized structured binding declaration error works with templates and macros
auto [t0, t2] MYC = {t, t1}; // expected-error{{structured binding declaration '[t0, t2]' requires an initializer; expected '=' or braced initializer list}} expected-error{{expected ';' at end of declaration}}
+ // CHECK: clang/test/Parser/cxx1z-decomposition.cpp:[[@LINE-1]]:19: error: structured binding declaration '[t0, t2]' requires an initializer; expected '=' or braced initializer list
}
}
More information about the cfe-commits
mailing list