Skip to content

Commit 3b07cc6

Browse files
committed
Fix #23224 - ImportC: parse C23 [[...]] attribute-specifier-sequences
ImportC now understands the C23 attribute syntax (C23 6.7.13) wherever a GNU __attribute__ is accepted: on declarations, pointers, struct/union/enum tags, struct members, enumerators, function parameters, and statements. [[deprecated]]/[[deprecated("msg")]] (6.7.13.5) maps to D's deprecated, and [[deprecated]] on an enumerator deprecates the D enum member. [[noreturn]]/ [[_Noreturn]] (6.7.13.7) sets the function's noreturn flag (a backend codegen hint, not D's noreturn type). The remaining standard attributes ([[nodiscard]], [[maybe_unused]], [[fallthrough]], [[unsequenced]], [[reproducible]]), vendor-prefixed (gnu::, clang::) and unknown attributes are accepted and ignored. A standard attribute spelled attr and __attr__ are treated identically (6.7.13.1).
1 parent 544fa7d commit 3b07cc6

6 files changed

Lines changed: 379 additions & 39 deletions

File tree

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
ImportC now parses C23 `[[...]]` attribute syntax
2+
3+
You can now use C23 attributes in C files imported with ImportC, anywhere a GNU
4+
`__attribute__` was already accepted: on declarations, pointers, struct/union/enum
5+
tags, struct members, enumerators, function parameters, and statements.
6+
7+
`[[deprecated]]` (with an optional message) marks a function, variable, type or enum
8+
member as deprecated in D, and `[[noreturn]]` flags a function as non-returning, just
9+
like GNU `__attribute__((noreturn))`. Every other standard attribute (such as
10+
`[[nodiscard]]`, `[[maybe_unused]]` and
11+
`[[fallthrough]]`), as well as vendor-prefixed ones like `[[gnu::...]]` and
12+
`[[clang::...]]` and any unknown attribute, is parsed and ignored, so existing C23
13+
headers keep compiling. The `attr` and `__attr__` spellings are equivalent (C23 6.7.13).
14+
15+
-------
16+
[[deprecated("use bar instead")]] int foo(void);
17+
[[nodiscard]] int bar(void);
18+
19+
struct [[deprecated]] S
20+
{
21+
[[maybe_unused]] int a;
22+
};
23+
24+
enum E { A [[deprecated]], B [[maybe_unused]] = 5 };
25+
26+
int baz(int x [[maybe_unused]]);
27+
-------

compiler/src/dmd/cparse.d

Lines changed: 227 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -400,6 +400,21 @@ final class CParser(AST) : Parser!AST
400400
break;
401401
}
402402

403+
case TOK.leftBracket:
404+
// C23 6.7.13 attribute-specifier-sequence prefixing a declaration or statement
405+
if (!isCAttributeStart())
406+
goto default;
407+
{
408+
Specifier attrspec;
409+
attrspec.packalign.setDefault();
410+
cparseCAttributes(attrspec);
411+
if (startsDeclaration(&token))
412+
goto Ldeclaration;
413+
// attributes appertain to the following statement
414+
s = cparseStatement(0);
415+
}
416+
break;
417+
403418
case TOK._Static_assert: // _Static_assert ( constant-expression, string-literal ) ;
404419
s = new AST.StaticAssertStatement(cparseStaticAssert());
405420
break;
@@ -1888,8 +1903,8 @@ final class CParser(AST) : Parser!AST
18881903
*/
18891904
first = false;
18901905
}
1891-
else if (token.value == TOK.__attribute__)
1892-
cparseGnuAttributes(specifier);
1906+
else if (token.value == TOK.__attribute__ || isCAttributeStart())
1907+
cparseAttributes(specifier); // GNU / C23 6.7.13
18931908
else
18941909
break;
18951910
}
@@ -2517,6 +2532,14 @@ final class CParser(AST) : Parser!AST
25172532
break;
25182533
}
25192534

2535+
case TOK.leftBracket: // C23 6.7.13 attribute-specifier-sequence
2536+
{
2537+
if (!isCAttributeStart())
2538+
goto default;
2539+
cparseCAttributes(specifier);
2540+
break;
2541+
}
2542+
25202543
case TOK.__declspec:
25212544
{
25222545
/* Microsoft extension
@@ -2923,8 +2946,7 @@ final class CParser(AST) : Parser!AST
29232946
const mod = cparseTypeQualifierList();
29242947
if (mod & MOD.xconst)
29252948
constTypes.push(t);
2926-
if (token.value == TOK.__attribute__)
2927-
cparseGnuAttributes(specifier);
2949+
cparseAttributes(specifier); // GNU / C23 6.7.13
29282950
continue;
29292951

29302952
default:
@@ -2954,6 +2976,13 @@ final class CParser(AST) : Parser!AST
29542976
{
29552977
case TOK.leftBracket:
29562978
{
2979+
if (isCAttributeStart())
2980+
{
2981+
// C23 6.7.13 attribute-specifier-sequence appertaining to
2982+
// the declarator, e.g. `int x [[maybe_unused]]`
2983+
cparseCAttributes(specifier);
2984+
continue;
2985+
}
29572986
// post [] syntax, pick up any leading type qualifiers, `static` and `*`
29582987
AST.Type ta;
29592988
nextToken();
@@ -3264,8 +3293,7 @@ final class CParser(AST) : Parser!AST
32643293

32653294
AST.ArgumentLabel id;
32663295
auto t = cparseDeclarator(DTR.xparameter, tspec, id, specifier);
3267-
if (token.value == TOK.__attribute__)
3268-
cparseGnuAttributes(specifier);
3296+
cparseAttributes(specifier); // GNU / C23 6.7.13
32693297
if (specifier.mod & MOD.xconst)
32703298
t = toConst(t);
32713299
auto param = new AST.Parameter(id.name ? id.loc : typeLoc,
@@ -3860,6 +3888,169 @@ final class CParser(AST) : Parser!AST
38603888
}
38613889
}
38623890

3891+
/*************************
3892+
* C23 6.7.13 attribute-specifier-sequence parser.
3893+
*
3894+
* Grammar (N3220 6.7.13.2):
3895+
* attribute-specifier-sequence:
3896+
* attribute-specifier-sequence(opt) attribute-specifier
3897+
* attribute-specifier:
3898+
* [ [ attribute-list ] ]
3899+
* attribute-list:
3900+
* attribute(opt)
3901+
* attribute-list , attribute(opt)
3902+
*
3903+
* The lexer emits two `TOK.leftBracket` for `[[` and two `TOK.rightBracket`
3904+
* for `]]`; there is no combined token.
3905+
* Params:
3906+
* specifier = filled in with the recognized attribute(s)
3907+
*/
3908+
private void cparseCAttributes(ref Specifier specifier)
3909+
{
3910+
while (isCAttributeStart())
3911+
{
3912+
nextToken(); // first `[`
3913+
nextToken(); // second `[`
3914+
if (token.value != TOK.rightBracket)
3915+
{
3916+
while (1)
3917+
{
3918+
cparseCAttribute(specifier);
3919+
if (token.value != TOK.comma)
3920+
break;
3921+
nextToken();
3922+
}
3923+
}
3924+
check(TOK.rightBracket);
3925+
check(TOK.rightBracket);
3926+
}
3927+
}
3928+
3929+
/*************************
3930+
* True if positioned at the start of a C23 `[[` attribute-specifier-sequence.
3931+
* A lone `[` (e.g. an array subscript) is not one.
3932+
*/
3933+
private bool isCAttributeStart()
3934+
{
3935+
return token.value == TOK.leftBracket && peekNext() == TOK.leftBracket;
3936+
}
3937+
3938+
/*************************
3939+
* Parse a single C23 attribute (N3220 6.7.13.2):
3940+
* attribute:
3941+
* attribute-token attribute-argument-clause(opt)
3942+
* attribute-token:
3943+
* standard-attribute // identifier
3944+
* attribute-prefixed-token // attribute-prefix :: identifier
3945+
* attribute-argument-clause:
3946+
* ( balanced-token-sequence(opt) )
3947+
*
3948+
* `deprecated` (6.7.13.5) and `noreturn`/`_Noreturn` (6.7.13.7) are mapped onto
3949+
* the existing Specifier fields; every other standard attribute
3950+
* (`nodiscard` 6.7.13.3, `maybe_unused` 6.7.13.4, `fallthrough` 6.7.13.6,
3951+
* `unsequenced`/`reproducible` 6.7.13.8), prefixed attribute, or unknown
3952+
* attribute is accepted and ignored (6.7.13.1). The argument clause is a
3953+
* balanced-token-sequence, not an expression, so it is skipped by paren
3954+
* balancing except for the `deprecated` message string.
3955+
* Params:
3956+
* specifier = filled in with the recognized attribute(s)
3957+
*/
3958+
private void cparseCAttribute(ref Specifier specifier)
3959+
{
3960+
// C23 6.7.13.2: a keyword used as an attribute-token is treated as an
3961+
// identifier; `_Noreturn` is the only such keyword with a mapping.
3962+
if (token.value == TOK._Noreturn)
3963+
{
3964+
specifier.noreturn = true; // C23 6.7.13.7
3965+
nextToken();
3966+
if (token.value == TOK.leftParenthesis)
3967+
cparseParens();
3968+
return;
3969+
}
3970+
if (token.value != TOK.identifier)
3971+
{
3972+
// empty attribute (e.g. a trailing comma) or some other keyword used as
3973+
// an attribute-token: accept and ignore
3974+
if (isGnuAttributeName())
3975+
{
3976+
nextToken();
3977+
if (token.value == TOK.leftParenthesis)
3978+
cparseParens();
3979+
}
3980+
return;
3981+
}
3982+
3983+
Identifier ident = token.ident;
3984+
nextToken();
3985+
3986+
// attribute-prefixed-token: attribute-prefix :: identifier
3987+
// implementation-specific (e.g. `gnu::`, `clang::`): accept and ignore
3988+
if (token.value == TOK.colonColon)
3989+
{
3990+
nextToken();
3991+
if (token.value == TOK.identifier || isGnuAttributeName())
3992+
nextToken();
3993+
if (token.value == TOK.leftParenthesis)
3994+
cparseParens();
3995+
return;
3996+
}
3997+
3998+
// standard attribute; normalize the `__attr__` spelling (C23 6.7.13.1)
3999+
const id = normalizeCAttributeName(ident);
4000+
if (id is Id._deprecated) // C23 6.7.13.5
4001+
{
4002+
specifier._deprecated = true;
4003+
if (token.value == TOK.leftParenthesis) // optional message string
4004+
{
4005+
nextToken();
4006+
specifier.depMsg = cparseExpression();
4007+
check(TOK.rightParenthesis);
4008+
}
4009+
return;
4010+
}
4011+
if (id is Id.noreturn) // C23 6.7.13.7
4012+
{
4013+
specifier.noreturn = true;
4014+
if (token.value == TOK.leftParenthesis)
4015+
cparseParens();
4016+
return;
4017+
}
4018+
4019+
// C23 6.7.13.3/.4/.6/.8 and any unknown attribute: accept and ignore
4020+
if (token.value == TOK.leftParenthesis)
4021+
cparseParens();
4022+
}
4023+
4024+
/*************************
4025+
* C23 6.7.13.1: a standard attribute spelled `attr` and the form `__attr__`
4026+
* behave identically. Strip a surrounding `__` pair so the core name can be
4027+
* compared against the known attribute identifiers.
4028+
*/
4029+
private Identifier normalizeCAttributeName(Identifier ident)
4030+
{
4031+
const s = ident.toString();
4032+
if (s.length > 4 && s[0] == '_' && s[1] == '_' && s[$ - 1] == '_' && s[$ - 2] == '_')
4033+
return Identifier.idPool(s[2 .. $ - 2]);
4034+
return ident;
4035+
}
4036+
4037+
/*************************
4038+
* Consume any mixture of GNU `__attribute__((...))` and C23 `[[...]]`
4039+
* attribute-specifier-sequences, in any order, into `specifier`.
4040+
*/
4041+
private void cparseAttributes(ref Specifier specifier)
4042+
{
4043+
while (1)
4044+
{
4045+
if (token.value == TOK.__attribute__)
4046+
cparseGnuAttributes(specifier);
4047+
else if (isCAttributeStart())
4048+
cparseCAttributes(specifier);
4049+
else
4050+
break;
4051+
}
4052+
}
4053+
38634054
//}
38644055
/******************************************************************************/
38654056
/***************************** Struct & Enum Parser ***************************/
@@ -3901,8 +4092,7 @@ final class CParser(AST) : Parser!AST
39014092
*/
39024093
Specifier specifier;
39034094
specifier.packalign.setDefault();
3904-
if (token.value == TOK.__attribute__)
3905-
cparseGnuAttributes(specifier);
4095+
cparseAttributes(specifier); // GNU / C23 6.7.13
39064096

39074097
Identifier tag;
39084098
if (token.value == TOK.identifier)
@@ -3943,15 +4133,14 @@ final class CParser(AST) : Parser!AST
39434133
nextToken();
39444134
auto mloc = token.loc;
39454135

3946-
if (token.value == TOK.__attribute__)
3947-
{
3948-
/* gnu-attributes can appear here, but just scan and ignore them
3949-
* https://gcc.gnu.org/onlinedocs/gcc/Enumerator-Attributes.html
3950-
*/
3951-
Specifier specifierx;
3952-
specifierx.packalign.setDefault();
3953-
cparseGnuAttributes(specifierx);
3954-
}
4136+
/* gnu-attributes / C23 6.7.13 attributes can appear before and after the
4137+
* (optional) value (https://gcc.gnu.org/onlinedocs/gcc/Enumerator-Attributes.html).
4138+
* `deprecated` (C23 6.7.13.5) is applied to the enumerator, since D
4139+
* supports deprecated enum members; the rest are accepted and ignored.
4140+
*/
4141+
Specifier emspec;
4142+
emspec.packalign.setDefault();
4143+
cparseAttributes(emspec);
39554144

39564145
AST.Expression value;
39574146
if (token.value == TOK.assign)
@@ -3961,17 +4150,17 @@ final class CParser(AST) : Parser!AST
39614150
// TODO C11 6.7.2.2-2 value must fit into an int
39624151
}
39634152

3964-
if (token.value == TOK.__attribute__)
4153+
cparseAttributes(emspec);
4154+
4155+
STC emstc = STC.none;
4156+
AST.DeprecatedDeclaration dd;
4157+
if (emspec._deprecated) // C23 6.7.13.5
39654158
{
3966-
/* gnu-attributes can appear here, but just scan and ignore them
3967-
* https://gcc.gnu.org/onlinedocs/gcc/Enumerator-Attributes.html
3968-
*/
3969-
Specifier specifierx;
3970-
specifierx.packalign.setDefault();
3971-
cparseGnuAttributes(specifierx);
4159+
emstc |= STC.deprecated_;
4160+
if (emspec.depMsg)
4161+
dd = new AST.DeprecatedDeclaration(emspec.depMsg, null);
39724162
}
3973-
3974-
auto em = new AST.EnumMember(mloc, ident, value, null, STC.none, null, null);
4163+
auto em = new AST.EnumMember(mloc, ident, value, null, emstc, null, dd);
39754164
members.push(em);
39764165

39774166
if (token.value == TOK.comma)
@@ -3983,11 +4172,10 @@ final class CParser(AST) : Parser!AST
39834172
}
39844173
check(TOK.rightCurly);
39854174

3986-
/* GNU Extensions
3987-
* Parse the postfix gnu-attributes (opt)
4175+
/* GNU Extensions / C23 6.7.13
4176+
* Parse the postfix attributes (opt)
39884177
*/
3989-
if (token.value == TOK.__attribute__)
3990-
cparseGnuAttributes(specifier);
4178+
cparseAttributes(specifier);
39914179
}
39924180
else if (!tag)
39934181
error("missing `identifier` after `enum`");
@@ -4035,6 +4223,8 @@ final class CParser(AST) : Parser!AST
40354223
{
40364224
if (token.value == TOK.__attribute__)
40374225
cparseGnuAttributes(tagSpecifier);
4226+
else if (isCAttributeStart()) // C23 6.7.13
4227+
cparseCAttributes(tagSpecifier);
40384228
else if (token.value == TOK.__declspec)
40394229
cparseDeclspec(tagSpecifier);
40404230
else if (token.value == TOK.__pragma)
@@ -4072,11 +4262,10 @@ final class CParser(AST) : Parser!AST
40724262
*/
40734263
}
40744264

4075-
/* GNU Extensions
4076-
* Parse the postfix gnu-attributes (opt)
4265+
/* GNU Extensions / C23 6.7.13
4266+
* Parse the postfix attributes (opt)
40774267
*/
4078-
if (token.value == TOK.__attribute__)
4079-
cparseGnuAttributes(tagSpecifier);
4268+
cparseAttributes(tagSpecifier);
40804269
if (!tagSpecifier.packalign.isUnknown)
40814270
{
40824271
foreach (ref d; (*members)[])
@@ -4232,13 +4421,12 @@ final class CParser(AST) : Parser!AST
42324421
width = cparseConstantExp();
42334422
}
42344423

4235-
/* GNU Extensions
4424+
/* GNU Extensions / C23 6.7.13
42364425
* struct-declarator:
4237-
* declarator gnu-attributes (opt)
4238-
* declarator (opt) : constant-expression gnu-attributes (opt)
4426+
* declarator attributes (opt)
4427+
* declarator (opt) : constant-expression attributes (opt)
42394428
*/
4240-
if (token.value == TOK.__attribute__)
4241-
cparseGnuAttributes(specifier);
4429+
cparseAttributes(specifier);
42424430

42434431
if (!tspec && !specifier.scw && !specifier.mod)
42444432
error("specifier-qualifier-list required");

0 commit comments

Comments
 (0)