Skip to content

Commit 6dd9061

Browse files
authored
[Clang] Implement C++26 Attributes for Structured Bindings (P0609R3) (#89906)
https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2024/p0609r3.pdf We support this feature in all language mode. maybe_unused applied to a binding makes the whole declaration unused.
1 parent 216787c commit 6dd9061

16 files changed

+127
-32
lines changed

clang/docs/LanguageExtensions.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1493,6 +1493,7 @@ Conditional ``explicit`` __cpp_conditional_explicit C+
14931493
``if consteval`` __cpp_if_consteval C++23 C++20
14941494
``static operator()`` __cpp_static_call_operator C++23 C++03
14951495
Attributes on Lambda-Expressions C++23 C++11
1496+
Attributes on Structured Bindings __cpp_structured_bindings C++26 C++03
14961497
``= delete ("should have a reason");`` __cpp_deleted_function C++26 C++03
14971498
-------------------------------------------- -------------------------------- ------------- -------------
14981499
Designated initializers (N494) C99 C89

clang/docs/ReleaseNotes.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,8 @@ C++2c Feature Support
143143

144144
- Implemented `P2573R2: = delete("should have a reason"); <https://wg21.link/P2573R2>`_
145145

146+
- Implemented `P0609R3: Attributes for Structured Bindings <https://wg21.link/P0609R3>`_
147+
146148

147149
Resolutions to C++ Defect Reports
148150
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

clang/include/clang/Basic/Attr.td

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3211,7 +3211,7 @@ def ObjCRequiresPropertyDefs : InheritableAttr {
32113211
def Unused : InheritableAttr {
32123212
let Spellings = [CXX11<"", "maybe_unused", 201603>, GCC<"unused">,
32133213
C23<"", "maybe_unused", 202106>];
3214-
let Subjects = SubjectList<[Var, ObjCIvar, Type, Enum, EnumConstant, Label,
3214+
let Subjects = SubjectList<[Var, Binding, ObjCIvar, Type, Enum, EnumConstant, Label,
32153215
Field, ObjCMethod, FunctionLike]>;
32163216
let Documentation = [WarnMaybeUnusedDocs];
32173217
}

clang/include/clang/Basic/DiagnosticParseKinds.td

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -478,6 +478,15 @@ def ext_decomp_decl_empty : ExtWarn<
478478
"ISO C++17 does not allow a decomposition group to be empty">,
479479
InGroup<DiagGroup<"empty-decomposition">>;
480480

481+
// C++26 structured bindings
482+
def ext_decl_attrs_on_binding : ExtWarn<
483+
"an attribute specifier sequence attached to a structured binding declaration "
484+
"is a C++2c extension">, InGroup<CXX26>;
485+
def warn_cxx23_compat_decl_attrs_on_binding : Warning<
486+
"an attribute specifier sequence attached to a structured binding declaration "
487+
"is incompatible with C++ standards before C++2c">,
488+
InGroup<CXXPre26Compat>, DefaultIgnore;
489+
481490
/// Objective-C parser diagnostics
482491
def err_expected_minus_or_plus : Error<
483492
"method type specifier must start with '-' or '+'">;

clang/include/clang/Sema/DeclSpec.h

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
#include "llvm/ADT/SmallVector.h"
3737
#include "llvm/Support/Compiler.h"
3838
#include "llvm/Support/ErrorHandling.h"
39+
#include <optional>
3940

4041
namespace clang {
4142
class ASTContext;
@@ -1790,6 +1791,7 @@ class DecompositionDeclarator {
17901791
struct Binding {
17911792
IdentifierInfo *Name;
17921793
SourceLocation NameLoc;
1794+
std::optional<ParsedAttributes> Attrs;
17931795
};
17941796

17951797
private:
@@ -2339,10 +2341,10 @@ class Declarator {
23392341
}
23402342

23412343
/// Set the decomposition bindings for this declarator.
2342-
void
2343-
setDecompositionBindings(SourceLocation LSquareLoc,
2344-
ArrayRef<DecompositionDeclarator::Binding> Bindings,
2345-
SourceLocation RSquareLoc);
2344+
void setDecompositionBindings(
2345+
SourceLocation LSquareLoc,
2346+
MutableArrayRef<DecompositionDeclarator::Binding> Bindings,
2347+
SourceLocation RSquareLoc);
23462348

23472349
/// AddTypeInfo - Add a chunk to this declarator. Also extend the range to
23482350
/// EndLoc, which should be the last token of the chunk.

clang/include/clang/Sema/ParsedAttr.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -948,6 +948,7 @@ class ParsedAttributes : public ParsedAttributesView {
948948
ParsedAttributes(AttributeFactory &factory) : pool(factory) {}
949949
ParsedAttributes(const ParsedAttributes &) = delete;
950950
ParsedAttributes &operator=(const ParsedAttributes &) = delete;
951+
ParsedAttributes(ParsedAttributes &&G) = default;
951952

952953
AttributePool &getPool() const { return pool; }
953954

clang/lib/AST/DeclBase.cpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1115,7 +1115,9 @@ int64_t Decl::getID() const {
11151115

11161116
const FunctionType *Decl::getFunctionType(bool BlocksToo) const {
11171117
QualType Ty;
1118-
if (const auto *D = dyn_cast<ValueDecl>(this))
1118+
if (const auto *D = dyn_cast<BindingDecl>(this))
1119+
return nullptr;
1120+
else if (const auto *D = dyn_cast<ValueDecl>(this))
11191121
Ty = D->getType();
11201122
else if (const auto *D = dyn_cast<TypedefNameDecl>(this))
11211123
Ty = D->getUnderlyingType();

clang/lib/Frontend/InitPreprocessor.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -703,7 +703,7 @@ static void InitializeCPlusPlusFeatureTestMacros(const LangOptions &LangOpts,
703703
Builder.defineMacro("__cpp_nested_namespace_definitions", "201411L");
704704
Builder.defineMacro("__cpp_variadic_using", "201611L");
705705
Builder.defineMacro("__cpp_aggregate_bases", "201603L");
706-
Builder.defineMacro("__cpp_structured_bindings", "201606L");
706+
Builder.defineMacro("__cpp_structured_bindings", "202403L");
707707
Builder.defineMacro("__cpp_nontype_template_args",
708708
"201411L"); // (not latest)
709709
Builder.defineMacro("__cpp_fold_expressions", "201603L");

clang/lib/Parse/ParseDecl.cpp

Lines changed: 31 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7038,18 +7038,23 @@ void Parser::ParseDirectDeclarator(Declarator &D) {
70387038
void Parser::ParseDecompositionDeclarator(Declarator &D) {
70397039
assert(Tok.is(tok::l_square));
70407040

7041+
TentativeParsingAction PA(*this);
7042+
BalancedDelimiterTracker T(*this, tok::l_square);
7043+
T.consumeOpen();
7044+
7045+
if (isCXX11AttributeSpecifier())
7046+
DiagnoseAndSkipCXX11Attributes();
7047+
70417048
// If this doesn't look like a structured binding, maybe it's a misplaced
70427049
// array declarator.
7043-
// FIXME: Consume the l_square first so we don't need extra lookahead for
7044-
// this.
7045-
if (!(NextToken().is(tok::identifier) &&
7046-
GetLookAheadToken(2).isOneOf(tok::comma, tok::r_square)) &&
7047-
!(NextToken().is(tok::r_square) &&
7048-
GetLookAheadToken(2).isOneOf(tok::equal, tok::l_brace)))
7050+
if (!(Tok.is(tok::identifier) &&
7051+
NextToken().isOneOf(tok::comma, tok::r_square, tok::kw_alignas,
7052+
tok::l_square)) &&
7053+
!(Tok.is(tok::r_square) &&
7054+
NextToken().isOneOf(tok::equal, tok::l_brace))) {
7055+
PA.Revert();
70497056
return ParseMisplacedBracketDeclarator(D);
7050-
7051-
BalancedDelimiterTracker T(*this, tok::l_square);
7052-
T.consumeOpen();
7057+
}
70537058

70547059
SmallVector<DecompositionDeclarator::Binding, 32> Bindings;
70557060
while (Tok.isNot(tok::r_square)) {
@@ -7074,13 +7079,27 @@ void Parser::ParseDecompositionDeclarator(Declarator &D) {
70747079
}
70757080
}
70767081

7082+
if (isCXX11AttributeSpecifier())
7083+
DiagnoseAndSkipCXX11Attributes();
7084+
70777085
if (Tok.isNot(tok::identifier)) {
70787086
Diag(Tok, diag::err_expected) << tok::identifier;
70797087
break;
70807088
}
70817089

7082-
Bindings.push_back({Tok.getIdentifierInfo(), Tok.getLocation()});
7090+
IdentifierInfo *II = Tok.getIdentifierInfo();
7091+
SourceLocation Loc = Tok.getLocation();
70837092
ConsumeToken();
7093+
7094+
ParsedAttributes Attrs(AttrFactory);
7095+
if (isCXX11AttributeSpecifier()) {
7096+
Diag(Tok, getLangOpts().CPlusPlus26
7097+
? diag::warn_cxx23_compat_decl_attrs_on_binding
7098+
: diag::ext_decl_attrs_on_binding);
7099+
MaybeParseCXX11Attributes(Attrs);
7100+
}
7101+
7102+
Bindings.push_back({II, Loc, std::move(Attrs)});
70847103
}
70857104

70867105
if (Tok.isNot(tok::r_square))
@@ -7095,6 +7114,8 @@ void Parser::ParseDecompositionDeclarator(Declarator &D) {
70957114
T.consumeClose();
70967115
}
70977116

7117+
PA.Commit();
7118+
70987119
return D.setDecompositionBindings(T.getOpenLocation(), Bindings,
70997120
T.getCloseLocation());
71007121
}

clang/lib/Sema/DeclSpec.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -293,7 +293,7 @@ DeclaratorChunk DeclaratorChunk::getFunction(bool hasProto,
293293

294294
void Declarator::setDecompositionBindings(
295295
SourceLocation LSquareLoc,
296-
ArrayRef<DecompositionDeclarator::Binding> Bindings,
296+
MutableArrayRef<DecompositionDeclarator::Binding> Bindings,
297297
SourceLocation RSquareLoc) {
298298
assert(!hasName() && "declarator given multiple names!");
299299

@@ -317,7 +317,7 @@ void Declarator::setDecompositionBindings(
317317
new DecompositionDeclarator::Binding[Bindings.size()];
318318
BindingGroup.DeleteBindings = true;
319319
}
320-
std::uninitialized_copy(Bindings.begin(), Bindings.end(),
320+
std::uninitialized_move(Bindings.begin(), Bindings.end(),
321321
BindingGroup.Bindings);
322322
}
323323
}

clang/lib/Sema/SemaDecl.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1974,7 +1974,7 @@ static bool ShouldDiagnoseUnusedDecl(const LangOptions &LangOpts,
19741974
// it is, by the bindings' expressions).
19751975
bool IsAllPlaceholders = true;
19761976
for (const auto *BD : DD->bindings()) {
1977-
if (BD->isReferenced())
1977+
if (BD->isReferenced() || BD->hasAttr<UnusedAttr>())
19781978
return false;
19791979
IsAllPlaceholders = IsAllPlaceholders && BD->isPlaceholderVar(LangOpts);
19801980
}

clang/lib/Sema/SemaDeclCXX.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -910,6 +910,8 @@ Sema::ActOnDecompositionDeclarator(Scope *S, Declarator &D,
910910

911911
auto *BD = BindingDecl::Create(Context, DC, B.NameLoc, VarName);
912912

913+
ProcessDeclAttributeList(S, BD, *B.Attrs);
914+
913915
// Find the shadowed declaration before filtering for scope.
914916
NamedDecl *ShadowedDecl = D.getCXXScopeSpec().isEmpty()
915917
? getShadowedDeclaration(BD, Previous)

clang/test/Lexer/cxx-features.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -222,7 +222,7 @@
222222
#error "wrong value for __cpp_aggregate_bases"
223223
#endif
224224

225-
#if check(structured_bindings, 0, 0, 0, 201606, 201606, 201606, 201606)
225+
#if check(structured_bindings, 0, 0, 0, 202403L, 202403L, 202403L, 202403L)
226226
#error "wrong value for __cpp_structured_bindings"
227227
#endif
228228

clang/test/Parser/cxx1z-decomposition.cpp

Lines changed: 54 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
// RUN: %clang_cc1 -std=c++17 %s -verify=expected,cxx17 -fcxx-exceptions
2-
// RUN: %clang_cc1 -std=c++2b %s -verify=expected,cxx2b -fcxx-exceptions
3-
// RUN: not %clang_cc1 -std=c++17 %s -emit-llvm-only -fcxx-exceptions
1+
// RUN: %clang_cc1 -std=c++17 %s -triple x86_64-unknown-linux-gnu -verify=expected,cxx17,pre2c -fcxx-exceptions
2+
// RUN: %clang_cc1 -std=c++2b %s -triple x86_64-unknown-linux-gnu -verify=expected,cxx2b,pre2c,post2b -fcxx-exceptions
3+
// RUN: %clang_cc1 -std=c++2c %s -triple x86_64-unknown-linux-gnu -verify=expected,cxx2c,post2b -fcxx-exceptions
4+
// RUN: not %clang_cc1 -std=c++17 %s -triple x86_64-unknown-linux-gnu -emit-llvm-only -fcxx-exceptions
45

56
struct S { int a, b, c; };
67

@@ -58,7 +59,7 @@ namespace OtherDecl {
5859
namespace GoodSpecifiers {
5960
void f() {
6061
int n[1];
61-
const volatile auto &[a] = n; // cxx2b-warning {{volatile qualifier in structured binding declaration is deprecated}}
62+
const volatile auto &[a] = n; // post2b-warning {{volatile qualifier in structured binding declaration is deprecated}}
6263
}
6364
}
6465

@@ -97,8 +98,8 @@ namespace BadSpecifiers {
9798
S [a] = s; // expected-error {{cannot be declared with type 'S'}}
9899
decltype(auto) [b] = s; // expected-error {{cannot be declared with type 'decltype(auto)'}}
99100
auto ([c2]) = s; // cxx17-error {{decomposition declaration cannot be declared with parenthese}} \
100-
// cxx2b-error {{use of undeclared identifier 'c2'}} \
101-
// cxx2b-error {{expected body of lambda expression}} \
101+
// post2b-error {{use of undeclared identifier 'c2'}} \
102+
// post2b-error {{expected body of lambda expression}} \
102103

103104
// FIXME: This error is not very good.
104105
auto [d]() = s; // expected-error {{expected ';'}} expected-error {{expected expression}}
@@ -119,9 +120,6 @@ namespace BadSpecifiers {
119120
[[]] auto [ok_3] = s;
120121
alignas(S) auto [ok_4] = s;
121122

122-
// ... but not after the identifier or declarator.
123-
// FIXME: These errors are not very good.
124-
auto [bad_attr_1 [[]]] = s; // expected-error {{attribute list cannot appear here}} expected-error 2{{}}
125123
auto [bad_attr_2] [[]] = s; // expected-error {{expected ';'}} expected-error {{}}
126124
}
127125
}
@@ -156,3 +154,50 @@ namespace Init {
156154
S [goodish4] { 4 }; // expected-error {{cannot be declared with type 'S'}}
157155
}
158156
}
157+
158+
159+
namespace attributes {
160+
161+
struct S{
162+
int a;
163+
int b = 0;
164+
};
165+
166+
void err() {
167+
auto [[]] = S{0}; // expected-error {{expected unqualified-id}}
168+
auto [ alignas(42) a, foo ] = S{0}; // expected-error {{an attribute list cannot appear here}}
169+
auto [ c, [[]] d ] = S{0}; // expected-error {{an attribute list cannot appear here}}
170+
auto [ e, alignas(42) f ] = S{0}; // expected-error {{an attribute list cannot appear here}}
171+
}
172+
173+
void ok() {
174+
auto [ a alignas(42) [[]], b alignas(42) [[]]] = S{0}; // expected-error 2{{'alignas' attribute only applies to variables, data members and tag types}} \
175+
// pre2c-warning 2{{an attribute specifier sequence attached to a structured binding declaration is a C++2c extension}}
176+
auto [ c [[]] alignas(42), d [[]] alignas(42) [[]]] = S{0}; // expected-error 2{{'alignas' attribute only applies to variables, data members and tag types}} \
177+
// pre2c-warning 2{{an attribute specifier sequence attached to a structured binding declaration is a C++2c extension}}
178+
}
179+
180+
181+
auto [G1 [[deprecated]], G2 [[deprecated]]] = S{42}; // #deprecated-here
182+
// pre2c-warning@-1 2{{an attribute specifier sequence attached to a structured binding declaration is a C++2c extension}}
183+
184+
int test() {
185+
return G1 + G2; // expected-warning {{'G1' is deprecated}} expected-note@#deprecated-here {{here}} \
186+
// expected-warning {{'G2' is deprecated}} expected-note@#deprecated-here {{here}}
187+
}
188+
189+
void invalid_attributes() {
190+
// pre2c-warning@+1 {{an attribute specifier sequence attached to a structured binding declaration is a C++2c extension}}
191+
auto [a alignas(42) // expected-error {{'alignas' attribute only applies to variables, data members and tag types}}
192+
[[assume(true), // expected-error {{'assume' attribute cannot be applied to a declaration}}
193+
carries_dependency, // expected-error {{'carries_dependency' attribute only applies to parameters, Objective-C methods, and functions}}
194+
fallthrough, // expected-error {{'fallthrough' attribute cannot be applied to a declaration}}
195+
likely, // expected-error {{'likely' attribute cannot be applied to a declaration}}
196+
unlikely, // expected-error {{'unlikely' attribute cannot be applied to a declaration}}
197+
nodiscard, // expected-warning {{'nodiscard' attribute only applies to Objective-C methods, enums, structs, unions, classes, functions, function pointers, and typedefs}}
198+
noreturn, // expected-error {{'noreturn' attribute only applies to functions}}
199+
no_unique_address]], // expected-error {{'no_unique_address' attribute only applies to non-bit-field non-static data members}}
200+
b] = S{0};
201+
}
202+
203+
}

clang/test/SemaCXX/unused.cpp

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,11 +102,21 @@ namespace PR33839 {
102102
for (auto [x] : a) { // expected-warning {{unused variable '[x]'}}
103103
}
104104
}
105-
void use() {
105+
void use() {
106106
f<int>(); // expected-note {{instantiation of}}
107107
g<true>();
108108
g<false>();
109109
h<int>(); // expected-note {{instantiation of}}
110110
}
111111
}
112+
113+
namespace maybe_unused_binding {
114+
115+
void test() {
116+
struct X { int a, b; } x;
117+
auto [a [[maybe_unused]], b] = x; // expected-warning {{an attribute specifier sequence attached to a structured binding declaration is a C++2c extension}}
118+
}
119+
120+
}
121+
112122
#endif

clang/www/cxx_status.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -187,7 +187,7 @@ <h2 id="cxx26">C++2c implementation status</h2>
187187
<tr>
188188
<td>Trivial infinite loops are not Undefined Behavior</td>
189189
<td><a href="https://wg21.link/P2809R3">P2809R3</a> (<a href="#dr">DR</a>)</td>
190-
<td class="none" align="center">No</td>
190+
<td class="unreleased" align="center">Clang 19</td>
191191
</tr>
192192
<tr>
193193
<td>Erroneous behaviour for uninitialized reads</td>

0 commit comments

Comments
 (0)