commit 50ea349da4ce1b6bbc6f96671f2ea2f060cd46bd (tree)
parent 64ce9659de4da6221505776e65c9d24744b38b4f
Author: Motiejus Jakštys <motiejus@jakstys.lt>
Date: Tue, 10 Feb 2026 18:58:51 +0000
parser: port switch, slice, array literal, formatting tests
Port tests:
- "switch cases trailing comma"
- "slice align"
- "add trailing comma to array literal"
- "first thing in file is line comment"
- "line comment after doc comment"
- "bit field alignment"
- "nested switch"
- "float literal with exponent"
- "if-else end of comptime"
- "nested blocks"
- "statements with comment between"
- "statements with empty line between"
- "ptr deref operator and unwrap optional operator"
Fix in parser.c:
- switch_case SubRange stored via addExtra (not inline)
- Switch case body uses parseAssignExpr (not expectExpr)
- TOKEN_PERIOD_ASTERISK for deref in parseSuffixOp
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Diffstat:
| M | parser.c | | | 15 | +++++++++++++-- |
| M | parser_test.zig | | | 173 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
2 files changed, 186 insertions(+), 2 deletions(-)
diff --git a/parser.c b/parser.c
@@ -638,6 +638,12 @@ static AstNodeIndex parseSuffixOp(Parser* p, AstNodeIndex lhs) {
return 0; // tcc
}
case TOKEN_PERIOD_ASTERISK:
+ return addNode(&p->nodes,
+ (AstNodeItem) {
+ .tag = AST_NODE_DEREF,
+ .main_token = nextToken(p),
+ .data = { .lhs = lhs, .rhs = 0 },
+ });
case TOKEN_INVALID_PERIODASTERISKS:
fprintf(stderr, "parseSuffixOp does not support %s\n",
tokenizerGetTagString(tok));
@@ -1825,7 +1831,8 @@ static AstNodeIndex parseSwitchExpr(Parser* p) {
const AstTokenIndex arrow
= expectToken(p, TOKEN_EQUAL_ANGLE_BRACKET_RIGHT);
parsePtrPayload(p);
- const AstNodeIndex case_body = expectExpr(p);
+ const AstNodeIndex case_body = parseAssignExpr(p);
+ assert(case_body != 0);
const uint32_t items_len = p->scratch.len - items_old_len;
AstNodeIndex case_node;
@@ -1851,7 +1858,11 @@ static AstNodeIndex parseSwitchExpr(Parser* p) {
(AstNodeItem) {
.tag = AST_NODE_SWITCH_CASE,
.main_token = arrow,
- .data = { .lhs = span.start, .rhs = case_body },
+ .data = {
+ .lhs = addExtra(p,
+ (AstNodeIndex[]) { span.start, span.end }, 2),
+ .rhs = case_body,
+ },
});
} break;
}
diff --git a/parser_test.zig b/parser_test.zig
@@ -2566,6 +2566,179 @@ test "zig fmt: extra newlines at the end" {
);
}
+test "zig fmt: switch cases trailing comma" {
+ try testTransform(
+ \\test "switch cases trailing comma"{
+ \\ switch (x) {
+ \\ 1,2,3 => {},
+ \\ 4,5, => {},
+ \\ 6... 8, => {},
+ \\ 9 ...
+ \\ 10 => {},
+ \\ 11 => {},
+ \\ 12, => {},
+ \\ else => {},
+ \\ }
+ \\}
+ ,
+ \\test "switch cases trailing comma" {
+ \\ switch (x) {
+ \\ 1, 2, 3 => {},
+ \\ 4,
+ \\ 5,
+ \\ => {},
+ \\ 6...8,
+ \\ => {},
+ \\ 9...10 => {},
+ \\ 11 => {},
+ \\ 12,
+ \\ => {},
+ \\ else => {},
+ \\ }
+ \\}
+ \\
+ );
+}
+
+test "zig fmt: slice align" {
+ try testCanonical(
+ \\const A = struct {
+ \\ items: []align(A) T,
+ \\};
+ \\
+ );
+}
+
+test "zig fmt: add trailing comma to array literal" {
+ try testTransform(
+ \\comptime {
+ \\ return []u16{'m', 's', 'y', 's', '-' // hi
+ \\ };
+ \\ return []u16{'m', 's', 'y', 's',
+ \\ '-'};
+ \\ return []u16{'m', 's', 'y', 's', '-'};
+ \\}
+ ,
+ \\comptime {
+ \\ return []u16{
+ \\ 'm', 's', 'y', 's', '-', // hi
+ \\ };
+ \\ return []u16{ 'm', 's', 'y', 's', '-' };
+ \\ return []u16{ 'm', 's', 'y', 's', '-' };
+ \\}
+ \\
+ );
+}
+
+test "zig fmt: first thing in file is line comment" {
+ try testCanonical(
+ \\// Introspection and determination of system libraries needed by zig.
+ \\
+ \\// Introspection and determination of system libraries needed by zig.
+ \\
+ \\const std = @import("std");
+ \\
+ );
+}
+
+test "zig fmt: line comment after doc comment" {
+ try testCanonical(
+ \\/// doc comment
+ \\// line comment
+ \\fn foo() void {}
+ \\
+ );
+}
+
+test "zig fmt: bit field alignment" {
+ try testCanonical(
+ \\test {
+ \\ assert(@TypeOf(&blah.b) == *align(1:3:6) const u3);
+ \\}
+ \\
+ );
+}
+
+test "zig fmt: nested switch" {
+ try testCanonical(
+ \\test {
+ \\ switch (state) {
+ \\ TermState.Start => switch (c) {
+ \\ '\x1b' => state = TermState.Escape,
+ \\ else => try out.writeByte(c),
+ \\ },
+ \\ }
+ \\}
+ \\
+ );
+}
+
+test "zig fmt: float literal with exponent" {
+ try testCanonical(
+ \\pub const f64_true_min = 4.94065645841246544177e-324;
+ \\const threshold = 0x1.a827999fcef32p+1022;
+ \\
+ );
+}
+
+test "zig fmt: if-else end of comptime" {
+ try testCanonical(
+ \\comptime {
+ \\ if (a) {
+ \\ b();
+ \\ } else {
+ \\ b();
+ \\ }
+ \\}
+ \\
+ );
+}
+
+test "zig fmt: nested blocks" {
+ try testCanonical(
+ \\comptime {
+ \\ {
+ \\ {
+ \\ {
+ \\ a();
+ \\ }
+ \\ }
+ \\ }
+ \\}
+ \\
+ );
+}
+
+test "zig fmt: statements with comment between" {
+ try testCanonical(
+ \\comptime {
+ \\ a = b;
+ \\ // comment
+ \\ a = b;
+ \\}
+ \\
+ );
+}
+
+test "zig fmt: statements with empty line between" {
+ try testCanonical(
+ \\comptime {
+ \\ a = b;
+ \\
+ \\ a = b;
+ \\}
+ \\
+ );
+}
+
+test "zig fmt: ptr deref operator and unwrap optional operator" {
+ try testCanonical(
+ \\const a = b.*;
+ \\const a = b.?;
+ \\
+ );
+}
+
test "zig fmt: nested struct literal with one item" {
try testCanonical(
\\const a = foo{