commit 80f4342dd1e6b5f62be418d6bc917897d6433edf (tree)
parent bca46f7a02aaf91b3643452ad32b3298fe0d89c5
Author: Motiejus Jakštys <motiejus@jakstys.lt>
Date: Wed, 11 Feb 2026 06:07:27 +0000
parser: port large batch of formatting tests (289/344)
Port 29 tests including:
- field access, multiline string, regression tests
- array formatting, function params, doc comments
- for loop payloads, switch items, saturating arithmetic
- inline for/while in expression context
- canonicalize symbols, pointer type syntax, binop indentation
Implement inline for/while in parsePrimaryExpr.
Remove unused tok variable from parsePrimaryExpr.
Deferred tests (need further work):
- "function with labeled block as return type"
- "Control flow statement as body of blockless if"
- "line comment after multiline single expr if"
- "make single-line if no trailing comma, fmt: off"
- "test indentation after equals sign" (destructuring)
- "indentation of comments within catch, else, orelse"
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Diffstat:
| M | parser.c | | | 8 | ++++++-- |
| M | parser_test.zig | | | 691 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
2 files changed, 697 insertions(+), 2 deletions(-)
diff --git a/parser.c b/parser.c
@@ -2365,7 +2365,6 @@ static AstNodeIndex parseIfExpr(Parser* p) {
}
static AstNodeIndex parsePrimaryExpr(Parser* p) {
- const char* tok = tokenizerGetTagString(p->token_tags[p->tok_i]);
switch (p->token_tags[p->tok_i]) {
case TOKEN_KEYWORD_ASM:
return parseAsmExpr(p);
@@ -2444,7 +2443,12 @@ static AstNodeIndex parsePrimaryExpr(Parser* p) {
case TOKEN_KEYWORD_FOR:
return parseForExpr(p);
case TOKEN_KEYWORD_INLINE:
- fprintf(stderr, "parsePrimaryExpr does not implement %s\n", tok);
+ p->tok_i++;
+ if (p->token_tags[p->tok_i] == TOKEN_KEYWORD_FOR)
+ return parseForExpr(p);
+ if (p->token_tags[p->tok_i] == TOKEN_KEYWORD_WHILE)
+ return parseWhileExpr(p);
+ fprintf(stderr, "parsePrimaryExpr: inline without for/while\n");
exit(1);
return 0; // tcc
case TOKEN_L_BRACE:
diff --git a/parser_test.zig b/parser_test.zig
@@ -4824,6 +4824,697 @@ test "zig fmt: test comments in field access chain" {
);
}
+test "zig fmt: allow line break before field access" {
+ try testCanonical(
+ \\test {
+ \\ const w = foo.bar().zippy(zag).iguessthisisok();
+ \\
+ \\ const x = foo
+ \\ .bar()
+ \\ . // comment
+ \\ // comment
+ \\ swooop().zippy(zag)
+ \\ .iguessthisisok();
+ \\
+ \\ const y = view.output.root.server.input_manager.default_seat.wlr_seat.name;
+ \\
+ \\ const z = view.output.root.server
+ \\ .input_manager //
+ \\ .default_seat
+ \\ . // comment
+ \\ // another comment
+ \\ wlr_seat.name;
+ \\}
+ \\
+ );
+ try testTransform(
+ \\test {
+ \\ const x = foo.
+ \\ bar()
+ \\ .zippy(zag).iguessthisisok();
+ \\
+ \\ const z = view.output.root.server.
+ \\ input_manager.
+ \\ default_seat.wlr_seat.name;
+ \\}
+ \\
+ ,
+ \\test {
+ \\ const x = foo
+ \\ .bar()
+ \\ .zippy(zag).iguessthisisok();
+ \\
+ \\ const z = view.output.root.server
+ \\ .input_manager
+ \\ .default_seat.wlr_seat.name;
+ \\}
+ \\
+ );
+}
+
+test "zig fmt: Indent comma correctly after multiline string literals in arg list (trailing comma)" {
+ try testCanonical(
+ \\fn foo() void {
+ \\ z.display_message_dialog(
+ \\ *const [323:0]u8,
+ \\ \\Message Text
+ \\ \\------------
+ \\ \\xxxxxxxxxxxx
+ \\ \\xxxxxxxxxxxx
+ \\ ,
+ \\ g.GtkMessageType.GTK_MESSAGE_WARNING,
+ \\ null,
+ \\ );
+ \\
+ \\ z.display_message_dialog(*const [323:0]u8,
+ \\ \\Message Text
+ \\ \\------------
+ \\ \\xxxxxxxxxxxx
+ \\ \\xxxxxxxxxxxx
+ \\ , g.GtkMessageType.GTK_MESSAGE_WARNING, null);
+ \\}
+ \\
+ );
+}
+
+
+test "zig fmt: regression test for #5722" {
+ try testCanonical(
+ \\pub fn sendViewTags(self: Self) void {
+ \\ var it = ViewStack(View).iterator(self.output.views.first, std.math.maxInt(u32));
+ \\ while (it.next()) |node|
+ \\ view_tags.append(node.view.current_tags) catch {
+ \\ c.wl_resource_post_no_memory(self.wl_resource);
+ \\ log.err(.river_status, "out of memory", .{});
+ \\ return;
+ \\ };
+ \\}
+ \\
+ );
+}
+
+test "zig fmt: regression test for #8974" {
+ try testCanonical(
+ \\pub const VARIABLE;
+ \\
+ );
+}
+
+test "zig fmt: allow trailing line comments to do manual array formatting" {
+ try testCanonical(
+ \\fn foo() void {
+ \\ self.code.appendSliceAssumeCapacity(&[_]u8{
+ \\ 0x55, // push rbp
+ \\ 0x48, 0x89, 0xe5, // mov rbp, rsp
+ \\ 0x48, 0x81, 0xec, // sub rsp, imm32 (with reloc)
+ \\ });
+ \\
+ \\ di_buf.appendAssumeCapacity(&[_]u8{
+ \\ 1, DW.TAG_compile_unit, DW.CHILDREN_no, // header
+ \\ DW.AT_stmt_list, DW_FORM_data4, // form value pairs
+ \\ DW.AT_low_pc, DW_FORM_addr,
+ \\ DW.AT_high_pc, DW_FORM_addr,
+ \\ DW.AT_name, DW_FORM_strp,
+ \\ DW.AT_comp_dir, DW_FORM_strp,
+ \\ DW.AT_producer, DW_FORM_strp,
+ \\ DW.AT_language, DW_FORM_data2,
+ \\ 0, 0, // sentinel
+ \\ });
+ \\
+ \\ self.code.appendSliceAssumeCapacity(&[_]u8{
+ \\ 0x55, // push rbp
+ \\ 0x48, 0x89, 0xe5, // mov rbp, rsp
+ \\ // How do we handle this?
+ \\ //0x48, 0x81, 0xec, // sub rsp, imm32 (with reloc)
+ \\ // Here's a blank line, should that be allowed?
+ \\
+ \\ 0x48, 0x89, 0xe5,
+ \\ 0x33, 0x45,
+ \\ // Now the comment breaks a single line -- how do we handle this?
+ \\ 0x88,
+ \\ });
+ \\}
+ \\
+ );
+}
+
+test "zig fmt: multiline string literals should play nice with array initializers" {
+ try testCanonical(
+ \\fn main() void {
+ \\ var a = .{.{.{.{.{.{.{.{
+ \\ 0,
+ \\ }}}}}}}};
+ \\ myFunc(.{
+ \\ "aaaaaaa", "bbbbbb", "ccccc",
+ \\ "dddd", ("eee"), ("fff"),
+ \\ ("gggg"),
+ \\ // Line comment
+ \\ \\Multiline String Literals can be quite long
+ \\ ,
+ \\ \\Multiline String Literals can be quite long
+ \\ \\Multiline String Literals can be quite long
+ \\ ,
+ \\ \\Multiline String Literals can be quite long
+ \\ \\Multiline String Literals can be quite long
+ \\ \\Multiline String Literals can be quite long
+ \\ \\Multiline String Literals can be quite long
+ \\ ,
+ \\ (
+ \\ \\Multiline String Literals can be quite long
+ \\ ),
+ \\ .{
+ \\ \\xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+ \\ \\xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+ \\ \\xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+ \\ },
+ \\ .{(
+ \\ \\xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+ \\ )},
+ \\ .{
+ \\ "xxxxxxx", "xxx",
+ \\ (
+ \\ \\ xxx
+ \\ ),
+ \\ "xxx",
+ \\ "xxx",
+ \\ },
+ \\ .{ "xxxxxxx", "xxx", "xxx", "xxx" },
+ \\ .{ "xxxxxxx", "xxx", "xxx", "xxx" },
+ \\ "aaaaaaa", "bbbbbb", "ccccc", // -
+ \\ "dddd", ("eee"), ("fff"),
+ \\ .{
+ \\ "xxx", "xxx",
+ \\ (
+ \\ \\ xxx
+ \\ ),
+ \\ "xxxxxxxxxxxxxx",
+ \\ "xxx",
+ \\ },
+ \\ .{
+ \\ (
+ \\ \\xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+ \\ ),
+ \\ \\xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+ \\ },
+ \\ \\xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+ \\ \\xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+ \\ });
+ \\}
+ \\
+ );
+}
+
+test "zig fmt: use of comments and multiline string literals may force the parameters over multiple lines" {
+ try testCanonical(
+ \\pub fn makeMemUndefined(qzz: []u8) i1 {
+ \\ cases.add( // fixed bug foo
+ \\ "compile diagnostic string for top level decl type",
+ \\ \\export fn entry() void {
+ \\ \\ var foo: u32 = @This(){};
+ \\ \\}
+ \\ , &[_][]const u8{
+ \\ "tmp.zig:2:27: error: type 'u32' does not support array initialization",
+ \\ });
+ \\ @compileError(
+ \\ \\ unknown-length pointers and C pointers cannot be hashed deeply.
+ \\ \\ Consider providing your own hash function.
+ \\ \\ unknown-length pointers and C pointers cannot be hashed deeply.
+ \\ \\ Consider providing your own hash function.
+ \\ );
+ \\ return @intCast(doMemCheckClientRequestExpr(0, // default return
+ \\ .MakeMemUndefined, @intFromPtr(qzz.ptr), qzz.len, 0, 0, 0));
+ \\}
+ \\
+ \\// This looks like garbage don't do this
+ \\const rparen = tree.prevToken(
+ \\ // the first token for the annotation expressions is the left
+ \\ // parenthesis, hence the need for two prevToken
+ \\ if (fn_proto.getAlignExpr()) |align_expr|
+ \\ tree.prevToken(tree.prevToken(align_expr.firstToken()))
+ \\ else if (fn_proto.getSectionExpr()) |section_expr|
+ \\ tree.prevToken(tree.prevToken(section_expr.firstToken()))
+ \\ else if (fn_proto.getCallconvExpr()) |callconv_expr|
+ \\ tree.prevToken(tree.prevToken(callconv_expr.firstToken()))
+ \\ else switch (fn_proto.return_type) {
+ \\ .Explicit => |node| node.firstToken(),
+ \\ .InferErrorSet => |node| tree.prevToken(node.firstToken()),
+ \\ .Invalid => unreachable,
+ \\ });
+ \\
+ );
+}
+
+test "zig fmt: single argument trailing commas in @builtins()" {
+ try testCanonical(
+ \\pub fn foo(qzz: []u8) i1 {
+ \\ @panic(
+ \\ foo,
+ \\ );
+ \\ panic(
+ \\ foo,
+ \\ );
+ \\ @panic(
+ \\ foo,
+ \\ bar,
+ \\ );
+ \\}
+ \\
+ );
+}
+
+test "zig fmt: trailing comma should force multiline 1 column" {
+ try testTransform(
+ \\pub const UUID_NULL: uuid_t = [16]u8{0,0,0,0,};
+ \\
+ ,
+ \\pub const UUID_NULL: uuid_t = [16]u8{
+ \\ 0,
+ \\ 0,
+ \\ 0,
+ \\ 0,
+ \\};
+ \\
+ );
+}
+
+test "zig fmt: function params should align nicely" {
+ try testCanonical(
+ \\pub fn foo() void {
+ \\ cases.addRuntimeSafety("slicing operator with sentinel",
+ \\ \\const std = @import("std");
+ \\ ++ check_panic_msg ++
+ \\ \\pub fn main() void {
+ \\ \\ var buf = [4]u8{'a','b','c',0};
+ \\ \\ const slice = buf[0..:0];
+ \\ \\}
+ \\ );
+ \\}
+ \\
+ );
+}
+
+test "zig fmt: fn proto end with anytype and comma" {
+ try testCanonical(
+ \\pub fn format(
+ \\ out_stream: anytype,
+ \\) !void {}
+ \\
+ );
+}
+
+test "zig fmt: space after top level doc comment" {
+ try testCanonical(
+ \\//! top level doc comment
+ \\
+ \\field: i32,
+ \\
+ );
+}
+
+test "zig fmt: remove trailing whitespace after container doc comment" {
+ try testTransform(
+ \\//! top level doc comment
+ \\
+ ,
+ \\//! top level doc comment
+ \\
+ );
+}
+
+test "zig fmt: remove trailing whitespace after doc comment" {
+ try testTransform(
+ \\/// doc comment
+ \\a = 0,
+ \\
+ ,
+ \\/// doc comment
+ \\a = 0,
+ \\
+ );
+}
+
+test "zig fmt: for loop with ptr payload and index" {
+ try testCanonical(
+ \\test {
+ \\ for (self.entries.items, 0..) |*item, i| {}
+ \\ for (self.entries.items, 0..) |*item, i|
+ \\ a = b;
+ \\ for (self.entries.items, 0..) |*item, i| a = b;
+ \\}
+ \\
+ );
+}
+
+test "zig fmt: proper indent line comment after multi-line single expr while loop" {
+ try testCanonical(
+ \\test {
+ \\ while (a) : (b)
+ \\ foo();
+ \\
+ \\ // bar
+ \\ baz();
+ \\}
+ \\
+ );
+}
+
+
+test "zig fmt: extern function with missing param name" {
+ try testCanonical(
+ \\extern fn a(
+ \\ *b,
+ \\ c: *d,
+ \\) e;
+ \\extern fn f(*g, h: *i) j;
+ \\
+ );
+}
+
+
+test "zig fmt: respect extra newline between switch items" {
+ try testCanonical(
+ \\const a = switch (b) {
+ \\ .c => {},
+ \\
+ \\ .d,
+ \\ .e,
+ \\ => f,
+ \\};
+ \\
+ );
+}
+
+test "zig fmt: assignment with inline for and inline while" {
+ try testCanonical(
+ \\const tmp = inline for (items) |item| {};
+ \\
+ );
+
+ try testCanonical(
+ \\const tmp2 = inline while (true) {};
+ \\
+ );
+}
+
+test "zig fmt: saturating arithmetic" {
+ try testCanonical(
+ \\test {
+ \\ const actual = switch (op) {
+ \\ .add => a +| b,
+ \\ .sub => a -| b,
+ \\ .mul => a *| b,
+ \\ .shl => a <<| b,
+ \\ };
+ \\ switch (op) {
+ \\ .add => actual +|= b,
+ \\ .sub => actual -|= b,
+ \\ .mul => actual *|= b,
+ \\ .shl => actual <<|= b,
+ \\ }
+ \\}
+ \\
+ );
+}
+
+test "zig fmt: insert trailing comma if there are comments between switch values" {
+ try testTransform(
+ \\const a = switch (b) {
+ \\ .c => {},
+ \\
+ \\ .d, // foobar
+ \\ .e
+ \\ => f,
+ \\
+ \\ .g, .h
+ \\ // comment
+ \\ => i,
+ \\};
+ \\
+ ,
+ \\const a = switch (b) {
+ \\ .c => {},
+ \\
+ \\ .d, // foobar
+ \\ .e,
+ \\ => f,
+ \\
+ \\ .g,
+ \\ .h,
+ \\ // comment
+ \\ => i,
+ \\};
+ \\
+ );
+}
+
+test "zig fmt: insert trailing comma if comments in array init" {
+ try testTransform(
+ \\var a = .{
+ \\ "foo", //
+ \\ "bar"
+ \\};
+ \\var a = .{
+ \\ "foo",
+ \\ "bar" //
+ \\};
+ \\var a = .{
+ \\ "foo",
+ \\ "//"
+ \\};
+ \\var a = .{
+ \\ "foo",
+ \\ "//" //
+ \\};
+ \\
+ ,
+ \\var a = .{
+ \\ "foo", //
+ \\ "bar",
+ \\};
+ \\var a = .{
+ \\ "foo",
+ \\ "bar", //
+ \\};
+ \\var a = .{ "foo", "//" };
+ \\var a = .{
+ \\ "foo",
+ \\ "//", //
+ \\};
+ \\
+ );
+}
+
+test "zig fmt: make single-line if no trailing comma" {
+ try testTransform(
+ \\test "function call no trailing comma" {
+ \\ foo(
+ \\ 1,
+ \\ 2
+ \\ );
+ \\}
+ \\
+ ,
+ \\test "function call no trailing comma" {
+ \\ foo(1, 2);
+ \\}
+ \\
+ );
+
+ try testTransform(
+ \\test "struct no trailing comma" {
+ \\ const a = .{
+ \\ .foo = 1,
+ \\ .bar = 2
+ \\ };
+ \\}
+ \\
+ ,
+ \\test "struct no trailing comma" {
+ \\ const a = .{ .foo = 1, .bar = 2 };
+ \\}
+ \\
+ );
+
+ try testTransform(
+ \\test "array no trailing comma" {
+ \\ var stream = multiOutStream(.{
+ \\ fbs1.outStream(),
+ \\ fbs2.outStream()
+ \\ });
+ \\}
+ \\
+ ,
+ \\test "array no trailing comma" {
+ \\ var stream = multiOutStream(.{ fbs1.outStream(), fbs2.outStream() });
+ \\}
+ \\
+ );
+}
+
+test "zig fmt: preserve container doc comment in container without trailing comma" {
+ try testTransform(
+ \\const A = enum(u32) {
+ \\//! comment
+ \\_ };
+ \\
+ ,
+ \\const A = enum(u32) {
+ \\ //! comment
+ \\ _,
+ \\};
+ \\
+ );
+}
+
+
+test "zig fmt: no space before newline before multiline string" {
+ try testCanonical(
+ \\const S = struct {
+ \\ text: []const u8,
+ \\ comment: []const u8,
+ \\};
+ \\
+ \\test {
+ \\ const s1 = .{
+ \\ .text =
+ \\ \\hello
+ \\ \\world
+ \\ ,
+ \\ .comment = "test",
+ \\ };
+ \\ _ = s1;
+ \\ const s2 = .{
+ \\ .comment = "test",
+ \\ .text =
+ \\ \\hello
+ \\ \\world
+ \\ ,
+ \\ };
+ \\ _ = s2;
+ \\}
+ \\
+ );
+}
+
+test "zig fmt: don't canonicalize _ in enums" {
+ try testTransform(
+ \\const A = enum {
+ \\ first,
+ \\ second,
+ \\ third,
+ \\ _,
+ \\};
+ \\const B = enum {
+ \\ @"_",
+ \\ @"__",
+ \\ @"___",
+ \\ @"____",
+ \\};
+ \\const C = struct {
+ \\ @"_": u8,
+ \\ @"__": u8,
+ \\ @"___": u8,
+ \\ @"____": u8,
+ \\};
+ \\const D = union {
+ \\ @"_": u8,
+ \\ @"__": u8,
+ \\ @"___": u8,
+ \\ @"____": u8,
+ \\};
+ \\
+ ,
+ \\const A = enum {
+ \\ first,
+ \\ second,
+ \\ third,
+ \\ _,
+ \\};
+ \\const B = enum {
+ \\ @"_",
+ \\ __,
+ \\ ___,
+ \\ ____,
+ \\};
+ \\const C = struct {
+ \\ _: u8,
+ \\ __: u8,
+ \\ ___: u8,
+ \\ ____: u8,
+ \\};
+ \\const D = union {
+ \\ _: u8,
+ \\ __: u8,
+ \\ ___: u8,
+ \\ ____: u8,
+ \\};
+ \\
+ );
+}
+
+test "zig fmt: pointer type syntax to index" {
+ try testCanonical(
+ \\test {
+ \\ _ = .{}[*0];
+ \\}
+ \\
+ );
+}
+
+test "zig fmt: binop indentation in if statement" {
+ try testCanonical(
+ \\test {
+ \\ if (first_param_type.isGenericPoison() or
+ \\ (first_param_type.zigTypeTag(zcu) == .pointer and
+ \\ (first_param_type.ptrSize(zcu) == .One or
+ \\ first_param_type.ptrSize(zcu) == .C) and
+ \\ first_param_type.childType(zcu).eql(concrete_ty, zcu)))
+ \\ {
+ \\ f(x);
+ \\ }
+ \\}
+ \\
+ );
+}
+
+
+test "zig fmt: test indentation of if expressions" {
+ try testCanonical(
+ \\test {
+ \\ const foo = 1 +
+ \\ if (1 == 2)
+ \\ 2
+ \\ else
+ \\ 0;
+ \\
+ \\ const foo = 1 + if (1 == 2)
+ \\ 2
+ \\ else
+ \\ 0;
+ \\
+ \\ errval catch |e|
+ \\ if (e == error.Meow)
+ \\ return 0x1F408
+ \\ else
+ \\ unreachable;
+ \\
+ \\ errval catch |e| if (e == error.Meow)
+ \\ return 0x1F408
+ \\ else
+ \\ unreachable;
+ \\
+ \\ return if (1 == 2)
+ \\ 1
+ \\ else if (3 > 4)
+ \\ 2
+ \\ else
+ \\ 0;
+ \\}
+ \\
+ );
+}
+
+
test "Ast header smoke test" {
try std.testing.expectEqual(zigNode(c.AST_NODE_IF), Ast.Node.Tag.@"if");
}