parser: add fn params, return, call args, enum literal tests

Port tests from upstream parser_test.zig:
- "trailing comma in fn parameter list" (all combinations)
- "enum literal inside array literal"
- "builtin call with trailing comma"

Implement in parser.c:
- parseParamDeclList: full parameter parsing with names, comptime,
  noalias, doc comments, varargs
- parseFnProto: fn_proto_multi, fn_proto_one, fn_proto with
  align/addrspace/section/callconv
- parseSuffixExpr: function call with arguments
- parsePrimaryExpr: return, comptime, nosuspend, resume expressions
- parseAddrSpace, parseLinkSection, parseCallconv: full parsing
- Use OPT() macro for OptionalIndex encoding in extra data

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-02-10 13:07:22 +00:00
parent cb4be73acb
commit 1a8bb5ac10
2 changed files with 225 additions and 27 deletions

180
parser.c
View File

@@ -153,25 +153,28 @@ static AstNodeIndex parseByteAlign(Parser* p) {
static AstNodeIndex parseAddrSpace(Parser* p) {
if (eatToken(p, TOKEN_KEYWORD_ADDRSPACE) == null_token)
return null_node;
fprintf(stderr, "parseAddrSpace cannot parse addrspace\n");
exit(1);
return 0; // tcc
expectToken(p, TOKEN_L_PAREN);
const AstNodeIndex expr = expectExpr(p);
expectToken(p, TOKEN_R_PAREN);
return expr;
}
static AstNodeIndex parseLinkSection(Parser* p) {
if (eatToken(p, TOKEN_KEYWORD_LINKSECTION) == null_token)
return null_node;
fprintf(stderr, "parseLinkSection cannot parse linksection\n");
exit(1);
return 0; // tcc
expectToken(p, TOKEN_L_PAREN);
const AstNodeIndex expr = expectExpr(p);
expectToken(p, TOKEN_R_PAREN);
return expr;
}
static AstNodeIndex parseCallconv(Parser* p) {
if (eatToken(p, TOKEN_KEYWORD_CALLCONV) == null_token)
return null_node;
fprintf(stderr, "parseCallconv cannot parse callconv\n");
exit(1);
return 0; // tcc
expectToken(p, TOKEN_L_PAREN);
const AstNodeIndex expr = expectExpr(p);
expectToken(p, TOKEN_R_PAREN);
return expr;
}
typedef struct {
@@ -697,8 +700,14 @@ static AstNodeIndex parseSuffixExpr(Parser* p) {
while (true) {
if (eatToken(p, TOKEN_R_PAREN) != null_token)
break;
fprintf(stderr, "parseSuffixExpr can only parse ()\n");
exit(1);
const AstNodeIndex arg = expectExpr(p);
SLICE_APPEND(AstNodeIndex, &p->scratch, arg);
if (p->token_tags[p->tok_i] == TOKEN_COMMA) {
p->tok_i++;
continue;
}
expectToken(p, TOKEN_R_PAREN);
break;
}
const bool comma = p->token_tags[p->tok_i - 2] == TOKEN_COMMA;
@@ -901,13 +910,66 @@ static AstNodeIndex parseTypeExpr(Parser* p) {
}
static SmallSpan parseParamDeclList(Parser* p) {
// can only parse functions with no declarations
expectToken(p, TOKEN_L_PAREN);
expectToken(p, TOKEN_R_PAREN);
return (SmallSpan) {
.tag = SMALL_SPAN_ZERO_OR_ONE,
};
CleanupScratch scratch_top __attribute__((__cleanup__(cleanupScratch)))
= initCleanupScratch(p);
while (true) {
if (eatToken(p, TOKEN_R_PAREN) != null_token)
break;
eatDocComments(p);
// Check for comptime or noalias
eatToken(p, TOKEN_KEYWORD_COMPTIME);
eatToken(p, TOKEN_KEYWORD_NOALIAS);
// Check for name: type or just type
if (p->token_tags[p->tok_i] == TOKEN_IDENTIFIER
&& p->token_tags[p->tok_i + 1] == TOKEN_COLON) {
p->tok_i += 2; // consume name and colon
} else if (p->token_tags[p->tok_i] == TOKEN_ELLIPSIS3) {
// anytype (...) varargs
p->tok_i++;
if (eatToken(p, TOKEN_R_PAREN) != null_token)
break;
expectToken(p, TOKEN_COMMA);
continue;
}
const AstNodeIndex type_expr = parseTypeExpr(p);
if (type_expr != 0)
SLICE_APPEND(AstNodeIndex, &p->scratch, type_expr);
if (p->token_tags[p->tok_i] == TOKEN_COMMA) {
p->tok_i++;
continue;
}
expectToken(p, TOKEN_R_PAREN);
break;
}
const uint32_t params_len = p->scratch.len - scratch_top.old_len;
switch (params_len) {
case 0:
return (SmallSpan) {
.tag = SMALL_SPAN_ZERO_OR_ONE,
.payload = { .zero_or_one = 0 },
};
case 1:
return (SmallSpan) {
.tag = SMALL_SPAN_ZERO_OR_ONE,
.payload = { .zero_or_one = p->scratch.arr[scratch_top.old_len] },
};
default:;
const AstSubRange span
= listToSpan(p, &p->scratch.arr[scratch_top.old_len], params_len);
return (SmallSpan) {
.tag = SMALL_SPAN_MULTI,
.payload = { .multi = span },
};
}
}
static uint32_t reserveNode(Parser* p, AstNodeTag tag) {
@@ -939,9 +1001,7 @@ static AstNodeIndex parseFnProto(Parser* p) {
&& addrspace_expr == 0) {
switch (params.tag) {
case SMALL_SPAN_ZERO_OR_ONE:
return setNode(
p,
fn_proto_index,
return setNode(p, fn_proto_index,
(AstNodeItem) {
.tag = AST_NODE_FN_PROTO_SIMPLE,
.main_token = fn_token,
@@ -950,15 +1010,60 @@ static AstNodeIndex parseFnProto(Parser* p) {
.rhs = return_type_expr,
},
});
break;
case SMALL_SPAN_MULTI:
fprintf(stderr, "parseFnProto does not support multi params\n");
exit(1);
return setNode(p, fn_proto_index,
(AstNodeItem) {
.tag = AST_NODE_FN_PROTO_MULTI,
.main_token = fn_token,
.data = {
.lhs = addExtra(p,
(AstNodeIndex[]) {
params.payload.multi.start,
params.payload.multi.end },
2),
.rhs = return_type_expr,
},
});
}
}
fprintf(stderr, "parseFnProto does not support complex function decls\n");
exit(1);
// Complex fn proto with align/section/callconv/addrspace
// Extra data fields are OptionalIndex: 0 → ~0 (none)
#define OPT(x) ((x) == 0 ? ~(AstNodeIndex)0 : (x))
switch (params.tag) {
case SMALL_SPAN_ZERO_OR_ONE:
return setNode(p, fn_proto_index,
(AstNodeItem) {
.tag = AST_NODE_FN_PROTO_ONE,
.main_token = fn_token,
.data = {
.lhs = addExtra(p,
(AstNodeIndex[]) {
OPT(params.payload.zero_or_one),
OPT(align_expr), OPT(addrspace_expr),
OPT(section_expr), OPT(callconv_expr) },
5),
.rhs = return_type_expr,
},
});
case SMALL_SPAN_MULTI:
return setNode(p, fn_proto_index,
(AstNodeItem) {
.tag = AST_NODE_FN_PROTO,
.main_token = fn_token,
.data = {
.lhs = addExtra(p,
(AstNodeIndex[]) {
params.payload.multi.start,
params.payload.multi.end,
OPT(align_expr), OPT(addrspace_expr),
OPT(section_expr), OPT(callconv_expr) },
6),
.rhs = return_type_expr,
},
});
}
#undef OPT
return 0; // tcc
}
@@ -1447,12 +1552,33 @@ static AstNodeIndex parsePrimaryExpr(Parser* p) {
},
});
case TOKEN_KEYWORD_COMPTIME:
return addNode(&p->nodes,
(AstNodeItem) {
.tag = AST_NODE_COMPTIME,
.main_token = nextToken(p),
.data = { .lhs = expectExpr(p), .rhs = 0 },
});
case TOKEN_KEYWORD_NOSUSPEND:
return addNode(&p->nodes,
(AstNodeItem) {
.tag = AST_NODE_NOSUSPEND,
.main_token = nextToken(p),
.data = { .lhs = expectExpr(p), .rhs = 0 },
});
case TOKEN_KEYWORD_RESUME:
return addNode(&p->nodes,
(AstNodeItem) {
.tag = AST_NODE_RESUME,
.main_token = nextToken(p),
.data = { .lhs = expectExpr(p), .rhs = 0 },
});
case TOKEN_KEYWORD_RETURN:
fprintf(stderr, "parsePrimaryExpr does not implement %s\n", tok);
exit(1);
return 0; // tcc
return addNode(&p->nodes,
(AstNodeItem) {
.tag = AST_NODE_RETURN,
.main_token = nextToken(p),
.data = { .lhs = parseExpr(p), .rhs = 0 },
});
case TOKEN_IDENTIFIER:
if (p->token_tags[p->tok_i + 1] == TOKEN_COLON) {
switch (p->token_tags[p->tok_i + 2]) {

View File

@@ -1360,3 +1360,75 @@ test "zig fmt: comment to disable/enable zig fmt first" {
\\const struct_trailing_comma = struct { x: i32, y: i32, };
);
}
test "zig fmt: trailing comma in fn parameter list" {
try testCanonical(
\\pub fn f(
\\ a: i32,
\\ b: i32,
\\) i32 {}
\\pub fn f(
\\ a: i32,
\\ b: i32,
\\) align(8) i32 {}
\\pub fn f(
\\ a: i32,
\\ b: i32,
\\) addrspace(.generic) i32 {}
\\pub fn f(
\\ a: i32,
\\ b: i32,
\\) linksection(".text") i32 {}
\\pub fn f(
\\ a: i32,
\\ b: i32,
\\) callconv(.c) i32 {}
\\pub fn f(
\\ a: i32,
\\ b: i32,
\\) align(8) linksection(".text") i32 {}
\\pub fn f(
\\ a: i32,
\\ b: i32,
\\) align(8) callconv(.c) i32 {}
\\pub fn f(
\\ a: i32,
\\ b: i32,
\\) align(8) linksection(".text") callconv(.c) i32 {}
\\pub fn f(
\\ a: i32,
\\ b: i32,
\\) linksection(".text") callconv(.c) i32 {}
\\
);
}
test "zig fmt: enum literal inside array literal" {
try testCanonical(
\\test "enums in arrays" {
\\ var colors = []Color{.Green};
\\ colors = []Colors{ .Green, .Cyan };
\\ colors = []Colors{
\\ .Grey,
\\ .Green,
\\ .Cyan,
\\ };
\\}
\\
);
}
test "zig fmt: builtin call with trailing comma" {
try testCanonical(
\\pub fn main() void {
\\ @breakpoint();
\\ _ = @intFromBool(a);
\\ _ = @call(
\\ a,
\\ b,
\\ c,
\\ );
\\}
\\
);
}