commit f239c0bf5132caa2426163cf7ce112cb0cd266c3 (tree)
parent d520182253d43cc8b2812640eb54fe78a221c800
Author: Motiejus Jakštys <motiejus.jakstys@chronosphere.io>
Date: Tue, 10 Feb 2026 13:07:22 +0000
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>
Diffstat:
| M | parser.c | | | 180 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------ |
| M | parser_test.zig | | | 72 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
2 files changed, 225 insertions(+), 27 deletions(-)
diff --git a/parser.c b/parser.c
@@ -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]) {
diff --git a/parser_test.zig b/parser_test.zig
@@ -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,
+ \\ );
+ \\}
+ \\
+ );
+}