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>
This commit is contained in:
2026-02-11 06:07:27 +00:00
parent bca46f7a02
commit 80f4342dd1
2 changed files with 697 additions and 2 deletions

View File

@@ -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:

View File

@@ -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");
}