Parse UDAs like template arguments (#14881)

This commit is contained in:
Dennis 2023-03-30 07:23:45 +02:00 committed by GitHub
parent d98f586771
commit ef1bca90e0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 93 additions and 14 deletions

View file

@ -0,0 +1,27 @@
User Defined Attributes now parse Template Arguments
It was already allowed to put types in UDAs, but the parser would reject basic types written directly, requiring the use of an alias.
---
alias Tint = int;
@Tint void f();
---
Also, simple literals that can appear in template instantiations without brackets (example: `foo!"arg"`) require parentheses when used as an attribute:
---
@("my test") unittest
{
}
---
Now, arguments that can appear after a template instantiation `foo!` can also appear after an `@` attribute.
---
@int void f();
@"my test" unittest
{
}
---

View file

@ -1316,12 +1316,38 @@ class Parser(AST, Lexer = dmd.lexer.Lexer) : Lexer
return 0; return 0;
} }
AST.Expression templateArgToExp(RootObject o, const ref Loc loc)
{
switch (o.dyncast)
{
case DYNCAST.expression:
return cast(AST.Expression) o;
case DYNCAST.type:
return new AST.TypeExp(loc, cast(AST.Type)o);
default:
assert(0);
}
}
if (token.value == TOK.leftParenthesis) if (token.value == TOK.leftParenthesis)
{ {
// Multi-UDAs ( `@( ArgumentList )`) form, concatenate with existing // Multi-UDAs ( `@( ArgumentList )`) form, concatenate with existing
if (peekNext() == TOK.rightParenthesis) if (peekNext() == TOK.rightParenthesis)
error("empty attribute list is not allowed"); error("empty attribute list is not allowed");
udas = AST.UserAttributeDeclaration.concat(udas, parseArguments());
if (udas is null)
udas = new AST.Expressions();
auto args = parseTemplateArgumentList();
foreach (arg; *args)
udas.push(templateArgToExp(arg, token.loc));
return 0;
}
if (auto o = parseTemplateSingleArgument())
{
if (udas is null)
udas = new AST.Expressions();
udas.push(templateArgToExp(o, token.loc));
return 0; return 0;
} }
@ -1778,7 +1804,16 @@ class Parser(AST, Lexer = dmd.lexer.Lexer) : Lexer
else else
{ {
// ident!template_argument // ident!template_argument
tiargs = parseTemplateSingleArgument(); RootObject o = parseTemplateSingleArgument();
if (!o)
{
error("template argument expected following `!`");
}
else
{
tiargs = new AST.Objects();
tiargs.push(o);
}
} }
if (token.value == TOK.not) if (token.value == TOK.not)
{ {
@ -1844,11 +1879,11 @@ class Parser(AST, Lexer = dmd.lexer.Lexer) : Lexer
* foo!arg * foo!arg
* Input: * Input:
* current token is the arg * current token is the arg
* Returns: An AST.Type, AST.Expression, or `null` on error
*/ */
private AST.Objects* parseTemplateSingleArgument() private RootObject parseTemplateSingleArgument()
{ {
//printf("parseTemplateSingleArgument()\n"); //printf("parseTemplateSingleArgument()\n");
auto tiargs = new AST.Objects();
AST.Type ta; AST.Type ta;
switch (token.value) switch (token.value)
{ {
@ -1956,9 +1991,8 @@ class Parser(AST, Lexer = dmd.lexer.Lexer) : Lexer
ta = AST.Type.tdchar; ta = AST.Type.tdchar;
goto LabelX; goto LabelX;
LabelX: LabelX:
tiargs.push(ta);
nextToken(); nextToken();
break; return ta;
case TOK.int32Literal: case TOK.int32Literal:
case TOK.uns32Literal: case TOK.uns32Literal:
@ -1988,15 +2022,11 @@ class Parser(AST, Lexer = dmd.lexer.Lexer) : Lexer
case TOK.this_: case TOK.this_:
{ {
// Template argument is an expression // Template argument is an expression
AST.Expression ea = parsePrimaryExp(); return parsePrimaryExp();
tiargs.push(ea);
break;
} }
default: default:
error("template argument expected following `!`"); return null;
break;
} }
return tiargs;
} }
/********************************** /**********************************

View file

@ -0,0 +1,22 @@
enum Test;
@true @null @byte int x;
@(int) int y;
@"test" @`test2` @30 @'a' @__LINE__ void f();
@Test void h();
static assert( __traits(getAttributes, x)[0] == true);
static assert( __traits(getAttributes, x)[1] == null);
static assert(is(__traits(getAttributes, x)[2] == byte));
static assert(is(__traits(getAttributes, y)[0] == int));
static assert( __traits(getAttributes, f)[0] == "test");
static assert( __traits(getAttributes, f)[1] == "test2");
static assert( __traits(getAttributes, f)[2] == 30);
static assert( __traits(getAttributes, f)[3] == 'a');
static assert( __traits(getAttributes, f)[4] == 6);
static assert(is(__traits(getAttributes, h)[0] == enum));

View file

@ -1,13 +1,13 @@
/** /**
TEST_OUTPUT: TEST_OUTPUT:
--- ---
fail_compilation/named_arguments_parse.d(10): Error: named arguments not allowed here
fail_compilation/named_arguments_parse.d(13): Error: named arguments not allowed here fail_compilation/named_arguments_parse.d(13): Error: named arguments not allowed here
fail_compilation/named_arguments_parse.d(14): Error: named arguments not allowed here fail_compilation/named_arguments_parse.d(14): Error: named arguments not allowed here
--- ---
*/ */
@(attribute: 3)
// @(attribute: 3) Currently gives an ugly parse error, will be better when named template arguments are implemented
void main() void main()
{ {
mixin(thecode: "{}"); mixin(thecode: "{}");